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

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

There is a newer version: 5.4.1.Final
Show newest version
/*
 * 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 static org.modeshape.jcr.RepositoryConfiguration.QueryRebuild.FAIL_IF_MISSING;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.security.AccessControlContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import javax.jcr.AccessDeniedException;
import javax.jcr.Credentials;
import javax.jcr.LoginException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import javax.naming.OperationNotSupportedException;
import javax.security.auth.login.LoginContext;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.hibernate.search.backend.TransactionContext;
import org.infinispan.Cache;
import org.infinispan.manager.CacheContainer;
import org.infinispan.schematic.Schematic;
import org.infinispan.schematic.SchematicDb;
import org.infinispan.schematic.document.Array;
import org.infinispan.schematic.document.Changes;
import org.infinispan.schematic.document.Editor;
import org.infinispan.schematic.document.Path;
import org.infinispan.schematic.internal.document.Paths;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.collection.Problems;
import org.modeshape.common.collection.SimpleProblems;
import org.modeshape.common.i18n.I18n;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.common.util.NamedThreadFactory;
import org.modeshape.jcr.ModeShapeEngine.State;
import org.modeshape.jcr.RepositoryConfiguration.AnonymousSecurity;
import org.modeshape.jcr.RepositoryConfiguration.BinaryStorage;
import org.modeshape.jcr.RepositoryConfiguration.Component;
import org.modeshape.jcr.RepositoryConfiguration.DocumentOptimization;
import org.modeshape.jcr.RepositoryConfiguration.FieldName;
import org.modeshape.jcr.RepositoryConfiguration.GarbageCollection;
import org.modeshape.jcr.RepositoryConfiguration.JaasSecurity;
import org.modeshape.jcr.RepositoryConfiguration.QuerySystem;
import org.modeshape.jcr.RepositoryConfiguration.Security;
import org.modeshape.jcr.RepositoryConfiguration.TransactionMode;
import org.modeshape.jcr.api.AnonymousCredentials;
import org.modeshape.jcr.api.Repository;
import org.modeshape.jcr.api.RepositoryManager;
import org.modeshape.jcr.api.Workspace;
import org.modeshape.jcr.api.monitor.ValueMetric;
import org.modeshape.jcr.api.query.Query;
import org.modeshape.jcr.api.value.DateTime;
import org.modeshape.jcr.bus.ChangeBus;
import org.modeshape.jcr.bus.ClusteredRepositoryChangeBus;
import org.modeshape.jcr.bus.RepositoryChangeBus;
import org.modeshape.jcr.cache.NodeCache;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.cache.RepositoryCache;
import org.modeshape.jcr.cache.SessionCache;
import org.modeshape.jcr.cache.SessionEnvironment;
import org.modeshape.jcr.cache.SessionEnvironment.Monitor;
import org.modeshape.jcr.cache.SessionEnvironment.MonitorFactory;
import org.modeshape.jcr.cache.WorkspaceNotFoundException;
import org.modeshape.jcr.cache.document.DocumentStore;
import org.modeshape.jcr.cache.document.LocalDocumentStore;
import org.modeshape.jcr.cache.document.TransactionalWorkspaceCaches;
import org.modeshape.jcr.federation.FederatedDocumentStore;
import org.modeshape.jcr.mimetype.MimeTypeDetector;
import org.modeshape.jcr.mimetype.MimeTypeDetectors;
import org.modeshape.jcr.query.QueryIndexing;
import org.modeshape.jcr.query.parse.FullTextSearchParser;
import org.modeshape.jcr.query.parse.JcrQomQueryParser;
import org.modeshape.jcr.query.parse.JcrSql2QueryParser;
import org.modeshape.jcr.query.parse.JcrSqlQueryParser;
import org.modeshape.jcr.query.parse.QueryParsers;
import org.modeshape.jcr.query.xpath.XPathQueryParser;
import org.modeshape.jcr.security.AnonymousProvider;
import org.modeshape.jcr.security.AuthenticationProvider;
import org.modeshape.jcr.security.AuthenticationProviders;
import org.modeshape.jcr.security.JaasProvider;
import org.modeshape.jcr.security.SecurityContext;
import org.modeshape.jcr.txn.NoClientTransactions;
import org.modeshape.jcr.txn.SynchronizedTransactions;
import org.modeshape.jcr.txn.Transactions;
import org.modeshape.jcr.value.DateTimeFactory;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.NamespaceRegistry;
import org.modeshape.jcr.value.Property;
import org.modeshape.jcr.value.ValueFactories;
import org.modeshape.jcr.value.binary.BinaryStore;
import org.modeshape.jcr.value.binary.BinaryUsageChangeSetListener;
import org.modeshape.jcr.value.binary.infinispan.InfinispanBinaryStore;
import org.modeshape.jmx.RepositoryStatisticsBean;

/**
 * 
 */
public class JcrRepository implements org.modeshape.jcr.api.Repository {

    /**
     * The set of supported query language string constants.
     * 
     * @see javax.jcr.query.QueryManager#getSupportedQueryLanguages()
     * @see javax.jcr.query.QueryManager#createQuery(String, String)
     */
    public static final class QueryLanguage {
        /**
         * The standard JCR 1.0 XPath query language.
         */
        @SuppressWarnings( "deprecation" )
        public static final String XPATH = Query.XPATH;

        /**
         * The SQL dialect that is based upon an enhanced version of the JCR-SQL query language defined by the JCR 1.0.1
         * specification.
         */
        @SuppressWarnings( "deprecation" )
        public static final String JCR_SQL = Query.SQL;

        /**
         * The SQL dialect that is based upon an enhanced version of the JCR-SQL2 query language defined by the JCR 2.0
         * specification.
         */
        public static final String JCR_SQL2 = Query.JCR_SQL2;

        /**
         * The enhanced Query Object Model language defined by the JCR 2.0 specification.
         */
        public static final String JCR_JQOM = Query.JCR_JQOM;
        /**
         * The full-text search language defined as part of the abstract query model, in Section 6.7.19 of the JCR 2.0
         * specification.
         */
        public static final String SEARCH = Query.FULL_TEXT_SEARCH;
    }

    protected static final Set MISSING_JAAS_POLICIES = new CopyOnWriteArraySet();

    private static final boolean AUTO_START_REPO_UPON_LOGIN = true;

    private static final String INTERNAL_WORKER_USERNAME = "";

    protected final Logger logger;
    private final AtomicReference config = new AtomicReference();
    private final AtomicReference repositoryName = new AtomicReference();
    private final Map descriptors;
    private final AtomicReference runningState = new AtomicReference();
    private final AtomicReference state = new AtomicReference(State.NOT_RUNNING);
    private final Lock stateLock = new ReentrantLock();
    private final AtomicBoolean allowAutoStartDuringLogin = new AtomicBoolean(AUTO_START_REPO_UPON_LOGIN);
    private Problems configurationProblems = null;

    /**
     * Create a Repository instance given the {@link RepositoryConfiguration configuration}.
     * 
     * @param configuration the repository configuration; may not be null
     * @throws ConfigurationException if there is a problem with the configuration
     */
    protected JcrRepository( RepositoryConfiguration configuration ) throws ConfigurationException {
        ModeShape.getName(); // force log message right up front
        this.config.set(configuration);
        RepositoryConfiguration config = this.config.get();

        // Validate the configuration to make sure there are no errors ...
        Problems results = configuration.validate();
        setConfigurationProblems(results);
        if (results.hasErrors()) {
            String msg = JcrI18n.errorsInRepositoryConfiguration.text(this.repositoryName,
                                                                      results.errorCount(),
                                                                      results.toString());
            throw new ConfigurationException(results, msg);
        }

        this.repositoryName.set(config.getName());
        this.logger = Logger.getLogger(getClass());
        this.logger.debug("Activating '{0}' repository", this.repositoryName);

        // Set up the descriptors ...
        this.descriptors = new HashMap();
        initializeDescriptors();
    }

    void setConfigurationProblems(Problems configurationProblems) {
        this.configurationProblems = configurationProblems;
    }

    RepositoryConfiguration repositoryConfiguration() {
        return config.get();
    }

    /**
     * Get the state of this JCR repository instance.
     * 
     * @return the state; never null
     */
    public State getState() {
        return state.get();
    }

    /**
     * Get the name of this JCR repository instance.
     * 
     * @return the name; never null
     */
    @Override
    public String getName() {
        return repositoryName.get();
    }

    @Override
    public int getActiveSessionsCount() {
        RunningState state = runningState.get();
        return state == null ? 0 : state.activeSessionCount();
    }

