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 }