/*
* 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 EDU.oswego.cs.dl.util.concurrent.Mutex;
import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.api.JackrabbitRepository;
import org.apache.jackrabbit.commons.AbstractRepository;
import org.apache.jackrabbit.core.cluster.ClusterContext;
import org.apache.jackrabbit.core.cluster.ClusterException;
import org.apache.jackrabbit.core.cluster.ClusterNode;
import org.apache.jackrabbit.core.cluster.WorkspaceEventChannel;
import org.apache.jackrabbit.core.cluster.WorkspaceListener;
import org.apache.jackrabbit.core.cluster.LockEventChannel;
import org.apache.jackrabbit.core.cluster.UpdateEventChannel;
import org.apache.jackrabbit.core.cluster.UpdateEventListener;
import org.apache.jackrabbit.core.config.ClusterConfig;
import org.apache.jackrabbit.core.config.PersistenceManagerConfig;
import org.apache.jackrabbit.core.config.RepositoryConfig;
import org.apache.jackrabbit.core.config.SecurityManagerConfig;
import org.apache.jackrabbit.core.config.VersioningConfig;
import org.apache.jackrabbit.core.config.WorkspaceConfig;
import org.apache.jackrabbit.core.data.DataStore;
import org.apache.jackrabbit.core.fs.BasedFileSystem;
import org.apache.jackrabbit.core.fs.FileSystem;
import org.apache.jackrabbit.core.fs.FileSystemException;
import org.apache.jackrabbit.core.fs.FileSystemResource;
import org.apache.jackrabbit.core.lock.LockManager;
import org.apache.jackrabbit.core.lock.LockManagerImpl;
import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
import org.apache.jackrabbit.core.nodetype.virtual.VirtualNodeTypeStateManager;
import org.apache.jackrabbit.core.observation.DelegatingObservationDispatcher;
import org.apache.jackrabbit.core.observation.EventStateCollection;
import org.apache.jackrabbit.core.observation.ObservationDispatcher;
import org.apache.jackrabbit.core.persistence.PMContext;
import org.apache.jackrabbit.core.persistence.PersistenceManager;
import org.apache.jackrabbit.core.security.JackrabbitSecurityManager;
import org.apache.jackrabbit.core.security.authentication.AuthContext;
import org.apache.jackrabbit.core.security.simple.SimpleSecurityManager;
import org.apache.jackrabbit.core.state.CacheManager;
import org.apache.jackrabbit.core.state.ChangeLog;
import org.apache.jackrabbit.core.state.ISMLocking;
import org.apache.jackrabbit.core.state.ItemStateCacheFactory;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.ManagedMLRUItemStateCacheFactory;
import org.apache.jackrabbit.core.state.SharedItemStateManager;
import org.apache.jackrabbit.core.util.RepositoryLock;
import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.core.version.VersionManager;
import org.apache.jackrabbit.core.version.VersionManagerImpl;
import org.apache.jackrabbit.core.xml.ClonedInputSource;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
import org.apache.jackrabbit.spi.commons.namespace.RegistryNamespaceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import javax.jcr.AccessDeniedException;
import javax.jcr.Credentials;
import javax.jcr.LoginException;
import javax.jcr.NamespaceRegistry;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.ObservationManager;
import javax.security.auth.Subject;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* A <code>RepositoryImpl</code> ...
*/
public class RepositoryImpl extends AbstractRepository
implements JackrabbitRepository, SessionListener, EventListener, WorkspaceListener {
private static Logger log = LoggerFactory.getLogger(RepositoryImpl.class);
/**
* hardcoded id of the repository root node
*/
public static final NodeId ROOT_NODE_ID = NodeId.valueOf("cafebabe-cafe-babe-cafe-babecafebabe");
/**
* hardcoded id of the "/jcr:system" node
*/
public static final NodeId SYSTEM_ROOT_NODE_ID = NodeId.valueOf("deadbeef-cafe-babe-cafe-babecafebabe");
/**
* hardcoded id of the "/jcr:system/jcr:versionStorage" node
*/
public static final NodeId VERSION_STORAGE_NODE_ID = NodeId.valueOf("deadbeef-face-babe-cafe-babecafebabe");
/**
* hardcoded id of the "/jcr:system/jcr:nodeTypes" node
*/
public static final NodeId NODETYPES_NODE_ID = NodeId.valueOf("deadbeef-cafe-cafe-cafe-babecafebabe");
/**
* the name of the file system resource containing the properties of the
* repository.
*/
private static final String PROPERTIES_RESOURCE = "rep.properties";
/**
* the repository properties.
*/
private final Properties repProps;
// names of well-known repository properties
public static final String STATS_NODE_COUNT_PROPERTY = "jcr.repository.stats.nodes.count";
public static final String STATS_PROP_COUNT_PROPERTY = "jcr.repository.stats.properties.count";
private NodeId rootNodeId;
private final NamespaceRegistryImpl nsReg;
private final NodeTypeRegistry ntReg;
private final VersionManagerImpl vMgr;
private final VirtualNodeTypeStateManager virtNTMgr;
/**
* Security manager
*/
private JackrabbitSecurityManager securityMgr;
/**
* Search manager for the jcr:system tree. May be <code>null</code> if
* none is configured.
*/
private SearchManager systemSearchMgr;
// configuration of the repository
protected final RepositoryConfig repConfig;
// the virtual repository file system
private final FileSystem repStore;
// sub file system where the repository stores meta data such as uuid of root node, etc.
private final FileSystem metaDataStore;
/**
* Data store for binary properties.
*/
private final DataStore dataStore;
/**
* the delegating observation dispatcher for all workspaces
*/
private final DelegatingObservationDispatcher delegatingDispatcher =
new DelegatingObservationDispatcher();
/**
* map of workspace names and <code>WorkspaceInfo<code>s.
*/
private final HashMap wspInfos = new HashMap();
/**
* active sessions (weak references)
*/
private final ReferenceMap activeSessions =
new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK);
// misc. statistics
private long nodesCount = 0;
private long propsCount = 0;
// flag indicating if respository has been shut down
private boolean disposed = false;
/**
* the lock that guards instantiation of multiple repositories.
*/
private RepositoryLock repLock;
/**
* Clustered node used, <code>null</code> if clustering is not configured.
*/
private ClusterNode clusterNode;
/**
* Shutdown lock for guaranteeing that no new sessions are started during
* repository shutdown and that a repository shutdown is not initiated
* during a login. Each session login acquires a read lock while the
* repository shutdown requires a write lock. This guarantees that there
* can be multiple concurrent logins when the repository is not shutting
* down, but that only a single shutdown and no concurrent logins can
* happen simultaneously.
*/
private final ReadWriteLock shutdownLock = new WriterPreferenceReadWriteLock();
/**
* There is one cache manager per repository that manages the sizes of the caches used.
*/
private final CacheManager cacheMgr = new CacheManager();
/**
* There is only one item state cache factory
*/
private final ItemStateCacheFactory cacheFactory = new ManagedMLRUItemStateCacheFactory(cacheMgr);
/**
* Chanel for posting create workspace messages.
*/
private WorkspaceEventChannel createWorkspaceEventChannel;
/**
* private constructor
*
* @param repConfig
*/
protected RepositoryImpl(RepositoryConfig repConfig) throws RepositoryException {
log.info("Starting repository...");
boolean succeeded = false;
try {
this.repConfig = repConfig;
// Acquire a lock on the repository home
repLock = new RepositoryLock(repConfig.getHomeDir());
repLock.acquire();
// setup file systems
repStore = repConfig.getFileSystem();
String fsRootPath = "/meta";
try {
if (!repStore.exists(fsRootPath) || !repStore.isFolder(fsRootPath)) {
repStore.createFolder(fsRootPath);
}
} catch (FileSystemException fse) {
String msg = "failed to create folder for repository meta data";
log.error(msg, fse);
throw new RepositoryException(msg, fse);
}
metaDataStore = new BasedFileSystem(repStore, fsRootPath);
// init root node uuid
rootNodeId = loadRootNodeId(metaDataStore);
// load repository properties
repProps = loadRepProps();
nodesCount = Long.parseLong(repProps.getProperty(STATS_NODE_COUNT_PROPERTY, "0"));
propsCount = Long.parseLong(repProps.getProperty(STATS_PROP_COUNT_PROPERTY, "0"));
// create registries
nsReg = createNamespaceRegistry(new BasedFileSystem(repStore, "/namespaces"));
ntReg = createNodeTypeRegistry(nsReg, new BasedFileSystem(repStore, "/nodetypes"));
dataStore = repConfig.getDataStore();
if (dataStore != null) {
assert InternalValue.USE_DATA_STORE;
}
// init workspace configs
Iterator iter = repConfig.getWorkspaceConfigs().iterator();
while (iter.hasNext()) {
WorkspaceConfig config = (WorkspaceConfig) iter.next();
WorkspaceInfo info = createWorkspaceInfo(config);
wspInfos.put(config.getName(), info);
}
// initialize optional clustering
// put here before setting up any other external event source that a cluster node
// will be interested in
if (repConfig.getClusterConfig() != null) {
clusterNode = createClusterNode();
nsReg.setEventChannel(clusterNode);
ntReg.setEventChannel(clusterNode);
createWorkspaceEventChannel = clusterNode;
clusterNode.setListener(this);
}
// init version manager
vMgr = createVersionManager(repConfig.getVersioningConfig(),
delegatingDispatcher);
if (clusterNode != null) {
vMgr.setEventChannel(clusterNode.createUpdateChannel(null));
}
// init virtual node type manager
virtNTMgr = new VirtualNodeTypeStateManager(getNodeTypeRegistry(),
delegatingDispatcher, NODETYPES_NODE_ID, SYSTEM_ROOT_NODE_ID);
// initialize startup workspaces
initStartupWorkspaces();
// initialize system search manager
getSystemSearchManager(repConfig.getDefaultWorkspaceName());
// after the workspace is initialized we pass a system session to
// the virtual node type manager
// todo FIXME the *global* virtual node type manager is using a session that is bound to a single specific workspace...
virtNTMgr.setSession(getSystemSession(repConfig.getDefaultWorkspaceName()));
// now start cluster node as last step
if (clusterNode != null) {
try {
clusterNode.start();
} catch (ClusterException e) {
String msg = "Unable to start clustered node, forcing shutdown...";
log.error(msg, e);
shutdown();
throw new RepositoryException(msg, e);
}
}
// amount of time in seconds before an idle workspace is automatically
// shut down
int maxIdleTime = repConfig.getWorkspaceMaxIdleTime();
if (maxIdleTime != 0) {
// start workspace janitor thread
Thread wspJanitor = new Thread(new WorkspaceJanitor(maxIdleTime * 1000));
wspJanitor.setName("WorkspaceJanitor");
wspJanitor.setPriority(Thread.MIN_PRIORITY);
wspJanitor.setDaemon(true);
wspJanitor.start();
}
succeeded = true;
log.info("Repository started");
} catch (RepositoryException e) {
log.error("failed to start Repository: " + e.getMessage(), e);
throw e;
} finally {
if (!succeeded) {
// repository startup failed, clean up...
shutdown();
}
}
}
public DataStore getDataStore() {
return dataStore;
}
/**
* Get the cache manager of this repository, useful
* for setting its memory parameters.
*
* @return the cache manager
* @since 1.3
*/
public CacheManager getCacheManager() {
return cacheMgr;
}
/**
* Get the item state cache factory of this repository.
*
* @return the cache factory
*/
public ItemStateCacheFactory getItemStateCacheFactory() {
return cacheFactory;
}
/**
* Get the cluster node. Returns <code>null</code> if this repository
* is not running clustered.
*
* @return cluster node
*/
public ClusterNode getClusterNode() {
return clusterNode;
}
/**
* Returns the {@link org.apache.jackrabbit.core.security.JackrabbitSecurityManager SecurityManager}
* of this <code>Repository</code>
*
* @return the security manager
* @throws RepositoryException if an error occurs.
*/
protected synchronized JackrabbitSecurityManager getSecurityManager()
throws RepositoryException {
if (securityMgr == null) {
SecurityManagerConfig smc = getConfig().getSecurityConfig().getSecurityManagerConfig();
String workspaceName = getConfig().getDefaultWorkspaceName();
if (smc != null && smc.getWorkspaceName() != null) {
workspaceName = smc.getWorkspaceName();
}
SystemSession securitySession = getSystemSession(workspaceName);
// mark system session as 'active' for that the system workspace does
// not get disposed by workspace-janitor
onSessionCreated(securitySession);
if (smc == null) {
log.debug("No configuration entry for SecurityManager. Using org.apache.jackrabbit.core.security.simple.SimpleSecurityManager");
securityMgr = new SimpleSecurityManager();
} else {
securityMgr = (JackrabbitSecurityManager) smc.newInstance();
}
securityMgr.init(this, securitySession);
log.info("SecurityManager = " + securityMgr.getClass());
}
return securityMgr;
}
/**
* Creates the version manager.
*
* @param vConfig the versioning config
* @return the newly created version manager
* @throws RepositoryException if an error occurrs
*/
protected VersionManagerImpl createVersionManager(VersioningConfig vConfig,
DelegatingObservationDispatcher delegatingDispatcher)
throws RepositoryException {
FileSystem fs = vConfig.getFileSystem();
PersistenceManager pm = createPersistenceManager(vConfig.getHomeDir(),
fs,
vConfig.getPersistenceManagerConfig(),
rootNodeId,
nsReg,
ntReg,
dataStore);
ISMLocking ismLocking = vConfig.getISMLockingConfig().createISMLocking();
return new VersionManagerImpl(pm, fs, ntReg, delegatingDispatcher,
VERSION_STORAGE_NODE_ID, SYSTEM_ROOT_NODE_ID, cacheFactory,
ismLocking);
}
/**
* Initialize startup workspaces. Base implementation will initialize the
* default workspace. Derived classes may initialize their own startup
* workspaces <b>after</b> having called the base implementation.
*
* @throws RepositoryException if an error occurs
*/
protected void initStartupWorkspaces() throws RepositoryException {
String wspName = repConfig.getDefaultWorkspaceName();
String secWspName = null;
SecurityManagerConfig smc = repConfig.getSecurityConfig().getSecurityManagerConfig();
if (smc != null) {
secWspName = smc.getWorkspaceName();
}
try {
initWorkspace((WorkspaceInfo) wspInfos.get(wspName));
if (secWspName != null && !wspInfos.containsKey(secWspName)) {
createWorkspace(secWspName);
log.info("created system workspace: {}", secWspName);
}
} catch (RepositoryException e) {
// if default workspace failed to initialize, shutdown again
log.error("Failed to initialize workspace '" + wspName + "'", e);
log.error("Unable to start repository, forcing shutdown...");
shutdown();
throw e;
}
}
/**
* Returns the root node uuid.
* @param fs
* @return
* @throws RepositoryException
*/
protected NodeId loadRootNodeId(FileSystem fs) throws RepositoryException {
FileSystemResource uuidFile = new FileSystemResource(fs, "rootUUID");
try {
if (uuidFile.exists()) {
try {
// load uuid of the repository's root node
InputStream in = uuidFile.getInputStream();
/*
// uuid is stored in binary format (16 bytes)
byte[] bytes = new byte[16];
try {
in.read(bytes);
} finally {
try {
in.close();
} catch (IOException ioe) {
// ignore
}
}
rootNodeUUID = new UUID(bytes).toString(); // uuid is stored in binary format (16 bytes)
*/
// uuid is stored in text format (36 characters) for better readability
char[] chars = new char[36];
InputStreamReader reader = new InputStreamReader(in);
try {
reader.read(chars);
} finally {
IOUtils.closeQuietly(reader);
}
return NodeId.valueOf(new String(chars));
} catch (Exception e) {
String msg = "failed to load persisted repository state";
log.debug(msg);
throw new RepositoryException(msg, e);
}
} else {
// create new uuid
/*
UUID rootUUID = UUID.randomUUID(); // version 4 uuid
rootNodeUUID = rootUUID.toString();
*/
/**
* use hard-coded uuid for root node rather than generating
* a different uuid per repository instance; using a
* hard-coded uuid makes it easier to copy/move entire
* workspaces from one repository instance to another.
*/
try {
// persist uuid of the repository's root node
OutputStream out = uuidFile.getOutputStream();
/*
// store uuid in binary format
try {
out.write(rootUUID.getBytes());
} finally {
try {
out.close();
} catch (IOException ioe) {
// ignore
}
}
*/
// store uuid in text format for better readability
OutputStreamWriter writer = new OutputStreamWriter(out);
try {
writer.write(ROOT_NODE_ID.toString());
} finally {
IOUtils.closeQuietly(writer);
}
return ROOT_NODE_ID;
} catch (Exception e) {
String msg = "failed to persist repository state";
log.debug(msg);
throw new RepositoryException(msg, e);
}
}
} catch (FileSystemException fse) {
String msg = "failed to access repository state";
log.debug(msg);
throw new RepositoryException(msg, fse);
}
}
/**
* Creates the <code>NamespaceRegistry</code> instance.
*
* @param fs
* @return
* @throws RepositoryException
*/
protected NamespaceRegistryImpl createNamespaceRegistry(FileSystem fs)
throws RepositoryException {
return new NamespaceRegistryImpl(fs);
}
/**
* Creates the <code>NodeTypeRegistry</code> instance.
*
* @param fs
* @return
* @throws RepositoryException
*/
protected NodeTypeRegistry createNodeTypeRegistry(NamespaceRegistry nsReg,
FileSystem fs)
throws RepositoryException {
return NodeTypeRegistry.create(nsReg, fs);
}
/**
* Creates a new <code>RepositoryImpl</code> instance.
* <p/>
*
* @param config the configuration of the repository
* @return a new <code>RepositoryImpl</code> instance
* @throws RepositoryException If an error occurs
*/
public static RepositoryImpl create(RepositoryConfig config)
throws RepositoryException {
return new RepositoryImpl(config);
}
/**
* Performs a sanity check on this repository instance.
*
* @throws IllegalStateException if this repository has been rendered
* invalid for some reason (e.g. if it has
* been shut down)
*/
protected void sanityCheck() throws IllegalStateException {
// check repository status
if (disposed) {
throw new IllegalStateException("repository instance has been shut down");
}
}
private void initWorkspace(WorkspaceInfo wspInfo) throws RepositoryException {
// first initialize workspace info
if (!wspInfo.initialize()) {
// workspace has already been initialized, we're done
return;
}
// get system session and Workspace instance
SessionImpl sysSession = wspInfo.getSystemSession();
WorkspaceImpl wsp = (WorkspaceImpl) sysSession.getWorkspace();
/**
* todo implement 'System' workspace
* FIXME
* - there should be one 'System' workspace per repository
* - the 'System' workspace should have the /jcr:system node
* - versions, version history and node types should be reflected in
* this system workspace as content under /jcr:system
* - all other workspaces should be dynamic workspaces based on
* this 'read-only' system workspace
*
* for now, the jcr:system node is created in
* {@link org.apache.jackrabbit.core.state.SharedItemStateManager#createRootNodeState}
*/
// register the repository as event listener for keeping repository statistics
wsp.getObservationManager().addEventListener(this,
Event.NODE_ADDED | Event.NODE_REMOVED
| Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED,
"/", true, null, null, false);
// register SearchManager as event listener
SearchManager searchMgr = wspInfo.getSearchManager();
if (searchMgr != null) {
wsp.getObservationManager().addEventListener(searchMgr,
Event.NODE_ADDED | Event.NODE_REMOVED
| Event.PROPERTY_ADDED | Event.PROPERTY_REMOVED
| Event.PROPERTY_CHANGED,
"/", true, null, null, false);
}
}
/**
* Returns the system search manager or <code>null</code> if none is
* configured.
*/
private SearchManager getSystemSearchManager(String wspName)
throws RepositoryException {
if (systemSearchMgr == null) {
if (repConfig.getSearchConfig() != null) {
SystemSession defSysSession = getSystemSession(wspName);
systemSearchMgr = new SearchManager(repConfig.getSearchConfig(),
nsReg, ntReg, defSysSession.getItemStateManager(),
vMgr.getPersistenceManager(), SYSTEM_ROOT_NODE_ID, null, null);
ObservationManager obsMgr = defSysSession.getWorkspace().getObservationManager();
obsMgr.addEventListener(systemSearchMgr, Event.NODE_ADDED
| Event.NODE_REMOVED | Event.PROPERTY_ADDED
| Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED,
"/" + defSysSession.getJCRName(NameConstants.JCR_SYSTEM),
true, null, null, false);
}
}
return systemSearchMgr;
}
/**
* Creates the cluster node.
*
* @return clustered node
*/
protected ClusterNode createClusterNode() throws RepositoryException {
try {
ClusterNode clusterNode = new ClusterNode();
clusterNode.init(new ExternalEventListener());
return clusterNode;
} catch (Exception e) {
throw new RepositoryException(e);
}
}
protected NamespaceRegistryImpl getNamespaceRegistry() {
// check sanity of this instance
sanityCheck();
return nsReg;
}
protected NodeTypeRegistry getNodeTypeRegistry() {
// check sanity of this instance
sanityCheck();
return ntReg;
}
protected VersionManager getVersionManager() {
// check sanity of this instance
sanityCheck();
return vMgr;
}
protected NodeId getRootNodeId() {
// check sanity of this instance
sanityCheck();
return rootNodeId;
}
/**
* Returns the names of <i>all</i> workspaces in this repository.
*
* @return the names of all workspaces in this repository.
* @see javax.jcr.Workspace#getAccessibleWorkspaceNames()
*/
protected String[] getWorkspaceNames() {
synchronized (wspInfos) {
return (String[]) wspInfos.keySet().toArray(new String[wspInfos.keySet().size()]);
}
}
/**
* Returns the {@link WorkspaceInfo} for the named workspace.
*
* @param workspaceName The name of the workspace whose {@link WorkspaceInfo}
* is to be returned. This must not be <code>null</code>.
* @return The {@link WorkspaceInfo} for the named workspace. This will
* never be <code>null</code>.
* @throws IllegalStateException If this repository has already been
* shut down.
* @throws NoSuchWorkspaceException If the named workspace does not exist.
*/
protected WorkspaceInfo getWorkspaceInfo(String workspaceName)
throws IllegalStateException, NoSuchWorkspaceException {
// check sanity of this instance
sanityCheck();
WorkspaceInfo wspInfo;
synchronized (wspInfos) {
wspInfo = (WorkspaceInfo) wspInfos.get(workspaceName);
if (wspInfo == null) {
throw new NoSuchWorkspaceException(workspaceName);
}
}
try {
initWorkspace(wspInfo);
} catch (RepositoryException e) {
log.error("Unable to initialize workspace '" + workspaceName + "'", e);
throw new NoSuchWorkspaceException(workspaceName);
}
return wspInfo;
}
/**
* Creates a workspace with the given name.
*
* @param workspaceName name of the new workspace
* @throws RepositoryException if a workspace with the given name
* already exists or if another error occurs
* @see SessionImpl#createWorkspace(String)
*/
protected void createWorkspace(String workspaceName)
throws RepositoryException {
synchronized (wspInfos) {
if (wspInfos.containsKey(workspaceName)) {
throw new RepositoryException("workspace '"
+ workspaceName + "' already exists.");
}
// needed to get newly created workspace config file content when runnin in clustered environment
StringBuffer workspaceConfigContent = clusterNode != null ? new StringBuffer() : null;
// create the workspace configuration
WorkspaceConfig config = repConfig.createWorkspaceConfig(workspaceName, workspaceConfigContent);
WorkspaceInfo info = createWorkspaceInfo(config);
wspInfos.put(workspaceName, info);
if (workspaceConfigContent != null && createWorkspaceEventChannel != null) {
// notify other cluster node that workspace has been created
InputSource s = new InputSource(new StringReader(workspaceConfigContent.toString()));
createWorkspaceEventChannel.workspaceCreated(workspaceName, new ClonedInputSource(s));
}
}
}
public void externalWorkspaceCreated(String workspaceName,
InputSource configTemplate) throws RepositoryException {
createWorkspaceInternal(workspaceName, configTemplate);
}
/**
* Creates a workspace with the given name and given workspace configuration
* template.
*
* The difference between this method and {@link #createWorkspace(String, InputSource)}
* is that the later notifies the other cluster node that workspace has been created
* whereas this method only creates the workspace.
*
* @param workspaceName name of the new workspace
* @param configTemplate the workspace configuration template of the new
* workspace
* @throws RepositoryException if a workspace with the given name already
* exists or if another error occurs
* @see SessionImpl#createWorkspace(String,InputSource)
*/
private void createWorkspaceInternal(String workspaceName,
InputSource configTemplate)
throws RepositoryException {
synchronized (wspInfos) {
if (wspInfos.containsKey(workspaceName)) {
throw new RepositoryException("workspace '"
+ workspaceName + "' already exists.");
}
// create the workspace configuration
WorkspaceConfig config = repConfig.createWorkspaceConfig(workspaceName, configTemplate);
WorkspaceInfo info = createWorkspaceInfo(config);
wspInfos.put(workspaceName, info);
}
}
/**
* Creates a workspace with the given name and given workspace configuration
* template.
*
* @param workspaceName name of the new workspace
* @param configTemplate the workspace configuration template of the new
* workspace
* @throws RepositoryException if a workspace with the given name already
* exists or if another error occurs
* @see SessionImpl#createWorkspace(String,InputSource)
*/
protected void createWorkspace(String workspaceName,
InputSource configTemplate)
throws RepositoryException {
if (createWorkspaceEventChannel == null) {
createWorkspaceInternal(workspaceName, configTemplate);
}
else {
ClonedInputSource template = new ClonedInputSource(configTemplate);
createWorkspaceInternal(workspaceName, template.cloneInputSource());
createWorkspaceEventChannel.workspaceCreated(workspaceName, template);
}
}
SharedItemStateManager getWorkspaceStateManager(String workspaceName)
throws NoSuchWorkspaceException, RepositoryException {
// check sanity of this instance
sanityCheck();
return getWorkspaceInfo(workspaceName).getItemStateProvider();
}
/**
* Enables or disables referential integrity checking for given workspace.
* Disabling referential integrity checks can result in a corrupted
* workspace, and thus this feature is only available to customized
* implementations that subclass RepositoryImpl.
*
* @see https://issues.apache.org/jira/browse/JCR-954
* @param workspace name of the workspace
* @param enabled <code>true</code> to enable integrity checking (default),
* <code>false</code> to disable it
* @throws RepositoryException if an error occurs
*/
protected void setReferentialIntegrityChecking(
String workspace, boolean enabled) throws RepositoryException {
SharedItemStateManager manager = getWorkspaceStateManager(workspace);
manager.setCheckReferences(enabled);
}
ObservationDispatcher getObservationDispatcher(String workspaceName)
throws NoSuchWorkspaceException {
// check sanity of this instance
sanityCheck();
return getWorkspaceInfo(workspaceName).getObservationDispatcher();
}
/**
* Returns the {@link SearchManager} for the workspace with name
* <code>workspaceName</code>.
*
* @param workspaceName the name of the workspace.
* @return the <code>SearchManager</code> for the workspace, or
* <code>null</code> if the workspace does not have a
* <code>SearchManager</code> configured.
* @throws NoSuchWorkspaceException if there is no workspace with name
* <code>workspaceName</code>.
* @throws RepositoryException if an error occurs while opening the
* search index.
*/
SearchManager getSearchManager(String workspaceName)
throws NoSuchWorkspaceException, RepositoryException {
// check sanity of this instance
sanityCheck();
return getWorkspaceInfo(workspaceName).getSearchManager();
}
/**
* Returns the {@link LockManager} for the workspace with name
* <code>workspaceName</code>
*
* @param workspaceName workspace name
* @return <code>LockManager</code> for the workspace
* @throws NoSuchWorkspaceException if such a workspace does not exist
* @throws RepositoryException if some other error occurs
*/
LockManager getLockManager(String workspaceName) throws
NoSuchWorkspaceException, RepositoryException {
// check sanity of this instance
sanityCheck();
return getWorkspaceInfo(workspaceName).getLockManager();
}
/**
* Returns the {@link SystemSession} for the workspace with name
* <code>workspaceName</code>
*
* @param workspaceName workspace name
* @return system session of the specified workspace
* @throws NoSuchWorkspaceException if such a workspace does not exist
* @throws RepositoryException if some other error occurs
*/
SystemSession getSystemSession(String workspaceName)
throws NoSuchWorkspaceException, RepositoryException {
// check sanity of this instance
sanityCheck();
return getWorkspaceInfo(workspaceName).getSystemSession();
}
/**
* Creates a new repository session on the specified workspace for the
* <b><i>authenticated</i></b> subject of the given login context and
* adds it to the <i>active</i> sessions.
* <p/>
* Calls {@link #createSessionInstance(AuthContext, WorkspaceConfig)} to
* create the actual <code>SessionImpl</code> instance.
*
* @param loginContext login context with authenticated subject
* @param workspaceName workspace name
* @return a new session
* @throws NoSuchWorkspaceException if the specified workspace does not exist
* @throws AccessDeniedException if the subject of the given login context
* is not granted access to the specified
* workspace
* @throws RepositoryException if another error occurs
*/
protected final SessionImpl createSession(AuthContext loginContext,
String workspaceName)
throws NoSuchWorkspaceException, AccessDeniedException,
RepositoryException {
WorkspaceInfo wspInfo = getWorkspaceInfo(workspaceName);
SessionImpl ses = createSessionInstance(loginContext, wspInfo.getConfig());
onSessionCreated(ses);
// reset idle timestamp
wspInfo.setIdleTimestamp(0);
return ses;
}
/**
* Creates a new repository session on the specified workspace for the given
* <b><i>authenticated</i></b> subject and adds it to the <i>active</i>
* sessions.
* <p/>
* Calls {@link #createSessionInstance(Subject, WorkspaceConfig)} to
* create the actual <code>SessionImpl</code> instance.
*
* @param subject authenticated subject
* @param workspaceName workspace name
* @return a new session
* @throws NoSuchWorkspaceException if the specified workspace does not exist
* @throws AccessDeniedException if the subject of the given login context
* is not granted access to the specified
* workspace
* @throws RepositoryException if another error occurs
*/
protected final SessionImpl createSession(Subject subject,
String workspaceName)
throws NoSuchWorkspaceException, AccessDeniedException,
RepositoryException {
WorkspaceInfo wspInfo = getWorkspaceInfo(workspaceName);
SessionImpl ses = createSessionInstance(subject, wspInfo.getConfig());
onSessionCreated(ses);
// reset idle timestamp
wspInfo.setIdleTimestamp(0);
return ses;
}
/**
* Adds the given session to the list of active sessions and registers this
* repository as listener.
*
* @param session the session to register
*/
protected void onSessionCreated(SessionImpl session) {
synchronized (activeSessions) {
session.addListener(this);
activeSessions.put(session, session);
}
}
/**
* Tries to add Principals to a given subject:
* First Access the Subject from the current AccessControlContext,
* If Subject is found the LoginContext is evoked for it, in order
* to possibly allow for extension of preauthenticated Subject.<br>
* In contrast to a login with Credentials, a Session is created, even if the
* Authentication failed.<br>
* If the {@link Subject} is marked to be unmodificable or if the
* authentication of the the Subject failed Session is build for unchanged
* Subject.
*
* @param workspaceName must not be null
* @return if a Subject is exsting null else
* @throws RepositoryException
* @throws AccessDeniedException
*/
private Session extendAuthentication(String workspaceName)
throws RepositoryException, AccessDeniedException {
Subject subject = null;
try {
AccessControlContext acc = AccessController.getContext();
subject = Subject.getSubject(acc);
} catch (SecurityException e) {
log.warn("Can't check for preauthentication. Reason:", e.getMessage());
}
if (subject == null) {
log.debug("No preauthenticated subject found -> return null.");
return null;
}
Session s;
if (subject.isReadOnly()) {
log.debug("Preauthenticated Subject is read-only -> create Session");
s = createSession(subject, workspaceName);
} else {
log.debug("Found preauthenticated Subject, try to extend authentication");
// login either using JAAS or custom LoginModule
AuthContext authCtx = getSecurityManager().getAuthContext(null, subject);
try {
authCtx.login();
s = createSession(authCtx, workspaceName);
} catch (javax.security.auth.login.LoginException e) {
// subject could not be extended
log.debug("Preauthentication could not be extended");
s = createSession(subject, workspaceName);
}
}
return s;
}
//-------------------------------------------------< JackrabbitRepository >
/**
* Shuts down this repository. The shutdown is guarded by a shutdown lock
* that prevents any new sessions from being started simultaneously.
*/
public void shutdown() {
try {
shutdownLock.writeLock().acquire();
} catch (InterruptedException e) {
// TODO: Should this be a checked exception?
throw new RuntimeException("Shutdown lock could not be acquired", e);
}
try {
// check status of this instance
if (!disposed) {
doShutdown();
}
} finally {
shutdownLock.writeLock().release();
}
}
/**
* Private method that performs the actual shutdown after the shutdown
* lock has been acquired by the {@link #shutdown()} method.
*/
private synchronized void doShutdown() {
log.info("Shutting down repository...");
// stop optional cluster node
if (clusterNode != null) {
clusterNode.stop();
}
if (securityMgr != null) {
securityMgr.close();
}
// close active user sessions
// (copy sessions to array to avoid ConcurrentModificationException;
// manually copy entries rather than calling ReferenceMap#toArray() in
// order to work around http://issues.apache.org/bugzilla/show_bug.cgi?id=25551)
SessionImpl[] sa;
synchronized (activeSessions) {
int cnt = 0;
sa = new SessionImpl[activeSessions.size()];
for (Iterator it = activeSessions.values().iterator(); it.hasNext(); cnt++) {
sa[cnt] = (SessionImpl) it.next();
}
}
for (int i = 0; i < sa.length; i++) {
if (sa[i] != null) {
sa[i].logout();
}
}
// shutdown system search manager if there is one
if (systemSearchMgr != null) {
systemSearchMgr.close();
}
// shut down workspaces
synchronized (wspInfos) {
for (Iterator it = wspInfos.values().iterator(); it.hasNext();) {
WorkspaceInfo wspInfo = (WorkspaceInfo) it.next();
wspInfo.dispose();
}
}
if (vMgr != null) {
try {
vMgr.close();
} catch (Exception e) {
log.error("Error while closing Version Manager.", e);
}
}
if (repProps != null) {
// persist repository properties
try {
storeRepProps(repProps);
} catch (RepositoryException e) {
log.error("failed to persist repository properties", e);
}
}
if (repStore != null) {
try {
// close repository file system
repStore.close();
} catch (FileSystemException e) {
log.error("error while closing repository file system", e);
}
}
// make sure this instance is not used anymore
disposed = true;
// wake up threads waiting on this instance's monitor (e.g. workspace janitor)
notifyAll();
// finally release repository lock
repLock.release();
log.info("Repository has been shutdown");
}
/**
* Returns the configuration of this repository.
* @return repository configuration
*/
public RepositoryConfig getConfig() {
return repConfig;
}
/**
* Returns the repository file system.
* @return repository file system
*/
protected FileSystem getFileSystem() {
return repStore;
}
/**
* Sets the default properties of the repository.
* <p/>
* This method loads the <code>Properties</code> from the
* <code>org/apache/jackrabbit/core/repository.properties</code> resource
* found in the class path and (re)sets the statistics properties, if not
* present.
*
* @param props the properties object to load
*
* @throws RepositoryException if the properties can not be loaded
*/
protected void setDefaultRepositoryProperties(Properties props)
throws RepositoryException {
InputStream in = RepositoryImpl.class.getResourceAsStream("repository.properties");
try {
props.load(in);
in.close();
// set counts
if (!props.containsKey(STATS_NODE_COUNT_PROPERTY)) {
props.setProperty(STATS_NODE_COUNT_PROPERTY, Long.toString(nodesCount));
}
if (!props.containsKey(STATS_PROP_COUNT_PROPERTY)) {
props.setProperty(STATS_PROP_COUNT_PROPERTY, Long.toString(propsCount));
}
} catch (IOException e) {
String msg = "Failed to load repository properties: " + e.toString();
log.error(msg);
throw new RepositoryException(msg, e);
}
}
/**
* Loads the repository properties by executing the following steps:
* <ul>
* <li> if the {@link #PROPERTIES_RESOURCE} exists in the meta data store,
* the properties are loaded from that resource.</li>
* <li> {@link #setDefaultRepositoryProperties(Properties)} is called
* afterwards in order to initialize/update the repository properties
* since some default properties might have changed and need updating.</li>
* <li> finally {@link #storeRepProps(Properties)} is called in order to
* persist the newly generated properties.</li>
* </ul>
*
* @return the newly loaded/initialized repository properties
*
* @throws RepositoryException
*/
protected Properties loadRepProps() throws RepositoryException {
FileSystemResource propFile = new FileSystemResource(metaDataStore, PROPERTIES_RESOURCE);
try {
Properties props = new Properties();
if (propFile.exists()) {
InputStream in = propFile.getInputStream();
try {
props.load(in);
} finally {
in.close();
}
}
// now set the default props
setDefaultRepositoryProperties(props);
// and store
storeRepProps(props);
return props;
} catch (Exception e) {
String msg = "failed to load repository properties";
log.debug(msg);
throw new RepositoryException(msg, e);
}
}
/**
* Stores the properties to a persistent resource in the meta filesytem.
*
* @throws RepositoryException
*/
protected void storeRepProps(Properties props) throws RepositoryException {
FileSystemResource propFile = new FileSystemResource(metaDataStore, PROPERTIES_RESOURCE);
try {
propFile.makeParentDirs();
OutputStream os = propFile.getOutputStream();
try {
props.store(os, null);
} finally {
// make sure stream is closed
os.close();
}
} catch (Exception e) {
String msg = "failed to persist repository properties";
log.debug(msg);
throw new RepositoryException(msg, e);
}
}
/**
* Creates a workspace persistence manager based on the given
* configuration. The persistence manager is instantiated using
* information in the given persistence manager configuration and
* initialized with a persistence manager context containing the other
* arguments.
*
* @return the created workspace persistence manager
* @throws RepositoryException if the persistence manager could
* not be instantiated/initialized
*/
private static PersistenceManager createPersistenceManager(File homeDir,
FileSystem fs,
PersistenceManagerConfig pmConfig,
NodeId rootNodeId,
NamespaceRegistry nsReg,
NodeTypeRegistry ntReg,
DataStore dataStore)
throws RepositoryException {
try {
PersistenceManager pm = (PersistenceManager) pmConfig.newInstance();
pm.init(new PMContext(homeDir, fs, rootNodeId, nsReg, ntReg, dataStore));
return pm;
} catch (Exception e) {
String msg = "Cannot instantiate persistence manager " + pmConfig.getClassName();
throw new RepositoryException(msg, e);
}
}
/**
* Creates a <code>SharedItemStateManager</code> or derivative.
*
* @param persistMgr persistence manager
* @param rootNodeId root node id
* @param ntReg node type registry
* @param usesReferences <code>true</code> if the item state manager should use
* node references to verify integrity of its reference properties;
* <code>false</code> otherwise
* @param cacheFactory cache factory
* @return item state manager
* @throws ItemStateException if an error occurs
*/
protected SharedItemStateManager createItemStateManager(PersistenceManager persistMgr,
NodeId rootNodeId,
NodeTypeRegistry ntReg,
boolean usesReferences,
ItemStateCacheFactory cacheFactory,
ISMLocking locking)
throws ItemStateException {
return new SharedItemStateManager(persistMgr, rootNodeId, ntReg, true, cacheFactory, locking);
}
//-----------------------------------------------------------< Repository >
/**
* {@inheritDoc}
*/
public Session login(Credentials credentials, String workspaceName)
throws LoginException, NoSuchWorkspaceException, RepositoryException {
try {
shutdownLock.readLock().acquire();
} catch (InterruptedException e) {
throw new RepositoryException("Login lock could not be acquired", e);
}
try {
// check sanity of this instance
sanityCheck();
if (workspaceName == null) {
workspaceName = repConfig.getDefaultWorkspaceName();
}
// check if workspace exists (will throw NoSuchWorkspaceException if not)
getWorkspaceInfo(workspaceName);
if (credentials == null) {
// try to obtain the identity of the already authenticated
// subject from access control context
Session session = extendAuthentication(workspaceName);
if (session != null) {
// successful extended authentication
return session;
} else {
log.debug("Attempt to login without Credentials and Subject -> try login with null credentials.");
}
}
// not preauthenticated -> try login with credentials
AuthContext authCtx = getSecurityManager().getAuthContext(credentials, new Subject());
authCtx.login();
// create session
return createSession(authCtx, workspaceName);
} catch (SecurityException se) {
throw new LoginException("Unable to access authentication information", se);
} catch (javax.security.auth.login.LoginException le) {
throw new LoginException(le.getMessage(), le);
} catch (AccessDeniedException ade) {
// authenticated subject is not authorized for the specified workspace
throw new LoginException("Workspace access denied", ade);
} finally {
shutdownLock.readLock().release();
}
}
/**
* {@inheritDoc}
*/
public String getDescriptor(String key) {
return repProps.getProperty(key);
}
/**
* {@inheritDoc}
*/
public String[] getDescriptorKeys() {
String[] keys = (String[]) repProps.keySet().toArray(new String[repProps.keySet().size()]);
Arrays.sort(keys);
return keys;
}
//------------------------------------------------------< SessionListener >
/**
* {@inheritDoc}
*/
public void loggingOut(SessionImpl session) {
}
/**
* {@inheritDoc}
*/
public void loggedOut(SessionImpl session) {
synchronized (activeSessions) {
// remove session from active sessions
activeSessions.remove(session);
}
}
//--------------------------------------------------------< EventListener >
/**
* {@inheritDoc}
*/
public void onEvent(EventIterator events) {
// check status of this instance
if (disposed) {
// ignore, repository instance has been shut down
return;
}
synchronized (repProps) {
while (events.hasNext()) {
Event event = events.nextEvent();
long type = event.getType();
if ((type & Event.NODE_ADDED) == Event.NODE_ADDED) {
nodesCount++;
repProps.setProperty(STATS_NODE_COUNT_PROPERTY, Long.toString(nodesCount));
}
if ((type & Event.NODE_REMOVED) == Event.NODE_REMOVED) {
nodesCount--;
repProps.setProperty(STATS_NODE_COUNT_PROPERTY, Long.toString(nodesCount));
}
if ((type & Event.PROPERTY_ADDED) == Event.PROPERTY_ADDED) {
propsCount++;
repProps.setProperty(STATS_PROP_COUNT_PROPERTY, Long.toString(propsCount));
}
if ((type & Event.PROPERTY_REMOVED) == Event.PROPERTY_REMOVED) {
propsCount--;
repProps.setProperty(STATS_PROP_COUNT_PROPERTY, Long.toString(propsCount));
}
}
}
}
//------------------------------------------< overridable factory methods >
/**
* Creates an instance of the {@link SessionImpl} class representing a
* user authenticated by the <code>loginContext</code> instance attached
* to the workspace configured by the <code>wspConfig</code>.
*
* @throws AccessDeniedException if the subject of the given login context
* is not granted access to the specified
* workspace
* @throws RepositoryException If any other error occurs creating the
* session.
*/
protected SessionImpl createSessionInstance(AuthContext loginContext,
WorkspaceConfig wspConfig)
throws AccessDeniedException, RepositoryException {
return new XASessionImpl(this, loginContext, wspConfig);
}
/**
* Creates an instance of the {@link SessionImpl} class representing a
* user represented by the <code>subject</code> instance attached
* to the workspace configured by the <code>wspConfig</code>.
*
* @throws AccessDeniedException if the subject of the given login context
* is not granted access to the specified
* workspace
* @throws RepositoryException If any other error occurs creating the
* session.
*/
protected SessionImpl createSessionInstance(Subject subject,
WorkspaceConfig wspConfig)
throws AccessDeniedException, RepositoryException {
return new XASessionImpl(this, subject, wspConfig);
}
/**
* Creates a new {@link RepositoryImpl.WorkspaceInfo} instance for
* <code>wspConfig</code>.
*
* @param wspConfig the workspace configuration.
* @return a new <code>WorkspaceInfo</code> instance.
*/
protected WorkspaceInfo createWorkspaceInfo(WorkspaceConfig wspConfig) {
return new WorkspaceInfo(wspConfig);
}
//--------------------------------------------------------< inner classes >
/**
* <code>WorkspaceInfo</code> holds the objects that are shared
* among multiple per-session <code>WorkspaceImpl</code> instances
* representing the same named workspace, i.e. the same physical
* storage.
*/
protected class WorkspaceInfo implements UpdateEventListener {
/**
* workspace configuration (passed in constructor)
*/
private final WorkspaceConfig config;
/**
* file system (instantiated on init)
*/
private FileSystem fs;
/**
* persistence manager (instantiated on init)
*/
private PersistenceManager persistMgr;
/**
* item state provider (instantiated on init)
*/
private SharedItemStateManager itemStateMgr;
/**
* observation dispatcher (instantiated on init)
*/
private ObservationDispatcher dispatcher;
/**
* system session (lazily instantiated)
*/
private SystemSession systemSession;
/**
* search manager (lazily instantiated)
*/
private SearchManager searchMgr;
/**
* lock manager (lazily instantiated)
*/
private LockManagerImpl lockMgr;
/**
* flag indicating whether this instance has been initialized.
*/
private boolean initialized;
/**
* lock that guards the initialization of this instance
*/
private final ReadWriteLock initLock =
new ReentrantWriterPreferenceReadWriteLock();
/**
* timestamp when the workspace has been determined being idle
*/
private long idleTimestamp;
/**
* mutex for this workspace, used for locking transactions
*/
private final Mutex xaLock = new Mutex();
/**
* Update event channel, used in clustered environment.
*/
private UpdateEventChannel updateChannel;
/**
* Lock event channel, used in clustered environment.
*/
private LockEventChannel lockChannel;
/**
* Creates a new <code>WorkspaceInfo</code> based on the given
* <code>config</code>.
*
* @param config workspace configuration
*/
protected WorkspaceInfo(WorkspaceConfig config) {
this.config = config;
idleTimestamp = 0;
initialized = false;
}
/**
* Returns the workspace name.
*
* @return the workspace name
*/
protected String getName() {
return config.getName();
}
/**
* Returns the workspace configuration.
*
* @return the workspace configuration
*/
public WorkspaceConfig getConfig() {
return config;
}
/**
* Returns the timestamp when the workspace has become idle or zero
* if the workspace is currently not idle.
*
* @return the timestamp when the workspace has become idle or zero if
* the workspace is not idle.
*/
final long getIdleTimestamp() {
return idleTimestamp;
}
/**
* Sets the timestamp when the workspace has become idle. if
* <code>ts == 0</code> the workspace is marked as being currently
* active.
*
* @param ts timestamp when workspace has become idle.
*/
final void setIdleTimestamp(long ts) {
idleTimestamp = ts;
}
/**
* Returns <code>true</code> if this workspace info is initialized,
* otherwise returns <code>false</code>.
*
* @return <code>true</code> if this workspace info is initialized.
*/
protected final boolean isInitialized() {
try {
if (!initLock.readLock().attempt(0)) {
return false;
}
} catch (InterruptedException e) {
return false;
}
// can't use 'finally' pattern here
boolean ret = initialized;
initLock.readLock().release();
return ret;
}
/**
* Returns the workspace file system.
*
* @return the workspace file system
*/
protected FileSystem getFileSystem() {
if (!isInitialized()) {
throw new IllegalStateException("workspace '" + getName()
+ "' not initialized");
}
return fs;
}
/**
* Returns the workspace persistence manager.
*
* @return the workspace persistence manager
* @throws RepositoryException if the persistence manager could not be
* instantiated/initialized
*/
protected PersistenceManager getPersistenceManager()
throws RepositoryException {
if (!isInitialized()) {
throw new IllegalStateException("workspace '" + getName()
+ "' not initialized");
}
return persistMgr;
}
/**
* Returns the workspace item state provider
*
* @return the workspace item state provider
* @throws RepositoryException if the workspace item state provider
* could not be created
*/
protected SharedItemStateManager getItemStateProvider()
throws RepositoryException {
if (!isInitialized()) {
throw new IllegalStateException("workspace '" + getName()
+ "' not initialized");
}
return itemStateMgr;
}
/**
* Returns the observation dispatcher for this workspace
*
* @return the observation dispatcher for this workspace
*/
protected ObservationDispatcher getObservationDispatcher() {
if (!isInitialized()) {
throw new IllegalStateException("workspace '" + getName()
+ "' not initialized");
}
return dispatcher;
}
/**
* Returns the search manager for this workspace.
*
* @return the search manager for this workspace, or <code>null</code>
* if no <code>SearchManager</code>
* @throws RepositoryException if the search manager could not be created
*/
protected SearchManager getSearchManager() throws RepositoryException {
if (!isInitialized()) {
throw new IllegalStateException("workspace '" + getName()
+ "' not initialized");
}
synchronized (this) {
if (searchMgr == null) {
if (config.getSearchConfig() == null) {
// no search index configured
return null;
}
// search manager is lazily instantiated in order to avoid
// 'chicken & egg' bootstrap problems
searchMgr = new SearchManager(config.getSearchConfig(),
nsReg,
ntReg,
itemStateMgr,
persistMgr,
rootNodeId,
getSystemSearchManager(getName()),
SYSTEM_ROOT_NODE_ID);
}
return searchMgr;
}
}
/**
* Returns the lock manager for this workspace.
*
* @return the lock manager for this workspace
* @throws RepositoryException if the lock manager could not be created
*/
protected LockManager getLockManager() throws RepositoryException {
if (!isInitialized()) {
throw new IllegalStateException("workspace '" + getName()
+ "' not initialized");
}
synchronized (this) {
// lock manager is lazily instantiated in order to avoid
// 'chicken & egg' bootstrap problems
if (lockMgr == null) {
lockMgr = new LockManagerImpl(getSystemSession(), fs);
if (clusterNode != null && config.isClustered()) {
lockChannel = clusterNode.createLockChannel(getName());
lockMgr.setEventChannel(lockChannel);
}
}
return lockMgr;
}
}
/**
* Returns the system session for this workspace.
*
* @return the system session for this workspace
* @throws RepositoryException if the system session could not be created
*/
protected SystemSession getSystemSession() throws RepositoryException {
if (!isInitialized()) {
throw new IllegalStateException("workspace '" + getName()
+ "' not initialized");
}
synchronized (this) {
// system session is lazily instantiated in order to avoid
// 'chicken & egg' bootstrap problems
if (systemSession == null) {
systemSession =
SystemSession.create(RepositoryImpl.this, config);
}
return systemSession;
}
}
/**
* Initializes this workspace info. The following components are
* initialized immediately:
* <ul>
* <li>file system</li>
* <li>persistence manager</li>
* <li>shared item state manager</li>
* <li>observation manager factory</li>
* </ul>
* The following components are initialized lazily (i.e. on demand)
* in order to save resources and to avoid 'chicken & egg' bootstrap
* problems:
* <ul>
* <li>system session</li>
* <li>lock manager</li>
* <li>search manager</li>
* </ul>
* @return <code>true</code> if this instance has been successfully
* initialized, <code>false</code> if it is already initialized.
* @throws RepositoryException if an error occurred during the initialization
*/
final boolean initialize() throws RepositoryException {
// check initialize status
try {
initLock.readLock().acquire();
} catch (InterruptedException e) {
throw new RepositoryException("Unable to aquire read lock.", e);
}
try {
if (initialized) {
// already initialized, we're done
return false;
}
} finally {
initLock.readLock().release();
}
// workspace info was not initialized, now check again with write lock
try {
initLock.writeLock().acquire();
} catch (InterruptedException e) {
throw new RepositoryException("Unable to aquire write lock.", e);
}
try {
if (initialized) {
// already initialized, some other thread was quicker, we're done
return false;
}
log.info("initializing workspace '" + getName() + "'...");
doInitialize();
initialized = true;
log.info("workspace '" + getName() + "' initialized");
return true;
} finally {
initLock.writeLock().release();
}
}
/**
* Does the actual initialization work. assumes holding write lock.
* @throws RepositoryException if an error occurs.
*/
protected void doInitialize() throws RepositoryException {
fs = config.getFileSystem();
persistMgr = createPersistenceManager(new File(config.getHomeDir()),
fs,
config.getPersistenceManagerConfig(),
rootNodeId,
nsReg,
ntReg,
dataStore);
ISMLocking ismLocking = config.getISMLockingConfig().createISMLocking();
// create item state manager
try {
itemStateMgr = createItemStateManager(persistMgr, rootNodeId,
ntReg, true, cacheFactory, ismLocking);
try {
itemStateMgr.addVirtualItemStateProvider(
vMgr.getVirtualItemStateProvider());
itemStateMgr.addVirtualItemStateProvider(
virtNTMgr.getVirtualItemStateProvider());
} catch (Exception e) {
log.error("Unable to add vmgr: " + e.toString(), e);
}
if (clusterNode != null && config.isClustered()) {
updateChannel = clusterNode.createUpdateChannel(getName());
itemStateMgr.setEventChannel(updateChannel);
updateChannel.setListener(this);
}
} catch (ItemStateException ise) {
String msg = "failed to instantiate shared item state manager";
log.debug(msg);
throw new RepositoryException(msg, ise);
}
dispatcher = new ObservationDispatcher();
// register the observation factory of that workspace
delegatingDispatcher.addDispatcher(dispatcher);
}
/**
* Disposes this <code>WorkspaceInfo</code> if it has been idle for more
* than <code>maxIdleTime</code> milliseconds.
*
* @param maxIdleTime amount of time in mmilliseconds before an idle
* workspace is automatically shutdown.
*/
final void disposeIfIdle(long maxIdleTime) {
try {
initLock.readLock().acquire();
} catch (InterruptedException e) {
return;
}
try {
if (!initialized) {
return;
}
long currentTS = System.currentTimeMillis();
if (idleTimestamp == 0) {
// set idle timestamp
idleTimestamp = currentTS;
} else {
if ((currentTS - idleTimestamp) > maxIdleTime) {
// temporarily shutdown workspace
log.info("disposing workspace '" + getName()
+ "' which has been idle for "
+ (currentTS - idleTimestamp) + " ms");
dispose();
}
}
} finally {
initLock.readLock().release();
}
}
/**
* Disposes all objects this <code>WorkspaceInfo</code> is holding.
*/
final void dispose() {
try {
initLock.writeLock().acquire();
} catch (InterruptedException e) {
throw new IllegalStateException("Unable to aquire write lock.");
}
try {
if (!initialized) {
// nothing to dispose of, we're already done
return;
}
log.info("shutting down workspace '" + getName() + "'...");
doDispose();
// reset idle timestamp
idleTimestamp = 0;
initialized = false;
log.info("workspace '" + getName() + "' has been shutdown");
} finally {
initLock.writeLock().release();
}
}
/**
* Does the actual disposal. assumes holding write lock.
*/
protected void doDispose() {
// inform cluster node about disposal
if (updateChannel != null) {
updateChannel.setListener(null);
}
if (lockChannel != null) {
lockChannel.setListener(null);
}
// deregister the observation factory of that workspace
delegatingDispatcher.removeDispatcher(dispatcher);
// dispose observation manager factory
dispatcher.dispose();
dispatcher = null;
// shutdown search managers
if (searchMgr != null) {
searchMgr.close();
searchMgr = null;
}
// deregister
if (securityMgr != null) {
securityMgr.dispose(getName());
}
// close system session
if (systemSession != null) {
systemSession.removeListener(RepositoryImpl.this);
systemSession.logout();
systemSession = null;
}
// dispose shared item state manager
itemStateMgr.dispose();
itemStateMgr = null;
// close persistence manager
try {
persistMgr.close();
} catch (Exception e) {
log.error("error while closing persistence manager of workspace "
+ config.getName(), e);
}
persistMgr = null;
// close lock manager
if (lockMgr != null) {
lockMgr.close();
lockMgr = null;
}
// close workspace file system
try {
fs.close();
} catch (FileSystemException fse) {
log.error("error while closing file system of workspace "
+ config.getName(), fse);
}
fs = null;
}
/**
* Locks this workspace info. This is used (and only should be) by
* the {@link XASessionImpl} in order to lock all internal resources
* during a commit.
*
* @throws TransactionException
*/
void lockAcquire() throws TransactionException {
try {
xaLock.acquire();
} catch (InterruptedException e) {
throw new TransactionException("Error while acquiering lock", e);
}
}
/**
* Unlocks this workspace info. This is used (and only should be) by
* the {@link XASessionImpl} in order to lock all internal resources
* during a commit.
*/
void lockRelease() {
xaLock.release();
}
//----------------------------------------------< UpdateEventListener >
/**
* {@inheritDoc}
*/
public void externalUpdate(ChangeLog external, List events) throws RepositoryException {
try {
EventStateCollection esc = new EventStateCollection(
getObservationDispatcher(), null, null);
esc.addAll(events);
getItemStateProvider().externalUpdate(external, esc);
} catch (IllegalStateException e) {
String msg = "Unable to deliver events: " + e.getMessage();
throw new RepositoryException(msg);
}
}
}
/**
* The workspace janitor thread that will shutdown workspaces that have
* been idle for a certain amount of time.
*/
private class WorkspaceJanitor implements Runnable {
/**
* amount of time in milliseconds before an idle workspace is
* automatically shutdown.
*/
private long maxIdleTime;
/**
* interval in milliseconds between checks for idle workspaces.
*/
private long checkInterval;
/**
* Creates a new <code>WorkspaceJanitor</code> instance responsible for
* shutting down idle workspaces.
*
* @param maxIdleTime amount of time in milliseconds before an idle
* workspace is automatically shutdown.
*/
WorkspaceJanitor(long maxIdleTime) {
this.maxIdleTime = maxIdleTime;
// compute check interval as 10% of maxIdleTime
checkInterval = (long) (0.1 * maxIdleTime);
}
/**
* {@inheritDoc}
* <p/>
* Performs the following tasks in a <code>while (true)</code> loop:
* <ol>
* <li>wait for <code>checkInterval</code> milliseconds</li>
* <li>build list of initialized but currently inactive workspaces
* (excluding the default workspace)</li>
* <li>shutdown those workspaces that have been idle for at least
* <code>maxIdleTime</code> milliseconds</li>
* </ol>
*/
public void run() {
while (true) {
synchronized (RepositoryImpl.this) {
try {
RepositoryImpl.this.wait(checkInterval);
} catch (InterruptedException e) {
// ignore
}
if (disposed) {
return;
}
}
// get names of workspaces
Set wspNames;
synchronized (wspInfos) {
wspNames = new HashSet(wspInfos.keySet());
}
// remove default workspace (will never be shutdown when idle)
wspNames.remove(repConfig.getDefaultWorkspaceName());
synchronized (activeSessions) {
// remove workspaces with active sessions
for (Iterator it = activeSessions.values().iterator(); it.hasNext();) {
SessionImpl ses = (SessionImpl) it.next();
wspNames.remove(ses.getWorkspace().getName());
}
}
// remaining names denote workspaces which currently have not
// active sessions
for (Iterator it = wspNames.iterator(); it.hasNext();) {
WorkspaceInfo wspInfo;
synchronized (wspInfos) {
wspInfo = (WorkspaceInfo) wspInfos.get(it.next());
}
wspInfo.disposeIfIdle(maxIdleTime);
}
}
}
}
/**
* Cluster context passed to a <code>ClusterNode</code>.
*/
class ExternalEventListener implements ClusterContext {
/**
* {@inheritDoc}
*/
public ClusterConfig getClusterConfig() {
return getConfig().getClusterConfig();
}
/**
* {@inheritDoc}
*/
public File getRepositoryHome() {
return new File(getConfig().getHomeDir());
}
/**
* {@inheritDoc}
*/
public NamespaceResolver getNamespaceResolver() {
return new RegistryNamespaceResolver(getNamespaceRegistry());
}
/**
* {@inheritDoc}
*/
public void updateEventsReady(String workspace) throws RepositoryException {
// toggle the initialization of some workspace
getWorkspaceInfo(workspace);
}
/**
* {@inheritDoc}
*/
public void lockEventsReady(String workspace) throws RepositoryException {
// toggle the initialization of some workspace's lock manager
getWorkspaceInfo(workspace).getLockManager();
}
/**
* {@inheritDoc}
*/
public DataStore getDataStore() {
return RepositoryImpl.this.getDataStore();
}
}
}