    /**
     * Get the component that can be used to obtain statistics for this repository.
     * 

* Note that this provides un-checked access to the statistics, unlike {@link RepositoryManager#getRepositoryMonitor()} in the * public API which only exposes the statistics if the session's user has administrative privileges. *

* * @return the statistics component; never null * @throws IllegalStateException if the repository is not {@link #getState() running} * @see Workspace#getRepositoryManager() * @see RepositoryManager#getRepositoryMonitor() */ public RepositoryStatistics getRepositoryStatistics() { return statistics(); } /** * Starts this repository instance (if not already started) and returns all the possible startup problems & warnings which did not * prevent the repository from starting up. *

* The are 2 general categories of issues that can be logged as problems: *

    *
  • * configuration warnings - any warnings raised by the structure of the repository configuration file *
  • *
  • * startup warnings/error - any warnings/errors raised by various repository components which didn't prevent them from * starting up, but could mean they are only partly intialized. *
  • *
*

* * @return a {@link Problems} instance which may contains errors and warnings raised by various components; may be empty if * nothing unusual happened during start but never {@code null} * * @throws FileNotFoundException if the Infinispan configuration file is specified but could not be found * @throws IOException if there is a problem with the specified Infinispan configuration file * @throws Exception if there is a problem with underlying resource setup */ public Problems getStartupProblems() throws Exception { doStart(); SimpleProblems result = new SimpleProblems(); result.addAll(this.configurationProblems); result.addAll(runningState().problems()); return result; } /** * Start this repository instance. * * @throws FileNotFoundException if the Infinispan configuration file is specified but could not be found * @throws IOException if there is a problem with the specified Infinispan configuration file * @throws Exception if there is a problem with underlying resource setup */ void start() throws Exception { doStart(); } /** * Terminate all active sessions. * * @return a future representing the asynchronous session termination process. */ Future shutdown() { // Create a simple executor that will do the backgrounding for us ... final ExecutorService executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("modeshape-repository-shutdown")); try { // Submit a runnable to terminate all sessions ... Future future = executor.submit(new Callable() { @Override public Boolean call() throws Exception { return doShutdown(); } }); return future; } finally { // Now shutdown the executor and return the future ... executor.shutdown(); } } /** * Apply the supplied changes to this repository's configuration, and if running change the services to reflect the updated * configuration. Note that this method assumes the proposed changes have already been validated; see * {@link RepositoryConfiguration#validate(Changes)}. * * @param changes the changes for the configuration * @throws FileNotFoundException if the Infinispan configuration file is changed but could not be found * @throws IOException if there is a problem with the specified Infinispan configuration file * @throws Exception if there is a problem with underlying resources * @see ModeShapeEngine#update(String, Changes) */ void apply( Changes changes ) throws Exception { try { stateLock.lock(); logger.debug("Applying changes to '{0}' repository configuration: {1} --> {2}", repositoryName, changes, config); // Get the configuration and apply the same changes ... final RepositoryConfiguration oldConfiguration = this.config.get(); Editor copy = oldConfiguration.edit(); ConfigurationChange configChanges = new ConfigurationChange(); copy.apply(changes, configChanges); // Always update the configuration ... RunningState oldState = this.runningState.get(); this.config.set(new RepositoryConfiguration(copy.unwrap(), copy.getString(FieldName.NAME), oldConfiguration.environment())); if (oldState != null) { assert state.get() == State.RUNNING; // Repository is running, so create a new running state ... this.runningState.set(new RunningState(oldState, configChanges)); // Handle a few special cases that the running state doesn't really handle itself ... if (!configChanges.storageChanged && configChanges.predefinedWorkspacesChanged) refreshWorkspaces(); if (configChanges.nameChanged) repositoryNameChanged(); } logger.debug("Applied changes to '{0}' repository configuration: {1} --> {2}", repositoryName, changes, config); } finally { stateLock.unlock(); } } protected final RunningState doStart() throws Exception { try { stateLock.lock(); if (this.state.get() == State.RESTORING) { throw new IllegalStateException(JcrI18n.repositoryIsBeingRestoredAndCannotBeStarted.text(getName())); } RunningState state = this.runningState.get(); if (state == null) { // start the repository by creating the running state ... this.state.set(State.STARTING); state = new RunningState(); this.runningState.compareAndSet(null, state); state.completeInitialization(); this.state.set(State.RUNNING); state.postInitialize(); } return state; } catch (Exception e) { // we should set the state to NOT_RUNNING regardless of the error/exception that occurs this.state.set(State.NOT_RUNNING); throw e; } finally { stateLock.unlock(); } } protected final boolean doShutdown() { if (this.state.get() == State.NOT_RUNNING) return true; try { stateLock.lock(); RunningState running = this.runningState.get(); if (running != null) { // Prevent future 'login(...)' calls from restarting the repository ... this.allowAutoStartDuringLogin.set(false); this.state.set(State.STOPPING); // Terminate each of the still-open sessions ... running.terminateSessions(); // Now shutdown the running state ... running.shutdown(); // Null out the running state ... this.runningState.set(null); } this.state.set(State.NOT_RUNNING); } finally { stateLock.unlock(); } return true; } protected final DocumentStore documentStore() { return runningState().documentStore(); } protected final String repositoryName() { return repositoryName.get(); } protected final RepositoryCache repositoryCache() { return runningState().repositoryCache(); } protected final RepositoryStatistics statistics() { return runningState().statistics(); } protected final RepositoryNodeTypeManager nodeTypeManager() { return runningState().nodeTypeManager(); } protected final RepositoryQueryManager queryManager() { return runningState().queryManager(); } protected final RepositoryLockManager lockManager() { return runningState().lockManager(); } protected final NamespaceRegistry persistentRegistry() { return runningState().persistentRegistry(); } protected final String systemWorkspaceName() { return runningState().systemWorkspaceName(); } protected final String systemWorkspaceKey() { return runningState().systemWorkspaceKey(); } protected final ChangeBus changeBus() { return runningState().changeBus(); } protected final String repositoryKey() { return runningState().repositoryKey(); } protected final JcrRepository.RunningState runningState() { RunningState running = runningState.get(); if (running == null) { throw new IllegalStateException(JcrI18n.repositoryIsNotRunningOrHasBeenShutDown.text(repositoryName())); } return running; } protected final boolean hasWorkspace( String workspaceName ) { return repositoryCache().getWorkspaceNames().contains(workspaceName); } protected final NodeCache workspaceCache( String workspaceName ) { return repositoryCache().getWorkspaceCache(workspaceName); } final SessionCache createSystemSession( ExecutionContext context, boolean readOnly ) { return repositoryCache().createSession(context, systemWorkspaceName(), readOnly); } protected final TransactionManager transactionManager() { return runningState().txnManager(); } protected final void prepareToRestore() throws RepositoryException { logger.debug("Preparing to restore '{0}' repository; setting state to RESTORING", getName()); if (getState() == State.RESTORING) { throw new RepositoryException(JcrI18n.repositoryIsCurrentlyBeingRestored.text(getName())); } state.set(State.RESTORING); } protected final void completeRestore() throws ExecutionException, Exception { if (getState() == State.RESTORING) { logger.debug("Shutting down '{0}' after content has been restored", getName()); Future future = shutdown(); try { future.get(); } catch (InterruptedException e) { Thread.interrupted(); } logger.debug("Starting '{0}' after content has been restored", getName()); start(); logger.debug("Started '{0}' after content has been restored; beginning indexing of content", getName()); // Reindex all content ... queryManager().reindexContent(true, false, false); logger.debug("Completed reindexing all content in '{0}' after restore.", getName()); } } /** * Get the immutable configuration for this repository. * * @return the configuration; never null */ public RepositoryConfiguration getConfiguration() { return this.config.get(); } @Override public String getDescriptor( String key ) { if (key == null) return null; if (!isSingleValueDescriptor(key)) return null; JcrValue value = (JcrValue)descriptors.get(key); try { return value.getString(); } catch (RepositoryException re) { throw new IllegalStateException(re); } } @Override public JcrValue getDescriptorValue( String key ) { if (key == null) return null; if (!isSingleValueDescriptor(key)) return null; return (JcrValue)descriptors.get(key); } @Override public JcrValue[] getDescriptorValues( String key ) { Object value = descriptors.get(key); if (value instanceof JcrValue[]) { // Make a defensive copy of the array; the elements are immutable ... JcrValue[] values = (JcrValue[])value; JcrValue[] newValues = new JcrValue[values.length]; System.arraycopy(values, 0, newValues, 0, values.length); return newValues; } if (value instanceof JcrValue) { return new JcrValue[] {(JcrValue)value}; } return null; } @Override public boolean isSingleValueDescriptor( String key ) { if (key == null) return true; return descriptors.get(key) instanceof JcrValue; } @Override public boolean isStandardDescriptor( String key ) { return STANDARD_DESCRIPTORS.contains(key); } @Override public String[] getDescriptorKeys() { return descriptors.keySet().toArray(new String[descriptors.size()]); } @Override public synchronized JcrSession login() throws RepositoryException { return login(null, null); } @Override public synchronized JcrSession login( Credentials credentials ) throws RepositoryException { return login(credentials, null); } @Override public synchronized JcrSession login( String workspaceName ) throws RepositoryException { return login(null, workspaceName); } /** * @throws IllegalArgumentException if credentials is not null but: *
    *
  • provides neither a getLoginContext() nor a getAccessControlContext() method and is * not an instance of {@code SimpleCredentials}.
  • *
  • provides a getLoginContext() method that doesn't return a {@link LoginContext}. *
  • provides a getLoginContext() method that returns a * null {@link LoginContext}. *
  • does not provide a getLoginContext() method, but provides a getAccessControlContext() * method that doesn't return an {@link AccessControlContext}. *
  • does not provide a getLoginContext() method, but provides a getAccessControlContext() * method that returns a null {@link AccessControlContext}. *
* @see javax.jcr.Repository#login(javax.jcr.Credentials, java.lang.String) */ @Override public synchronized JcrSession login( final Credentials credentials, String workspaceName ) throws RepositoryException { final String repoName = this.repositoryName(); // Get the running state ... RunningState running = this.runningState.get(); if (running == null) { if (this.allowAutoStartDuringLogin.get()) { // Try starting ... try { running = doStart(); } catch (Throwable t) { throw new RepositoryException(JcrI18n.errorStartingRepository.text(repoName, t.getMessage()), t); } if (running == null) { throw new RepositoryException(JcrI18n.repositoryIsNotRunningOrHasBeenShutDown.text(repoName)); } } else { throw new RepositoryException(JcrI18n.repositoryIsNotRunningOrHasBeenShutDown.text(repoName)); } } else { if (this.state.get() == State.RESTORING) { throw new RepositoryException(JcrI18n.repositoryIsBeingRestoredAndCannotBeStarted.text(getName())); } } workspaceName = validateWorkspaceName(running, workspaceName); final AuthenticationProviders authenticators = running.authenticators(); final Credentials anonCredentials = running.anonymousCredentials(); final Map attributes = new HashMap(); // Try to authenticate with the provider(s) ... ExecutionContext context = running.context(); ExecutionContext sessionContext = authenticators.authenticate(credentials, repoName, workspaceName, context, attributes); if (sessionContext == null && credentials != null && anonCredentials != null) { // Failed non-anonymous authentication, so try anonymous authentication ... if (logger.isDebugEnabled()) logger.debug(JcrI18n.usingAnonymousUser.text()); attributes.clear(); sessionContext = authenticators.authenticate(anonCredentials, repoName, workspaceName, context, attributes); } if (sessionContext == null) { // Failed authentication ... throw new javax.jcr.LoginException(JcrI18n.loginFailed.text(repoName, workspaceName)); } // We have successfully authenticated ... try { // Look for whether this context is read-only ... SecurityContext securityContext = sessionContext.getSecurityContext(); boolean writable = JcrSession.hasRole(securityContext, ModeShapeRoles.READWRITE, repoName, workspaceName) || JcrSession.hasRole(securityContext, ModeShapeRoles.ADMIN, repoName, workspaceName); JcrSession session = null; if (running.useXaSessions()) { session = new JcrXaSession(this, workspaceName, sessionContext, attributes, !writable); } else { session = new JcrSession(this, workspaceName, sessionContext, attributes, !writable); } // Need to make sure that the user has access to this session session.checkPermission(workspaceName, null, ModeShapePermissions.READ); running.addSession(session, false); return session; } catch (AccessDeniedException ace) { throw new LoginException(JcrI18n.loginFailed.text(repoName, workspaceName), ace); } catch (WorkspaceNotFoundException e) { throw new NoSuchWorkspaceException(e.getMessage(), e); } } private String validateWorkspaceName( RunningState runningState, String workspaceName ) throws RepositoryException { if (workspaceName == null) { return runningState.defaultWorkspaceName(); } if (runningState.systemWorkspaceName().equals(workspaceName)) { throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(repositoryName(), workspaceName)); } return workspaceName; } protected static class ConfigurationChange implements org.infinispan.schematic.document.Editor.Observer { private final Path SECURITY_PATH = Paths.path(FieldName.SECURITY); private final Path QUERY_PATH = Paths.path(FieldName.QUERY); private final Path SEQUENCING_PATH = Paths.path(FieldName.SEQUENCING); private final Path EXTRACTORS_PATH = Paths.path(FieldName.QUERY, FieldName.EXTRACTORS); private final Path STORAGE_PATH = Paths.path(FieldName.STORAGE); private final Path BINARY_STORAGE_PATH = Paths.path(FieldName.STORAGE, FieldName.BINARY_STORAGE); private final Path WORKSPACES_PATH = Paths.path(FieldName.WORKSPACES); private final Path PREDEFINED_PATH = Paths.path(FieldName.WORKSPACES, FieldName.PREDEFINED); private final Path JNDI_PATH = Paths.path(FieldName.JNDI_NAME); private final Path TRANSACTION_MODE_PATH = Paths.path(FieldName.TRANSACTION_MODE); private final Path MINIMUM_BINARY_SIZE_IN_BYTES_PATH = Paths.path(FieldName.STORAGE, FieldName.BINARY_STORAGE, FieldName.MINIMUM_BINARY_SIZE_IN_BYTES); private final Path NAME_PATH = Paths.path(FieldName.NAME); private final Path MONITORING_PATH = Paths.path(FieldName.MONITORING); private final Path[] IGNORE_PATHS = new Path[] {STORAGE_PATH, BINARY_STORAGE_PATH}; protected boolean securityChanged = false; protected boolean sequencingChanged = false; protected boolean extractorsChanged = false; protected boolean storageChanged = false; protected boolean binaryStorageChanged = false; protected boolean indexingChanged = false; protected boolean workspacesChanged = false; protected boolean predefinedWorkspacesChanged = false; protected boolean jndiChanged = false; protected boolean transactionMode = false; protected boolean largeValueChanged = false; protected boolean nameChanged = false; protected boolean monitoringChanged = false; @Override public void setArrayValue( Path path, Array.Entry entry ) { checkForChanges(path); } @Override public void addArrayValue( Path path, Array.Entry entry ) { checkForChanges(path); } @Override public void removeArrayValue( Path path, Array.Entry entry ) { checkForChanges(path); } @Override public void clear( Path path ) { checkForChanges(path); } @Override public void put( Path parentPath, String field, Object newValue ) { checkForChanges(parentPath.with(field)); } @Override public void remove( Path path, String field ) { checkForChanges(path.with(field)); } private void checkForChanges( Path path ) { for (Path ignorePath : IGNORE_PATHS) { if (path.equals(ignorePath)) return; } if (!largeValueChanged && path.equals(MINIMUM_BINARY_SIZE_IN_BYTES_PATH)) largeValueChanged = true; else if (!binaryStorageChanged && path.startsWith(BINARY_STORAGE_PATH)) binaryStorageChanged = true; else if (!storageChanged && path.startsWith(STORAGE_PATH)) storageChanged = true; if (!sequencingChanged && path.startsWith(SEQUENCING_PATH)) sequencingChanged = true; if (!extractorsChanged && path.startsWith(EXTRACTORS_PATH)) extractorsChanged = true; if (!securityChanged && path.startsWith(SECURITY_PATH)) securityChanged = true; if (!workspacesChanged && path.startsWith(WORKSPACES_PATH) && !path.startsWith(PREDEFINED_PATH)) workspacesChanged = true; if (!predefinedWorkspacesChanged && path.startsWith(PREDEFINED_PATH)) predefinedWorkspacesChanged = true; if (!indexingChanged && path.startsWith(QUERY_PATH) && !path.startsWith(EXTRACTORS_PATH)) indexingChanged = true; if (!jndiChanged && path.equals(JNDI_PATH)) jndiChanged = true; if (!transactionMode && path.equals(TRANSACTION_MODE_PATH)) transactionMode = true; if (!nameChanged && path.equals(NAME_PATH)) nameChanged = true; if (!monitoringChanged && path.equals(MONITORING_PATH)) monitoringChanged = true; } } @SuppressWarnings( "deprecation" ) private void initializeDescriptors() { ValueFactories factories = new ExecutionContext().getValueFactories(); descriptors.put(Repository.LEVEL_1_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.LEVEL_2_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_LOCKING_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_OBSERVATION_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_QUERY_SQL_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_TRANSACTIONS_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_VERSIONING_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.QUERY_XPATH_DOC_ORDER, valueFor(factories, false)); // see MODE-613 descriptors.put(Repository.QUERY_XPATH_POS_INDEX, valueFor(factories, false)); // no support doc order searching in xpath descriptors.put(Repository.WRITE_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.IDENTIFIER_STABILITY, valueFor(factories, Repository.IDENTIFIER_STABILITY_INDEFINITE_DURATION)); descriptors.put(Repository.OPTION_XML_IMPORT_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_XML_EXPORT_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_UNFILED_CONTENT_SUPPORTED, valueFor(factories, false)); descriptors.put(Repository.OPTION_SIMPLE_VERSIONING_SUPPORTED, valueFor(factories, false)); descriptors.put(Repository.OPTION_ACTIVITIES_SUPPORTED, valueFor(factories, false)); descriptors.put(Repository.OPTION_BASELINES_SUPPORTED, valueFor(factories, false)); descriptors.put(Repository.OPTION_ACCESS_CONTROL_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_JOURNALED_OBSERVATION_SUPPORTED, valueFor(factories, false)); descriptors.put(Repository.OPTION_RETENTION_SUPPORTED, valueFor(factories, false)); descriptors.put(Repository.OPTION_LIFECYCLE_SUPPORTED, valueFor(factories, false)); descriptors.put(Repository.OPTION_NODE_AND_PROPERTY_WITH_SAME_NAME_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_UPDATE_PRIMARY_NODE_TYPE_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_UPDATE_MIXIN_NODE_TYPES_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_SHAREABLE_NODES_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.OPTION_NODE_TYPE_MANAGEMENT_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_INHERITANCE, valueFor(factories, Repository.NODE_TYPE_MANAGEMENT_INHERITANCE_MULTIPLE)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_PRIMARY_ITEM_NAME_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_ORDERABLE_CHILD_NODES_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_RESIDUAL_DEFINITIONS_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_AUTOCREATED_DEFINITIONS_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_SAME_NAME_SIBLINGS_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_PROPERTY_TYPES, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_OVERRIDES_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_MULTIVALUED_PROPERTIES_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_MULTIPLE_BINARY_PROPERTIES_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_VALUE_CONSTRAINTS_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.NODE_TYPE_MANAGEMENT_UPDATE_IN_USE_SUPORTED, valueFor(factories, true)); descriptors.put(Repository.QUERY_LANGUAGES, new JcrValue[] {valueFor(factories, Query.XPATH), valueFor(factories, Query.JCR_SQL2), valueFor(factories, Query.SQL), valueFor(factories, Query.JCR_JQOM)}); descriptors.put(Repository.QUERY_STORED_QUERIES_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.QUERY_FULL_TEXT_SEARCH_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.QUERY_JOINS, valueFor(factories, Repository.QUERY_JOINS_INNER_OUTER)); descriptors.put(Repository.SPEC_NAME_DESC, valueFor(factories, JcrI18n.SPEC_NAME_DESC.text())); descriptors.put(Repository.SPEC_VERSION_DESC, valueFor(factories, "2.0")); descriptors.put(Repository.REP_NAME_DESC, valueFor(factories, ModeShape.getName())); descriptors.put(Repository.REP_VENDOR_DESC, valueFor(factories, ModeShape.getVendor())); descriptors.put(Repository.REP_VENDOR_URL_DESC, valueFor(factories, ModeShape.getUrl())); descriptors.put(Repository.REP_VERSION_DESC, valueFor(factories, ModeShape.getVersion())); descriptors.put(Repository.OPTION_WORKSPACE_MANAGEMENT_SUPPORTED, valueFor(factories, true)); descriptors.put(Repository.REPOSITORY_NAME, valueFor(factories, repositoryName())); } private static JcrValue valueFor( ValueFactories valueFactories, int type, Object value ) { return new JcrValue(valueFactories, type, value); } private static JcrValue valueFor( ValueFactories valueFactories, String value ) { return valueFor(valueFactories, PropertyType.STRING, value); } private static JcrValue valueFor( ValueFactories valueFactories, boolean value ) { return valueFor(valueFactories, PropertyType.BOOLEAN, value); } protected void refreshWorkspaces() { RunningState running = runningState(); if (running != null) { Set workspaceNames = running.repositoryCache().getWorkspaceNames(); ValueFactories factories = running.context().getValueFactories(); JcrValue[] values = new JcrValue[workspaceNames.size()]; int i = 0; for (String workspaceName : workspaceNames) { values[i++] = valueFor(factories, workspaceName); } descriptors.put(Repository.REPOSITORY_WORKSPACES, values); } } private void repositoryNameChanged() { descriptors.put(Repository.REPOSITORY_NAME, repositoryName()); } Collection> caches() { RunningState running = runningState.get(); if (running == null) return Collections.emptyList(); List> caches = new ArrayList>(); LocalDocumentStore localDocumentStore = running.documentStore().localStore(); caches.add(localDocumentStore.localCache()); // Add the binary store's cache, if there is one ... BinaryStore store = running.binaryStore(); if (store instanceof InfinispanBinaryStore) { InfinispanBinaryStore ispnStore = (InfinispanBinaryStore)store; caches.addAll(ispnStore.getCaches()); } return caches; } @Immutable protected class RunningState { private final RepositoryConfiguration config; private final DocumentStore documentStore; private final AuthenticationProviders authenticators; private final Credentials anonymousCredentialsIfSuppliedCredentialsFail; private final String defaultWorkspaceName; private final String systemWorkspaceName; private final String systemWorkspaceKey; private final RepositoryNodeTypeManager nodeTypes; private final RepositoryLockManager lockManager; private final TransactionManager txnMgr; private final Transactions transactions; private final String jndiName; private final SystemNamespaceRegistry persistentRegistry; private final ExecutionContext context; private final ExecutionContext internalWorkerContext; private final ReadWriteLock activeSessionLock = new ReentrantReadWriteLock(); private final WeakHashMap activeSessions = new WeakHashMap(); private final WeakHashMap internalSessions = new WeakHashMap(); private final RepositoryStatistics statistics; private final RepositoryStatisticsBean mbean; private final BinaryStore binaryStore; private final ScheduledExecutorService statsRollupService; private final Sequencers sequencers; private final QueryParsers queryParsers; private final RepositoryQueryManager repositoryQueryManager; private final ExecutorService indexingExecutor; private final TextExtractors extractors; private final ChangeBus changeBus; private final ExecutorService changeDispatchingQueue; private final boolean useXaSessions; private final MimeTypeDetectors mimeTypeDetector; private final BackupService backupService; private final InitialContentImporter initialContentImporter; private final SystemContentInitializer systemContentInitializer; private final NodeTypesImporter nodeTypesImporter; private final Connectors connectors; private final RepositoryConfiguration.IndexRebuildOptions indexRebuildOptions; private final List> backgroundProcesses = new ArrayList>(); private final Problems problems; private Transaction existingUserTransaction; private RepositoryCache cache; protected RunningState() throws Exception { this(null, null); } @SuppressWarnings( "deprecation" ) protected RunningState( JcrRepository.RunningState other, JcrRepository.ConfigurationChange change ) throws Exception { this.config = repositoryConfiguration(); this.systemContentInitializer = new SystemContentInitializer(); if (other == null) { logger.debug("Starting '{0}' repository with configuration: \n{1}", repositoryName(), this.config); this.problems = new SimpleProblems(); } else { logger.debug("Updating '{0}' repository with configuration: \n{1}", repositoryName(), this.config); this.problems = other.problems; } ExecutionContext tempContext = new ExecutionContext(); // Set up monitoring (doing this early in the process so it is available to other components to use) ... if (other != null && !change.monitoringChanged) { this.statistics = other.statistics; this.statsRollupService = other.statsRollupService; this.mbean = other.mbean; } else { this.statistics = other != null ? other.statistics : new RepositoryStatistics(tempContext); if (this.config.getMonitoring().enabled()) { // Start the Cron service, with a minimum of a single thread ... this.statsRollupService = tempContext.getScheduledThreadPool("modeshape-stats"); this.statistics.start(this.statsRollupService); this.mbean = new RepositoryStatisticsBean(statistics, getName()); this.mbean.start(); } else { this.statsRollupService = null; this.mbean = null; } } this.systemWorkspaceName = RepositoryConfiguration.SYSTEM_WORKSPACE_NAME; this.systemWorkspaceKey = NodeKey.keyForWorkspaceName(this.systemWorkspaceName); if (other != null && !change.workspacesChanged) { this.defaultWorkspaceName = other.defaultWorkspaceName; } else { // Set up some of the defaults ... this.defaultWorkspaceName = config.getDefaultWorkspaceName(); } try { if (other != null) { if (change.storageChanged) { // Can't change where we're storing the content while we're running, so take effect upon next startup warn(JcrI18n.storageRelatedConfigurationChangesWillTakeEffectAfterShutdown, getName()); } if (change.binaryStorageChanged) { // Can't change where we're storing the content while we're running, so take effect upon next startup warn(JcrI18n.storageRelatedConfigurationChangesWillTakeEffectAfterShutdown, getName()); } // reuse the existing storage-related components ... this.cache = other.cache; this.context = other.context; this.connectors = other.connectors; this.documentStore = other.documentStore; this.txnMgr = documentStore.transactionManager(); MonitorFactory monitorFactory = new RepositoryMonitorFactory(this); this.transactions = createTransactions(config.getTransactionMode(), monitorFactory, this.txnMgr); suspendExistingUserTransaction(); if (change.largeValueChanged) { // We can update the value used in the repository cache dynamically ... BinaryStorage binaryStorage = config.getBinaryStorage(); this.cache.setLargeStringLength(binaryStorage.getMinimumBinarySizeInBytes()); this.context.getBinaryStore().setMinimumBinarySizeInBytes(binaryStorage.getMinimumBinarySizeInBytes()); } if (change.workspacesChanged) { // Make sure that all the predefined workspaces are available ... for (String workspaceName : config.getPredefinedWorkspaceNames()) { this.cache.createWorkspace(workspaceName); } } this.mimeTypeDetector = new MimeTypeDetectors(other.config.environment(), this.problems); this.binaryStore = other.binaryStore; this.internalWorkerContext = other.internalWorkerContext; this.nodeTypes = other.nodeTypes.with(this, true, true); this.lockManager = other.lockManager.with(this); this.cache.register(this.lockManager); other.cache.unregister(other.lockManager); this.persistentRegistry = other.persistentRegistry; this.changeDispatchingQueue = other.changeDispatchingQueue; this.changeBus = other.changeBus; } else { // find the Schematic database and Infinispan Cache ... CacheContainer container = config.getContentCacheContainer(); String cacheName = config.getCacheName(); List connectorComponents = config.getFederation().getConnectors(this.problems); Map> preconfiguredProjectionsByWorkspace = config.getFederation() .getProjectionsByWorkspace(); this.connectors = new Connectors(this, connectorComponents, preconfiguredProjectionsByWorkspace); logger.debug("Loading cache '{0}' from cache container {1}", cacheName, container); SchematicDb database = Schematic.get(container, cacheName); this.documentStore = connectors.hasConnectors() ? new FederatedDocumentStore(connectors, database) : new LocalDocumentStore(database); this.txnMgr = this.documentStore.transactionManager(); MonitorFactory monitorFactory = new RepositoryMonitorFactory(this); this.transactions = createTransactions(config.getTransactionMode(), monitorFactory, this.txnMgr); suspendExistingUserTransaction(); // Set up the binary store ... BinaryStorage binaryStorageConfig = config.getBinaryStorage(); binaryStore = binaryStorageConfig.getBinaryStore(); binaryStore.start(); tempContext = tempContext.with(binaryStore); // Now create the registry implementation and the execution context that uses it ... this.persistentRegistry = new SystemNamespaceRegistry(this); this.mimeTypeDetector = new MimeTypeDetectors(this.config.environment(), this.problems); this.context = tempContext.with(persistentRegistry); this.persistentRegistry.setContext(this.context); this.internalWorkerContext = this.context.with(new InternalSecurityContext(INTERNAL_WORKER_USERNAME)); // Create the event bus this.changeDispatchingQueue = this.context().getCachedTreadPool("modeshape-event-dispatcher"); this.changeBus = createBus(config.getClustering(), this.changeDispatchingQueue, systemWorkspaceName(), context.getProcessId()); this.changeBus.start(); // Set up the repository cache ... QuerySystem query = config.getQuery(); boolean indexingClustered = query.queriesEnabled() && query.indexingClustered(); final SessionEnvironment sessionEnv = new RepositorySessionEnvironment(this.transactions, indexingClustered); CacheContainer workspaceCacheContainer = this.config.getWorkspaceContentCacheContainer(); this.cache = new RepositoryCache(context, documentStore, config, systemContentInitializer, sessionEnv, changeBus, workspaceCacheContainer, Upgrades.STANDARD_UPGRADES); // Set up the node type manager ... this.nodeTypes = new RepositoryNodeTypeManager(this, true, true); this.cache.register(this.nodeTypes); // Set up the lock manager ... this.lockManager = new RepositoryLockManager(this); this.cache.register(this.lockManager); // Set up the unused binary value listener ... this.cache.register(new BinaryUsageChangeSetListener(binaryStore)); // Refresh several of the components information from the repository cache ... this.persistentRegistry.refreshFromSystem(); this.lockManager.refreshFromSystem(); if (!this.nodeTypes.refreshFromSystem()) { try { // Read in the built-in node types ... CndImporter importer = new CndImporter(context, true); importer.importBuiltIns(this.problems); this.nodeTypes.registerNodeTypes(importer.getNodeTypeDefinitions(), false, true, true); } catch (RepositoryException re) { throw new IllegalStateException("Could not load node type definition files", re); } catch (IOException ioe) { throw new IllegalStateException("Could not access node type definition files", ioe); } } // Add the built-ins, ensuring we overwrite any badly-initialized values ... this.persistentRegistry.register(JcrNamespaceRegistry.STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX); // Record the number of workspaces that are available/predefined ... this.statistics.set(ValueMetric.WORKSPACE_COUNT, cache.getWorkspaceNames().size()); } this.useXaSessions = this.transactions instanceof SynchronizedTransactions; if (other != null && !change.securityChanged) { this.authenticators = other.authenticators; this.anonymousCredentialsIfSuppliedCredentialsFail = other.anonymousCredentialsIfSuppliedCredentialsFail; } else { // Set up the security ... AtomicBoolean useAnonymouOnFailedLogins = new AtomicBoolean(); this.authenticators = createAuthenticationProviders(useAnonymouOnFailedLogins); this.anonymousCredentialsIfSuppliedCredentialsFail = useAnonymouOnFailedLogins.get() ? new AnonymousCredentials() : null; } if (other != null && !change.extractorsChanged) { this.extractors = new TextExtractors(this, other.config.getQuery().getTextExtracting()); } else { this.extractors = new TextExtractors(this, config.getQuery().getTextExtracting()); } this.binaryStore.setMimeTypeDetector(this.mimeTypeDetector); this.binaryStore.setTextExtractors(this.extractors); if (other != null && !change.sequencingChanged) { this.sequencers = other.sequencers.with(this); if (!sequencers.isEmpty()) this.cache.register(this.sequencers); this.cache.unregister(other.sequencers); } else { this.sequencers = new Sequencers(this, config, cache.getWorkspaceNames()); } if (other != null && !change.indexingChanged) { this.indexingExecutor = other.indexingExecutor; this.queryParsers = other.queryParsers; } else { String indexThreadPoolName = config.getQuery().getThreadPoolName(); this.indexingExecutor = this.context.getThreadPool(indexThreadPoolName); this.queryParsers = new QueryParsers(new JcrSql2QueryParser(), new XPathQueryParser(), new FullTextSearchParser(), new JcrSqlQueryParser(), new JcrQomQueryParser()); } QuerySystem query = config.getQuery(); if (query.queriesEnabled()) { // The query system is enabled ... Properties backendProps = query.getIndexingBackendProperties(); Properties indexingProps = query.getIndexingProperties(); Properties indexStorageProps = query.getIndexStorageProperties(); this.repositoryQueryManager = new RepositoryQueryManager(this, indexingExecutor, backendProps, indexingProps, indexStorageProps); this.indexRebuildOptions = query.getIndexRebuildOptions(); } else { this.repositoryQueryManager = new RepositoryDisabledQueryManager(this, config.getQuery()); this.indexRebuildOptions = null; logger.debug("Queries have been DISABLED for the '{0}' repository. Nothing will be indexed, and all queries will return empty results.", repositoryName()); } // Check that we have parsers for all the required languages ... assert this.queryParsers.getParserFor(Query.XPATH) != null; assert this.queryParsers.getParserFor(Query.SQL) != null; assert this.queryParsers.getParserFor(Query.JCR_SQL2) != null; assert this.queryParsers.getParserFor(Query.JCR_JQOM) != null; assert this.queryParsers.getParserFor(QueryLanguage.SEARCH) != null; if (other != null && !change.jndiChanged) { // The repository is already registered (or not registered) this.jndiName = other.jndiName; } else { // The JNDI location has changed, so register the new one ... this.jndiName = config.getJndiName(); bindIntoJndi(); // And unregister the old name ... if (other != null) { other.unbindFromJndi(); } } // Set up the backup service and executor ... this.backupService = new BackupService(this); // Set up the initial content importer this.initialContentImporter = new InitialContentImporter(config.getInitialContent(), this); // Set up the node types importer this.nodeTypesImporter = new NodeTypesImporter(config.getNodeTypes(), this); } catch (Throwable t) { // remove the document that was written as part of the initialization procedure if (cache != null) { cache.rollbackRepositoryInfo(); } // resume any user transaction that may have been suspended earlier resumeExistingUserTransaction(); throw (t instanceof Exception) ? (Exception)t : new RuntimeException(t); } } protected Transactions createTransactions( TransactionMode mode, MonitorFactory monitorFactory, TransactionManager txnMgr ) { if (txnMgr == null) { throw new ConfigurationException(JcrI18n.repositoryCannotBeStartedWithoutTransactionalSupport.text(getName())); } switch (mode) { case NONE: return new NoClientTransactions(monitorFactory, txnMgr); case AUTO: break; } return new SynchronizedTransactions(monitorFactory, txnMgr); } /** * Performs the steps required after the running state has been created and before a repository is considered * "initialized" * * @throws Exception if anything goes wrong in this phase. If it does, the transaction used for startup should be rolled * back */ protected final void completeInitialization() throws Exception { try { refreshWorkspaces(); this.sequencers.initialize(); // import the preconfigured node types before the initial content, in case the latter use custom types this.nodeTypesImporter.importNodeTypes(); if (repositoryCache().isInitializingRepository()) { // import initial content for each of the workspaces; this has to be done after the running state has started this.cache.runOneTimeSystemInitializationOperation(new Callable() { @Override public Void call() throws Exception { for (String workspaceName : repositoryCache().getWorkspaceNames()) { initialContentImporter().importInitialContent(workspaceName); } return null; } }); } // connectors must be initialized after initial content because that can have an influence on projections this.connectors.initialize(); // Now record in the content that we're finished initializing the repository. // This will commit the startup transaction. repositoryCache().completeInitialization(); // Now complete the upgrade of the repository, if required. This will be done transactionally. repositoryCache().completeUpgrade(new Upgrades.Context() { @Override public RunningState getRepository() { return RunningState.this; } @Override public Problems getProblems() { return RunningState.this.problems(); } }); } catch (Throwable t) { repositoryCache().rollbackRepositoryInfo(); resumeExistingUserTransaction(); throw t instanceof Exception ? (Exception)t : new RuntimeException(t); } } /** * Perform any initialization code that requires the repository to be in a running state. The repository has been * considered started up. * * @throws Exception if there is a problem during this phase. */ protected final void postInitialize() throws Exception { try { // check the re-indexing options and do the re-indexing (this must be done after all of the above have finished) if (indexRebuildOptions != null) { RepositoryConfiguration.QueryRebuild when = indexRebuildOptions.getWhen(); boolean includeSystemContent = indexRebuildOptions.includeSystemContent(); boolean async = indexRebuildOptions.getMode() == RepositoryConfiguration.IndexingMode.ASYNC; switch (when) { case ALWAYS: { this.repositoryQueryManager.reindexContent(includeSystemContent, async, false); break; } case IF_MISSING: { this.repositoryQueryManager.reindexContent(includeSystemContent, async, true); break; } case NEVER: { logger.debug(JcrI18n.noReindex.text(getName())); break; } case FAIL_IF_MISSING: { if (this.repositoryQueryManager.indexesEmpty()) { throw new RepositoryException(JcrI18n.noIndexesExist.text(getName(), FAIL_IF_MISSING.name())); } logger.debug("Index rebuild mode is '{0}' and there are some indexes present. Nothing will be reindexed.", FAIL_IF_MISSING.name()); break; } } } // Register the background processes. // Do this last since we want the repository running before these are started ... GarbageCollection gcConfig = config.getGarbageCollection(); String threadPoolName = gcConfig.getThreadPoolName(); long binaryGcInitialTimeInMillis = determineInitialDelay(gcConfig.getInitialTimeExpression()); long binaryGcIntervalInHours = gcConfig.getIntervalInHours(); int lockSweepIntervalInMinutes = gcConfig.getLockSweepIntervalInMinutes(); assert binaryGcInitialTimeInMillis >= 0; long binaryGcIntervalInMillis = TimeUnit.MILLISECONDS.convert(binaryGcIntervalInHours, TimeUnit.HOURS); ScheduledExecutorService garbageCollectionService = this.context.getScheduledThreadPool(threadPoolName); backgroundProcesses.add(garbageCollectionService.scheduleAtFixedRate(new LockGarbageCollectionTask( JcrRepository.this), lockSweepIntervalInMinutes, lockSweepIntervalInMinutes, TimeUnit.MINUTES)); backgroundProcesses.add(garbageCollectionService.scheduleAtFixedRate(new BinaryValueGarbageCollectionTask( JcrRepository.this), binaryGcInitialTimeInMillis, binaryGcIntervalInMillis, TimeUnit.MILLISECONDS)); DocumentOptimization optConfig = config.getDocumentOptimization(); if (optConfig.isEnabled()) { warn(JcrI18n.enablingDocumentOptimization, name()); threadPoolName = optConfig.getThreadPoolName(); long optInitialTimeInMillis = determineInitialDelay(optConfig.getInitialTimeExpression()); long optIntervalInHours = optConfig.getIntervalInHours(); int targetCount = optConfig.getChildCountTarget(); int tolerance = optConfig.getChildCountTolerance(); assert optInitialTimeInMillis >= 0; long optIntervalInMillis = TimeUnit.MILLISECONDS.convert(optIntervalInHours, TimeUnit.HOURS); ScheduledExecutorService optService = this.context.getScheduledThreadPool(threadPoolName); OptimizationTask optTask = new OptimizationTask(JcrRepository.this, targetCount, tolerance); backgroundProcesses.add(optService.scheduleAtFixedRate(optTask, optInitialTimeInMillis, optIntervalInMillis, TimeUnit.MILLISECONDS)); } } finally { resumeExistingUserTransaction(); } } protected final Sequencers sequencers() { return sequencers; } protected final boolean useXaSessions() { return useXaSessions; } final String name() { return repositoryName(); } final ExecutionContext context() { return context; } final ExecutionContext internalWorkerContext() { return internalWorkerContext; } final RepositoryCache repositoryCache() { return cache; } final QueryParsers queryParsers() { return queryParsers; } final RepositoryQueryManager queryManager() { return repositoryQueryManager; } protected final DocumentStore documentStore() { return documentStore; } protected final BinaryStore binaryStore() { return binaryStore; } protected final MimeTypeDetector mimeTypeDetector() { return mimeTypeDetector; } protected final TextExtractors textExtractors() { return extractors; } protected final Environment environment() { return config.environment(); } protected final TransactionManager txnManager() { TransactionManager mgr = documentStore().transactionManager(); assert mgr != null; return mgr; } protected final RepositoryNodeTypeManager nodeTypeManager() { return nodeTypes; } protected final RepositoryLockManager lockManager() { return lockManager; } protected final String systemWorkspaceName() { return systemWorkspaceName; } protected final String systemWorkspaceKey() { return systemWorkspaceKey; } protected final String defaultWorkspaceName() { return defaultWorkspaceName; } protected final NamespaceRegistry persistentRegistry() { return persistentRegistry; } protected final AuthenticationProviders authenticators() { return authenticators; } protected final RepositoryStatistics statistics() { return statistics; } protected final Credentials anonymousCredentials() { return anonymousCredentialsIfSuppliedCredentialsFail; } protected final ChangeBus changeBus() { return changeBus; } final Connectors connectors() { return connectors; } protected final String repositoryKey() { return cache.getKey(); } protected final boolean isFullTextSearchEnabled() { return config.getQuery().fullTextSearchEnabled(); } protected final BackupService backupService() { return backupService; } protected final void warn(I18n message, Object...params) { logger.warn(message, params); problems.addWarning(message, params); } protected final void error(I18n message, Object...params) { logger.error(message, params); problems.addError(message, params); } protected final void error(Throwable t, I18n message, Object...params) { logger.error(t, message, params); problems.addError(t, message, params); } protected final Problems problems() { return this.problems; } final InitialContentImporter initialContentImporter() { return initialContentImporter; } private AuthenticationProviders createAuthenticationProviders( AtomicBoolean useAnonymouOnFailedLogins ) { // Prepare to create the authenticators and authorizers ... AuthenticationProviders authenticators = new AuthenticationProviders(); Security securityConfig = config.getSecurity(); // Set up the JAAS providers ... JaasSecurity jaasSecurity = securityConfig.getJaas(); if (jaasSecurity != null) { String policyName = jaasSecurity.getPolicyName(); if (policyName != null && policyName.trim().length() != 0) { try { JaasProvider jaasProvider = new JaasProvider(policyName); authenticators = authenticators.with(jaasProvider); } catch (java.lang.SecurityException e) { if (MISSING_JAAS_POLICIES.add(policyName)) { warn(JcrI18n.loginConfigNotFound, policyName, RepositoryConfiguration.FieldName.SECURITY + "/" + RepositoryConfiguration.FieldName.JAAS_POLICY_NAME, repositoryName()); } } catch (javax.security.auth.login.LoginException e) { if (MISSING_JAAS_POLICIES.add(policyName)) { warn(JcrI18n.loginConfigNotFound, policyName, RepositoryConfiguration.FieldName.SECURITY + "/" + RepositoryConfiguration.FieldName.JAAS_POLICY_NAME, repositoryName()); } } } } // Set up any custom AuthenticationProvider classes ... for (Component component : securityConfig.getCustomProviders(problems())) { try { AuthenticationProvider provider = component.createInstance(getClass().getClassLoader()); authenticators = authenticators.with(provider); if (provider instanceof AnonymousProvider) { Object value = component.getDocument().get(FieldName.USE_ANONYMOUS_ON_FAILED_LOGINS); if (Boolean.TRUE.equals(value)) { useAnonymouOnFailedLogins.set(true); } } } catch (Throwable t) { logger.error(t, JcrI18n.unableToInitializeAuthenticationProvider, component, repositoryName(), t.getMessage()); } } // And last set up the anonymous provider ... AnonymousSecurity anonSecurity = securityConfig.getAnonymous(); if (anonSecurity != null) { // Set up the anonymous provider (if appropriate) ... Set anonRoles = anonSecurity.getAnonymousRoles(); if (!anonRoles.isEmpty()) { String anonUsername = anonSecurity.getAnonymousUsername(); AnonymousProvider anonProvider = new AnonymousProvider(anonUsername, anonRoles); authenticators = authenticators.with(anonProvider); logger.debug("Enabling anonymous authentication and authorization."); } if (anonSecurity.useAnonymousOnFailedLogings()) { useAnonymouOnFailedLogins.set(true); } } return authenticators; } final SessionCache createSystemSession( ExecutionContext context, boolean readOnly ) { return cache.createSession(context, systemWorkspaceName(), readOnly); } protected void shutdown() { // if reindexing was asynchronous and is still going on, we need to terminate it before we stop any of caches // or we do anything that affects the nodes if (repositoryQueryManager != null) { repositoryQueryManager.stopReindexing(); } // shutdown the connectors this.connectors.shutdown(); // Remove the scheduled operations ... for (ScheduledFuture future : backgroundProcesses) { future.cancel(true); } // Unregister from JNDI ... unbindFromJndi(); // Shutdown the sequencers ... sequencers().shutdown(); // Now wait until all the internal sessions are gone ... if (!internalSessions.isEmpty()) { try { int counter = 200; // this will block at most for 10 sec (200*50ms) while (counter > 0 && !internalSessions.isEmpty()) { Thread.sleep(50L); --counter; } } catch (InterruptedException e) { // do nothing ... } } // Now shutdown the repository caches ... this.cache.startShutdown(); // shutdown the event bus if (this.changeBus != null) { this.changeBus.shutdown(); } // Shutdown the query engine ... if (repositoryQueryManager != null) { repositoryQueryManager.shutdown(); } // Shutdown the text extractors if (extractors != null) { extractors.shutdown(); } if (backupService != null) { backupService.shutdown(); } // Shutdown the binary store ... this.binaryStore.shutdown(); // Now shutdown the repository caches ... this.cache.completeShutdown(); if (statistics != null) { statistics.stop(); } if (mbean != null) { mbean.stop(); } this.context().terminateAllPools(30, TimeUnit.SECONDS); // Shutdown the environment's resources. this.environment().shutdown(); } protected void bindIntoJndi() { if (jndiName != null && jndiName.trim().length() != 0) { try { InitialContext ic = new InitialContext(); ic.bind(jndiName, JcrRepository.this); } catch (NoInitialContextException e) { // No JNDI here ... logger.debug("No JNDI found, so not registering '{0}' repository", getName()); } catch (OperationNotSupportedException e) { warn(JcrI18n.jndiReadOnly, config.getName(), jndiName); } catch (NamingException e) { logger.error(e, JcrI18n.unableToBindToJndi, config.getName(), jndiName, e.getMessage()); } catch (Exception e) { logger.debug(e, "Error while registering the '{0}' repository from the '{1}' name in JNDI", config.getName(), jndiName); } } } protected void unbindFromJndi() { if (jndiName != null && jndiName.trim().length() != 0) { try { InitialContext ic = new InitialContext(); ic.unbind(jndiName); } catch (NoInitialContextException e) { // No JNDI here ... logger.debug("No JNDI found, so not registering '{0}' repository", getName()); } catch (OperationNotSupportedException e) { warn(JcrI18n.jndiReadOnly, config.getName(), jndiName); } catch (Exception e) { logger.debug(e, "Error while unregistering the '{0}' repository from the '{1}' name in JNDI", config.getName(), jndiName); } } } void addSession( JcrSession session, boolean internal ) { Map sessions = internal ? internalSessions : activeSessions; Lock lock = this.activeSessionLock.writeLock(); try { lock.lock(); sessions.put(session, null); } finally { lock.unlock(); } } int activeSessionCount() { Lock lock = this.activeSessionLock.writeLock(); try { lock.lock(); return activeSessions.size(); } finally { lock.unlock(); } } void removeSession( JcrSession session ) { Lock lock = this.activeSessionLock.writeLock(); try { lock.lock(); if (activeSessions.remove(session) == null) { internalSessions.remove(session); } } finally { lock.unlock(); } } void terminateSessions() { Lock lock = this.activeSessionLock.writeLock(); try { lock.lock(); for (JcrSession session : this.activeSessions.keySet()) { session.terminate(false); // don't remove from active sessions, as we're blocked and iterating on it ... } this.activeSessions.clear(); } finally { lock.unlock(); } } /** * @see LockGarbageCollectionTask */ void cleanUpLocks() { if (logger.isDebugEnabled()) { logger.debug("Starting lock cleanup in the '{0}' repository", repositoryName()); } try { // Get the IDs for the active sessions ... Set activeSessionIds = new HashSet(); Lock lock = this.activeSessionLock.writeLock(); try { lock.lock(); Iterator> iter = this.activeSessions.entrySet().iterator(); while (iter.hasNext()) { JcrSession session = iter.next().getKey(); if (session.isLive()) activeSessionIds.add(session.sessionId()); } } finally { lock.unlock(); } // Create a system session and delegate to its logic ... SessionCache systemSession = createSystemSession(context(), false); SystemContent system = new SystemContent(systemSession); system.cleanUpLocks(activeSessionIds); system.save(); } catch (Throwable e) { logger.error(e, JcrI18n.errorDuringGarbageCollection, e.getMessage()); } if (logger.isDebugEnabled()) { logger.debug("Finishing lock cleanup in the '{0}' repository", repositoryName()); } } /** * @see BinaryValueGarbageCollectionTask */ void cleanUpBinaryValues() { if (logger.isDebugEnabled()) { logger.debug("Starting binary value cleanup in the '{0}' repository", repositoryName()); } try { this.binaryStore.removeValuesUnusedLongerThan(RepositoryConfiguration.UNUSED_BINARY_VALUE_AGE_IN_MILLIS, TimeUnit.MILLISECONDS); } catch (Throwable e) { logger.error(e, JcrI18n.errorDuringGarbageCollection, e.getMessage()); } if (logger.isDebugEnabled()) { logger.debug("Finishing binary value cleanup in the '{0}' repository", repositoryName()); } } protected Session loginInternalSession() throws RepositoryException { return loginInternalSession(defaultWorkspaceName()); } protected JcrSession loginInternalSession( String workspaceName ) throws RepositoryException { try { boolean readOnly = false; // assume not RunningState running = runningState(); ExecutionContext sessionContext = running.internalWorkerContext(); Map attributes = Collections.emptyMap(); JcrSession session = new JcrSession(JcrRepository.this, workspaceName, sessionContext, attributes, readOnly); running.addSession(session, true); return session; } catch (WorkspaceNotFoundException e) { throw new NoSuchWorkspaceException(e.getMessage(), e); } } protected ChangeBus createBus( RepositoryConfiguration.Clustering clusteringConfiguration, ExecutorService executor, String systemWorkspaceName, String processId ) { RepositoryChangeBus standaloneBus = new RepositoryChangeBus(executor, systemWorkspaceName); return clusteringConfiguration.isEnabled() ? new ClusteredRepositoryChangeBus(clusteringConfiguration, standaloneBus, processId) : standaloneBus; } void suspendExistingUserTransaction() throws SystemException { // suspend any potential existing transaction, so that the initialization is "atomic" this.existingUserTransaction = this.transactions.suspend(); } void resumeExistingUserTransaction() throws SystemException { if (transactions != null && existingUserTransaction != null) { transactions.resume(existingUserTransaction); existingUserTransaction = null; } } } protected static class RepositorySessionEnvironment implements SessionEnvironment { private final Transactions transactions; private final TransactionalWorkspaceCaches transactionalWorkspaceCacheFactory; private final boolean indexingClustered; protected RepositorySessionEnvironment( Transactions transactions, boolean indexingClustered ) { this.transactions = transactions; this.transactionalWorkspaceCacheFactory = new TransactionalWorkspaceCaches(transactions); this.indexingClustered = indexingClustered; } @Override public Transactions getTransactions() { return transactions; } @Override public TransactionalWorkspaceCaches getTransactionalWorkspaceCacheFactory() { return transactionalWorkspaceCacheFactory; } @Override public boolean indexingClustered() { return indexingClustered; } } protected static class RepositoryMonitorFactory implements MonitorFactory { private final RunningState runningState; protected RepositoryMonitorFactory( RunningState runningState ) { this.runningState = runningState; } protected Transaction currentTransaction() { try { Transaction txn = runningState.txnManager().getTransaction(); return (txn != null && txn.getStatus() == Status.STATUS_ACTIVE) ? txn : null; } catch (Exception e) { throw new RuntimeException(e); } } @Override public Monitor createMonitor() { final RepositoryNodeTypeManager nodeTypeManager = this.runningState.nodeTypeManager(); final RepositoryQueryManager queryManager = this.runningState.queryManager(); if (nodeTypeManager == null || queryManager == null) { // query isn't enabled most likely, so we'll only record statistics return statisticsMonitor(); } return indexingMonitor(nodeTypeManager, queryManager); } private Monitor indexingMonitor( RepositoryNodeTypeManager nodeTypeManager, RepositoryQueryManager queryManager ) { final NodeTypeSchemata schemata = nodeTypeManager.getRepositorySchemata(); final QueryIndexing indexes = queryManager.getIndexes(); // a transaction will be returned only if it exists and is in ACTIVE status final Transaction txn = currentTransaction(); final TransactionContext txnCtx = new TransactionContext() { @Override public Object getTransactionIdentifier() { return txn; } @Override public boolean isTransactionInProgress() { return txn != null; } @Override public void registerSynchronization( Synchronization synchronization ) { // Never log a synchronization ... CheckArg.isNotNull(synchronization, "synchronization"); try { if (txn != null && txn.getStatus() == Status.STATUS_ACTIVE) { txn.registerSynchronization(synchronization); } } catch (Exception e) { throw new RuntimeException(e); } } }; // Create a monitor that forwards everything to the correct component ... final RunningState runningState = this.runningState; return new Monitor() { @Override public void recordChanged( long changedNodesCount ) { // ValueMetric.SESSION_SAVES are tracked in JcrSession.save() ... runningState.statistics().increment(ValueMetric.NODE_CHANGES, changedNodesCount); } @Override public void recordAdd( String workspace, NodeKey key, org.modeshape.jcr.value.Path path, Name primaryType, Set mixinTypes, Iterator propertiesIterator ) { indexes.addToIndex(workspace, key, path, primaryType, mixinTypes, propertiesIterator, schemata, txnCtx); } @Override public void recordUpdate( String workspace, NodeKey key, org.modeshape.jcr.value.Path path, Name primaryType, Set mixinTypes, Iterator properties ) { indexes.updateIndex(workspace, key, path, primaryType, mixinTypes, properties, schemata, txnCtx); } @Override public void recordRemove( String workspace, Iterable keys ) { indexes.removeFromIndex(workspace, keys, txnCtx); } }; } private Monitor statisticsMonitor() { // Happens only when the repository's initial content is being initialized, // so return a monitor that captures statistics but does not index ... final RunningState runningState = this.runningState; return new Monitor() { @Override public void recordChanged( long changedNodesCount ) { // ValueMetric.SESSION_SAVES are tracked in JcrSession.save() ... runningState.statistics().increment(ValueMetric.NODE_CHANGES, changedNodesCount); } @Override public void recordAdd( String workspace, NodeKey key, org.modeshape.jcr.value.Path path, Name primaryType, Set mixinTypes, Iterator propertiesIterator ) { } @Override public void recordRemove( String workspace, Iterable keys ) { } @Override public void recordUpdate( String workspace, NodeKey key, org.modeshape.jcr.value.Path path, Name primaryType, Set mixinTypes, Iterator properties ) { } }; } } private final class InternalSecurityContext implements SecurityContext { private final String username; protected InternalSecurityContext( String username ) { this.username = username; } @Override public boolean isAnonymous() { return false; } @Override public String getUserName() { return username; } @Override public boolean hasRole( String roleName ) { return true; } @Override public void logout() { // do nothing } } /** * Determine the initial delay before the garbage collection process(es) should be run, based upon the supplied initial * expression. Note that the initial expression specifies the hours and minutes in local time, whereas this method should * return the delay in milliseconds after the current time. * * @param initialTimeExpression the expression of the form "hh:mm"; never null * @return the number of milliseconds after now that the process(es) should be started */ protected long determineInitialDelay( String initialTimeExpression ) { Matcher matcher = RepositoryConfiguration.INITIAL_TIME_PATTERN.matcher(initialTimeExpression); if (matcher.matches()) { int hours = Integer.decode(matcher.group(1)); int mins = Integer.decode(matcher.group(2)); DateTimeFactory factory = runningState().context().getValueFactories().getDateFactory(); DateTime now = factory.create(); DateTime initialTime = factory.create(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), hours, mins, 0, 0); long delay = initialTime.getMilliseconds() - System.currentTimeMillis(); if (delay <= 0L) { initialTime = initialTime.plusDays(1); delay = initialTime.getMilliseconds() - System.currentTimeMillis(); } if (delay < 10000L) delay += 10000L; // at least 10 second delay to let repository finish starting ... assert delay >= 0; return delay; } String msg = JcrI18n.invalidGarbageCollectionInitialTime.text(repositoryName(), initialTimeExpression); throw new IllegalArgumentException(msg); } /** * The garbage collection tasks should get cancelled before the repository is shut down, but just in case we'll use a weak * reference to hold onto the JcrRepository instance and we'll also check that the repository is running before we actually do * any work. */ protected static abstract class BackgroundRepositoryTask implements Runnable { private WeakReference repositoryRef; protected BackgroundRepositoryTask( JcrRepository repository ) { assert repository != null; this.repositoryRef = new WeakReference(repository); } @Override public final void run() { JcrRepository repository = repositoryRef.get(); if (repository != null && repository.getState() == State.RUNNING) { doRun(repository); } } /** * Perform the garbage collection task. * * @param repository the non-null and {@link State#RUNNING running} repository instance */ protected abstract void doRun( JcrRepository repository ); } protected static class BinaryValueGarbageCollectionTask extends BackgroundRepositoryTask { protected BinaryValueGarbageCollectionTask( JcrRepository repository ) { super(repository); } @Override protected void doRun( JcrRepository repository ) { repository.runningState().cleanUpBinaryValues(); } } protected static class LockGarbageCollectionTask extends BackgroundRepositoryTask { protected LockGarbageCollectionTask( JcrRepository repository ) { super(repository); } @Override protected void doRun( JcrRepository repository ) { repository.runningState().cleanUpLocks(); } } protected static class OptimizationTask extends BackgroundRepositoryTask { private final int targetCount; private final int tolerance; protected OptimizationTask( JcrRepository repository, int targetCount, int tolerance ) { super(repository); this.targetCount = targetCount; this.tolerance = tolerance; } @Override protected void doRun( JcrRepository repository ) { repository.runningState().repositoryCache().optimizeChildren(targetCount, tolerance); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy