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