/*
 * 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());
   }
}
