/* * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * 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. * * This software 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.exoplatform.services.jcr.impl.core; import org.exoplatform.container.ExoContainer; import org.exoplatform.services.jcr.access.PermissionType; import org.exoplatform.services.jcr.core.ExtendedWorkspace; import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager; import org.exoplatform.services.jcr.dataflow.ItemState; import org.exoplatform.services.jcr.dataflow.PlainChangesLog; import org.exoplatform.services.jcr.dataflow.PlainChangesLogImpl; import org.exoplatform.services.jcr.datamodel.ItemType; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.QPathEntry; import org.exoplatform.services.jcr.impl.core.nodetype.NodeTypeImpl; import org.exoplatform.services.jcr.impl.core.nodetype.NodeTypeManagerImpl; import org.exoplatform.services.jcr.impl.core.query.QueryManagerFactory; import org.exoplatform.services.jcr.impl.core.query.QueryManagerImpl; import org.exoplatform.services.jcr.impl.core.version.VersionImpl; import org.exoplatform.services.jcr.impl.dataflow.ItemDataCloneVisitor; import org.exoplatform.services.jcr.impl.dataflow.ItemDataCopyVisitor; import org.exoplatform.services.jcr.impl.dataflow.ItemDataMoveVisitor; import org.exoplatform.services.jcr.impl.dataflow.session.SessionChangesLog; import org.exoplatform.services.jcr.impl.dataflow.session.TransactionableDataManager; import org.exoplatform.services.jcr.impl.dataflow.version.VersionHistoryDataHelper; import org.exoplatform.services.jcr.impl.xml.ExportImportFactory; import org.exoplatform.services.jcr.impl.xml.importing.ContentImporter; import org.exoplatform.services.jcr.impl.xml.importing.StreamImporter; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.xml.sax.ContentHandler; import java.io.IOException; import java.io.InputStream; import java.security.AccessControlException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidItemStateException; import javax.jcr.InvalidSerializedDataException; 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.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; /** * Created by The eXo Platform SAS. * * @author Gennady Azarenkov * @version $Id: WorkspaceImpl.java 14100 2008-05-12 10:53:47Z gazarenkov $ */ public class WorkspaceImpl implements ExtendedWorkspace { protected static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.WorkspaceImpl"); private final SessionImpl session; private final NamespaceRegistryImpl namespaceRegistry; private final NodeTypeDataManager nodeTypeManager; private final ObservationManager observationManager; private final QueryManagerImpl queryManager; private final String name; public WorkspaceImpl(String name, ExoContainer container, SessionImpl session, ObservationManager observationManager) throws RepositoryException { this.session = session; this.name = name; this.observationManager = observationManager; this.namespaceRegistry = (NamespaceRegistryImpl)container.getComponentInstanceOfType(NamespaceRegistry.class, false); this.nodeTypeManager = (NodeTypeDataManager)container.getComponentInstanceOfType(NodeTypeDataManager.class, false); QueryManagerFactory qf = (QueryManagerFactory)container.getComponentInstanceOfType(QueryManagerFactory.class, false); if (qf == null) { this.queryManager = null; } else { this.queryManager = qf.getQueryManager(session); } } /** * @see javax.jcr.Workspace#clone */ public void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, RepositoryException { session.checkLive(); SessionChangesLog changes = new SessionChangesLog(session); clone(srcWorkspace, srcAbsPath, destAbsPath, removeExisting, changes); session.getTransientNodesManager().getTransactManager().save(changes); } /** * @see javax.jcr.Workspace#copy */ public void copy(String srcPath, String destPath) throws ItemExistsException, VersionException, PathNotFoundException, ItemExistsException, ConstraintViolationException, RepositoryException { copy(getName(), srcPath, destPath); } /** * @see javax.jcr.Workspace#copy */ public void copy(String srcWorkspace, String srcAbsPath, String destAbsPath) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, RepositoryException { session.checkLive(); // get source session SessionImpl srcSession = null; if (!getName().equals(srcWorkspace)) { srcSession = repository().internalLogin(session.getUserState(), srcWorkspace); } else { srcSession = session; } // get destination node JCRPath destNodePath = session.getLocationFactory().parseAbsPath(destAbsPath); if (destNodePath.isIndexSetExplicitly()) { throw new RepositoryException("The path provided must not have an index on its final element. " + destNodePath.getAsString(false)); } // get source node JCRPath srcNodePath = srcSession.getLocationFactory().parseAbsPath(srcAbsPath); NodeImpl srcNode = (NodeImpl)srcSession.getTransientNodesManager().getItem(srcNodePath.getInternalPath(), false); // get dst parent node NodeImpl destParentNode = (NodeImpl)session.getTransientNodesManager().getItem(destNodePath.makeParentPath().getInternalPath(), true); if (srcNode == null || destParentNode == null) { throw new PathNotFoundException("No node exists at " + srcAbsPath + " or no node exists one level above " + destAbsPath); } try { destParentNode.checkPermission(PermissionType.ADD_NODE); } catch (AccessControlException e) { throw new AccessDeniedException(e.getMessage()); } destParentNode.validateChildNode(destNodePath.getName().getInternalName(), ((NodeTypeImpl)srcNode .getPrimaryNodeType()).getQName()); NodeImpl destNode = (NodeImpl)session.getTransientNodesManager().getItem((NodeData)destParentNode.getData(), new QPathEntry(destNodePath.getInternalPath().getName(), 0), false, ItemType.NODE); if (destNode != null) { if (!destNode.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException("A node with name (" + destAbsPath + ") is already exists."); } } ItemDataCopyVisitor initializer = new ItemDataCopyVisitor((NodeData)destParentNode.getData(), destNodePath.getName().getInternalName(), nodeTypeManager, srcSession.getTransientNodesManager(), session.getTransientNodesManager(), false); srcNode.getData().accept(initializer); PlainChangesLogImpl changesLog = new PlainChangesLogImpl(initializer.getItemAddStates(), session); session.getTransientNodesManager().getTransactManager().save(changesLog); } /** * {@inheritDoc} */ public String[] getAccessibleWorkspaceNames() throws RepositoryException { RepositoryImpl rep = (RepositoryImpl)session.getRepository(); return rep.getWorkspaceNames(); } /** * {@inheritDoc} */ public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException, ConstraintViolationException, VersionException, RepositoryException { session.checkLive(); NodeImpl node = (NodeImpl)session.getItem(parentAbsPath); // checked-in check if (!node.checkedOut()) { throw new VersionException("Node " + node.getPath() + " or its nearest ancestor is checked-in"); } // Check if node is not protected if (node.getDefinition().isProtected()) { throw new ConstraintViolationException("Can't add protected node " + node.getName() + " to " + node.getParent().getPath()); } // Check locking if (!node.checkLocking()) { throw new LockException("Node " + node.getPath() + " is locked "); } Map<String, Object> context = new HashMap<String, Object>(); context.put(ContentImporter.RESPECT_PROPERTY_DEFINITIONS_CONSTRAINTS, true); return new ExportImportFactory().getImportHandler(((NodeData)node.getData()), uuidBehavior, session .getTransientNodesManager().getTransactManager(), session.getTransientNodesManager().getTransactManager(), nodeTypeManager, session.getLocationFactory(), session.getValueFactory(), getNamespaceRegistry(), session .getAccessManager(), session.getUserState(), context, (RepositoryImpl)session.getRepository(), name); } /** * {@inheritDoc} */ public String getName() { return name; } /** * {@inheritDoc} */ public NamespaceRegistry getNamespaceRegistry() throws RepositoryException { return namespaceRegistry; } /** * {@inheritDoc} */ public NodeTypeManager getNodeTypeManager() throws RepositoryException { // incl Session mapping return new NodeTypeManagerImpl(session.getLocationFactory(), session.getValueFactory(), nodeTypeManager, session .getTransientNodesManager()); } /** * {@inheritDoc} */ public ObservationManager getObservationManager() throws UnsupportedRepositoryOperationException, RepositoryException { return observationManager; } /** * {@inheritDoc} */ public QueryManager getQueryManager() throws RepositoryException { if (queryManager == null) { throw new RepositoryException( "Query Manager Factory not found. Check configuration of the query-handler for workspace " + getName() + "."); } return queryManager; } /** * {@inheritDoc} */ public Session getSession() { return session; } /** * {@inheritDoc} */ public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException, InvalidSerializedDataException, RepositoryException { Map<String, Object> context = new HashMap<String, Object>(); context.put(ContentImporter.RESPECT_PROPERTY_DEFINITIONS_CONSTRAINTS, true); importXML(parentAbsPath, in, uuidBehavior, context); } /* * (non-Javadoc) * @see * org.exoplatform.services.jcr.core.ExtendedWorkspace#importXML(java.lang * .String, java.io.InputStream, int, boolean) */ public void importXML(String parentAbsPath, InputStream in, int uuidBehavior, boolean respectPropertyDefinitionsConstraints) throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException, InvalidSerializedDataException, RepositoryException { Map<String, Object> context = new HashMap<String, Object>(); context.put(ContentImporter.RESPECT_PROPERTY_DEFINITIONS_CONSTRAINTS, respectPropertyDefinitionsConstraints); importXML(parentAbsPath, in, uuidBehavior, context); } /** * {@inheritDoc} */ public void importXML(String parentAbsPath, InputStream in, int uuidBehavior, Map<String, Object> context) throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException, InvalidSerializedDataException, RepositoryException { session.checkLive(); NodeImpl node = (NodeImpl)session.getItem(parentAbsPath); if (!node.checkedOut()) { throw new VersionException("Node " + node.getPath() + " or its nearest ancestor is checked-in"); } // Check if node is not protected if (node.getDefinition().isProtected()) { throw new ConstraintViolationException("Can't add protected node " + node.getName() + " to " + node.getParent().getPath()); } // Check locking if (!node.checkLocking()) { throw new LockException("Node " + node.getPath() + " is locked "); } StreamImporter importer = new ExportImportFactory().getStreamImporter(((NodeData)node.getData()), uuidBehavior, session .getTransientNodesManager().getTransactManager(), session.getTransientNodesManager().getTransactManager(), nodeTypeManager, session.getLocationFactory(), session.getValueFactory(), getNamespaceRegistry(), session .getAccessManager(), session.getUserState(), context, (RepositoryImpl)session.getRepository(), name); importer.importStream(in); } /** * {@inheritDoc} */ public void move(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, RepositoryException { session.checkLive(); // get destination node JCRPath destNodePath = session.getLocationFactory().parseAbsPath(destAbsPath); if (destNodePath.isIndexSetExplicitly()) { throw new RepositoryException("The destination path provided must not have an index on its final element. " + destNodePath.getAsString(false)); } // get source node JCRPath srcNodePath = session.getLocationFactory().parseAbsPath(srcAbsPath); NodeImpl srcNode = (NodeImpl)session.getTransientNodesManager().getItem(srcNodePath.getInternalPath(), false); // get dst parent node NodeImpl destParentNode = (NodeImpl)session.getTransientNodesManager().getItem(destNodePath.makeParentPath().getInternalPath(), true); if (srcNode == null || destParentNode == null) { throw new PathNotFoundException("No node exists at " + srcAbsPath + " or no node exists one level above " + destAbsPath); } try { destParentNode.checkPermission(PermissionType.ADD_NODE); srcNode.checkPermission(PermissionType.REMOVE); } catch (AccessControlException e) { throw new AccessDeniedException(e.getMessage()); } destParentNode.validateChildNode(destNodePath.getName().getInternalName(), ((NodeTypeImpl)srcNode .getPrimaryNodeType()).getQName()); // Check for node with destAbsPath name in session NodeImpl destNode = (NodeImpl)session.getTransientNodesManager().getItem((NodeData)destParentNode.getData(), new QPathEntry(destNodePath.getInternalPath().getName(), 0), false, ItemType.NODE); if (destNode != null && !destNode.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException("A node with this name (" + destAbsPath + ") already exists. "); } NodeImpl srcParentNode = null; Boolean triggerEventsForDescendants; if (destParentNode.getIdentifier().equals(srcNode.getParentIdentifier())) { // move to same parent srcParentNode = destParentNode; triggerEventsForDescendants = session.triggerEventsForDescendantsOnRename; } else { srcParentNode = srcNode.parent(); triggerEventsForDescendants = session.triggerEventsForDescendantsOnMove; } // Check if versionable ancestor is not checked-in if (!srcParentNode.checkedOut()) { throw new VersionException("Source parent node " + srcNode.getPath() + " or its nearest ancestor is checked-in"); } // Check locking if (!srcNode.checkLocking()) { throw new LockException("Source parent node " + srcNode.getPath() + " is locked "); } ItemDataMoveVisitor initializer = new ItemDataMoveVisitor((NodeData)destParentNode.getData(), destNodePath.getName().getInternalName(), (NodeData)srcParentNode.getData(), nodeTypeManager, session.getTransientNodesManager(), true, triggerEventsForDescendants, session.maxDescendantNodesAllowed); srcNode.getData().accept(initializer); PlainChangesLog changes = new PlainChangesLogImpl(initializer.getAllStates(), session); // reload items pool session.getTransientNodesManager().reloadItems(initializer); session.getActionHandler().postMove(srcNode, (NodeImpl) session.getTransientNodesManager(). readItem(initializer.getItemMoveState().getData(), destParentNode.nodeData(), false, false)); // persist the changes session.getTransientNodesManager().getTransactManager().save(changes); } /** * @see javax.jcr.Workspace#restore */ public void restore(Version[] versions, boolean removeExisting) throws UnsupportedRepositoryOperationException, VersionException, RepositoryException, InvalidItemStateException { restoreVersions(versions, removeExisting); } protected void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting, SessionChangesLog changes) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, RepositoryException { if (srcWorkspace.equals(getName())) { throw new RepositoryException("Source and destination workspace are equals " + name); } // make dest node path JCRPath destNodePath = session.getLocationFactory().parseAbsPath(destAbsPath); if (destNodePath.isIndexSetExplicitly()) { throw new RepositoryException("DestPath should not contain an index " + destAbsPath); } // find src node SessionImpl srcSession = repository().internalLogin(session.getUserState(), srcWorkspace); // get source node JCRPath srcNodePath = srcSession.getLocationFactory().parseAbsPath(srcAbsPath); NodeImpl srcNode = (NodeImpl)srcSession.getTransientNodesManager().getItem(srcNodePath.getInternalPath(), false); NodeImpl destParentNode = (NodeImpl)session.getTransientNodesManager().getItem(destNodePath.makeParentPath().getInternalPath(), true); if (srcNode == null || destParentNode == null) { throw new PathNotFoundException("No node exists at source path " + srcAbsPath + " or no node exists one level above on destenation " + destAbsPath); } try { destParentNode.checkPermission(PermissionType.ADD_NODE); } catch (AccessControlException e) { throw new AccessDeniedException(e.getMessage()); } destParentNode.validateChildNode(destNodePath.getName().getInternalName(), ((NodeTypeImpl)srcNode .getPrimaryNodeType()).getQName()); // Check for node with destAbsPath name in session NodeImpl destNode = (NodeImpl)session.getTransientNodesManager().getItem((NodeData)destParentNode.getData(), new QPathEntry(destNodePath.getInternalPath().getName(), 0), false, ItemType.NODE); ItemState changesItemState = null; if (changes != null) { changesItemState = changes.getItemState(destNodePath.getInternalPath()); } if (destNode != null && !(changesItemState != null && changesItemState.isDeleted())) { if (!destNode.getDefinition().allowsSameNameSiblings()) { // Throw exception String msg = "A node with name (" + destAbsPath + ") is already exists."; throw new ItemExistsException(msg); } } ItemDataCloneVisitor initializer = new ItemDataCloneVisitor((NodeData)destParentNode.getData(), destNodePath.getName().getInternalName(), nodeTypeManager, srcSession.getTransientNodesManager(), session.getTransientNodesManager(), removeExisting, changes); srcNode.getData().accept(initializer); // removeing existing nodes and properties if (removeExisting && initializer.getItemDeletedExistingStates(false).size() > 0) { changes.addAll(initializer.getItemDeletedExistingStates(true)); } changes.addAll(initializer.getItemAddStates()); } protected void restoreVersions(Version[] versions, boolean removeExisting) throws UnsupportedRepositoryOperationException, VersionException, RepositoryException, InvalidItemStateException { session.checkLive(); if (session.hasPendingChanges()) { throw new InvalidItemStateException("Session has pending changes "); } // for restore operation List<String> existedIdentifiers = new ArrayList<String>(); // InWorkspace List<VersionImpl> notExistedVersions = new ArrayList<VersionImpl>(); TransactionableDataManager dataManager = session.getTransientNodesManager().getTransactManager(); SessionChangesLog changesLog = new SessionChangesLog(session); for (Version v : versions) { String versionableIdentifier = v.getContainingHistory().getVersionableUUID(); NodeData node = (NodeData)dataManager.getItemData(versionableIdentifier); if (node != null) { existedIdentifiers.add(versionableIdentifier); // restore version at existed parent try { NodeData destParent = (NodeData)dataManager.getItemData(node.getParentIdentifier()); NodeData vh = (NodeData)dataManager.getItemData(((VersionImpl)v).getParentIdentifier()); // version parent // it's a VH VersionHistoryDataHelper historyHelper = new VersionHistoryDataHelper(vh, dataManager, nodeTypeManager); changesLog.addAll(((VersionImpl)v).restoreLog(destParent, node.getQPath().getName(), historyHelper, session, removeExisting, changesLog).getAllStates()); } catch (ItemExistsException e) { throw new ItemExistsException("Workspace restore. Can't restore a node. " + v.getContainingHistory().getVersionableUUID() + ". " + e.getMessage(), e); } catch (RepositoryException e) { throw new RepositoryException("Workspace restore. Can't restore a node. " + v.getContainingHistory().getVersionableUUID() + ". Repository error: " + e.getMessage(), e); } } else { // not found, looking for parent // SPEC: For every version V in S that corresponds to a missing node in // the workspace, there must also be a parent of V in S // ========================================= // Trying search for corresponding node, // If we have a corr node and her parent in the existed list - all ok, // otherwise exception will be thrown. NodeData corrNode = null; String versionableParentIdentifier = null; if (!v.getSession().getWorkspace().getName().equals(session.getWorkspace().getName())) { TransactionableDataManager vDataManager = ((SessionImpl)v.getSession()).getTransientNodesManager().getTransactManager(); corrNode = (NodeData)vDataManager.getItemData(versionableIdentifier); if (corrNode != null) { versionableParentIdentifier = corrNode.getParentIdentifier(); } else { LOG.warn("Workspace.restore(). Correspondent node is not found " + versionableIdentifier); } } if (versionableParentIdentifier != null && existedIdentifiers.contains(versionableParentIdentifier)) { notExistedVersions.add((VersionImpl)v); continue; } if (versionableParentIdentifier == null) { //check in changes log ItemState itemState = changesLog.getItemState(versionableIdentifier); if (itemState != null && !itemState.isDeleted()) { notExistedVersions.add((VersionImpl)v); continue; } } throw new VersionException( "No such node (for version, from the array of versions, " + "that corresponds to a missing node in the workspace, there must also be a parent in the array). UUID: " + versionableIdentifier); } } for (VersionImpl v : notExistedVersions) { String versionableIdentifier = v.getContainingHistory().getVersionableUUID(); try { NodeData node = null; for (ItemState change : changesLog.getAllStates()) { if (change.isNode() && change.isAdded() && ((NodeData)change.getData()).getIdentifier().equals(versionableIdentifier)) { node = (NodeData)change.getData(); break; } } if (node != null) { NodeData destParent = (NodeData)dataManager.getItemData(node.getParentIdentifier()); // version parent it's a VH NodeData vh = (NodeData)dataManager.getItemData(v.getParentIdentifier()); VersionHistoryDataHelper historyHelper = new VersionHistoryDataHelper(vh, dataManager, nodeTypeManager); changesLog.addAll(v.restoreLog(destParent, node.getQPath().getName(), historyHelper, session, removeExisting, changesLog).getAllStates()); } else { throw new VersionException("No such node restored before (for version, from the array of versions, " + "that corresponds to a missing node in the workspace, " + "there must also be a parent in the array). UUID: " + versionableIdentifier); } } catch (ItemExistsException e) { throw new ItemExistsException("Workspace restore. Can't restore a node not existed before. " + versionableIdentifier + ". " + e.getMessage(), e); } catch (RepositoryException e) { throw new RepositoryException("Workspace restore. Can't restore a node not existed before. " + versionableIdentifier + ". Repository error: " + e.getMessage(), e); } } dataManager.save(changesLog); } private RepositoryImpl repository() { return (RepositoryImpl)session.getRepository(); } /** * {@inheritDoc} */ public NodeTypeDataManager getNodeTypesHolder() throws RepositoryException { return nodeTypeManager; } @Override public String toString() { return String.format("Workspace {\n name: %s/%s;\n session-id: %s \n}", repository().getName(), name, session .getId()); } }