Package org.jboss.dna.repository

Source Code of org.jboss.dna.repository.RepositoryLibrary

/*
* JBoss DNA (http://www.jboss.org/dna)
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.  Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* JBoss DNA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.dna.repository;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.jcip.annotations.ThreadSafe;
import org.jboss.dna.common.util.CheckArg;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.Graph;
import org.jboss.dna.graph.Subgraph;
import org.jboss.dna.graph.connector.RepositoryConnection;
import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
import org.jboss.dna.graph.connector.RepositoryConnectionPool;
import org.jboss.dna.graph.connector.RepositoryContext;
import org.jboss.dna.graph.connector.RepositorySource;
import org.jboss.dna.graph.observe.Observable;
import org.jboss.dna.graph.observe.ObservationBus;
import org.jboss.dna.graph.observe.Observer;
import org.jboss.dna.graph.property.Path;
import org.jboss.dna.repository.service.AbstractServiceAdministrator;
import org.jboss.dna.repository.service.ServiceAdministrator;

/**
* A library of {@link RepositorySource} instances and the {@link RepositoryConnectionPool} used to manage the connections for
* each.
*/
@ThreadSafe
public class RepositoryLibrary implements RepositoryConnectionFactory, Observable {

    /**
     * The administrative component for this service.
     *
     * @author Randall Hauch
     */
    protected class Administrator extends AbstractServiceAdministrator {

