/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jackrabbit.core; import java.util.ArrayList; import java.util.List; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.ItemExistsException; import javax.jcr.NamespaceRegistry; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.observation.ObservationManager; import javax.jcr.query.QueryManager; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionManager; import org.apache.jackrabbit.api.JackrabbitWorkspace; import org.apache.jackrabbit.api.security.authorization.PrivilegeManager; import org.apache.jackrabbit.commons.AbstractWorkspace; import org.apache.jackrabbit.core.config.WorkspaceConfig; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.lock.LockManager; import org.apache.jackrabbit.core.lock.LockManagerImpl; import org.apache.jackrabbit.core.lock.SessionLockManager; import org.apache.jackrabbit.core.lock.XALockManager; import org.apache.jackrabbit.core.observation.EventStateCollection; import org.apache.jackrabbit.core.observation.EventStateCollectionFactory; import org.apache.jackrabbit.core.observation.ObservationManagerImpl; import org.apache.jackrabbit.core.query.QueryManagerImpl; import org.apache.jackrabbit.core.retention.RetentionRegistry; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateCacheFactory; import org.apache.jackrabbit.core.state.LocalItemStateManager; import org.apache.jackrabbit.core.state.SharedItemStateManager; import org.apache.jackrabbit.core.state.XAItemStateManager; import org.apache.jackrabbit.core.xml.ImportHandler; import org.apache.jackrabbit.core.xml.Importer; import org.apache.jackrabbit.core.xml.WorkspaceImporter; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; /** * A <code>WorkspaceImpl</code> ... */ public class WorkspaceImpl extends AbstractWorkspace implements JackrabbitWorkspace, javax.jcr.Workspace, EventStateCollectionFactory { private static Logger log = LoggerFactory.getLogger(WorkspaceImpl.class); /** * The component context of this session. */ protected final SessionContext context; /** * The configuration of this <code>Workspace</code> */ protected final WorkspaceConfig wspConfig; /** * The persistent state mgr associated with the workspace represented by <i>this</i> * <code>Workspace</code> instance. */ protected final LocalItemStateManager stateMgr; /** * The hierarchy mgr that reflects persistent state only * (i.e. that is isolated from transient changes made through * the session). */ protected final CachingHierarchyManager hierMgr; /** * The <code>ObservationManager</code> instance for this session. */ protected ObservationManagerImpl obsMgr; /** * The <code>QueryManager</code> for this <code>Workspace</code>. */ protected QueryManagerImpl queryManager; /** * the session that was used to acquire this <code>Workspace</code> */ protected final SessionImpl session; /** * The <code>LockManager</code> for this <code>Workspace</code> */ protected LockManager lockMgr; /** * The API LockManager for this workspace, used to create and release * locks and determine the lock status. */ private javax.jcr.lock.LockManager jcr283LockManager; /** * The API Version manager for this workspace */ protected VersionManagerImpl versionMgr; /** * The internal manager used to evaluate effective retention policies and * holds. */ private RetentionRegistry retentionRegistry; /** * Creates a new workspace instance * * @param context component context of this session * @param wspConfig The workspace configuration * @throws RepositoryException if the workspace can not be accessed */ public WorkspaceImpl( SessionContext context, WorkspaceConfig wspConfig) throws RepositoryException { this.context = context; this.wspConfig = wspConfig; this.stateMgr = createItemStateManager(); this.hierMgr = new CachingHierarchyManager( context.getRootNodeId(), this.stateMgr); this.stateMgr.addListener(hierMgr); this.session = context.getSessionImpl(); } /** * The hierarchy manager that reflects workspace state only * (i.e. that is isolated from transient changes made through * the session) * * @return the hierarchy manager of this workspace */ public HierarchyManager getHierarchyManager() { return hierMgr; } /** * Returns the item state manager associated with the workspace * represented by <i>this</i> <code>WorkspaceImpl</code> instance. * * @return the item state manager of this workspace */ public LocalItemStateManager getItemStateManager() { return stateMgr; } /** * Disposes this <code>WorkspaceImpl</code> and frees resources. */ void dispose() { if (obsMgr != null) { obsMgr.dispose(); obsMgr = null; } // remove hierarchy manager as listener to avoid // unnecessary work during stateMgr.dispose() stateMgr.removeListener(hierMgr); stateMgr.dispose(); } /** * Performs a sanity check on this workspace and the associated session. * * @throws RepositoryException if this workspace has been rendered invalid * for some reason */ public void sanityCheck() throws RepositoryException { context.getSessionState().checkAlive(); } //--------------------------------------------------< new JSR 283 methods > /** * {@inheritDoc} */ public void createWorkspace(String name, String srcWorkspace) throws AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); context.getAccessManager().checkRepositoryPermission(Permission.WORKSPACE_MNGMT); WorkspaceManager manager = context.getRepositoryContext().getWorkspaceManager(); manager.createWorkspace(name); SessionImpl tmpSession = null; try { // create a temporary session on new workspace for current subject tmpSession = manager.createSession(session.getSubject(), name); WorkspaceImpl newWsp = (WorkspaceImpl) tmpSession.getWorkspace(); // Workspace#clone(String, String, String, boolean) doesn't // allow to clone to "/"... //newWsp.clone(srcWorkspace, "/", "/", false); Node root = getSession().getRootNode(); for (NodeIterator it = root.getNodes(); it.hasNext(); ) { Node child = it.nextNode(); // skip nodes that already exist in newly created workspace if (!tmpSession.nodeExists(child.getPath())) { newWsp.clone(srcWorkspace, child.getPath(), child.getPath(), false); } } } finally { if (tmpSession != null) { // we don't need the temporary session anymore, logout tmpSession.logout(); } } } /** * {@inheritDoc} * <p> * Always throws <code>UnsupportedRepositoryOperationException</code> since * removal of workspaces is currently not supported. */ public void deleteWorkspace(String name) throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException { // check if workspace exists (will throw NoSuchWorkspaceException if not) context.getRepository().getWorkspaceInfo(name); context.getAccessManager().checkRepositoryPermission(Permission.WORKSPACE_MNGMT); // todo implement deleteWorkspace throw new UnsupportedRepositoryOperationException("not yet implemented"); } /** * @see javax.jcr.Workspace#getLockManager() * @see javax.jcr.lock.LockManager */ public javax.jcr.lock.LockManager getLockManager() throws UnsupportedRepositoryOperationException, RepositoryException { if (jcr283LockManager == null) { jcr283LockManager = new SessionLockManager(context, getInternalLockManager()); } return jcr283LockManager; } /** * @see javax.jcr.Workspace#getVersionManager() */ public VersionManager getVersionManager() { return getVersionManagerImpl(); } VersionManagerImpl getVersionManagerImpl() { if (versionMgr == null) { versionMgr = new VersionManagerImpl(context, stateMgr, hierMgr); } return versionMgr; } //-------------------------------< JackrabbitWorkspace/new JSR 283 method > /** * Creates a new <code>Workspace</code> with the specified * <code>name</code>. The new workspace is empty, meaning it contains only * root node. * <p> * The new workspace can be accessed through a <code>login</code> * specifying its name. * <p> * Throws an <code>AccessDeniedException</code> if the session through which * this <code>Workspace</code> object was acquired does not have permission * to create the new workspace. * <p> * Throws an <code>UnsupportedRepositoryOperationException</code> if the repository does * not support the creation of workspaces. * <p> * A <code>RepositoryException</code> is thrown if another error occurs. * * @param name A <code>String</code>, the name of the new workspace. * @throws AccessDeniedException if the session through which * this <code>Workspace</code> object was acquired does not have permission * to create the new workspace. * @throws RepositoryException if another error occurs. * @since JCR 2.0 */ public void createWorkspace(String name) throws AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); context.getAccessManager().checkRepositoryPermission(Permission.WORKSPACE_MNGMT); context.getRepositoryContext().getWorkspaceManager().createWorkspace(name); } //--------------------------------------------------< JackrabbitWorkspace > /** * Creates a workspace with the given name and a workspace configuration * template. * * @param workspaceName name of the new workspace * @param configTemplate the configuration template of the new workspace * @throws AccessDeniedException if the current session is not allowed to * create the workspace * @throws RepositoryException if a workspace with the given name * already exists or if another error occurs * @see #getAccessibleWorkspaceNames() */ public void createWorkspace( String workspaceName, InputSource configTemplate) throws AccessDeniedException, RepositoryException { // check state of this instance sanityCheck(); context.getAccessManager().checkRepositoryPermission(Permission.WORKSPACE_MNGMT); context.getRepositoryContext().getWorkspaceManager().createWorkspace( workspaceName, configTemplate); } /** * Return the <code>PrivilegeManager</code>. * * @return * @throws RepositoryException * @see org.apache.jackrabbit.api.JackrabbitWorkspace#getPrivilegeManager() */ public PrivilegeManager getPrivilegeManager() throws RepositoryException { sanityCheck(); return context.getPrivilegeManager(); } /** * Returns the configuration of this workspace. * @return the workspace configuration */ public WorkspaceConfig getConfig() { return wspConfig; } /** * @param srcAbsPath * @param srcWsp * @param destAbsPath * @param flag one of * <ul> * <li><code>COPY</code></li> * <li><code>CLONE</code></li> * <li><code>CLONE_REMOVE_EXISTING</code></li> * </ul> * @return the path of the node at its new position * @throws ConstraintViolationException * @throws AccessDeniedException * @throws VersionException * @throws PathNotFoundException * @throws ItemExistsException * @throws LockException * @throws RepositoryException */ private String internalCopy(String srcAbsPath, WorkspaceImpl srcWsp, String destAbsPath, int flag) throws ConstraintViolationException, AccessDeniedException, VersionException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { Path srcPath; try { srcPath = context.getQPath(srcAbsPath).getNormalizedPath(); } catch (NameException e) { String msg = "invalid path: " + srcAbsPath; log.debug(msg); throw new RepositoryException(msg, e); } if (!srcPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + srcAbsPath); } Path destPath; try { destPath = context.getQPath(destAbsPath).getNormalizedPath(); } catch (NameException e) { String msg = "invalid path: " + destAbsPath; log.debug(msg); throw new RepositoryException(msg, e); } if (!destPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + destAbsPath); } BatchedItemOperations ops = new BatchedItemOperations(stateMgr, context); try { ops.edit(); } catch (IllegalStateException e) { String msg = "unable to start edit operation"; log.debug(msg); throw new RepositoryException(msg, e); } boolean succeeded = false; try { NodeId id = ops.copy(srcPath, srcWsp.getItemStateManager(), srcWsp.getHierarchyManager(), srcWsp.context.getAccessManager(), destPath, flag); ops.update(); succeeded = true; return context.getJCRPath(hierMgr.getPath(id)); } finally { if (!succeeded) { // update operation failed, cancel all modifications ops.cancel(); } } } /** * Handles a clone inside the same workspace, which is supported with * shareable nodes. * * @see {@link #clone()} * * @param srcAbsPath source path * @param destAbsPath destination path * @return the path of the node at its new position * @throws ConstraintViolationException * @throws AccessDeniedException * @throws VersionException * @throws PathNotFoundException * @throws ItemExistsException * @throws LockException * @throws RepositoryException */ private String internalClone(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, AccessDeniedException, VersionException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { Path srcPath; try { srcPath = context.getQPath(srcAbsPath).getNormalizedPath(); } catch (NameException e) { String msg = "invalid path: " + srcAbsPath; log.debug(msg); throw new RepositoryException(msg, e); } if (!srcPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + srcAbsPath); } Path destPath; try { destPath = context.getQPath(destAbsPath).getNormalizedPath(); } catch (NameException e) { String msg = "invalid path: " + destAbsPath; log.debug(msg); throw new RepositoryException(msg, e); } if (!destPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + destAbsPath); } BatchedItemOperations ops = new BatchedItemOperations(stateMgr, context); try { ops.edit(); } catch (IllegalStateException e) { String msg = "unable to start edit operation"; log.debug(msg); throw new RepositoryException(msg, e); } boolean succeeded = false; try { ItemId id = ops.clone(srcPath, destPath); ops.update(); succeeded = true; return context.getJCRPath(hierMgr.getPath(id)); } finally { if (!succeeded) { // update operation failed, cancel all modifications ops.cancel(); } } } /** * Return the lock manager for this workspace. If not already done, creates * a new instance. * * @return lock manager for this workspace * @throws RepositoryException if an error occurs */ public synchronized org.apache.jackrabbit.core.lock.LockManager getInternalLockManager() throws RepositoryException { // check state of this instance sanityCheck(); if (lockMgr == null) { lockMgr = context.getRepository().getLockManager(wspConfig.getName()); // FIXME Shouldn't need to use instanceof here if (context.getSessionImpl() instanceof XASessionImpl) { lockMgr = new XALockManager((LockManagerImpl) lockMgr); } } return lockMgr; } /** * Return the internal effective retention/hold manager for this workspace. * If not already done, creates a new instance. * * @return effective retention/hold manager for this workspace * @throws RepositoryException if an error occurs */ synchronized RetentionRegistry getRetentionRegistry() throws RepositoryException { // check state of this instance sanityCheck(); if (retentionRegistry == null) { retentionRegistry = context.getRepository().getRetentionRegistry(wspConfig.getName()); } return retentionRegistry; } //------------------------------------------------------------< Workspace > /** * {@inheritDoc} */ public String getName() { return wspConfig.getName(); } /** * {@inheritDoc} */ public Session getSession() { return session; } /** * {@inheritDoc} */ public NamespaceRegistry getNamespaceRegistry() throws RepositoryException { return context.getNamespaceRegistry(); } /** * {@inheritDoc} */ public NodeTypeManager getNodeTypeManager() throws RepositoryException { // check state of this instance sanityCheck(); return context.getNodeTypeManager(); } /** * {@inheritDoc} */ public void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { // check state of this instance sanityCheck(); // check workspace name if (getName().equals(srcWorkspace)) { // clone to same workspace is allowed for mix:shareable nodes, // but only if removeExisting is false if (!removeExisting) { internalClone(srcAbsPath, destAbsPath); return; } // same as current workspace String msg = srcWorkspace + ": illegal workspace (same as current)"; log.debug(msg); throw new RepositoryException(msg); } // check authorization for specified workspace if (!context.getAccessManager().canAccess(srcWorkspace)) { throw new AccessDeniedException("not authorized to access " + srcWorkspace); } // clone (i.e. pull) subtree at srcAbsPath from srcWorkspace // to 'this' workspace at destAbsPath SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) WorkspaceManager manager = context.getRepositoryContext().getWorkspaceManager(); srcSession = manager.createSession(session.getSubject(), srcWorkspace); WorkspaceImpl srcWsp = (WorkspaceImpl) srcSession.getWorkspace(); // do cross-workspace copy int mode = BatchedItemOperations.CLONE; if (removeExisting) { mode = BatchedItemOperations.CLONE_REMOVE_EXISTING; } internalCopy(srcAbsPath, srcWsp, destAbsPath, mode); } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public void copy(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { // check state of this instance sanityCheck(); // do intra-workspace copy internalCopy(srcAbsPath, this, destAbsPath, BatchedItemOperations.COPY); } /** * {@inheritDoc} */ public void copy(String srcWorkspace, String srcAbsPath, String destAbsPath) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { // check state of this instance sanityCheck(); // check workspace name if (getName().equals(srcWorkspace)) { // same as current workspace, delegate to intra-workspace copy method copy(srcAbsPath, destAbsPath); return; } // check authorization for specified workspace if (!context.getAccessManager().canAccess(srcWorkspace)) { throw new AccessDeniedException("not authorized to access " + srcWorkspace); } // copy (i.e. pull) subtree at srcAbsPath from srcWorkspace // to 'this' workspace at destAbsPath SessionImpl srcSession = null; try { // create session on other workspace for current subject // (may throw NoSuchWorkspaceException and AccessDeniedException) WorkspaceManager manager = context.getRepositoryContext().getWorkspaceManager(); srcSession = manager.createSession(session.getSubject(), srcWorkspace); WorkspaceImpl srcWsp = (WorkspaceImpl) srcSession.getWorkspace(); // do cross-workspace copy internalCopy(srcAbsPath, srcWsp, destAbsPath, BatchedItemOperations.COPY); } finally { if (srcSession != null) { // we don't need the other session anymore, logout srcSession.logout(); } } } /** * {@inheritDoc} */ public void move(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { // check state of this instance sanityCheck(); // intra-workspace move... Path srcPath; try { srcPath = context.getQPath(srcAbsPath).getNormalizedPath(); } catch (NameException e) { String msg = "invalid path: " + srcAbsPath; log.debug(msg); throw new RepositoryException(msg, e); } if (!srcPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + srcAbsPath); } Path destPath; try { destPath = context.getQPath(destAbsPath).getNormalizedPath(); } catch (NameException e) { String msg = "invalid path: " + destAbsPath; log.debug(msg); throw new RepositoryException(msg, e); } if (!destPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + destAbsPath); } BatchedItemOperations ops = new BatchedItemOperations(stateMgr, context); try { ops.edit(); } catch (IllegalStateException e) { String msg = "unable to start edit operation"; log.debug(msg); throw new RepositoryException(msg, e); } boolean succeeded = false; try { ops.move(srcPath, destPath); ops.update(); succeeded = true; } finally { if (!succeeded) { // update operation failed, cancel all modifications ops.cancel(); } } } /** * Returns the observation manager of this session. * * @return the observation manager of this session */ public ObservationManager getObservationManager() { return context.getObservationManager(); } /** * {@inheritDoc} */ public synchronized QueryManager getQueryManager() throws RepositoryException { // check state of this instance sanityCheck(); if (queryManager == null) { SearchManager searchManager; try { searchManager = context.getRepository().getSearchManager(wspConfig.getName()); if (searchManager == null) { String msg = "no search manager configured for this workspace"; log.debug(msg); throw new RepositoryException(msg); } } catch (NoSuchWorkspaceException nswe) { // should never get here String msg = "internal error: failed to instantiate query manager"; log.debug(msg); throw new RepositoryException(msg, nswe); } queryManager = new QueryManagerImpl(context, searchManager); } return queryManager; } /** * {@inheritDoc} */ @Deprecated public void restore(Version[] versions, boolean removeExisting) throws ItemExistsException, UnsupportedRepositoryOperationException, VersionException, LockException, InvalidItemStateException, RepositoryException { // check state of this instance sanityCheck(); getVersionManager().restore(versions, removeExisting); } /** * Returns the names of all workspaces of this repository with respect of the * access rights of this session. * * @return the names of all accessible workspaces * @throws RepositoryException if an error occurs */ public String[] getAccessibleWorkspaceNames() throws RepositoryException { // check state of this instance sanityCheck(); // filter workspaces according to access rights List<String> names = new ArrayList<String>(); WorkspaceManager manager = context.getRepositoryContext().getWorkspaceManager(); for (String name : manager.getWorkspaceNames()) { try { if (context.getAccessManager().canAccess(name)) { names.add(name); } } catch (NoSuchWorkspaceException e) { log.warn("Workspace disappeared unexpectedly: " + name, e); } } return names.toArray(new String[names.size()]); } /** * {@inheritDoc} */ public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException, ConstraintViolationException, VersionException, LockException, RepositoryException { // check state of this instance sanityCheck(); Path parentPath; try { parentPath = context.getQPath(parentAbsPath).getNormalizedPath(); } catch (NameException e) { String msg = "invalid path: " + parentAbsPath; log.debug(msg); throw new RepositoryException(msg, e); } if (!parentPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + parentAbsPath); } Importer importer = new WorkspaceImporter( parentPath, this, context, uuidBehavior, wspConfig.getImportConfig()); return new ImportHandler(importer, getSession()); } /** * Returns the shared item state manager of this workspace. * * @return shared item state manager * @throws RepositoryException if the workspace can not be accessed */ protected SharedItemStateManager getSharedItemStateManager() throws RepositoryException { WorkspaceManager manager = context.getRepositoryContext().getWorkspaceManager(); return manager.getWorkspaceStateManager(getName()); } /** * Create the persistent item state manager on top of the shared item * state manager. * * @return local item state manager * @throws RepositoryException if the workspace can not be accessed */ protected LocalItemStateManager createItemStateManager() throws RepositoryException { SharedItemStateManager sism = getSharedItemStateManager(); ItemStateCacheFactory iscf = context.getRepositoryContext().getItemStateCacheFactory(); // FIXME We should be able to avoid the instanceof operator here if (context.getSessionImpl() instanceof XASessionImpl) { return XAItemStateManager.createInstance( sism, this, null, iscf); } else { return LocalItemStateManager.createInstance( sism, this, iscf); } } //------------------------------------------< EventStateCollectionFactory > /** * {@inheritDoc} * <p> * Implemented in this object and forwarded rather than {@link #obsMgr} * since creation of the latter is lazy. */ public EventStateCollection createEventStateCollection() throws RepositoryException { return ((ObservationManagerImpl) getObservationManager()).createEventStateCollection(); } }