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 }