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.repository;
025
026 import java.util.Collection;
027 import java.util.Collections;
028 import java.util.HashSet;
029 import java.util.LinkedList;
030 import java.util.List;
031 import java.util.Set;
032 import java.util.concurrent.CopyOnWriteArrayList;
033 import java.util.concurrent.TimeUnit;
034 import java.util.concurrent.locks.ReadWriteLock;
035 import java.util.concurrent.locks.ReentrantReadWriteLock;
036 import net.jcip.annotations.ThreadSafe;
037 import org.jboss.dna.common.util.CheckArg;
038 import org.jboss.dna.graph.ExecutionContext;
039 import org.jboss.dna.graph.connector.RepositoryConnection;
040 import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
041 import org.jboss.dna.graph.connector.RepositoryConnectionPool;
042 import org.jboss.dna.graph.connector.RepositoryContext;
043 import org.jboss.dna.graph.connector.RepositorySource;
044 import org.jboss.dna.repository.mimetype.MimeTypeDetectors;
045 import org.jboss.dna.repository.service.AbstractServiceAdministrator;
046 import org.jboss.dna.repository.service.ServiceAdministrator;
047
048 /**
049 * A library of {@link RepositorySource} instances and the {@link RepositoryConnectionPool} used to manage the connections for
050 * each.
051 *
052 * @author Randall Hauch
053 */
054 @ThreadSafe
055 public class RepositoryLibrary implements RepositoryConnectionFactory {
056
057 /**
058 * The administrative component for this service.
059 *
060 * @author Randall Hauch
061 */
062 protected class Administrator extends AbstractServiceAdministrator {
063
064 protected Administrator() {
065 super(RepositoryI18n.federationServiceName, State.STARTED);
066 }
067
068 /**
069 * {@inheritDoc}
070 */
071 @Override
072 protected void doStart( State fromState ) {
073 super.doStart(fromState);
074 RepositoryLibrary.this.start();
075 }
076
077 /**
078 * {@inheritDoc}
079 */
080 @Override
081 protected void doShutdown( State fromState ) {
082 super.doShutdown(fromState);
083 RepositoryLibrary.this.shutdown();
084 }
085
086 /**
087 * {@inheritDoc}
088 */
089 public boolean awaitTermination( long timeout,
090 TimeUnit unit ) throws InterruptedException {
091 return RepositoryLibrary.this.awaitTermination(timeout, unit);
092 }
093
094 /**
095 * {@inheritDoc}
096 */
097 @Override
098 protected boolean doCheckIsTerminated() {
099 return RepositoryLibrary.this.isTerminated();
100 }
101
102 }
103
104 private final MimeTypeDetectors mimeTypeDetectors = new MimeTypeDetectors();
105 private final ServiceAdministrator administrator = new Administrator();
106 private final ReadWriteLock sourcesLock = new ReentrantReadWriteLock();
107 private final CopyOnWriteArrayList<RepositoryConnectionPool> pools = new CopyOnWriteArrayList<RepositoryConnectionPool>();
108 private RepositoryConnectionFactory delegate;
109 protected final ExecutionContext executionContext;
110 private final RepositoryContext repositoryContext;
111
112 /**
113 * Create a new manager instance.
114 */
115 public RepositoryLibrary() {
116 this(new ExecutionContext(), null);
117 }
118
119 /**
120 * Create a new manager instance.
121 *
122 * @param delegate the connection factory to which this instance should delegate in the event that a source is not found in
123 * this manager; may be null if there is no delegate
124 */
125 public RepositoryLibrary( RepositoryConnectionFactory delegate ) {
126 this(new ExecutionContext(), delegate);
127 }
128
129 /**
130 * Create a new manager instance.
131 *
132 * @param executionContext the execution context, which can be used used by sources to create other {@link ExecutionContext}
133 * instances with different JAAS security contexts
134 * @throws IllegalArgumentException if the <code>executionContextFactory</code> reference is null
135 */
136 public RepositoryLibrary( ExecutionContext executionContext ) {
137 this(executionContext, null);
138 }
139
140 /**
141 * Create a new manager instance.
142 *
143 * @param executionContext the execution context, which can be used used by sources to create other {@link ExecutionContext}
144 * instances with different JAAS security contexts
145 * @param delegate the connection factory to which this instance should delegate in the event that a source is not found in
146 * this manager; may be null if there is no delegate
147 * @throws IllegalArgumentException if the <code>executionContextFactory</code> reference is null
148 */
149 public RepositoryLibrary( ExecutionContext executionContext,
150 RepositoryConnectionFactory delegate ) {
151 CheckArg.isNotNull(executionContext, "executionContext");
152 this.delegate = delegate;
153 this.executionContext = executionContext;
154 this.repositoryContext = new RepositoryContext() {
155 /**
156 * {@inheritDoc}
157 *
158 * @see org.jboss.dna.graph.connector.RepositoryContext#getExecutionContext()
159 */
160 public ExecutionContext getExecutionContext() {
161 return RepositoryLibrary.this.executionContext;
162 }
163
164 /**
165 * {@inheritDoc}
166 *
167 * @see org.jboss.dna.graph.connector.RepositoryContext#getRepositoryConnectionFactory()
168 */
169 public RepositoryConnectionFactory getRepositoryConnectionFactory() {
170 return RepositoryLibrary.this;
171 }
172
173 };
174 }
175
176 /**
177 * @return executionContextFactory
178 */
179 public ExecutionContext getExecutionContext() {
180 return executionContext;
181 }
182
183 /**
184 * @return mimeTypeDetectors
185 */
186 public MimeTypeDetectors getMimeTypeDetectors() {
187 return mimeTypeDetectors;
188 }
189
190 /**
191 * Get the delegate connection factory.
192 *
193 * @return the connection factory to which this instance should delegate in the event that a source is not found in this
194 * manager, or null if there is no delegate
195 */
196 public RepositoryConnectionFactory getDelegate() {
197 return delegate;
198 }
199
200 /**
201 * Set the delegate connection factory.
202 *
203 * @param delegate the connection factory to which this instance should delegate in the event that a source is not found in
204 * this manager; may be null if there is no delegate
205 */
206 public void setDelegate( RepositoryConnectionFactory delegate ) {
207 this.delegate = delegate;
208 }
209
210 /**
211 * @return administrator
212 */
213 public ServiceAdministrator getAdministrator() {
214 return this.administrator;
215 }
216
217 /**
218 * Utility method called by the administrator.
219 */
220 protected void start() {
221 // Do not establish connections to the pools; these will be established as needed
222
223 }
224
225 /**
226 * Utility method called by the administrator.
227 */
228 protected void shutdown() {
229 // Close all connections to the pools. This is done inside the pools write lock.
230 try {
231 this.sourcesLock.readLock().lock();
232 for (RepositoryConnectionPool pool : this.pools) {
233 pool.shutdown();
234 }
235 } finally {
236 this.sourcesLock.readLock().unlock();
237 }
238 }
239
240 /**
241 * Utility method called by the administrator.
242 *
243 * @param timeout
244 * @param unit
245 * @return true if all pools were terminated in the supplied time (or were already terminated), or false if the timeout
246 * occurred before all the connections were closed
247 * @throws InterruptedException
248 */
249 protected boolean awaitTermination( long timeout,
250 TimeUnit unit ) throws InterruptedException {
251 // Check whether all source pools are shut down. This is done inside the pools write lock.
252 try {
253 this.sourcesLock.readLock().lock();
254 for (RepositoryConnectionPool pool : this.pools) {
255 if (!pool.awaitTermination(timeout, unit)) return false;
256 }
257 return true;
258 } finally {
259 this.sourcesLock.readLock().unlock();
260 }
261 }
262
263 /**
264 * Returns true if this federated repository is in the process of terminating after {@link ServiceAdministrator#shutdown()}
265 * has been called on the {@link #getAdministrator() administrator}, but the federated repository has connections that have
266 * not yet normally been {@link RepositoryConnection#close() closed}. This method may be useful for debugging. A return of
267 * <tt>true</tt> reported a sufficient period after shutdown may indicate that connection users have ignored or suppressed
268 * interruption, causing this repository not to properly terminate.
269 *
270 * @return true if terminating but not yet terminated, or false otherwise
271 * @see #isTerminated()
272 */
273 public boolean isTerminating() {
274 try {
275 this.sourcesLock.readLock().lock();
276 for (RepositoryConnectionPool pool : this.pools) {
277 if (pool.isTerminating()) return true;
278 }
279 return false;
280 } finally {
281 this.sourcesLock.readLock().unlock();
282 }
283 }
284
285 /**
286 * Return true if this federated repository has completed its termination and no longer has any open connections.
287 *
288 * @return true if terminated, or false otherwise
289 * @see #isTerminating()
290 */
291 public boolean isTerminated() {
292 try {
293 this.sourcesLock.readLock().lock();
294 for (RepositoryConnectionPool pool : this.pools) {
295 if (!pool.isTerminated()) return false;
296 }
297 return true;
298 } finally {
299 this.sourcesLock.readLock().unlock();
300 }
301 }
302
303 /**
304 * Get an unmodifiable collection of {@link RepositorySource} names.
305 *
306 * @return the pools
307 */
308 public Collection<String> getSourceNames() {
309 Set<String> sourceNames = new HashSet<String>();
310 for (RepositoryConnectionPool pool : this.pools) {
311 sourceNames.add(pool.getRepositorySource().getName());
312 }
313 return Collections.unmodifiableCollection(sourceNames);
314 }
315
316 /**
317 * Get an unmodifiable collection of {@link RepositorySource} instances managed by this instance.
318 *
319 * @return the pools
320 */
321 public Collection<RepositorySource> getSources() {
322 List<RepositorySource> sources = new LinkedList<RepositorySource>();
323 for (RepositoryConnectionPool pool : this.pools) {
324 sources.add(pool.getRepositorySource());
325 }
326 return Collections.unmodifiableCollection(sources);
327 }
328
329 /**
330 * Get the RepositorySource with the specified name managed by this instance.
331 *
332 * @param sourceName the name of the source
333 * @return the source, or null if no such source exists in this instance
334 */
335 public RepositorySource getSource( String sourceName ) {
336 try {
337 this.sourcesLock.readLock().lock();
338 for (RepositoryConnectionPool existingPool : this.pools) {
339 RepositorySource source = existingPool.getRepositorySource();
340 if (source.getName().equals(sourceName)) return source;
341 }
342 } finally {
343 this.sourcesLock.readLock().unlock();
344 }
345 return null;
346 }
347
348 /**
349 * Get the connection pool managing the {@link RepositorySource} with the specified name managed by this instance.
350 *
351 * @param sourceName the name of the source
352 * @return the pool, or null if no such pool exists in this instance
353 */
354 public RepositoryConnectionPool getConnectionPool( String sourceName ) {
355 try {
356 this.sourcesLock.readLock().lock();
357 for (RepositoryConnectionPool existingPool : this.pools) {
358 RepositorySource source = existingPool.getRepositorySource();
359 if (source.getName().equals(sourceName)) return existingPool;
360 }
361 } finally {
362 this.sourcesLock.readLock().unlock();
363 }
364 return null;
365 }
366
367 /**
368 * Add the supplied federated source. This method returns false if the source is null.
369 *
370 * @param source the source to add
371 * @return true if the source is added, or false if the reference is null or if there is already an existing source with the
372 * supplied name.
373 */
374 public boolean addSource( RepositorySource source ) {
375 if (source == null) return false;
376 try {
377 this.sourcesLock.writeLock().lock();
378 for (RepositoryConnectionPool existingPool : this.pools) {
379 if (existingPool.getRepositorySource().getName().equals(source.getName())) return false;
380 }
381 source.initialize(repositoryContext);
382 RepositoryConnectionPool pool = new RepositoryConnectionPool(source);
383 this.pools.add(pool);
384 return true;
385 } finally {
386 this.sourcesLock.writeLock().unlock();
387 }
388 }
389
390 /**
391 * Remove from this federated repository the supplied source (or a source with the same name as that supplied). This call
392 * shuts down the connections in the source in an orderly fashion, allowing those connection currently in use to be used and
393 * closed normally, but preventing further connections from being used.
394 * <p>
395 * This method can safely be called while the federation repository is in use.
396 * </p>
397 *
398 * @param source the source to be removed
399 * @param timeToAwait the amount of time to wait while all of the source's connections are closed, or non-positive if the call
400 * should not wait at all
401 * @param unit the time unit to be used for <code>timeToAwait</code>
402 * @return true if the source was removed, or false if the source was not a source for this repository.
403 * @throws InterruptedException if the thread is interrupted while awaiting closing of the connections
404 */
405 public boolean removeSource( RepositorySource source,
406 long timeToAwait,
407 TimeUnit unit ) throws InterruptedException {
408 // Use the name; don't use the object equality ...
409 return removeSource(source.getName(), timeToAwait, unit) != null;
410 }
411
412 /**
413 * Remove from this federated repository the source with the supplied name. This call shuts down the connections in the source
414 * in an orderly fashion, allowing those connection currently in use to be used and closed normally, but preventing further
415 * connections from being used.
416 *
417 * @param name the name of the source to be removed
418 * @param timeToAwait the amount of time to wait while all of the source's connections are closed, or non-positive if the call
419 * should not wait at all
420 * @param unit the time unit to be used for <code>timeToAwait</code>
421 * @return the source with the supplied name that was removed, or null if no existing source matching the supplied name could
422 * be found
423 * @throws InterruptedException if the thread is interrupted while awaiting closing of the connections
424 */
425 public RepositorySource removeSource( String name,
426 long timeToAwait,
427 TimeUnit unit ) throws InterruptedException {
428 try {
429 this.sourcesLock.writeLock().lock();
430 for (RepositoryConnectionPool existingPool : this.pools) {
431 if (existingPool.getRepositorySource().getName().equals(name)) {
432 // Shut down the source ...
433 existingPool.shutdown();
434 if (timeToAwait > 0L) existingPool.awaitTermination(timeToAwait, unit);
435 }
436 return existingPool.getRepositorySource();
437 }
438 } finally {
439 this.sourcesLock.writeLock().unlock();
440 }
441 return null;
442 }
443
444 /**
445 * {@inheritDoc}
446 *
447 * @see org.jboss.dna.graph.connector.RepositoryConnectionFactory#createConnection(java.lang.String)
448 */
449 public RepositoryConnection createConnection( String sourceName ) {
450 try {
451 this.sourcesLock.readLock().lock();
452 for (RepositoryConnectionPool existingPool : this.pools) {
453 RepositorySource source = existingPool.getRepositorySource();
454 if (source.getName().equals(sourceName)) return existingPool.getConnection();
455 }
456 RepositoryConnectionFactory delegate = this.delegate;
457 if (delegate != null) {
458 return delegate.createConnection(sourceName);
459 }
460 } finally {
461 this.sourcesLock.readLock().unlock();
462 }
463 return null;
464 }
465 }