All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.ModeShapeEngine Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * ModeShape (http://www.modeshape.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.modeshape.jcr;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import org.infinispan.schematic.document.Changes;
import org.infinispan.schematic.document.Editor;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.collection.Problems;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.common.util.ImmediateFuture;
import org.modeshape.common.util.NamedThreadFactory;
import org.modeshape.jcr.api.Repositories;

/**
 * A container for repositories.
 */
@ThreadSafe
public class ModeShapeEngine implements Repositories {

    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    private static final Logger LOGGER = Logger.getLogger(ModeShapeEngine.class);

    private final Map repositories = new HashMap();
    private ExecutorService repositoryStarterService;
    private volatile State state = State.NOT_RUNNING;

    public enum State {
        NOT_RUNNING,
        STARTING,
        RUNNING,
        RESTORING,
        STOPPING
    }

    public ModeShapeEngine() {
    }

    protected final boolean checkRunning() {
        if (state == State.RUNNING) return true;
        throw new IllegalStateException(JcrI18n.engineIsNotRunning.text());
    }

    /**
     * Get the running state of this engine.
     * 
     * @return the current state; never null
     */
    public State getState() {
        return state;
    }

    /**
     * Start this engine to make it available for use. This method does nothing if the engine is already running.
     * 
     * @see #shutdown()
     */
    public void start() {
        if (state == State.RUNNING) return;
        final Lock lock = this.lock.writeLock();
        try {
            lock.lock();
            this.state = State.STARTING;

            // Create an executor service that we'll use to start the repositories ...
            ThreadFactory threadFactory = new NamedThreadFactory("modeshape-start-repo");
            repositoryStarterService = Executors.newCachedThreadPool(threadFactory);

            state = State.RUNNING;
        } catch (RuntimeException e) {
            state = State.NOT_RUNNING;
            throw e;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Shutdown this engine to stop all repositories, terminate any ongoing background operations (such as sequencing), and
     * reclaim any resources that were acquired by this engine. This method may be called multiple times, but only the first time
     * has an effect.
     * 

* This is equivalent to calling shutdown(true) * * @return a future that allows the caller to block until the engine is shutdown; any error during shutdown will be thrown * when {@link Future#get() getting} the result from the future, where the exception is wrapped in a * {@link ExecutionException}. The value returned from the future will always be true if the engine shutdown (or was * not running), or false if the engine is still running. * @see #start() */ public Future shutdown() { return shutdown(true); } /** * Shutdown this engine, optionally stopping all still-running repositories. * * @param forceShutdownOfAllRepositories true if the engine should be shutdown even if there are currently-running * repositories, or false if the engine should not be shutdown if at least one repository is still running. * @return a future that allows the caller to block until the engine is shutdown; any error during shutdown will be thrown * when {@link Future#get() getting} the repository from the future, where the exception is wrapped in a * {@link ExecutionException}. The value returned from the future will always be true if the engine shutdown (or was * not running), or false if the engine is still running. * @see #start() */ public Future shutdown( boolean forceShutdownOfAllRepositories ) { if (!forceShutdownOfAllRepositories) { // Check to see if there are any still running ... final Lock lock = this.lock.readLock(); try { lock.lock(); for (JcrRepository repository : repositories.values()) { switch (repository.getState()) { case NOT_RUNNING: case STOPPING: break; case RESTORING: case RUNNING: case STARTING: // This repository is still running, so fail return ImmediateFuture.create(Boolean.FALSE); } } // If we got to here, there are no more running repositories ... } finally { lock.unlock(); } } // Create a simple executor that will do the backgrounding for us ... final ExecutorService executor = Executors.newSingleThreadExecutor(); try { // Submit a runnable to shutdown the repositories ... return executor.submit(new Callable() { @Override public Boolean call() throws Exception { return doShutdown(); } }); } finally { // Now shutdown the executor and return the future ... executor.shutdown(); } } /** * Do the work of shutting down this engine and its repositories. * * @return true if the engine was shutdown (or was not running), and false if the engine is still running and could not be * shutdown */ protected boolean doShutdown() { if (state == State.NOT_RUNNING) { LOGGER.debug("Engine already shut down."); return true; } LOGGER.debug("Shutting down engine..."); final Lock lock = this.lock.writeLock(); try { lock.lock(); state = State.STOPPING; if (!repositories.isEmpty()) { // Now go through all of the repositories and request they all be shutdown ... Queue> repoFutures = new LinkedList>(); Queue repoNames = new LinkedList(); for (JcrRepository repository : repositories.values()) { if (repository != null) { repoNames.add(repository.getName()); repoFutures.add(repository.shutdown()); } } // Now block while each is shutdown ... while (repoFutures.peek() != null) { String repoName = repoNames.poll(); try { // Get the results from the future (this will return only when the shutdown has completed) ... repoFutures.poll().get(); // We've successfully shut down, so remove it from the map ... repositories.remove(repoName); } catch (ExecutionException e) { Logger.getLogger(getClass()).error(e, JcrI18n.failedToShutdownDeployedRepository, repoName); } catch (InterruptedException e) { Logger.getLogger(getClass()).error(e, JcrI18n.failedToShutdownDeployedRepository, repoName); } } } if (repositories.isEmpty()) { // All repositories were properly shutdown, so now stop the service for starting and shutting down the repos ... repositoryStarterService.shutdown(); repositoryStarterService = null; // Do not clear the set of repositories, so that restarting will work just fine ... this.state = State.NOT_RUNNING; } else { // Could not shut down all repositories, so keep running .. this.state = State.RUNNING; } } catch (RuntimeException e) { this.state = State.RUNNING; throw e; } finally { lock.unlock(); } return this.state != State.RUNNING; } /** * Get the deployed {@link Repository} instance with the given the name. * * @param repositoryName the name of the deployed repository * @return the named repository instance * @throws IllegalArgumentException if the repository name is null, blank or invalid * @throws NoSuchRepositoryException if there is no repository with the specified name * @throws IllegalStateException if this engine is not {@link #getState() running} * @see #deploy(RepositoryConfiguration) * @see #undeploy(String) */ @Override public final JcrRepository getRepository( String repositoryName ) throws NoSuchRepositoryException { CheckArg.isNotEmpty(repositoryName, "repositoryName"); checkRunning(); final Lock lock = this.lock.readLock(); try { lock.lock(); JcrRepository repository = repositories.get(repositoryName); if (repository == null) { throw new NoSuchRepositoryException(JcrI18n.repositoryDoesNotExist.text(repositoryName)); } return repository; } finally { lock.unlock(); } } @Override public Set getRepositoryNames() { checkRunning(); final Lock lock = this.lock.readLock(); try { lock.lock(); Set names = new HashSet(); for (JcrRepository repository : repositories.values()) { names.add(repository.getName()); } return names; } finally { lock.unlock(); } } protected Set getRepositoryKeys() { checkRunning(); final Lock lock = this.lock.readLock(); try { lock.lock(); return new HashSet(repositories.keySet()); } finally { lock.unlock(); } } /** * Get the state of the deployed {@link Repository} instance with the given the name. * * @param repositoryName the name of the deployed repository * @return the state of the repository instance; never null * @throws IllegalArgumentException if the repository name is null, blank or invalid * @throws NoSuchRepositoryException if there is no repository with the specified name * @throws IllegalStateException if this engine is not {@link #getState() running} * @see #deploy(RepositoryConfiguration) * @see #undeploy(String) */ public final State getRepositoryState( String repositoryName ) throws NoSuchRepositoryException { return getRepository(repositoryName).getState(); } /** * Get the immutable configuration for the repository with the supplied name. * * @param repositoryName the name of the deployed repository * @return the repository configuration; never null * @throws IllegalArgumentException if the repository name is null, blank or invalid * @throws NoSuchRepositoryException if there is no repository with the specified name * @throws IllegalStateException if this engine is not {@link #getState() running} */ public final RepositoryConfiguration getRepositoryConfiguration( String repositoryName ) throws NoSuchRepositoryException { return getRepository(repositoryName).getConfiguration(); } /** * Asynchronously start the deployed {@link Repository} instance with the given the name, and return a future that will return * the Repository instance. If the Repository is already running, this method returns a future that returns immediately. *

* Note that the caller does not have to wait for the startup process to complete. However, to do so the caller merely calls * {@link Future#get() get()} or {@link Future#get(long, TimeUnit) get(long,TimeUnit)} on the future to return the repository * instance. Note that any exceptions thrown during the startup process will be wrapped in an {@link ExecutionException} * thrown by the Future's get methods. *

* * @param repositoryName the name of the deployed repository * @return the state of the repository instance; never null * @throws IllegalArgumentException if the repository name is null, blank or invalid * @throws NoSuchRepositoryException if there is no repository with the specified name * @throws IllegalStateException if this engine is not {@link #getState() running} * @see #deploy(RepositoryConfiguration) * @see #undeploy(String) */ public final Future startRepository( final String repositoryName ) throws NoSuchRepositoryException { final JcrRepository repository = getRepository(repositoryName); if (repository.getState() == State.RUNNING) { return ImmediateFuture.create(repository); } // Create an initializer that will start the repository ... return repositoryStarterService.submit(new Callable() { @Override public JcrRepository call() throws Exception { // Instantiate (and start) the repository ... try { repository.start(); return repository; } catch (Exception e) { // Something went wrong, so undeploy the repository ... undeploy(repositoryName); throw e; } } }); } /** * Asynchronously shutdown the deployed {@link Repository} instance with the given the name, and return a future that will * return whether the Repository instance is shutdown. If the Repository is not running, the resulting future will return * immediately. *

* Note that the caller does not have to wait for the shutdown to completed. However, to do so the caller merely calls * {@link Future#get() get()} or {@link Future#get(long, TimeUnit) get(long,TimeUnit)} on the future to return a boolean flag * specifying whether the Repository instance is shutdown (not running). Note that any exceptions thrown during the shutdown * will be wrapped in an {@link ExecutionException} thrown by the Future's get methods. *

* * @param repositoryName the name of the deployed repository * @return a future wrapping the asynchronous shutdown process; never null, and {@link Future#get()} will return whether the * @throws IllegalArgumentException if the repository name is null, blank or invalid * @throws NoSuchRepositoryException if there is no repository with the specified name * @throws IllegalStateException if this engine is not {@link #getState() running} * @see #deploy(RepositoryConfiguration) * @see #undeploy(String) */ public final Future shutdownRepository( String repositoryName ) throws NoSuchRepositoryException { return getRepository(repositoryName).shutdown(); } /** * Get an instantaneous snapshot of the JCR repositories and their state. Note that the results are accurate only when this * methods returns. * * @return the immutable map of repository states keyed by repository names; never null */ public Map getRepositories() { checkRunning(); Map results = new HashMap(); final Lock lock = this.lock.readLock(); try { lock.lock(); for (JcrRepository repository : repositories.values()) { results.put(repository.getName(), repository.getState()); } } finally { lock.unlock(); } return Collections.unmodifiableMap(results); } /** * Returns a copy of the repositories. Note that when returned, not all repositories may be active. * * @return a copy of the repositories; never null */ protected Collection repositories() { if (this.state == State.RUNNING) { final Lock lock = this.lock.readLock(); try { lock.lock(); return new ArrayList(repositories.values()); } finally { lock.unlock(); } } return Collections.emptyList(); } /** * Deploy a new repository with the given configuration. This method will fail if this engine already contains a repository * with the specified name. * * @param repositoryConfiguration the configuration for the repository * @return the deployed repository instance, which must be {@link #startRepository(String) started} before it can be used; * never null * @throws ConfigurationException if the configuration is not valid * @throws RepositoryException if there is already a deployed repository with the specified name, or if there is a problem * deploying the repository * @throws IllegalArgumentException if the configuration is null * @see #deploy(RepositoryConfiguration) * @see #update(String, Changes) * @see #undeploy(String) */ public JcrRepository deploy( final RepositoryConfiguration repositoryConfiguration ) throws ConfigurationException, RepositoryException { return deploy(repositoryConfiguration, null); } /** * Deploy a new repository with the given configuration. This method will fail if this engine already contains a repository * with the specified name. * * @param repositoryConfiguration the configuration for the repository * @param repositoryKey the key by which this repository is known to this engine; may be null if the * {@link RepositoryConfiguration#getName() repository's name} should be used * @return the deployed repository instance, which must be {@link #startRepository(String) started} before it can be used; * never null * @throws ConfigurationException if the configuration is not valid * @throws RepositoryException if there is already a deployed repository with the specified name, or if there is a problem * deploying the repository * @throws IllegalArgumentException if the configuration is null * @see #deploy(RepositoryConfiguration) * @see #update(String, Changes) * @see #undeploy(String) */ protected JcrRepository deploy( final RepositoryConfiguration repositoryConfiguration, final String repositoryKey ) throws ConfigurationException, RepositoryException { CheckArg.isNotNull(repositoryConfiguration, "repositoryConfiguration"); checkRunning(); final String repoName = repositoryKey != null ? repositoryKey : repositoryConfiguration.getName(); Problems problems = repositoryConfiguration.validate(); if (problems.hasErrors()) { throw new ConfigurationException(problems, JcrI18n.repositoryConfigurationIsNotValid.text(repoName, problems.toString())); } // Now try to deploy the repository ... JcrRepository repository = null; final Lock lock = this.lock.writeLock(); try { lock.lock(); if (this.repositories.containsKey(repoName)) { throw new RepositoryException(JcrI18n.repositoryIsAlreadyDeployed.text(repoName)); } // Instantiate (but do not start!) the repository, store it in our map, and return it ... repository = new JcrRepository(repositoryConfiguration); this.repositories.put(repoName, repository); } finally { lock.unlock(); } return repository; } /** * Update the configuration of a deployed repository by applying the set of changes to that repository's configuration. The * changes can be built by obtaining the configuration for the deployed instance, obtaining an editor (which actually contains * a copy of the configuration) and using it to make changes, and then getting the changes from the editor. The benefit of * this approach is that the changes can be made in an isolated copy of the configuration and all of the changes applied en * masse. *

* The basic outline for modifying a repository's configuration is as follows: *

    *
  1. Get the current configuration for the repository that is to be changed
  2. *
  3. Get an editor from that configuration
  4. *
  5. Use the editor to capture and validate the changes you want to make
  6. *
  7. Update the repository's configuration with the changes from the editor
  8. *
* Here's some code that shows how this is done: * *
     *   ModeShapeEngine engine = ...
     *   Repository deployed = engine.{@link ModeShapeEngine#getRepository(String) getRepository("repo")};
     *   RepositoryConfiguration deployedConfig = deployed.{@link JcrRepository#getConfiguration() getConfiguration()};
     *   
     *   // Create an editor, which is actually manipulating a copy of the configuration document ...
     *   Editor editor = deployedConfig.{@link RepositoryConfiguration#edit() edit()};
     *   
     *   // Modify the copy of the configuration (we'll do something trivial here) ...
     *   editor.setNumber(FieldName.LARGE_VALUE_SIZE_IN_BYTES,8096);
     *   
     *   // Get our changes and validate them ...
     *   Changes changes = editor.{@link Editor#getChanges() getChanges()};
     *   Results validationResults = deployedConfig.{@link RepositoryConfiguration#validate(Changes) validate(changes)};
     *   if ( validationResults.hasErrors() ) {
     *       // you've done something wrong with your editor
     *   } else {
     *       // Update the deployed repository's configuration with these changes ...
     *       Future<Boolean> future = engine.{@link ModeShapeEngine#update(String, Changes) update("repo",changes)};
     *           
     *       // Optionally block while the repository instance is changed to 
     *       // reflect the new configuration ...
     *       JcrRepository updated = future.get();
     *   }
     * 
* *

*

* Note that this method blocks while the changes to the configuration are validated and applied, but before the repository * has changed to reflect the new configuration, which is done asynchronously. The resulting future represents that * asynchronous process, and the future can be used to block until that updating is completed. *

* * @param repositoryName the name of the repository * @param changes the changes that should be applied to the repository's configuration * @return a future that allows the caller to block until the repository has completed all of its changes. * @throws ConfigurationException if the configuration is not valid with the supplied changes * @throws NoSuchRepositoryException if there is no repository with the specified name * @throws RepositoryException if there is a problem updating the repository * @throws IllegalArgumentException if any of the parameters are null or invalid * @see #deploy(RepositoryConfiguration) * @see #undeploy(String) */ public Future update( final String repositoryName, final Changes changes ) throws ConfigurationException, NoSuchRepositoryException, RepositoryException { final Lock lock = this.lock.writeLock(); try { lock.lock(); // Get the repository ... final JcrRepository repository = this.repositories.get(repositoryName); if (repository == null) { // There is no repository with this name ... throw new NoSuchRepositoryException(JcrI18n.repositoryDoesNotExist.text(repositoryName)); } // Determine if the changes would result in a valid repository configuration ... RepositoryConfiguration config = repository.getConfiguration(); Problems problems = config.validate(changes); repository.setConfigurationProblems(problems); if (problems.hasErrors()) { throw new ConfigurationException(problems, JcrI18n.repositoryConfigurationIsNotValid.text(repositoryName, problems.toString())); } // Apply the changes immediately (synchronously) ... try { repository.apply(changes); } catch (Exception e) { throw new ConfigurationException(problems, JcrI18n.repositoryConfigurationIsNotValid.text(repositoryName, e.getMessage()), e); } return new ImmediateFuture(repository); } finally { lock.unlock(); } } /** * Stop and undeploy the named {@link Repository}. * * @param repositoryName the name of the deployed repository * @return a future that allows the caller to block until the repository is shutdown; if the repository could not shutdown, * {@link Future#get() getting} the repository from the future will throw the exception wrapped in a * {@link ExecutionException} * @throws IllegalArgumentException if the repository name is null, blank or invalid * @throws NoSuchRepositoryException if there is no repository with the specified name * @throws IllegalStateException if this engine was not {@link #start() started} */ public Future undeploy( final String repositoryName ) throws NoSuchRepositoryException { CheckArg.isNotEmpty(repositoryName, "repositoryName"); checkRunning(); // Now try to undeploy the repository ... final Lock lock = this.lock.writeLock(); try { lock.lock(); final JcrRepository repository = this.repositories.remove(repositoryName); if (repository == null) { // There is no repository with this name ... throw new NoSuchRepositoryException(JcrI18n.repositoryDoesNotExist.text(repositoryName)); } // There is an existing repository, so start to shut it down (note that it may fail) ... return repository.shutdown(); } finally { lock.unlock(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy