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.Map;
029    import java.util.concurrent.CountDownLatch;
030    import java.util.concurrent.TimeUnit;
031    import java.util.concurrent.atomic.AtomicBoolean;
032    import java.util.concurrent.atomic.AtomicInteger;
033    import net.jcip.annotations.ThreadSafe;
034    import org.jboss.dna.common.util.CheckArg;
035    import org.jboss.dna.graph.ExecutionContext;
036    import org.jboss.dna.graph.connector.RepositoryConnection;
037    import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
038    import org.jboss.dna.graph.connector.RepositorySource;
039    import org.jboss.dna.graph.request.processor.RequestProcessor;
040    
041    /**
042     * The component that represents a single federated repository. The federated repository uses a set of {@link RepositorySource
043     * federated connectionFactory} as designated by name through the {@link #getWorkspaceConfigurations() configurations}, and
044     * provides the logic of interacting with those connectionFactory and presenting a single unified graph.
045     * 
046     * @author Randall Hauch
047     */
048    @ThreadSafe
049    public class FederatedRepository {
050    
051        private final String name;
052        private final ExecutionContext context;
053        private final RepositoryConnectionFactory connectionFactory;
054        private final Map<String, FederatedWorkspace> workspaceConfigsByName;
055        private final FederatedWorkspace defaultWorkspace;
056        private final AtomicInteger openExecutors = new AtomicInteger(0);
057        private final CountDownLatch shutdownLatch = new CountDownLatch(1);
058        private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
059    
060        /**
061         * Create a federated repository instance.
062         * 
063         * @param repositoryName the name of the repository
064         * @param context the execution context
065         * @param connectionFactory the factory for {@link RepositoryConnection} instances that should be used
066         * @param workspaces the workspace configurations for this repository, with the default workspace being first; may not be null
067         * @throws IllegalArgumentException if any of the parameters are null, or if the name is blank
068         */
069        public FederatedRepository( String repositoryName,
070                                    ExecutionContext context,
071                                    RepositoryConnectionFactory connectionFactory,
072                                    Iterable<FederatedWorkspace> workspaces ) {
073            CheckArg.isNotEmpty(repositoryName, "repositoryName");
074            CheckArg.isNotNull(connectionFactory, "connectionFactory");
075            CheckArg.isNotNull(context, "context");
076            CheckArg.isNotNull(workspaces, "workspaces");
077            this.name = repositoryName;
078            this.context = context;
079            this.connectionFactory = connectionFactory;
080            FederatedWorkspace defaultWorkspace = null;
081            Map<String, FederatedWorkspace> configsByName = new HashMap<String, FederatedWorkspace>();
082            for (FederatedWorkspace workspace : workspaces) {
083                if (defaultWorkspace == null) defaultWorkspace = workspace;
084                configsByName.put(workspace.getName(), workspace);
085            }
086            this.workspaceConfigsByName = Collections.unmodifiableMap(configsByName);
087            this.defaultWorkspace = defaultWorkspace;
088        }
089    
090        /**
091         * Get the name of this repository
092         * 
093         * @return name
094         */
095        public String getName() {
096            return name;
097        }
098    
099        /**
100         * @return the execution context
101         */
102        public ExecutionContext getExecutionContext() {
103            return context;
104        }
105    
106        /**
107         * @return the connectionFactory
108         */
109        protected RepositoryConnectionFactory getConnectionFactory() {
110            return connectionFactory;
111        }
112    
113        /**
114         * Utility method called by the administrator.
115         */
116        public synchronized void start() {
117            // Do not establish connections to the connectionFactory; these will be established as needed
118        }
119    
120        /**
121         * Return true if this federated repository is running and ready for connections.
122         * 
123         * @return true if running, or false otherwise
124         */
125        public boolean isRunning() {
126            return this.shutdownRequested.get() != true;
127        }
128    
129        /**
130         * Utility method called by the administrator.
131         */
132        public synchronized void shutdown() {
133            this.shutdownRequested.set(true);
134            if (this.openExecutors.get() <= 0) shutdownLatch.countDown();
135        }
136    
137        /**
138         * Utility method called by the administrator.
139         * 
140         * @param timeout
141         * @param unit
142         * @return true if all connections open at the time this method is called were {@link RepositoryConnection#close() closed} in
143         *         the supplied time, or false if the timeout occurred before all the connections were closed
144         * @throws InterruptedException
145         */
146        public boolean awaitTermination( long timeout,
147                                         TimeUnit unit ) throws InterruptedException {
148            // Await until all connections have been closed, or until the timeout occurs
149            return shutdownLatch.await(timeout, unit);
150        }
151    
152        /**
153         * Return true if this federated repository has completed its termination and no longer has any open connections.
154         * 
155         * @return true if terminated, or false otherwise
156         */
157        public boolean isTerminated() {
158            return this.openExecutors.get() != 0;
159        }
160    
161        /**
162         * Authenticate the supplied username with the supplied credentials, and return whether authentication was successful.
163         * 
164         * @param source the {@link RepositorySource} that should be affiliated with the resulting connection
165         * @param username the username
166         * @param credentials the credentials
167         * @return the repository connection if authentication succeeded, or null otherwise
168         */
169        public RepositoryConnection createConnection( RepositorySource source,
170                                                      String username,
171                                                      Object credentials ) {
172            return new FederatedRepositoryConnection(this, source.getName());
173        }
174    
175        /**
176         * Get the configuration of this repository's workspaces. This set of configurations (as well as each configuration) is
177         * immutable. Therefore, when using a configuration and needing a consistent configuration, maintain a reference to the
178         * configuration during that time (as the actual configuration may be replaced at any time).
179         * 
180         * @return the repository's worksapce configuration at the time this method is called.
181         */
182        public Map<String, FederatedWorkspace> getWorkspaceConfigurations() {
183            return workspaceConfigsByName;
184        }
185    
186        /**
187         * Called by {@link FederatedRepositoryConnection#execute(ExecutionContext, org.jboss.dna.graph.request.Request)}.
188         * 
189         * @param context the execution context in which the executor will be run; may not be null
190         * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
191         * @return the executor
192         */
193        protected RequestProcessor getProcessor( ExecutionContext context,
194                                                 String sourceName ) {
195            Map<String, FederatedWorkspace> workspaces = this.getWorkspaceConfigurations();
196            return new FederatingRequestProcessor(context, sourceName, workspaces, defaultWorkspace, getConnectionFactory());
197        }
198    
199        /**
200         * Called by {@link FederatedRepositoryConnection#FederatedRepositoryConnection(FederatedRepository, String)}.
201         * 
202         * @param connection the connection being opened
203         */
204        /*package*/void register( FederatedRepositoryConnection connection ) {
205            openExecutors.incrementAndGet();
206        }
207    
208        /**
209         * Called by {@link FederatedRepositoryConnection#close()}.
210         * 
211         * @param connection the connection being closed
212         */
213        /*package*/void unregister( FederatedRepositoryConnection connection ) {
214            if (openExecutors.decrementAndGet() <= 0 && shutdownRequested.get()) {
215                // Last connection, so turn out the lights ...
216                shutdownLatch.countDown();
217            }
218        }
219    
220    }