org.modeshape.jcr.JndiRepositoryFactory Maven / Gradle / Ivy
/*
* 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.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.event.EventContext;
import javax.naming.event.NamespaceChangeListener;
import javax.naming.event.NamingEvent;
import javax.naming.event.NamingExceptionEvent;
import javax.naming.spi.ObjectFactory;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.StringUtil;
import org.xml.sax.SAXException;
/**
* The {@code JndiRepositoryFactory} class provides a means of initializing and accessing {@link Repository repositories} in a
* JNDI tree. Example JNDI Configurations
Tomcat 5.5
*
* The following configuration can be added to added to server.xml in Tomcat 5.5 to initialize a {@code JcrRepository} and add it
* to the JNDI tree.
*
*
* <GlobalNamingResources>
* <!-- Other configuration omitted -->
* <Resource name="jcr/local" auth="Container"
* type="javax.jcr.Repository"
* factory="org.modeshape.jcr.JndiRepositoryFactory"
* configFile="/path/to/repository-config.json"
* repositoryName="Test Repository Source"
* />
* </GlobalNamingResources>
*
*
* This will create a repository loaded from the or file "/path/to/configRepository.json" and return the JCR repository
* named "Test Repository Source". The name of the repository will be used to more quickly look up the repository if it
* has been previously loaded.
*
*
* Note that if the "repositoryName" property is not specified or is empty, the factory will register the ModeShape engine at the
* supplied location in JNDI. This approach is compatible with the traditional JNDI URLs used with the
* {@link JcrRepositoryFactory JCR 2.0-style RepositoryFactory}.
*
*/
public class JndiRepositoryFactory implements ObjectFactory {
protected static final Logger LOG = Logger.getLogger(JndiRepositoryFactory.class);
private static final String CONFIG_FILE = "configFile";
private static final String CONFIG_FILES = "configFiles";
private static final String REPOSITORY_NAME = "repositoryName";
private static final ModeShapeEngine ENGINE = new ModeShapeEngine();
/**
* Method that shuts down the JDNI repository factory's engine, usually for testing purposes.
*
* @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.
*/
static Future shutdown() {
return ENGINE.shutdown();
}
/**
* Get or initialize the JCR Repository instance as described by the supplied configuration file and repository name.
*
* @param configFileName the name of the file containing the configuration information for the {@code ModeShapeEngine}; may
* not be null. This method will first attempt to load this file as a resource from the classpath. If no resource with
* the given name exists, the name will be treated as a file name and loaded from the file system. May be null if the
* repository should already exist.
* @param repositoryName the name of the repository; may be null if the repository name is to be read from the configuration
* file (note that this does require parsing the configuration file)
* @param nameCtx the naming context used to register a removal listener to shut down the repository when removed from JNDI;
* may be null
* @param jndiName the name in JNDI where the repository is to be found
* @return the JCR repository instance
* @throws IOException if there is an error or problem reading the configuration resource at the supplied path
* @throws RepositoryException if the repository could not be started
* @throws NamingException if there is an error registering the namespace listener
*/
private static synchronized JcrRepository getRepository( String configFileName,
String repositoryName,
final Context nameCtx,
final Name jndiName )
throws IOException, RepositoryException, NamingException {
if (!StringUtil.isBlank(repositoryName)) {
// Make sure the engine is running ...
ENGINE.start();
// See if we can shortcut the process by using the name ...
try {
JcrRepository repository = ENGINE.getRepository(repositoryName);
switch (repository.getState()) {
case STARTING:
case RUNNING:
return repository;
default:
LOG.error(JcrI18n.repositoryIsNotRunningOrHasBeenShutDown, repositoryName);
return null;
}
} catch (NoSuchRepositoryException e) {
if (configFileName == null) {
// No configuration file given, so we can't do anything ...
throw e;
}
// Nothing found, so continue ...
}
}
RepositoryConfiguration config = RepositoryConfiguration.read(configFileName);
if (repositoryName == null) {
repositoryName = config.getName();
} else if (!repositoryName.equals(config.getName())) {
LOG.warn(JcrI18n.repositoryNameDoesNotMatchConfigurationName, repositoryName, config.getName(), configFileName);
}
// Try to deploy and start the repository ...
ENGINE.start();
JcrRepository repository = ENGINE.deploy(config);
try {
ENGINE.startRepository(repository.getName()).get();
} catch (InterruptedException e) {
Thread.interrupted();
throw new RepositoryException(e);
} catch (ExecutionException e) {
throw new RepositoryException(e.getCause());
}
// Register the JNDI listener, to shut down the repository when removed from JNDI ...
if (nameCtx instanceof EventContext) {
registerNamingListener((EventContext)nameCtx, jndiName);
}
return repository;
}
private static void registerNamingListener( EventContext evtCtx,
final Name jndiName ) throws NamingException {
NamespaceChangeListener listener = new NamespaceChangeListener() {
@Override
public void namingExceptionThrown( NamingExceptionEvent evt ) {
evt.getException().printStackTrace();
}
@Override
public void objectAdded( NamingEvent evt ) {
// do nothing ...
}
@SuppressWarnings( "synthetic-access" )
@Override
public void objectRemoved( NamingEvent evt ) {
Object oldObject = evt.getOldBinding().getObject();
if (!(oldObject instanceof JcrRepository)) return;
JcrRepository repository = (JcrRepository)oldObject;
String repoName = repository.getName();
try {
ENGINE.shutdownRepository(repoName).get();
} catch (NoSuchRepositoryException e) {
// Ignore this ...
} catch (InterruptedException ie) {
LOG.error(ie, JcrI18n.errorWhileShuttingDownRepositoryInJndi, repoName, jndiName);
// Thread.interrupted();
} catch (ExecutionException e) {
LOG.error(e.getCause(), JcrI18n.errorWhileShuttingDownRepositoryInJndi, repoName, jndiName);
} finally {
// Try to shutdown the repository only if there are no more running repositories.
// IOW, shutdown but do not force shutdown of running repositories ...
ENGINE.shutdown(false); // no need to block on the futured returned by 'shutdown(boolean)'
}
}
@Override
public void objectRenamed( NamingEvent evt ) {
// do nothing ...
}
};
evtCtx.addNamingListener(jndiName, EventContext.OBJECT_SCOPE, listener);
}
/**
* Creates an {@code JcrRepository} or ModeShape engine using the reference information specified.
*
* This method first attempts to convert the {@code obj} parameter into a {@link Reference reference to JNDI configuration
* information}. If that is successful, a {@link ModeShapeEngine} will be created (if not previously created by a call to this
* method) and it will be configured from the resource or file at the location specified by the {@code configFile} key in the
* reference. After the configuration is successful, the {@link ModeShapeEngine#getRepository(String) ModeShapeEngine} will be
* queried} for the repository with the name specified by the value of the @{code repositoryName} key in the reference.
*
*
* @param obj the reference to the JNDI configuration information; must be a non-null instance of {@link Reference}
* @param name the name of the object
* @param nameCtx used to register the ModeShape engine when no repository name is provided in the Reference obj
* @param environment ignored
* @return the object registered in JDNI, which will be the named repository name or (if no repository name is given) the
* ModeShape engine; never null
* @throws IOException if there is an error or problem reading the configuration resource at the supplied path
* @throws SAXException if the contents of the configuration resource are not valid XML
* @throws NamingException if there is an error registering the namespace listener
* @throws RepositoryException if the {@link ModeShapeEngine#start() ModeShapeEngine could not be started}, the named
* repository does not exist in the given configuration resource, or the named repository could not be created
*/
@Override
public Object getObjectInstance( Object obj,
final Name name,
final Context nameCtx,
Hashtable, ?> environment )
throws IOException, SAXException, RepositoryException, NamingException {
if (!(obj instanceof Reference)) return null;
Reference ref = (Reference)obj;
// Get the name of the repository
RefAddr repositoryName = ref.get(REPOSITORY_NAME);
String repoName = repositoryName != null ? repositoryName.getContent().toString() : null;
// Get the configuration file
RefAddr configFileRef = ref.get(CONFIG_FILE);
String configFile = configFileRef != null ? configFileRef.getContent().toString() : null;
RefAddr configFilesRef = ref.get(CONFIG_FILES);
Set configFiles = configFilesRef != null ? parseStrings(configFilesRef.getContent().toString()) : null;
if (!StringUtil.isBlank(repoName) && !StringUtil.isBlank(configFile)) {
// Start the named repository ...
return getRepository(configFile, repoName, nameCtx, name);
}
else if (configFiles != null) {
// Start the configured repositories ...
for (String file : configFiles) {
getRepository(file, null, nameCtx, name);
}
return ENGINE;
}
return null;
}
protected Set parseStrings( String value ) {
if (StringUtil.isBlank(value)) {
return Collections.emptySet();
}
value = value.trim();
Set result = new HashSet();
for (String strValue : value.split(",")) {
if (StringUtil.isBlank(strValue)) {
continue;
}
result.add(strValue.trim());
}
return result;
}
}