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 }