/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.jackrabbit.core; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Properties; import javax.jcr.Credentials; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import org.apache.commons.collections.map.ReferenceMap; import org.apache.jackrabbit.api.JackrabbitRepository; import org.apache.jackrabbit.commons.AbstractRepository; import org.apache.jackrabbit.core.config.ConfigurationException; import org.apache.jackrabbit.core.config.RepositoryConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A repository proxy that automatically initializes and shuts down the * underlying repository instance when the first session is opened * or the last one closed. As long as all sessions are properly closed * when no longer used, this class can be used to avoid having to explicitly * shut down the repository. */ public class TransientRepository extends AbstractRepository implements JackrabbitRepository, SessionListener { /** * The logger instance used to log the repository and session lifecycles. */ private static final Logger logger = LoggerFactory.getLogger(TransientRepository.class); /** * Name of the repository configuration file property. */ private static final String CONF_PROPERTY = "org.apache.jackrabbit.repository.conf"; /** * Default value of the repository configuration file property. */ private static final String CONF_DEFAULT = "repository.xml"; /** * Name of the repository home directory property. */ private static final String HOME_PROPERTY = "org.apache.jackrabbit.repository.home"; /** * Default value of the repository home directory property. */ private static final String HOME_DEFAULT = "repository"; /** * Factory interface for creating {@link RepositoryImpl} instances. * Used to give greater control of the repository initialization process * to users of the TransientRepository class. */ public interface RepositoryFactory { /** * Creates and initializes a repository instance. The returned instance * will be used and finally shut down by the caller of this method. * * @return initialized repository instance * @throws RepositoryException if an instance can not be created */ RepositoryImpl getRepository() throws RepositoryException; } /** * The repository configuration. Set in the constructor and used to * initialize the repository instance when the first session is opened. */ private final RepositoryFactory factory; /** * The initialized repository instance. Set when the first session is * opened and cleared when the last one is closed. */ private RepositoryImpl repository; /** * The set of open sessions. When no more open sessions remain, the * repository instance is automatically shut down until a new session * is opened. */ private final Map<Session, Session> sessions = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK); /** * The static repository descriptors. The default {@link RepositoryImpl} * descriptors are loaded as the static descriptors and used whenever a * live repository instance is not available (no open sessions). */ private final Properties descriptors; /** * The path to the repository home directory. */ private final String home; /** * Creates a transient repository proxy that will use the given repository * factory to initialize the underlying repository instances. * * @param factory repository factory * @param home the path to the repository home directory. */ public TransientRepository(RepositoryFactory factory, String home) { this.factory = factory; this.home = home; this.repository = null; this.descriptors = new Properties(); // FIXME: The current RepositoryImpl class does not allow static // access to the repository descriptors, so we need to load them // directly from the underlying property file. try { InputStream in = RepositoryImpl.class.getResourceAsStream( "repository.properties"); try { descriptors.load(in); } finally { in.close(); } } catch (IOException e) { logger.warn("Unable to load static repository descriptors", e); } } /** * Creates a transient repository proxy that will use the repository * configuration file and home directory specified in system properties * <code>org.apache.jackrabbit.repository.conf</code> and * <code>org.apache.jackrabbit.repository.home</code>. If these properties * are not found, then the default values "<code>repository.xml</code>" * and "<code>repository</code>" are used. */ public TransientRepository() { this(System.getProperty(CONF_PROPERTY, CONF_DEFAULT), System.getProperty(HOME_PROPERTY, HOME_DEFAULT)); } /** * Creates a transient repository proxy that will use a copy of the given * repository configuration to initialize the underlying repository * instance. * * @param config repository configuration */ public TransientRepository(final RepositoryConfig config) { this(new RepositoryFactory() { public RepositoryImpl getRepository() throws RepositoryException { return RepositoryImpl.create(RepositoryConfig.create(config)); } }, config.getHomeDir()); } /** * Creates a transient repository proxy that will use the given repository * configuration file and home directory paths to initialize the underlying * repository instances. * * @see #TransientRepository(File, File) * @param config repository configuration file * @param home repository home directory */ public TransientRepository(String config, String home) { this(new File(config), new File(home)); } /** * Creates a transient repository proxy based on the given repository * home directory and the repository configuration file "repository.xml" * contained in that directory. * * @since Apache Jackrabbit 1.6 * @param dir repository home directory */ public TransientRepository(File dir) { this(new File(dir, "repository.xml"), dir); } /** * Creates a transient repository proxy that will use the given repository * configuration file and home directory paths to initialize the underlying * repository instances. The repository configuration file is reloaded * whenever the repository is restarted, so it is safe to modify the * configuration when all sessions have been closed. * <p> * If the given repository configuration file does not exist, then a * default configuration file is copied to the given location when the * first session starts. Similarly, if the given repository home * directory does not exist, it is automatically created when the first * session starts. This is a convenience feature designed to reduce the * need for manual configuration. * * @since Apache Jackrabbit 1.6 * @param xml repository configuration file * @param dir repository home directory */ public TransientRepository(final File xml, final File dir) { this(new RepositoryFactory() { public RepositoryImpl getRepository() throws RepositoryException { try { return RepositoryImpl.create( RepositoryConfig.install(xml, dir)); } catch (IOException e) { throw new RepositoryException( "Automatic repository configuration failed", e); } catch (ConfigurationException e) { throw new RepositoryException( "Invalid repository configuration file: " + xml, e); } } }, dir.getAbsolutePath()); } public TransientRepository(final Properties properties) throws ConfigurationException, IOException { this(new RepositoryFactory() { public RepositoryImpl getRepository() throws RepositoryException { try { return RepositoryImpl.create( RepositoryConfig.install(properties)); } catch (IOException e) { throw new RepositoryException( "Automatic repository configuration failed: " + properties, e); } catch (ConfigurationException e) { throw new RepositoryException( "Invalid repository configuration: " + properties, e); } } }, RepositoryConfig.getRepositoryHome(properties).getAbsolutePath()); } /** * @return the path to the repository home directory. */ public String getHomeDir() { return home; } /** * Starts the underlying repository. * * @throws RepositoryException if the repository cannot be started */ private synchronized void startRepository() throws RepositoryException { assert repository == null && sessions.isEmpty(); logger.debug("Initializing transient repository"); repository = factory.getRepository(); logger.info("Transient repository initialized"); } /** * Stops the underlying repository. */ private synchronized void stopRepository() { assert repository != null && sessions.isEmpty(); logger.debug("Shutting down transient repository"); repository.shutdown(); logger.info("Transient repository shut down"); repository = null; } //------------------------------------------------------------<Repository> /** * Returns the available descriptor keys. If the underlying repository * is initialized, then the call is proxied to it, otherwise the static * descriptor keys are returned. * * @return descriptor keys */ public synchronized String[] getDescriptorKeys() { if (repository != null) { return repository.getDescriptorKeys(); } else { String[] keys = Collections.list( descriptors.propertyNames()).toArray(new String[0]); Arrays.sort(keys); return keys; } } /** * Returns the identified repository descriptor. If the underlying * repository is initialized, then the call is proxied to it, otherwise * the static descriptors are used. * * @param key descriptor key * @return descriptor value * @see javax.jcr.Repository#getDescriptor(String) */ public synchronized String getDescriptor(String key) { if (repository != null) { return repository.getDescriptor(key); } else { return descriptors.getProperty(key); } } public Value getDescriptorValue(String key) { if (repository != null) { return repository.getDescriptorValue(key); } else { throw new UnsupportedOperationException( "not implemented yet - see JCR-2062"); } } public Value[] getDescriptorValues(String key) { if (repository != null) { return repository.getDescriptorValues(key); } else { throw new UnsupportedOperationException( "not implemented yet - see JCR-2062"); } } public boolean isSingleValueDescriptor(String key) { if (repository != null) { return repository.isSingleValueDescriptor(key); } else { throw new UnsupportedOperationException( "not implemented yet - see JCR-2062"); } } /** * Logs in to the content repository. Initializes the underlying repository * instance if needed. The opened session is added to the set of open * sessions and a session listener is added to track when the session gets * closed. * * @param credentials login credentials * @param workspaceName workspace name * @return new session * @throws RepositoryException if the session could not be created * @see javax.jcr.Repository#login(Credentials,String) */ public synchronized Session login( Credentials credentials, String workspaceName) throws RepositoryException { // Start the repository if this is the first login if (repository == null) { startRepository(); } try { logger.debug("Opening a new session"); SessionImpl session = (SessionImpl) repository.login( credentials, workspaceName); sessions.put(session, session); session.addListener(this); logger.info("Session opened"); return session; } finally { // Stop the repository if the login failed // and no other sessions are active if (sessions.isEmpty()) { stopRepository(); } } } //--------------------------------------------------<JackrabbitRepository> /** * Forces all active sessions to logout. Once the last session has logged * out, the underlying repository instance will automatically be shut down. * * @see Session#logout() */ public synchronized void shutdown() { Session[] copy = sessions.keySet().toArray(new Session[0]); for (Session session : copy) { session.logout(); } } //-------------------------------------------------------<SessionListener> /** * Removes the given session from the set of open sessions. If no open * sessions remain, then the underlying repository instance is shut down. * * @param session closed session * @see SessionListener#loggedOut(SessionImpl) */ public synchronized void loggedOut(SessionImpl session) { assert sessions.containsKey(session); sessions.remove(session); logger.info("Session closed"); if (sessions.isEmpty()) { // FIXME: This is an ugly hack to avoid an infinite loop when // RepositoryImpl.shutdown() repeatedly calls logout() on all // remaining active sessions including the one that just emitted // the loggedOut() message to us! repository.loggedOut(session); stopRepository(); } } /** * Ignored. {@inheritDoc} */ public void loggingOut(SessionImpl session) { } /** * Get the current repository. * * @return the repository */ RepositoryImpl getRepository() { return repository; } }