/* * 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.version; import org.exoplatform.services.jcr.access.AccessControlEntry; import org.exoplatform.services.jcr.access.AccessControlList; import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager; import org.exoplatform.services.jcr.dataflow.ItemState; import org.exoplatform.services.jcr.datamodel.Identifier; import org.exoplatform.services.jcr.datamodel.IllegalNameException; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.ItemData; import org.exoplatform.services.jcr.datamodel.ItemType; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.PropertyData; import org.exoplatform.services.jcr.datamodel.QPath; import org.exoplatform.services.jcr.datamodel.QPathEntry; import org.exoplatform.services.jcr.datamodel.ValueData; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.core.SessionImpl; import org.exoplatform.services.jcr.impl.dataflow.AbstractItemDataCopyVisitor; import org.exoplatform.services.jcr.impl.dataflow.ItemDataCopyVisitor; import org.exoplatform.services.jcr.impl.dataflow.ItemDataRemoveVisitor; import org.exoplatform.services.jcr.impl.dataflow.TransientItemData; import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData; import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData; import org.exoplatform.services.jcr.impl.dataflow.TransientValueData; import org.exoplatform.services.jcr.impl.dataflow.ValueDataUtil; import org.exoplatform.services.jcr.impl.dataflow.session.SessionChangesLog; import org.exoplatform.services.jcr.impl.dataflow.version.VersionHistoryDataHelper; import org.exoplatform.services.jcr.impl.storage.JCRItemExistsException; import org.exoplatform.services.jcr.util.IdGenerator; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.security.IdentityConstants; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; import javax.jcr.ItemExistsException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.version.OnParentVersionAction; /** * Created by The eXo Platform SAS. * * 14.12.2006 * * @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a> * @version $Id: ItemDataRestoreVisitor.java 14100 2008-05-12 10:53:47Z gazarenkov $ */ public class ItemDataRestoreVisitor extends AbstractItemDataCopyVisitor { private final Log log = ExoLogger.getLogger("exo.jcr.component.core.ItemDataRestoreVisitor"); protected final boolean removeExisting; protected final Stack<NodeDataContext> parents = new Stack<NodeDataContext>(); protected final NodeData context; protected final NodeData history; protected final InternalQName destName; protected NodeData restored; protected final SessionImpl userSession; protected final SessionChangesLog changes; /** * Node paths for updating instead of deleting. */ protected final Set<QPath> updatingPath = new HashSet<QPath>(); /** * Usecase of Workspace.restore(Version[], boolean), for not existing * versionable nodes in the target workspace. */ protected final SessionChangesLog delegatedChanges; private NodeTypeDataManager nodeTypeDataManager; protected class NodeDataContext { private final NodeData node; private final boolean existing; protected NodeDataContext(NodeData node) { this.node = node; this.existing = false; } protected NodeDataContext(NodeData node, boolean existing) { this.node = node; this.existing = existing; } protected NodeData getNode() { return this.node; } public boolean isExisting() { return existing; } } /** * Prepare item states for removing subtree before restoring. Accordingly to JCR specification * some nodes will remain and not be deleted. For this case visitor skips such nodes and stores * its paths. For nodes with OnParentVersion attribute IGNORE ItemDataCopyIgnoredVisitor will be used * to copy children/properties to the restored node. */ protected class RemoveVisitor extends ItemDataRemoveVisitor { /** * The stack. In the top it contains a parent node. */ protected Stack<NodeData> parents = new Stack<NodeData>(); /** * Last met node path during traversing which will remain during restore and not be deleted. */ private QPath remainedNode; /** * Node path for updating instead of deleting. All ancestors of remained nodes * not be deleted since updating will be applied later. */ private final Set<QPath> updatingPath = new HashSet<QPath>(); /** * RemoveVisitor constructor. * * @throws RepositoryException */ RemoveVisitor() throws RepositoryException { super(userSession.getTransientNodesManager(), null, nodeTypeDataManager, userSession.getAccessManager(), userSession.getUserState()); } /** * {@inheritDoc} */ @Override protected void validateReferential(NodeData node) throws RepositoryException { // no REFERENCE validation here } /** * {@inheritDoc} */ @Override protected void entering(NodeData node, int level) throws RepositoryException { if (level == 0) { removedRoot = node; } parents.push(node); } /** * {@inheritDoc} */ @Override public void visit(NodeData node) throws RepositoryException { if (currentLevel > 0) { NodeData parent = parents.peek(); int onParentVersion = nodeTypeDataManager.getChildNodeDefinition(node.getQPath().getName(), node.getPrimaryTypeName(), parent.getPrimaryTypeName(), parent.getMixinTypeNames()).getOnParentVersion(); if (onParentVersion == OnParentVersionAction.VERSION && nodeTypeDataManager.isNodeType(Constants.MIX_VERSIONABLE, node.getPrimaryTypeName(), node.getMixinTypeNames())) { remainedNode = node.getQPath(); // node and its children will remain and not be removed return; } } super.visit(node); } /** * {@inheritDoc} */ @Override protected void leaving(NodeData node, int level) throws RepositoryException { if (validate) { validate(node); } if (remainedNode != null && remainedNode.isDescendantOf(node.getQPath())) { updatingPath.add(node.getQPath()); } else { if (!(node instanceof TransientItemData)) { node = (NodeData)copyItemDataDelete(node); } ItemState state = new ItemState(node, ItemState.DELETED, true, ancestorToSave != null ? ancestorToSave : removedRoot.getQPath()); itemRemovedStates.add(state); } parents.pop(); } /** * {@inheritDoc} */ @Override public List<ItemState> getRemovedStates() { return itemRemovedStates; } /** * Return nodes paths for updating. * * @return Set */ public Set<QPath> getUpdatingPath() { return updatingPath; } }; ItemDataRestoreVisitor(NodeData context, InternalQName restoringName, NodeData history, SessionImpl userSession, boolean removeExisting) throws RepositoryException { this(context, restoringName, history, userSession, removeExisting, null); } ItemDataRestoreVisitor(NodeData context, InternalQName destName, NodeData history, SessionImpl userSession, boolean removeExisting, SessionChangesLog delegatedChanges) throws RepositoryException { super(userSession.getTransientNodesManager().getTransactManager()); this.userSession = userSession; this.changes = new SessionChangesLog(userSession); this.context = context; this.destName = destName; this.history = history; this.parents.push(new NodeDataContext(context)); this.removeExisting = removeExisting; this.nodeTypeDataManager = userSession.getWorkspace().getNodeTypesHolder(); this.delegatedChanges = delegatedChanges; } private NodeData currentNode() { return parents.peek().getNode(); } private NodeData pushCurrent(NodeData node) { return parents.push(new NodeDataContext(node)).getNode(); } private ItemData findDelegated(String identifier) { if (delegatedChanges != null) { for (ItemState state : delegatedChanges.getAllStates()) { if (state.getData().getIdentifier().equals(identifier)) { return state.getData(); } } } return null; } private ItemData findDelegated(QPath path) { if (delegatedChanges != null) { for (ItemState state : delegatedChanges.getAllStates()) { if (state.getData().getQPath().equals(path)) { return state.getData(); } } } return null; } private void deleteDelegated(QPath path) { if (delegatedChanges != null) { List<ItemState> removed = new ArrayList<ItemState>(); for (ItemState state : delegatedChanges.getAllStates()) { if (state.getData().getQPath().equals(path) || state.getData().getQPath().isDescendantOf(path)) { removed.add(state); } } for (ItemState state : removed) { delegatedChanges.remove(state.getData().getQPath()); } } } protected void initRestoreRoot(NodeData parentData, InternalQName name, NodeData frozen) throws RepositoryException { // WARNING: path with index=1 QPath nodePath = QPath.makeChildPath(parentData.getQPath(), name); if (log.isDebugEnabled()) { log.debug("Restore: " + nodePath.getAsString() + ", removeExisting=" + removeExisting); } PropertyData frozenIdentifier = (PropertyData)dataManager.getItemData(frozen, new QPathEntry(Constants.JCR_FROZENUUID, 1), ItemType.PROPERTY); String fidentifier = null; NodeData existing = null; // make new node from frozen try { fidentifier = ValueDataUtil.getString(frozenIdentifier.getValues().get(0)); NodeData sameIdentifierNodeRestored = (NodeData)findDelegated(fidentifier); if (sameIdentifierNodeRestored != null) { // already restored from delegated call, remove it as we interested in // this version state deleteDelegated(sameIdentifierNodeRestored.getQPath()); } else { NodeData sameIdentifierNode = (NodeData)dataManager.getItemData(fidentifier); if (sameIdentifierNode != null) { QPath sameIdentifierPath = sameIdentifierNode.getQPath(); if (sameIdentifierPath.makeParentPath().equals(nodePath.makeParentPath()) && // same // parent sameIdentifierPath.getName().equals(nodePath.getName())) { // same // name if (sameIdentifierPath.getIndex() != nodePath.getIndex()) { // but different index, see below... fix it nodePath = QPath.makeChildPath(parentData.getQPath(), name, sameIdentifierPath.getIndex()); } // if it's a target node existing = sameIdentifierNode; // remove existed node, with validation RemoveVisitor removeVisitor = new RemoveVisitor(); removeVisitor.visit(existing); changes.addAll(removeVisitor.getRemovedStates()); updatingPath.addAll(removeVisitor.getUpdatingPath()); } else if (!sameIdentifierPath.isDescendantOf(nodePath)) { if (removeExisting) { final QPath restorePath = nodePath; // remove same uuid node, with validation class RemoveVisitor extends ItemDataRemoveVisitor { RemoveVisitor() throws RepositoryException { super(userSession.getTransientNodesManager(), null, nodeTypeDataManager, userSession .getAccessManager(), userSession.getUserState()); } @Override protected boolean isRemoveDescendant(ItemData item) throws RepositoryException { return item.getQPath().isDescendantOf(removedRoot.getQPath()) || item.getQPath().isDescendantOf(restorePath); } }; ItemDataRemoveVisitor removeVisitor = new RemoveVisitor(); removeVisitor.visit(sameIdentifierNode); changes.addAll(removeVisitor.getRemovedStates()); } else { throw new ItemExistsException("Item with the same UUID as restored node " + nodePath.getAsString() + " already exists and removeExisting=false. Existed " + userSession.getLocationFactory().createJCRPath(sameIdentifierPath).getAsString(false) + " " + sameIdentifierNode.getIdentifier()); } } } } } catch (IllegalStateException e) { throw new RepositoryException("jcr:frozenUuid, error of data read " + userSession.getLocationFactory().createJCRPath(frozenIdentifier.getQPath()).getAsString(false), e); } PropertyData frozenPrimaryType = (PropertyData)dataManager.getItemData(frozen, new QPathEntry(Constants.JCR_FROZENPRIMARYTYPE, 0), ItemType.PROPERTY); PropertyData frozenMixinTypes = (PropertyData)dataManager.getItemData(frozen, new QPathEntry(Constants.JCR_FROZENMIXINTYPES, 0), ItemType.PROPERTY); List<AccessControlEntry> accessList = new ArrayList<AccessControlEntry>(parentData.getACL().getPermissionEntries()); String owner = parentData.getACL().getOwner(); InternalQName[] mixins = null; if (frozenMixinTypes != null) { try { List<ValueData> mvs = frozenMixinTypes.getValues(); mixins = new InternalQName[mvs.size()]; for (int i = 0; i < mvs.size(); i++) { ValueData mvd = mvs.get(i); mixins[i] = InternalQName.parse(ValueDataUtil.getString(mvd)); if (mixins[i].equals(Constants.EXO_PRIVILEGEABLE)) { PropertyData aclData = (PropertyData)dataManager.getItemData(frozen, new QPathEntry(Constants.EXO_PERMISSIONS, 0), ItemType.PROPERTY); AccessControlList acl = new AccessControlList(); acl.removePermissions(IdentityConstants.ANY); for (ValueData value : aclData.getValues()) { acl.addPermissions(ValueDataUtil.getString(value)); } accessList = acl.getPermissionEntries(); } else if (mixins[i].equals(Constants.EXO_OWNEABLE)) { PropertyData ownerData = (PropertyData)dataManager.getItemData(frozen, new QPathEntry(Constants.EXO_OWNER, 0), ItemType.PROPERTY); owner = ValueDataUtil.getString(ownerData.getValues().get(0)); } } } catch (IllegalNameException e) { throw new RepositoryException("jcr:frozenMixinTypes, error of data read " + userSession.getLocationFactory().createJCRPath(frozenMixinTypes.getQPath()).getAsString(false), e); } catch (IllegalStateException e) { throw new RepositoryException("jcr:frozenMixinTypes, error of data read " + userSession.getLocationFactory().createJCRPath(frozenMixinTypes.getQPath()).getAsString(false), e); } } AccessControlList acl = new AccessControlList(owner, accessList); InternalQName ptName = null; try { ptName = InternalQName.parse(ValueDataUtil.getString(frozenPrimaryType.getValues().get(0))); } catch (IllegalNameException e) { throw new RepositoryException("jcr:frozenPrimaryType, error of data read " + userSession.getLocationFactory().createJCRPath(frozenPrimaryType.getQPath()).getAsString(false), e); } catch (IllegalStateException e) { throw new RepositoryException("jcr:frozenPrimaryType, error of data read " + userSession.getLocationFactory().createJCRPath(frozenPrimaryType.getQPath()).getAsString(false), e); } // create restored version of the node NodeData restoredData = new TransientNodeData(nodePath, fidentifier, (existing != null ? existing.getPersistedVersion() : -1), ptName, mixins == null ? new InternalQName[0] : mixins, 0, parentData.getIdentifier(), acl); if (updatingPath.contains(nodePath)) { changes.add(ItemState.createUpdatedState(restoredData)); } else { changes.add(ItemState.createAddedState(restoredData)); } pushCurrent(restoredData); } @Override protected void entering(NodeData frozen, int level) throws RepositoryException { if (frozen == null) { return; } if (log.isDebugEnabled()) { log.debug("Visit node " + frozen.getQPath().getAsString() + ", HAS NULL FROZEN NODE"); } InternalQName qname = frozen.getQPath().getName(); if (qname.equals(Constants.JCR_FROZENNODE) && level == 0) { // child props/nodes will be restored if (log.isDebugEnabled()) { log.debug("jcr:frozenNode " + frozen.getQPath().getAsString()); } // init destenation node initRestoreRoot(currentNode(), destName, frozen); restored = currentNode(); } else if (nodeTypeDataManager.isNodeType(Constants.NT_VERSIONEDCHILD, frozen.getPrimaryTypeName())) { QPath cvhpPropPath = QPath.makeChildPath(frozen.getQPath(), Constants.JCR_CHILDVERSIONHISTORY); if (log.isDebugEnabled()) { log.debug("Versioned child node " + cvhpPropPath.getAsString()); } VersionHistoryDataHelper childHistory = null; try { String vhIdentifier = ValueDataUtil.getString(((PropertyData)dataManager.getItemData(frozen, new QPathEntry( Constants.JCR_CHILDVERSIONHISTORY, 0), ItemType.PROPERTY)).getValues().get(0)); NodeData cHistory = null; if ((cHistory = (NodeData)dataManager.getItemData(vhIdentifier)) == null) { throw new RepositoryException("Version history is not found with uuid " + vhIdentifier); } childHistory = new VersionHistoryDataHelper(cHistory, dataManager, nodeTypeDataManager); } catch (IllegalStateException e) { throw new RepositoryException("jcr:childVersionHistory, error of data read " + userSession.getLocationFactory().createJCRPath(cvhpPropPath).getAsString(false), e); } String versionableIdentifier = ValueDataUtil.getString(((PropertyData)dataManager.getItemData(childHistory, new QPathEntry( Constants.JCR_VERSIONABLEUUID, 0), ItemType.PROPERTY)).getValues().get(0)); NodeData versionable = (NodeData)dataManager.getItemData(versionableIdentifier); if (versionable != null) { // exists, // On restore of VN, if the workspace currently has an already // existing node corresponding to C’s version history and the // removeExisting flag of the restore is set to true, then that // instance of C becomes the child of the restored N. if (!removeExisting) { throw new JCRItemExistsException("Item with the same UUID " + versionableIdentifier + " as versionable child node " + userSession.getLocationFactory().createJCRPath(versionable.getQPath()).getAsString(false) + " already exists and removeExisting=false", versionableIdentifier); } // else - leaving existed unchanged } else { // not found, gets last version (by time of creation) and restore it NodeData lastVersionData = childHistory.getLastVersionData(); NodeData cvFrozen = (NodeData)dataManager.getItemData(lastVersionData, new QPathEntry(Constants.JCR_FROZENNODE, 1), ItemType.NODE); ItemDataRestoreVisitor restoreVisitor = new ItemDataRestoreVisitor(currentNode(), qname, childHistory, userSession, removeExisting, changes); cvFrozen.accept(restoreVisitor); changes.addAll(restoreVisitor.getRestoreChanges().getAllStates()); } pushCurrent(null); // skip any childs of that node } else if (currentNode() != null) { // ordinary node for copy under nt:frozenNode // [PN] 10.04.06 In case of COPY - copy node, otherwise we don't // 8.2.11.3 INITIALIZE; 8.2.11.4 COMPUTE // On restore of VN, the C stored as its child will be ignored, and the // current C in the workspace will be left unchanged. int action = nodeTypeDataManager.getChildNodeDefinition(qname, frozen.getPrimaryTypeName(), currentNode().getPrimaryTypeName(), currentNode().getMixinTypeNames()).getOnParentVersion(); if (log.isDebugEnabled()) { log.debug("Stored node " + frozen.getQPath().getAsString() + ", " + OnParentVersionAction.nameFromValue(action)); } if (action == OnParentVersionAction.COPY || action == OnParentVersionAction.VERSION) { // copy QPath restoredPath = QPath.makeChildPath(currentNode().getQPath(), frozen.getQPath().getName(), frozen.getQPath().getIndex()); // jcr:uuid String jcrUuid = null; NodeData existing = null; if (nodeTypeDataManager.isNodeType(Constants.MIX_REFERENCEABLE, frozen.getPrimaryTypeName(), frozen.getMixinTypeNames())) { // copy uuid from frozen state of mix:referenceable, // NOTE: mix:referenceable stored in frozen state with genereted ID // (JCR_XITEM PK) as UUID must be unique, // but jcr:uuid property containts real UUID. QPath jcrUuidPath = QPath.makeChildPath(frozen.getQPath(), Constants.JCR_UUID); jcrUuid = ValueDataUtil.getString(((PropertyData)dataManager.getItemData(frozen, new QPathEntry( Constants.JCR_UUID, 0), ItemType.PROPERTY)).getValues().get(0)); existing = (NodeData)dataManager.getItemData(jcrUuid); } else { // try to use existing node uuid, otherwise to generate one new existing = (NodeData)dataManager.getItemData(currentNode(), new QPathEntry(frozen.getQPath().getName(), frozen .getQPath().getIndex()), ItemType.NODE); if (existing != null) { jcrUuid = existing.getIdentifier(); } else { jcrUuid = IdGenerator.generate(); } } if (existing != null && !existing.getQPath().isDescendantOf(restored.getQPath())) { NodeData existingDelegared = (NodeData)findDelegated(existing.getQPath()); if (existingDelegared != null) { // was restored by previous restore (Workspace.restore(...)), remove // it from delegated log deleteDelegated(existing.getQPath()); } else { // exists in workspace if (removeExisting) { if (changes.getItemState(existing.getIdentifier(), ItemState.DELETED) == null) { // remove existed node, with validation (same as for restored // root) RemoveVisitor removeVisitor = new RemoveVisitor(); removeVisitor.visit(existing); changes.addAll(removeVisitor.getRemovedStates()); updatingPath.addAll(removeVisitor.getUpdatingPath()); } } else { throw new ItemExistsException("Node with the same UUID as restored child node " + userSession.getLocationFactory().createJCRPath(restoredPath).getAsString(false) + " already exists and removeExisting=false. Existed " + userSession.getLocationFactory().createJCRPath(existing.getQPath()).getAsString(false) + " " + existing.getIdentifier()); } } } AccessControlList acl = currentNode().getACL(); boolean isPrivilegeable = nodeTypeDataManager.isNodeType(Constants.EXO_PRIVILEGEABLE, frozen.getPrimaryTypeName(), frozen.getMixinTypeNames()); boolean isOwneable = nodeTypeDataManager.isNodeType(Constants.EXO_OWNEABLE, frozen.getPrimaryTypeName(), frozen.getMixinTypeNames()); if (isPrivilegeable || isOwneable) { List<AccessControlEntry> permissionEntries = new ArrayList<AccessControlEntry>(); permissionEntries.addAll((isPrivilegeable ? frozen.getACL() : currentNode().getACL()) .getPermissionEntries()); String owner = isOwneable ? frozen.getACL().getOwner() : currentNode().getACL().getOwner(); acl = new AccessControlList(owner, permissionEntries); } NodeData restoredData = new TransientNodeData(restoredPath, jcrUuid, frozen.getPersistedVersion(), frozen.getPrimaryTypeName(), frozen.getMixinTypeNames(), frozen.getOrderNumber(), currentNode().getIdentifier(), acl); if (updatingPath.contains(restoredPath)) { changes.add(ItemState.createUpdatedState(restoredData)); } else { changes.add(ItemState.createAddedState(restoredData)); } pushCurrent(restoredData); } else if (action == OnParentVersionAction.INITIALIZE || action == OnParentVersionAction.COMPUTE) { // current C in the workspace will be left unchanged, NodeData existed = (NodeData)dataManager.getItemData(currentNode(), new QPathEntry(frozen.getQPath().getName(), 0), ItemType.NODE); if (existed != null) { // copy existed - i.e. left unchanged ItemDataCopyVisitor copyVisitor = new ItemDataCopyVisitor(currentNode(), frozen.getQPath().getName(), nodeTypeDataManager, userSession.getTransientNodesManager(), userSession.getTransientNodesManager(), true); existed.accept(copyVisitor); changes.addAll(copyVisitor.getItemAddStates()); } // else - nothing to do, i.e. left unchanged pushCurrent(null); // JCR-193, skip any childs of that node now } } } @Override protected void entering(PropertyData property, int level) throws RepositoryException { if (currentNode() != null) { NodeData frozenParent = (NodeData)dataManager.getItemData(property.getParentIdentifier()); InternalQName qname = property.getQPath().getName(); if (nodeTypeDataManager.isNodeType(Constants.NT_FROZENNODE, frozenParent.getPrimaryTypeName())) { if (qname.equals(Constants.JCR_FROZENPRIMARYTYPE)) { qname = Constants.JCR_PRIMARYTYPE; } else if (qname.equals(Constants.JCR_FROZENUUID)) { qname = Constants.JCR_UUID; } else if (qname.equals(Constants.JCR_FROZENMIXINTYPES)) { qname = Constants.JCR_MIXINTYPES; } else if (qname.equals(Constants.JCR_PRIMARYTYPE) || qname.equals(Constants.JCR_UUID) || qname.equals(Constants.JCR_MIXINTYPES)) { // skip these props, as they are a nt:frozenNode special props return; } } int action = nodeTypeDataManager .getPropertyDefinitions(qname, currentNode().getPrimaryTypeName(), currentNode().getMixinTypeNames()) .getAnyDefinition().getOnParentVersion(); if (log.isDebugEnabled()) { log.debug("Visit property " + property.getQPath().getAsString() + " " + currentNode().getQPath().getAsString() + " " + OnParentVersionAction.nameFromValue(action)); } if (action == OnParentVersionAction.COPY || action == OnParentVersionAction.VERSION || action == OnParentVersionAction.INITIALIZE || action == OnParentVersionAction.COMPUTE) { // In case of COPY, VERSION - copy property PropertyData tagetProperty = null; if (qname.equals(Constants.JCR_PREDECESSORS)) { tagetProperty = TransientPropertyData.createPropertyData(currentNode(), qname, property.getType(), property.isMultiValued(), new ArrayList<ValueData>()); } else { tagetProperty = TransientPropertyData.createPropertyData(currentNode(), qname, property.getType(), property.isMultiValued(), copyValues(property)); } changes.add(ItemState.createAddedState(tagetProperty)); } else if (log.isDebugEnabled()) { // else - nothing to do, i.e. left unchanged log.debug("Visit property " + property.getQPath().getAsString() + " HAS " + OnParentVersionAction.nameFromValue(action) + " action"); } } else if (log.isDebugEnabled()) { log.debug("Visit property " + property.getQPath().getAsString() + " HAS NULL PARENT. Restore of this property is impossible."); } } @Override protected void leaving(NodeData frozen, int level) throws RepositoryException { InternalQName qname = frozen.getQPath().getName(); if (qname.equals(Constants.JCR_FROZENNODE) && level == 0) { if (log.isDebugEnabled()) { log.debug("leaving jcr:frozenNode " + frozen.getQPath().getAsString()); } // post init of a restored node PropertyData baseVersion = TransientPropertyData.createPropertyData(restored, Constants.JCR_BASEVERSION, PropertyType.REFERENCE, false, new TransientValueData(new Identifier(frozen.getParentIdentifier()))); PropertyData isCheckedOut = TransientPropertyData.createPropertyData(restored, Constants.JCR_ISCHECKEDOUT, PropertyType.BOOLEAN, false, new TransientValueData(false)); NodeData existing = (NodeData)dataManager.getItemData(restored.getIdentifier()); if (existing != null && !existing.getQPath().isDescendantOf(Constants.JCR_VERSION_STORAGE_PATH)) { // copy childs/properties with OnParentVersionAction.IGNORE to the // restored node ItemDataCopyIgnoredVisitor copyIgnoredVisitor = new ItemDataCopyIgnoredVisitor((NodeData)dataManager.getItemData(restored.getParentIdentifier()), restored.getQPath().getName(), nodeTypeDataManager, userSession.getTransientNodesManager(), changes); existing.accept(copyIgnoredVisitor); changes.addAll(copyIgnoredVisitor.getItemAddStates()); } changes.add(ItemState.createAddedState(baseVersion)); changes.add(ItemState.createAddedState(isCheckedOut)); } if (parents.size() <= 0) { log.error("Empty parents stack"); } parents.pop(); } @Override protected void leaving(PropertyData property, int level) throws RepositoryException { } public SessionChangesLog getRestoreChanges() { return changes; } public NodeData getRestoreRoot() { return restored; } }