        protected Administrator() {
            super(RepositoryI18n.federationServiceName, State.STARTED);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void doStart( State fromState ) {
            super.doStart(fromState);
            RepositoryLibrary.this.start();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void doShutdown( State fromState ) {
            super.doShutdown(fromState);
            RepositoryLibrary.this.shutdown();
        }

        /**
         * {@inheritDoc}
         */
        public boolean awaitTermination( long timeout,
                                         TimeUnit unit ) throws InterruptedException {
            return RepositoryLibrary.this.awaitTermination(timeout, unit);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected boolean doCheckIsTerminated() {
            return RepositoryLibrary.this.isTerminated();
        }

    }

    private final ServiceAdministrator administrator = new Administrator();
    private final ReadWriteLock sourcesLock = new ReentrantReadWriteLock();
    private final Map<String, RepositoryConnectionPool> pools = new HashMap<String, RepositoryConnectionPool>();
    private RepositoryConnectionFactory delegate;
    private final ExecutionContext executionContext;
    private final ObservationBus observationBus = new ObservationBus();
    private final RepositorySource configurationSource;
    private final String configurationWorkspaceName;
    private final Path pathToConfigurationRoot;

    /**
     * Create a new manager instance.
     *
     * @param configurationSource the {@link RepositorySource} that is the configuration repository
     * @param configurationWorkspaceName the name of the workspace in the {@link RepositorySource} that is the configuration
     *        repository, or null if the default workspace of the source should be used (if there is one)
     * @param pathToSourcesConfigurationRoot the path of the node in the configuration source repository that should be treated by
     *        this service as the root of the service's configuration; if null, then "/dna:system" is used
     * @param context the execution context in which this service should run
     * @throws IllegalArgumentException if the <code>executionContextFactory</code> reference is null
     */
    public RepositoryLibrary( RepositorySource configurationSource,
                              String configurationWorkspaceName,
                              Path pathToSourcesConfigurationRoot,
                              final ExecutionContext context ) {
        CheckArg.isNotNull(configurationSource, "configurationSource");
        CheckArg.isNotNull(context, "context");
        CheckArg.isNotNull(pathToSourcesConfigurationRoot, "pathToSourcesConfigurationRoot");
        this.executionContext = context;
        this.configurationSource = configurationSource;
        this.configurationWorkspaceName = configurationWorkspaceName;
        this.pathToConfigurationRoot = pathToSourcesConfigurationRoot;
    }

    /**
     * Get the path to the top-level of the configuration root.
     *
     * @return pathToConfigurationRoot
     */
    protected Path getPathToConfigurationRoot() {
        return pathToConfigurationRoot;
    }

    /**
     * @return configurationSource
     */
    protected RepositorySource getConfigurationSource() {
        return configurationSource;
    }

    /**
     * @return configurationWorkspaceName
     */
    protected String getConfigurationWorkspaceName() {
        return configurationWorkspaceName;
    }

    /**
     * {@inheritDoc}
     * <p>
     * This can be used to register observers for all of the repository sources managed by this library. The supplied observer
     * will receive all of the changes originating from these sources.
     * </p>
     *
     * @see org.jboss.dna.graph.observe.Observable#register(org.jboss.dna.graph.observe.Observer)
     */
    public boolean register( Observer observer ) {
        return observationBus.register(observer);
    }

    /**
     * {@inheritDoc}
     * <p>
     * This can be used to unregister observers for all of the repository sources managed by this library.
     * </p>
     *
     * @see org.jboss.dna.graph.observe.Observable#unregister(org.jboss.dna.graph.observe.Observer)
     */
    public boolean unregister( Observer observer ) {
        return observationBus.unregister(observer);
    }

    /**
     * @return executionContextFactory
     */
    public ExecutionContext getExecutionContext() {
        return executionContext;
    }

    /**
     * @return administrator
     */
    public ServiceAdministrator getAdministrator() {
        return this.administrator;
    }

    /**
     * Utility method called by the administrator.
     */
    protected void start() {
        // Do not establish connections to the pools; these will be established as needed

    }

    /**
     * Utility method called by the administrator.
     */
    protected void shutdown() {
        // Close all connections to the pools. This is done inside the pools write lock.
        try {
            this.sourcesLock.readLock().lock();
            for (RepositoryConnectionPool pool : this.pools.values()) {
                // Shutdown the pool of connections ...
                pool.shutdown();
                // Now close the source (still allows in-use connections to be used) ...
                pool.getRepositorySource().close();
            }
        } finally {
            this.sourcesLock.readLock().unlock();
        }
        // Remove all listeners ...
        this.observationBus.shutdown();
    }

    /**
     * Utility method called by the administrator.
     *
     * @param timeout
     * @param unit
     * @return true if all pools were terminated in the supplied time (or were already terminated), or false if the timeout
     *         occurred before all the connections were closed
     * @throws InterruptedException
     */
    protected boolean awaitTermination( long timeout,
                                        TimeUnit unit ) throws InterruptedException {
        // Check whether all source pools are shut down. This is done inside the pools write lock.
        try {
            this.sourcesLock.readLock().lock();
            for (RepositoryConnectionPool pool : this.pools.values()) {
                if (!pool.awaitTermination(timeout, unit)) return false;
            }
            return true;
        } finally {
            this.sourcesLock.readLock().unlock();
        }
    }

    /**
     * Returns true if this federated repository is in the process of terminating after {@link ServiceAdministrator#shutdown()}
     * has been called on the {@link #getAdministrator() administrator}, but the federated repository has connections that have
     * not yet normally been {@link RepositoryConnection#close() closed}. This method may be useful for debugging. A return of
     * <tt>true</tt> reported a sufficient period after shutdown may indicate that connection users have ignored or suppressed
     * interruption, causing this repository not to properly terminate.
     *
     * @return true if terminating but not yet terminated, or false otherwise
     * @see #isTerminated()
     */
    public boolean isTerminating() {
        try {
            this.sourcesLock.readLock().lock();
            for (RepositoryConnectionPool pool : this.pools.values()) {
                if (pool.isTerminating()) return true;
            }
            return false;
        } finally {
            this.sourcesLock.readLock().unlock();
        }
    }

    /**
     * Return true if this federated repository has completed its termination and no longer has any open connections.
     *
     * @return true if terminated, or false otherwise
     * @see #isTerminating()
     */
    public boolean isTerminated() {
        try {
            this.sourcesLock.readLock().lock();
            for (RepositoryConnectionPool pool : this.pools.values()) {
                if (!pool.isTerminated()) return false;
            }
            return true;
        } finally {
            this.sourcesLock.readLock().unlock();
        }
    }

    /**
     * Get an unmodifiable collection of {@link RepositorySource} names.
     *
     * @return the pools
     */
    public Collection<String> getSourceNames() {
        try {
            this.sourcesLock.readLock().lock();
            return Collections.unmodifiableCollection(new HashSet<String>(this.pools.keySet()));
        } finally {
            this.sourcesLock.readLock().unlock();
        }
    }

    /**
     * Get an unmodifiable collection of {@link RepositorySource} instances managed by this instance.
     *
     * @return the pools
     */
    public Collection<RepositorySource> getSources() {
        List<RepositorySource> sources = new LinkedList<RepositorySource>();
        try {
            this.sourcesLock.readLock().lock();
            for (RepositoryConnectionPool pool : this.pools.values()) {
                sources.add(pool.getRepositorySource());
            }
            return Collections.unmodifiableCollection(sources);
        } finally {
            this.sourcesLock.readLock().unlock();
        }
    }

    /**
     * Get the RepositorySource with the specified name managed by this instance.
     *
     * @param sourceName the name of the source
     * @return the source, or null if no such source exists in this instance
     */
    public RepositorySource getSource( String sourceName ) {
        try {
            this.sourcesLock.readLock().lock();
            RepositoryConnectionPool existingPool = this.pools.get(sourceName);
            return existingPool == null ? null : existingPool.getRepositorySource();
        } finally {
            this.sourcesLock.readLock().unlock();
        }
    }

    /**
     * Get the connection pool managing the {@link RepositorySource} with the specified name managed by this instance.
     *
     * @param sourceName the name of the source
     * @return the pool, or null if no such pool exists in this instance
     */
    public RepositoryConnectionPool getConnectionPool( String sourceName ) {
        try {
            this.sourcesLock.readLock().lock();
            return this.pools.get(sourceName);
        } finally {
            this.sourcesLock.readLock().unlock();
        }
    }

    /**
     * Add the supplied federated source. This method returns false if the source is null.
     *
     * @param source the source to add
     * @return true if the source is added, or false if the reference is null or if there is already an existing source with the
     *         supplied name.
     */
    public boolean addSource( RepositorySource source ) {
        return addSource(source, false);
    }

    /**
     * Add the supplied federated source. This method returns false if the source is null.
     * <p>
     * If a source with the same name already exists, it will be replaced only if <code>replaceIfExisting</code> is true. If this
     * is the case, then the existing source will be removed from the connection pool, and that pool will be
     * {@link RepositoryConnectionPool#shutdown() shutdown} (allowing any in-use connections to be used and finished normally).
     * </p>
     *
     * @param source the source to add
     * @param replaceIfExisting true if an existing source should be replaced, or false if this method should return false if
     *        there is already an existing source with the supplied name.
     * @return true if the source is added, or false if the reference is null or if there is already an existing source with the
     *         supplied name.
     */
    public boolean addSource( RepositorySource source,
                              boolean replaceIfExisting ) {
        if (source == null) return false;
        final String sourceName = source.getName();
        if (!replaceIfExisting) {
            // Don't want to replace existing, so make sure there isn't one already ...
            try {
                this.sourcesLock.readLock().lock();
                if (this.pools.containsKey(sourceName)) return false;
            } finally {
                this.sourcesLock.readLock().unlock();
            }
        }
        // Create a repository context for this source ...
        final ObservationBus observationBus = this.observationBus;
        RepositoryContext repositoryContext = new RepositoryContext() {
            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryContext#getExecutionContext()
             */
            public ExecutionContext getExecutionContext() {
                return RepositoryLibrary.this.getExecutionContext();
            }

            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryContext#getRepositoryConnectionFactory()
             */
            public RepositoryConnectionFactory getRepositoryConnectionFactory() {
                return RepositoryLibrary.this;
            }

            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryContext#getObserver()
             */
            public Observer getObserver() {
                return observationBus.hasObservers() ? observationBus : null;
            }

            /**
             * {@inheritDoc}
             *
             * @see org.jboss.dna.graph.connector.RepositoryContext#getConfiguration(int)
             */
            public Subgraph getConfiguration( int depth ) {
                Subgraph result = null;
                RepositorySource configSource = getConfigurationSource();
                if (configSource != null) {
                    Graph config = Graph.create(configSource, getExecutionContext());
                    String workspaceName = getConfigurationWorkspaceName();
                    if (workspaceName != null) {
                        config.useWorkspace(workspaceName);
                    }
                    Path configPath = getPathToConfigurationRoot();
                    Path sourcePath = getExecutionContext().getValueFactories().getPathFactory().create(configPath, sourceName);
                    result = config.getSubgraphOfDepth(depth).at(sourcePath);
                }
                return result;
            }
        };
        // Do this before we remove the existing pool ...
        source.initialize(repositoryContext);
        RepositoryConnectionPool pool = new RepositoryConnectionPool(source);
        try {
            this.sourcesLock.writeLock().lock();
            // Need to first remove any existing one ...
            RepositoryConnectionPool existingPool = this.pools.remove(sourceName);
            if (existingPool != null) {
                // Then shut down the source gracefully (and don't wait) ...
                existingPool.shutdown();
            }
            this.pools.put(sourceName, pool);
            return true;
        } finally {
            this.sourcesLock.writeLock().unlock();
        }
    }

    /**
     * Remove from this federated repository the supplied source (or a source with the same name as that supplied). This call
     * shuts down the connections in the source in an orderly fashion, allowing those connection currently in use to be used and
     * closed normally, but preventing further connections from being used.
     * <p>
     * This method can safely be called while the federation repository is in use.
     * </p>
     *
     * @param source the source to be removed
     * @param timeToAwait the amount of time to wait while all of the source's connections are closed, or non-positive if the call
     *        should not wait at all
     * @param unit the time unit to be used for <code>timeToAwait</code>
     * @return true if the source was removed, or false if the source was not a source for this repository.
     * @throws InterruptedException if the thread is interrupted while awaiting closing of the connections
     */
    public boolean removeSource( RepositorySource source,
                                 long timeToAwait,
                                 TimeUnit unit ) throws InterruptedException {
        // Use the name; don't use the object equality ...
        return removeSource(source.getName(), timeToAwait, unit) != null;
    }

    /**
     * Remove from this federated repository the source with the supplied name. This call shuts down the connections in the source
     * in an orderly fashion, allowing those connection currently in use to be used and closed normally, but preventing further
     * connections from being used. However, this method never waits until the connections are all closed, and is equivalent to
     * calling <code>removeSource(name,0,TimeUnit.SECONDS)</code>.
     *
     * @param name the name of the source to be removed
     * @return the source with the supplied name that was removed, or null if no existing source matching the supplied name could
     *         be found
     * @see #removeSource(String, long, TimeUnit)
     */
    public RepositorySource removeSource( String name ) {
        try {
            this.sourcesLock.writeLock().lock();
            RepositoryConnectionPool existingPool = this.pools.remove(name);
            if (existingPool != null) {
                // Then shut down the source gracefully (and don't wait) ...
                existingPool.shutdown();
                return existingPool.getRepositorySource();
            }
        } finally {
            this.sourcesLock.writeLock().unlock();
        }
        return null;
    }

    /**
     * Remove from this federated repository the source with the supplied name. This call shuts down the connections in the source
     * in an orderly fashion, allowing those connection currently in use to be used and closed normally, but preventing further
     * connections from being used.
     *
     * @param name the name of the source to be removed
     * @param timeToAwait the amount of time to wait while all of the source's connections are closed, or non-positive if the call
     *        should not wait at all
     * @param unit the time unit to be used for <code>timeToAwait</code>
     * @return the source with the supplied name that was removed, or null if no existing source matching the supplied name could
     *         be found
     * @throws InterruptedException if the thread is interrupted while awaiting closing of the connections
     * @see #removeSource(String)
     */
    public RepositorySource removeSource( String name,
                                          long timeToAwait,
                                          TimeUnit unit ) throws InterruptedException {
        try {
            this.sourcesLock.writeLock().lock();
            RepositoryConnectionPool existingPool = this.pools.remove(name);
            if (existingPool != null) {
                // Then shut down the source gracefully (and don't wait) ...
                existingPool.shutdown();
                if (timeToAwait > 0L) existingPool.awaitTermination(timeToAwait, unit);
                return existingPool.getRepositorySource();
            }
        } finally {
            this.sourcesLock.writeLock().unlock();
        }
        return null;
    }

    /**
     * {@inheritDoc}
     *
     * @see org.jboss.dna.graph.connector.RepositoryConnectionFactory#createConnection(java.lang.String)
     */
    public RepositoryConnection createConnection( String sourceName ) {
        try {
            this.sourcesLock.readLock().lock();
            RepositoryConnectionPool existingPool = this.pools.get(sourceName);
            if (existingPool != null) return existingPool.getConnection();
            RepositoryConnectionFactory delegate = this.delegate;
            if (delegate != null) {
                return delegate.createConnection(sourceName);
            }
        } finally {
            this.sourcesLock.readLock().unlock();
        }
        return null;
    }
}
TOP

Related Classes of org.jboss.dna.repository.RepositoryLibrary

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.