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

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

/*
 * ModeShape (http://www.modeshape.org)
 * 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. 
 *
 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
 * 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.
 *
 * ModeShape 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.modeshape.jcr;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.infinispan.schematic.document.ParsingException;
import org.modeshape.common.annotation.ThreadSafe;
import org.modeshape.common.logging.Logger;
import org.modeshape.jcr.api.Repositories;
import org.modeshape.jcr.api.RepositoryFactory;

/**
 * Service provider for the JCR2 {@code RepositoryFactory} interface. This class provides a single public method,
 * {@link #getRepository(Map)}, that allows for a runtime link to a ModeShape JCR repository.
 * 

* The canonical way to get a reference to this class is to use the {@link ServiceLoader}: * *

 * String configUrl = ... ; // URL that points to your configuration file
 * Map parameters = Collections.singletonMap(JcrRepositoryFactory.URL, configUrl);
 * Repository repository;
 * 
 * for (RepositoryFactory factory : ServiceLoader.load(RepositoryFactory.class)) {
 *     repository = factory.getRepository(parameters);
 *     if (repository != null) break;
 * }
 * 
* * It is also possible to instantiate this class directly. * *
 * RepositoryFactory repoFactory = new JcrRepositoryFactory();    
 * String url = ... ; // URL that points to your configuration file
 * Map params = Collections.singletonMap(JcrRepositoryFactory.URL, url);
 * 
 * Repository repository = repoFactory.getRepository(params);]]>
 * 
* *

*

* Several URL formats are supported: *

    *
  • JNDI location of repository - The URL contains the location in JNDI of an existing * javax.jcr.Repository instance. For example, "jndi:jcr/local/my_repository" is a URL that identifies * the JCR repository located in JNDI at the name "jcr/local/my_repository". Note that the use of such URLs requires that the * repository already be registered in JNDI at that location.
  • *
  • JNDI location of engine and repository name - The URL contains the location in JNDI of an existing * ModeShape {@link org.modeshape.jcr.ModeShapeEngine engine} instance and the name of the javax.jcr.Repository * repository as a URL query parameter. For example, "jndi:jcr/local?repositoryName=my_repository" identifies a * ModeShape engine registered in JNDI at "jcr/local", and looks in that engine for a JCR repository named " * my_repository".
  • *
  • Location of a repository configuration - The URL contains a location that is resolvable to a configuration * file for the repository. If the configuration file has not already been loaded by the factory, then the configuration file is * read and used to deploy a new repository; subsequent uses of the same URL will return the previously deployed repository * instance. Several URL schemes are supported, including classpath:, "file:", http: and * any other URL scheme that can be {@link URL#openConnection() resolved and opened}. For example, " * file://path/to/myRepoConfig.json" identifies the file on the file system at the absolute path " * /path/to/myRepoConfig.json"; "classpath://path/to/myRepoConfig.json" identifies the file at " * /path/to/myRepoConfig.json" on the classpath, and "http://www.example.com/path/to/myRepoConfig.json * " identifies the file "myRepoConfig.json" at the given URL.
  • *
*

* * @see #getRepository(Map) * @see RepositoryFactory#getRepository(Map) */ @ThreadSafe public class JcrRepositoryFactory implements RepositoryFactory { private static final Logger LOG = Logger.getLogger(JcrRepositoryFactory.class); /** * The engine that hosts the deployed repository instances. */ private static final ModeShapeEngine ENGINE = new ModeShapeEngine(); /** * The name of the key for the ModeShape JCR URL in the parameter map * * @deprecated use {@link RepositoryFactory#URL} instead */ @Deprecated public static final String URL = RepositoryFactory.URL; /** * The name of the URL parameter that specifies the repository name. */ public static final String REPOSITORY_NAME_PARAM = JndiJcrRepositoryFactory.REPOSITORY_NAME_PARAM; static { ENGINE.start(); } /** * Shutdown this engine to stop all repositories created by calls to {@link #getRepository(Map)}, terminate any ongoing * background operations (such as sequencing), and reclaim any resources that were acquired by the repositories. This method * may be called multiple times, but only the first time has an effect. *

* Calling this static method is identical to calling the {@link #shutdown()} method on any JcrRepositoryFactory instance, and * is provided for convenience. *

*

* Invoking this method does not preclude creating new {@link Repository} instances with future calls to * {@link #getRepository(Map)}. Any caller using this method as part of an application shutdown process should take care to * cease invocations of {@link #getRepository(Map)} prior to invoking this method. *

*

* This method returns immediately, even before the repositories have been shut down. However, the caller can simply call the * {@link Future#get() get()} method on the returned {@link Future} to block until all repositories have shut down. Note that * the {@link Future#get(long, TimeUnit)} method can be called to block for a maximum amount of time. *

* * @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. */ public static Future shutdownAll() { return ENGINE.shutdown(); } /** * Returns a reference to the appropriate repository for the given parameter map, if one exists. Although the * {@code parameters} map can have any number of entries, this method only considers the entry with the key * JcrRepositoryFactory#URL. *

* The value of this key is treated as a URL with the format {@code PROTOCOL://PATH[?repositoryName=REPOSITORY_NAME]} where * PROTOCOL is "jndi" or "file", PATH is the JNDI name of the {@link ModeShapeEngine} or the path to the configuration file, * and REPOSITORY_NAME is the name of the repository to return if there is more than one JCR repository in the given * {@link ModeShapeEngine} or configuration file. *

* * @param parameters a map of parameters to use to look up the repository; may be null * @return the repository specified by the value of the entry with key {@link #URL}, or null if any of the following are true: *
    *
  • the parameters map is empty; or,
  • *
  • there is no parameter with the {@link #URL}; or,
  • *
  • the value for the {@link #URL} key is null or cannot be parsed into a ModeShape JCR URL; or,
  • *
  • the ModeShape JCR URL is parseable but does not point to a {@link ModeShapeEngine} (in the JNDI tree) or a * configuration file (in the classpath or file system); or,
  • *
  • or there is an error starting up the {@link ModeShapeEngine} with the given configuration information.
  • *
      * @see RepositoryFactory#getRepository(Map) */ @Override @SuppressWarnings( {"unchecked", "rawtypes"} ) public Repository getRepository( Map parameters ) throws RepositoryException { LOG.debug("Trying to load ModeShape JCR Repository with parameters: " + parameters); if (parameters == null) return null; Object rawUrl = parameters.get(RepositoryFactory.URL); if (rawUrl == null) { LOG.debug("No parameter found with key: " + RepositoryFactory.URL); return null; } // Get the URL ... URL url = null; if (rawUrl instanceof URL) { url = (URL)rawUrl; } else { url = urlFor(rawUrl.toString(), null); } if (url == null) return null; // Get the name from the parameters or the URL ... String repositoryName = getRepositoryNameFrom(url, parameters); // Now look up the Repository instance using the URL, the repository name, and any extra parameters ... return getRepository(url, repositoryName, parameters); } protected Repository getRepository( URL url, String repositoryName, Map parameters ) throws RepositoryException { // See if the URL refers to a Repository instance in JNDI, which is probably what would be required // when registering particular repository instances rather than the engine (e.g., via JndiJcrRepositoryFactory). // This enables JCR-2.0-style lookups while using JCR-1.0-style of registering individual Repository instances in JNDI. if ("jndi".equals(url.getProtocol())) { Repository repository = getRepositoryFromJndi(url.getPath(), repositoryName, parameters); if (repository != null) return repository; } else { // Otherwise just use the URL ... JcrRepository repo = getRepositoryFromConfigFile(url); // The name might not match the configuration's name ... if (repositoryName == null || repo.getName().equals(repositoryName)) return repo; } LOG.warn(JcrI18n.repositoryNotFound, repositoryName, url, parameters); return null; } private ModeShapeEngine getEngine() { // Make sure the engine is started ... switch (ENGINE.getState()) { case NOT_RUNNING: ENGINE.start(); break; case STOPPING: // Wait until it's shutdown ... try { ENGINE.shutdown().get(10, TimeUnit.SECONDS); } catch (Exception e) { // ignore and let if fail ... } break; case RESTORING: case RUNNING: case STARTING: // do nothing ... } return ENGINE; } protected String getRepositoryNameFrom( URL url, Map parameters ) { // First look in the parameters ... Object repoName = parameters.get(REPOSITORY_NAME); if (repoName != null) { return repoName.toString(); } // Then look for a query parameter in the URL ... String query = url.getQuery(); if (query != null) { for (String keyValuePair : query.split("&")) { String[] splitPair = keyValuePair.split("="); if (splitPair.length == 2 && REPOSITORY_NAME_PARAM.equals(splitPair[0])) { return splitPair[1]; } } } return null; } /** * Returns a {@link ModeShapeEngine} for the configuration in the given file. *

      * If a {@link ModeShapeEngine} has already been loaded by this class for the given configuration file, that engine will be * reused. *

      * * @param configUrl the URL to the file in the file system or relative to the classpath; may not be null * @return a {@code ModeShapeEngine} that was initialized from the given configuration file or null if no engine could be * initialized from that file without errors. * @throws RepositoryException if there is an error attempting to get the repository */ private JcrRepository getRepositoryFromConfigFile( URL configUrl ) throws RepositoryException { assert configUrl != null; try { if ("file".equals(configUrl.getProtocol())) { try { // Strip any query parameters from the incoming file URLs by creating a new URL with the same protocol, host, // and // port, // but using the URL path as returned by URL#getPath() instead of the URL path and query parameters as // returned by // URL#getFile(). // // We need to strip for the file protocol only because an URL with a file protocol and query parameters has // the // query parameters appended to the file name (e.g., "file:/tmp/foo/bar?repositoryName=foo" turns into an // attempt // to access the "/tmp/foo/bar?repositoryName=foo" file). Other protocol handlers like http handle this // better. configUrl = new URL(configUrl.getProtocol(), configUrl.getHost(), configUrl.getPort(), configUrl.getPath()); } catch (MalformedURLException mfe) { // This shouldn't be possible, since we're creating a new URL from an existing, valid URL throw new IllegalStateException(mfe); } } // Look for an existing repository with the same URL ... String configKey = configUrl.toString(); ModeShapeEngine engine = getEngine(); if (engine.getRepositoryKeys().contains(configKey)) { try { return engine.getRepository(configKey); } catch (NoSuchRepositoryException e) { // Must have been removed since we checked, so just continue on to redeploy it ... } } // Otherwise, we need to deploy and start a new repository ... // Now try reading the configuration ... RepositoryConfiguration config = null; if ("file".equals(configUrl.getProtocol())) { // Strip any query parameters from the incoming file URLs by creating a new URL with the same protocol, host, and // port, // but using the URL path as returned by URL#getPath() instead of the URL path and query parameters as returned by // URL#getFile(). // // We need to strip for the file protocol only because an URL with a file protocol and query parameters has the // query parameters appended to the file name (e.g., "file:/tmp/foo/bar?repositoryName=foo" turns into an attempt // to access the "/tmp/foo/bar?repositoryName=foo" file). Other protocol handlers like http handle this better. try { config = RepositoryConfiguration.read(configUrl); } catch (ParsingException e) { // Try reading from the classpath ... try { String path = classpathResource(configUrl); if (path == null) throw e; config = RepositoryConfiguration.read(path); } catch (Throwable t) { // This didn't work, so throw the original exception throw e; } } } else if ("classpath".equals(configUrl.getProtocol())) { // Look for the configuration file on the classpath ... String path = classpathResource(configUrl); config = RepositoryConfiguration.read(path); } else { // Just try resolving the URL ... config = RepositoryConfiguration.read(configUrl); } // Check if the engine contains repository with the name specified in the configuration String configName = config.getName(); if (engine.getRepositoryNames().contains(configName)) { try { return engine.getRepository(configName); } catch (NoSuchRepositoryException e) { // Must have been removed since we checked, so just continue on to redeploy it ... } } // Now deploy the new repository ... JcrRepository repository = engine.deploy(config); repository.start(); return repository; } catch (RepositoryException err) { throw err; } catch (Exception err) { throw new RepositoryException(err); } } private String classpathResource( URL url ) { String path = url.getPath(); while (path.startsWith("/") && path.length() > 1) { path = path.substring(1); } return path.length() != 0 ? path : null; } /** * Returns a hashtable with the same key/value mappings as the given map * * @param map the set of key/value mappings to convert; may not be null * @return a hashtable with the same key/value mappings as the given map; may be empty, never null */ private Hashtable hashtable( Map map ) { assert map != null; Hashtable hash = new Hashtable(map.size()); for (Map.Entry entry : map.entrySet()) { Object value = entry.getValue(); hash.put(entry.getKey(), value != null ? value.toString() : null); } return hash; } /** * Attempts to look up a {@link Repository} at the given JNDI name. All parameters in the parameters map are passed to the * {@link InitialContext} constructor in a {@link Hashtable}. * * @param jndiName the JNDI name of the JCR repository; may not be null * @param repositoryName the name of the repository; may be null if it was not supplied in the parameters * @param parameters any additional parameters that should be passed to the {@code InitialContext}'s constructor; may be empty * or null * @return the Repository object from JNDI, if one exists at the given name * @throws RepositoryException if there is an error attempting to get the repository */ private Repository getRepositoryFromJndi( String jndiName, String repositoryName, Map parameters ) throws RepositoryException { if (parameters == null) parameters = Collections.emptyMap(); // There should be a parameter with the name ... try { InitialContext ic = new InitialContext(hashtable(parameters)); Object ob = ic.lookup(jndiName); if (ob instanceof ModeShapeEngine) { ModeShapeEngine engine = (ModeShapeEngine)ob; switch (engine.getState()) { case NOT_RUNNING: case STOPPING: LOG.error(JcrI18n.engineAtJndiLocationIsNotRunning, jndiName); return null; case RUNNING: case RESTORING: case STARTING: break; // continue } // There should be a parameter with the name ... if (repositoryName == null) { // No repository name was specified, so see if there's just one in the engine ... if (engine.getRepositories().size() == 1) { repositoryName = engine.getRepositories().keySet().iterator().next(); } } if (repositoryName != null) { repositoryName = repositoryName.trim(); if (repositoryName.length() != 0) { // Look for a repository with the supplied name ... try { JcrRepository repository = engine.getRepository(repositoryName); switch (repository.getState()) { case STARTING: case RUNNING: return repository; default: LOG.debug("The '{0}' repository in JNDI at '{1}' is not (yet) running, but may be (re)started when needed.", repositoryName, jndiName); return null; } } catch (NoSuchRepositoryException e) { LOG.warn(JcrI18n.repositoryNotFoundInEngineAtJndiLocation, repositoryName, jndiName); return null; } } } // Since we also have JndiJcrRepositoryFactory, we can just return null without warning anyone ... } else if (ob instanceof Repositories) { Repositories repos = (Repositories)ob; return repos.getRepository(repositoryName); } else if (ob instanceof Repository) { // Just return the repository instance ... return (Repository)ob; } return null; } catch (NamingException ne) { throw new RepositoryException(ne); } } @Override public Future shutdown() { return ENGINE.shutdown(); } @Override public boolean shutdown( long timeout, TimeUnit unit ) throws InterruptedException { try { return ENGINE.shutdown().get(timeout, unit); } catch (ExecutionException e) { LOG.error(e, JcrI18n.errorShuttingDownJcrRepositoryFactory); return false; } catch (TimeoutException e) { LOG.warn(e, JcrI18n.timeoutWhileShuttingRepositoryDown); return false; } } /** * Returns the repository with the given name from the {@link Repositories} referenced by {@code jcrUrl} if the engine and the * named repository exist, null otherwise. *

      * If the {@code jcrUrl} parameter contains a valid, ModeShape-compatible URL for a {@link ModeShapeEngine} that has not yet * been started, that {@code ModeShapeEngine} will be created and {@link ModeShapeEngine#start() started} as a side effect of * this method. *

      *

      * If the {@code repositoryName} parameter is null, the repository name specified in the {@code jcrUrl} parameter will be used * instead. If no repository name is specified in the {@code jcrUrl} parameter and the {@code ModeShapeEngine} has exactly one * repository, that repository will be used. Otherwise, this method will return {@code null}. *

      * * @param jcrUrl the ModeShape-compatible URL that specifies the {@link Repositories} to be used; may not be null * @param repositoryName the name of the repository to return; may be null * @return the {@link Repository} with the given name from the {@link Repositories} referenced by {@code jcrUrl} if the engine * exists and can be started (or is already started), and the named repository exists or {@code null} is provided as * the {@code repositoryName} and the given engine has exactly one repository or the {@code jcrUrl} specifies a * repository. If any of these conditions do not hold, {@code null} is returned. * @throws RepositoryException if the named repository exists but cannot be accessed * @see ModeShapeEngine#getRepository(String) */ public Repository getRepository( String jcrUrl, String repositoryName ) throws RepositoryException { URL url = urlFor(jcrUrl, repositoryName); if (url == null) return null; return getRepository(url, repositoryName, null); } @Override public Repository getRepository( String repositoryName ) throws RepositoryException { return getEngine().getRepository(repositoryName); } @Override public Set getRepositoryNames() { return getEngine().getRepositoryNames(); } /** * Convenience method to convert a {@code String} into an {@code URL}. This method never throws a * {@link MalformedURLException}, but may throw a {@link NullPointerException} if {@code jcrUrl} is null. * * @param jcrUrl the string representation of an URL that should be converted into an URL; may not be null * @param repoName the optional name of the repository; may be null * @return the URL version of {@code jcrUrl} if {@code jcrUrl} is a valid URL, otherwise null */ private URL urlFor( String jcrUrl, String repoName ) { if (jcrUrl == null || jcrUrl.isEmpty()) { throw new IllegalStateException(JcrI18n.invalidJcrUrl.text(jcrUrl)); } try { if (repoName != null) { repoName = repoName.trim(); String queryParam = "?" + REPOSITORY_NAME_PARAM + "="; if (repoName.length() != 0 && !jcrUrl.contains(queryParam)) { jcrUrl = jcrUrl + queryParam + repoName; } } return new URL(jcrUrl); } catch (MalformedURLException mue) { LOG.warn(mue, JcrI18n.invalidUrl, jcrUrl); return null; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy