001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.connector.federation;
025    
026    import java.util.Collections;
027    import java.util.HashMap;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.concurrent.CopyOnWriteArrayList;
031    import java.util.concurrent.CountDownLatch;
032    import java.util.concurrent.TimeUnit;
033    import java.util.concurrent.atomic.AtomicBoolean;
034    import java.util.concurrent.atomic.AtomicInteger;
035    import net.jcip.annotations.ThreadSafe;
036    import org.jboss.dna.common.util.CheckArg;
037    import org.jboss.dna.graph.ExecutionContext;
038    import org.jboss.dna.graph.connector.RepositoryConnection;
039    import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
040    import org.jboss.dna.graph.connector.RepositorySource;
041    import org.jboss.dna.graph.connector.RepositorySourceListener;
042    import org.jboss.dna.graph.request.processor.RequestProcessor;
043    
044    /**
045     * The component that represents a single federated repository. The federated repository uses a set of {@link RepositorySource
046     * federated connectionFactory} as designated by name through the {@link #getWorkspaceConfigurations() configurations}, and
047     * provides the logic of interacting with those connectionFactory and presenting a single unified graph.
048     * 
049     * @author Randall Hauch
050     */
051    @ThreadSafe
052    public class FederatedRepository {
053    
054        private final String name;
055        private final ExecutionContext context;
056        private final RepositoryConnectionFactory connectionFactory;
057        private final Map<String, FederatedWorkspace> workspaceConfigsByName;
058        private final FederatedWorkspace defaultWorkspace;
059        private final AtomicInteger openExecutors = new AtomicInteger(0);
060        private final CountDownLatch shutdownLatch = new CountDownLatch(1);
061        private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
062        private final CopyOnWriteArrayList<RepositorySourceListener> listeners = new CopyOnWriteArrayList<RepositorySourceListener>();
063    
064        /**
065         * Create a federated repository instance.
066         * 
067         * @param repositoryName the name of the repository
068         * @param context the execution context
069         * @param connectionFactory the factory for {@link RepositoryConnection} instances that should be used
070         * @param workspaces the workspace configurations for this repository, with the default workspace being first; may not be null
071         * @throws IllegalArgumentException if any of the parameters are null, or if the name is blank
072         */
073        public FederatedRepository( String repositoryName,
074                                    ExecutionContext context,
075                                    RepositoryConnectionFactory connectionFactory,
076                                    Iterable<FederatedWorkspace> workspaces ) {
077            CheckArg.isNotEmpty(repositoryName, "repositoryName");
078            CheckArg.isNotNull(connectionFactory, "connectionFactory");
079            CheckArg.isNotNull(context, "context");
080            CheckArg.isNotNull(workspaces, "workspaces");
081            this.name = repositoryName;
082            this.context = context;
083            this.connectionFactory = connectionFactory;
084            FederatedWorkspace defaultWorkspace = null;
085            Map<String, FederatedWorkspace> configsByName = new HashMap<String, FederatedWorkspace>();
086            for (FederatedWorkspace workspace : workspaces) {
087                if (defaultWorkspace == null) defaultWorkspace = workspace;
088                configsByName.put(workspace.getName(), workspace);
089            }
090            this.workspaceConfigsByName = Collections.unmodifiableMap(configsByName);
091            this.defaultWorkspace = defaultWorkspace;
092        }
093    
094        /**
095         * Get the name of this repository
096         * 
097         * @return name
098         */
099        public String getName() {
100            return name;
101        }
102    
103        /**
104         * @return the execution context
105         */
106        public ExecutionContext getExecutionContext() {
107            return context;
108        }
109    
110        /**
111         * @return the connectionFactory
112         */
113        protected RepositoryConnectionFactory getConnectionFactory() {
114            return connectionFactory;
115        }
116    
117        /**
118         * Utility method called by the administrator.
119         */
120        public synchronized void start() {
121            // Do not establish connections to the connectionFactory; these will be established as needed
122        }
123    
124        /**
125         * Return true if this federated repository is running and ready for connections.
126         * 
127         * @return true if running, or false otherwise
128         */
129        public boolean isRunning() {
130            return this.shutdownRequested.get() != true;
131        }
132    
133        /**
134         * Utility method called by the administrator.
135         */
136        public synchronized void shutdown() {
137            this.shutdownRequested.set(true);
138            if (this.openExecutors.get() <= 0) shutdownLatch.countDown();
139        }
140    
141        /**
142         * Utility method called by the administrator.
143         * 
144         * @param timeout
145         * @param unit
146         * @return true if all connections open at the time this method is called were {@link RepositoryConnection#close() closed} in
147         *         the supplied time, or false if the timeout occurred before all the connections were closed
148         * @throws InterruptedException
149         */
150        public boolean awaitTermination( long timeout,
151                                         TimeUnit unit ) throws InterruptedException {
152            // Await until all connections have been closed, or until the timeout occurs
153            return shutdownLatch.await(timeout, unit);
154        }
155    
156        /**
157         * Return true if this federated repository has completed its termination and no longer has any open connections.
158         * 
159         * @return true if terminated, or false otherwise
160         */
161        public boolean isTerminated() {
162            return this.openExecutors.get() != 0;
163        }
164    
165        /**
166         * Add a listener that is to receive notifications to changes to content within this repository. This method does nothing if
167         * the supplied listener is null.
168         * 
169         * @param listener the new listener
170         * @return true if the listener was added, or false if the listener was not added (if reference is null, or if non-null
171         *         listener is already an existing listener)
172         */
173        public boolean addListener( RepositorySourceListener listener ) {
174            if (listener == null) return false;
175            return this.listeners.addIfAbsent(listener);
176        }
177    
178        /**
179         * Remove the supplied listener. This method does nothing if the supplied listener is null.
180         * <p>
181         * This method can safely be called while the federation repository is in use.
182         * </p>
183         * 
184         * @param listener the listener to remove
185         * @return true if the listener was removed, or false if the listener was not registered
186         */
187        public boolean removeListener( RepositorySourceListener listener ) {
188            if (listener == null) return false;
189            return this.listeners.remove(listener);
190        }
191    
192        /**
193         * Get the list of listeners, which is the actual list used by the repository.
194         * 
195         * @return the listeners
196         */
197        public List<RepositorySourceListener> getListeners() {
198            return this.listeners;
199        }
200    
201        /**
202         * Authenticate the supplied username with the supplied credentials, and return whether authentication was successful.
203         * 
204         * @param source the {@link RepositorySource} that should be affiliated with the resulting connection
205         * @param username the username
206         * @param credentials the credentials
207         * @return the repository connection if authentication succeeded, or null otherwise
208         */
209        public RepositoryConnection createConnection( RepositorySource source,
210                                                      String username,
211                                                      Object credentials ) {
212            return new FederatedRepositoryConnection(this, source.getName());
213        }
214    
215        /**
216         * Get the configuration of this repository's workspaces. This set of configurations (as well as each configuration) is
217         * immutable. Therefore, when using a configuration and needing a consistent configuration, maintain a reference to the
218         * configuration during that time (as the actual configuration may be replaced at any time).
219         * 
220         * @return the repository's worksapce configuration at the time this method is called.
221         */
222        public Map<String, FederatedWorkspace> getWorkspaceConfigurations() {
223            return workspaceConfigsByName;
224        }
225    
226        /**
227         * Called by {@link FederatedRepositoryConnection#execute(ExecutionContext, org.jboss.dna.graph.request.Request)}.
228         * 
229         * @param context the execution context in which the executor will be run; may not be null
230         * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
231         * @return the executor
232         */
233        protected RequestProcessor getProcessor( ExecutionContext context,
234                                                 String sourceName ) {
235            Map<String, FederatedWorkspace> workspaces = this.getWorkspaceConfigurations();
236            return new FederatingRequestProcessor(context, sourceName, workspaces, defaultWorkspace, getConnectionFactory());
237        }
238    
239        /**
240         * Called by {@link FederatedRepositoryConnection#FederatedRepositoryConnection(FederatedRepository, String)}.
241         * 
242         * @param connection the connection being opened
243         */
244        /*package*/void register( FederatedRepositoryConnection connection ) {
245            openExecutors.incrementAndGet();
246        }
247    
248        /**
249         * Called by {@link FederatedRepositoryConnection#close()}.
250         * 
251         * @param connection the connection being closed
252         */
253        /*package*/void unregister( FederatedRepositoryConnection connection ) {
254            if (openExecutors.decrementAndGet() <= 0 && shutdownRequested.get()) {
255                // Last connection, so turn out the lights ...
256                shutdownLatch.countDown();
257            }
258        }
259    
260    }