/*
 * 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.commons.utils.PropertyManager;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.services.jcr.access.AccessControlList;
import org.exoplatform.services.jcr.access.AccessManager;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.config.WorkspaceEntry;
import org.exoplatform.services.jcr.core.CredentialsImpl;
import org.exoplatform.services.jcr.core.ExtendedSession;
import org.exoplatform.services.jcr.core.NamespaceAccessor;
import org.exoplatform.services.jcr.core.SessionLifecycleListener;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.dataflow.PlainChangesLog;
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.QPathEntry;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.lock.SessionLockManager;
import org.exoplatform.services.jcr.impl.core.lock.WorkspaceLockManager;
import org.exoplatform.services.jcr.impl.core.nodetype.NodeTypeImpl;
import org.exoplatform.services.jcr.impl.core.observation.ObservationManagerImpl;
import org.exoplatform.services.jcr.impl.core.observation.ObservationManagerRegistry;
import org.exoplatform.services.jcr.impl.core.value.ValueFactoryImpl;
import org.exoplatform.services.jcr.impl.dataflow.ItemDataMoveVisitor;
import org.exoplatform.services.jcr.impl.dataflow.persistent.LocalWorkspaceDataManagerStub;
import org.exoplatform.services.jcr.impl.dataflow.session.TransactionableResourceManager;
import org.exoplatform.services.jcr.impl.ext.action.SessionActionCatalog;
import org.exoplatform.services.jcr.impl.ext.action.SessionActionInterceptor;
import org.exoplatform.services.jcr.impl.util.io.FileCleanerHolder;
import org.exoplatform.services.jcr.impl.xml.ExportImportFactory;
import org.exoplatform.services.jcr.impl.xml.ItemDataKeeperAdapter;
import org.exoplatform.services.jcr.impl.xml.XmlMapping;
import org.exoplatform.services.jcr.impl.xml.exporting.BaseXmlExporter;
import org.exoplatform.services.jcr.impl.xml.importing.ContentImporter;
import org.exoplatform.services.jcr.impl.xml.importing.StreamImporter;
import org.exoplatform.services.jcr.storage.WorkspaceDataContainer;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.Identity;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import javax.jcr.AccessDeniedException;
import javax.jcr.Credentials;
import javax.jcr.InvalidItemStateException;
import javax.jcr.InvalidSerializedDataException;
import javax.jcr.Item;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.LoginException;
import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.observation.ObservationManager;
import javax.jcr.version.VersionException;
import javax.transaction.xa.XAResource;
import javax.xml.stream.XMLStreamException;

/**
 * Created by The eXo Platform SAS.
 * 
 * @author <a href="mailto:geaz@users.sourceforge.net">Gennady Azarenkov </a>
 * @version $Id: SessionImpl.java 14244 2008-05-14 11:44:54Z ksm $ The
 *          implementation supported CredentialsImpl
 */
public class SessionImpl implements ExtendedSession, NamespaceAccessor
{

   /**
    * Logger.
    */
   private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.SessionImpl");

   /**
    * Indicates if it must fail in case we use a closed session.
    */
   public static final boolean ALLOW_CLOSED_SESSION_USAGE = Boolean.valueOf(PropertyManager
      .getProperty("exo.jcr.allow.closed.session.usage"));

   protected static boolean FORCE_USE_GET_NODES_LAZILY = Boolean.valueOf(PropertyManager
      .getProperty("org.exoplatform.jcr.forceUserGetNodesLazily"));

   static
   {
      if (!ALLOW_CLOSED_SESSION_USAGE)
      {
         LOG.info("The JCR will throw an exception anytime we will try to use a dead session.");
      }
      if (FORCE_USE_GET_NODES_LAZILY)
      {
         LOG.warn("EXPERIMENTAL! The JCR will use ExtendedNode.getNodesLazily() for each Node.getNodes() "
            + "invocation. This is an experimental feauture and should be used with care.");
      }
   }

   private static final AtomicLong SEQUENCE = new AtomicLong();

   public static final int DEFAULT_LAZY_READ_THRESHOLD = 100;

   private final RepositoryImpl repository;

   private final ConversationState userState;

   private final WorkspaceImpl workspace;

   private final Map<String, String> namespaces;

   private final Map<String, String> prefixes;

   private final AccessManager accessManager;

   private final LocationFactory locationFactory;

   private final ValueFactoryImpl valueFactory;

   private final ExoContainer container;

   private final LocationFactory systemLocationFactory;

   private final SessionLockManager lockManager;

   protected final String workspaceName;

   private boolean live;

   private boolean expired;

   private Exception closedByCallStack;

   private final List<SessionLifecycleListener> lifecycleListeners;

   private final String id;

   private final SessionActionInterceptor actionHandler;

   private long lastAccessTime;

   Boolean triggerEventsForDescendantsOnMove;

   Boolean triggerEventsForDescendantsOnRename;

   int maxDescendantNodesAllowed;

   private int lazyNodeIteatorPageSize;

   private final int lazyReadThreshold;

   private final SessionRegistry sessionRegistry;

   protected final SessionDataManager dataManager;

   protected final NodeTypeDataManager nodeTypeManager;

   protected final FileCleanerHolder cleanerHolder;

   /**
    * Transaction resources manager.
    */
   private final TransactionableResourceManager txResourceManager;

   /**
    * The local timeout of the session, by default it will use the global timeout defined at repository configuration level
    */
   private long timeout;

   public SessionImpl(String workspaceName, ConversationState userState, ExoContainer container)
      throws RepositoryException
   {
      this.workspaceName = workspaceName;
      this.container = container;
      this.live = true;
      this.id = System.currentTimeMillis() + "_" + SEQUENCE.incrementAndGet();
      this.userState = userState;
      this.txResourceManager =
         (TransactionableResourceManager)container.getComponentInstanceOfType(TransactionableResourceManager.class, false);

      this.repository = (RepositoryImpl)container.getComponentInstanceOfType(RepositoryImpl.class, false);
      this.systemLocationFactory = (LocationFactory)container.getComponentInstanceOfType(LocationFactory.class, false);

      this.accessManager = (AccessManager)container.getComponentInstanceOfType(AccessManager.class, false);
      WorkspaceEntry wsConfig = (WorkspaceEntry)container.getComponentInstanceOfType(WorkspaceEntry.class, false);

      this.lazyReadThreshold =
         wsConfig.getLazyReadThreshold() > 0 ? wsConfig.getLazyReadThreshold() : DEFAULT_LAZY_READ_THRESHOLD;

      this.locationFactory = new LocationFactory(this);

      this.cleanerHolder = (FileCleanerHolder)container.getComponentInstanceOfType(FileCleanerHolder.class, false);
      this.valueFactory = new ValueFactoryImpl(locationFactory, wsConfig, cleanerHolder);

      this.namespaces = new LinkedHashMap<String, String>();
      this.prefixes = new LinkedHashMap<String, String>();

      // Observation manager per session
      ObservationManagerRegistry observationManagerRegistry =
         (ObservationManagerRegistry)container.getComponentInstanceOfType(ObservationManagerRegistry.class, false);
      ObservationManager observationManager = observationManagerRegistry.createObservationManager(this);

      LocalWorkspaceDataManagerStub workspaceDataManager =
         (LocalWorkspaceDataManagerStub)container.getComponentInstanceOfType(LocalWorkspaceDataManagerStub.class, false);

      this.dataManager = new SessionDataManager(this, workspaceDataManager);

      this.lockManager =
         ((WorkspaceLockManager)container.getComponentInstanceOfType(WorkspaceLockManager.class, false))
            .getSessionLockManager(id, dataManager);

      this.nodeTypeManager = (NodeTypeDataManager)container.getComponentInstanceOfType(NodeTypeDataManager.class, false);

      this.workspace = new WorkspaceImpl(workspaceName, container, this, observationManager);

      this.lifecycleListeners = new ArrayList<SessionLifecycleListener>();
      this.registerLifecycleListener((ObservationManagerImpl)observationManager);
      this.registerLifecycleListener(lockManager);

      SessionActionCatalog catalog =
         (SessionActionCatalog)container.getComponentInstanceOfType(SessionActionCatalog.class, false);
      actionHandler = new SessionActionInterceptor(catalog, container, workspaceName);

      sessionRegistry = (SessionRegistry)container.getComponentInstanceOfType(SessionRegistry.class, false);

      sessionRegistry.registerSession(this);
      this.lastAccessTime = System.currentTimeMillis();

      this.triggerEventsForDescendantsOnMove =
         wsConfig.getContainer().getParameterBoolean(WorkspaceDataContainer.TRIGGER_EVENTS_FOR_DESCENDANTS_ON_MOVE,
            WorkspaceDataContainer.TRIGGER_EVENTS_FOR_DESCENDANTS_ON_MOVE_DEFAULT);

      // first check the parameter misspelled
      this.triggerEventsForDescendantsOnRename =
         wsConfig.getContainer().getParameterBoolean(WorkspaceDataContainer.TRIGGER_EVENTS_FOR_DESCENDENTS_ON_RENAME,
            triggerEventsForDescendantsOnMove);

      this.triggerEventsForDescendantsOnRename =
         wsConfig.getContainer().getParameterBoolean(WorkspaceDataContainer.TRIGGER_EVENTS_FOR_DESCENDANTS_ON_RENAME,
            triggerEventsForDescendantsOnRename);

      this.maxDescendantNodesAllowed =
         wsConfig.getContainer().getParameterInteger(WorkspaceDataContainer.MAX_DESCENDANTS_NODES_ALLOWED_ON_MOVE,
            WorkspaceDataContainer.MAX_DESCENDANTS_NODES_ALLOWED_ON_MOVE_DEFAULT);

      this.lazyNodeIteatorPageSize =
         wsConfig.getContainer().getParameterInteger(WorkspaceDataContainer.LAZY_NODE_ITERATOR_PAGE_SIZE,
            WorkspaceDataContainer.LAZY_NODE_ITERATOR_PAGE_SIZE_DEFAULT);
   }

   /**
    * {@inheritDoc}
    */
   public void addLockToken(String lt)
   {
      getLockManager().addLockToken(lt);
   }

   /**
    * {@inheritDoc}
    */
   public void checkPermission(String absPath, String actions) throws AccessControlException
   {
      try
      {
         JCRPath jcrPath = locationFactory.parseAbsPath(absPath);
         AccessControlList acl = dataManager.getACL(jcrPath.getInternalPath());
         if (!accessManager.hasPermission(acl, actions, getUserState().getIdentity()))
         {
            throw new AccessControlException("Permission denied " + absPath + " : " + actions);
         }
      }
      catch (RepositoryException e)
      {
         throw new AccessControlException("Could not check permission for " + absPath + " " + e);
      }
   }

   /**
    * {@inheritDoc}
    */
   public void exportDocumentView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse)
      throws InvalidSerializedDataException, PathNotFoundException, SAXException, RepositoryException
   {
      checkLive();

      LocationFactory factory = new LocationFactory(((NamespaceRegistryImpl)repository.getNamespaceRegistry()));

      WorkspaceEntry wsConfig = (WorkspaceEntry)container.getComponentInstanceOfType(WorkspaceEntry.class);

      ValueFactoryImpl valueFactoryImpl = new ValueFactoryImpl(factory, wsConfig, cleanerHolder);

      try
      {
         BaseXmlExporter exporter =
            new ExportImportFactory().getExportVisitor(XmlMapping.DOCVIEW, contentHandler, skipBinary, noRecurse,
               getTransientNodesManager(), repository.getNamespaceRegistry(), valueFactoryImpl);

         JCRPath srcNodePath = getLocationFactory().parseAbsPath(absPath);
         ItemData srcItemData = dataManager.getItemData(srcNodePath.getInternalPath());

         if (srcItemData == null)
         {
            throw new PathNotFoundException("No node exists at " + absPath);
         }

         exporter.export((NodeData)srcItemData);

      }
      catch (XMLStreamException e)
      {
         throw new SAXException(e);
      }
   }

   /**
    * {@inheritDoc}
    */
   public void exportDocumentView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse)
      throws InvalidSerializedDataException, IOException, PathNotFoundException, RepositoryException
   {
      checkLive();

      LocationFactory factory = new LocationFactory(((NamespaceRegistryImpl)repository.getNamespaceRegistry()));

      WorkspaceEntry wsConfig = (WorkspaceEntry)container.getComponentInstanceOfType(WorkspaceEntry.class);

      ValueFactoryImpl valueFactoryImpl = new ValueFactoryImpl(factory, wsConfig, cleanerHolder);

      try
      {
         BaseXmlExporter exporter =
            new ExportImportFactory().getExportVisitor(XmlMapping.DOCVIEW, out, skipBinary, noRecurse,
               getTransientNodesManager(), repository.getNamespaceRegistry(), valueFactoryImpl);

         JCRPath srcNodePath = getLocationFactory().parseAbsPath(absPath);
         ItemData srcItemData = dataManager.getItemData(srcNodePath.getInternalPath());

         if (srcItemData == null)
         {
            throw new PathNotFoundException("No node exists at " + absPath);
         }

         exporter.export((NodeData)srcItemData);
      }
      catch (XMLStreamException e)
      {
         throw new IOException(e.getLocalizedMessage(), e);
      }
      catch (SAXException e)
      {
         throw new IOException(e.getLocalizedMessage(), e);
      }
   }

   /**
    * {@inheritDoc}
    */
   public void exportWorkspaceSystemView(OutputStream out, boolean skipBinary, boolean noRecurse) throws IOException,
      PathNotFoundException, RepositoryException
   {
      checkLive();

      LocationFactory factory = new LocationFactory(((NamespaceRegistryImpl)repository.getNamespaceRegistry()));

      WorkspaceEntry wsConfig = (WorkspaceEntry)container.getComponentInstanceOfType(WorkspaceEntry.class);

      ValueFactoryImpl valueFactoryImpl = new ValueFactoryImpl(factory, wsConfig, cleanerHolder);

      try
      {
         BaseXmlExporter exporter =
            new ExportImportFactory().getExportVisitor(XmlMapping.BACKUP, out, skipBinary, noRecurse,
               getTransientNodesManager(), repository.getNamespaceRegistry(), valueFactoryImpl);

         ItemData srcItemData = dataManager.getItemData(Constants.ROOT_UUID);
         if (srcItemData == null)
         {
            throw new PathNotFoundException("Root node not found");
         }

         exporter.export((NodeData)srcItemData);
      }
      catch (XMLStreamException e)
      {
         throw new IOException(e.getLocalizedMessage(), e);
      }
      catch (SAXException e)
      {
         throw new IOException(e.getLocalizedMessage(), e);
      }
   }

   /**
    * {@inheritDoc}
    */
   public void exportSystemView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse)
      throws PathNotFoundException, SAXException, RepositoryException
   {
      checkLive();

      LocationFactory factory = new LocationFactory(((NamespaceRegistryImpl)repository.getNamespaceRegistry()));

      WorkspaceEntry wsConfig = (WorkspaceEntry)container.getComponentInstanceOfType(WorkspaceEntry.class);

      ValueFactoryImpl valueFactoryImpl = new ValueFactoryImpl(factory, wsConfig, cleanerHolder);
      try
      {
         BaseXmlExporter exporter =
            new ExportImportFactory().getExportVisitor(XmlMapping.SYSVIEW, contentHandler, skipBinary, noRecurse,
               getTransientNodesManager(), repository.getNamespaceRegistry(), valueFactoryImpl);

         JCRPath srcNodePath = getLocationFactory().parseAbsPath(absPath);
         ItemData srcItemData = dataManager.getItemData(srcNodePath.getInternalPath());
         if (srcItemData == null)
         {
            throw new PathNotFoundException("No node exists at " + absPath);
         }

         exporter.export((NodeData)srcItemData);

      }
      catch (XMLStreamException e)
      {
         throw new SAXException(e);
      }
   }

   /**
    * {@inheritDoc}
    */
   public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse,
      boolean exportChildVersionHisotry) throws IOException, PathNotFoundException, RepositoryException
   {
      checkLive();

      LocationFactory factory = new LocationFactory(((NamespaceRegistryImpl)repository.getNamespaceRegistry()));

      WorkspaceEntry wsConfig = (WorkspaceEntry)container.getComponentInstanceOfType(WorkspaceEntry.class);

      ValueFactoryImpl valueFactoryImpl = new ValueFactoryImpl(factory, wsConfig, cleanerHolder);
      try
      {
         BaseXmlExporter exporter =
            new ExportImportFactory().getExportVisitor(XmlMapping.SYSVIEW, out, skipBinary, noRecurse,
               exportChildVersionHisotry, getTransientNodesManager(), repository.getNamespaceRegistry(),
               valueFactoryImpl);

         JCRPath srcNodePath = getLocationFactory().parseAbsPath(absPath);
         ItemData srcItemData = dataManager.getItemData(srcNodePath.getInternalPath());

         if (srcItemData == null)
         {
            throw new PathNotFoundException("No node exists at " + absPath);
         }

         exporter.export((NodeData)srcItemData);
      }
      catch (XMLStreamException e)
      {
         throw new IOException(e.getLocalizedMessage(), e);
      }
      catch (SAXException e)
      {
         throw new IOException(e.getLocalizedMessage(), e);
      }

   }

   /**
    * {@inheritDoc}
    */
   public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse)
      throws IOException, PathNotFoundException, RepositoryException
   {
      exportSystemView(absPath, out, skipBinary, noRecurse, false);
   }

   /**
    * @return Returns the accessManager.
    */
   public AccessManager getAccessManager()
   {
      return accessManager;
   }

   public SessionActionInterceptor getActionHandler()
   {
      return actionHandler;
   }

   /**
    * {@inheritDoc}
    */
   public String[] getAllNamespacePrefixes() throws RepositoryException
   {
      return getNamespacePrefixes();
   }

   /**
    * {@inheritDoc}
    */
   public Object getAttribute(String name)
   {
      return userState.getAttribute(name);
   }

   /**
    * {@inheritDoc}
    */
   public String[] getAttributeNames()
   {

      Set<String> attributes = userState.getAttributeNames();

      String[] names = new String[attributes.size()];
      int i = 0;
      for (String name : attributes)
      {
         names[i++] = name;
      }
      return names;
   }

   /**
    * For debug purpose! Can accessed by admin only, otherwise null will be
    * returned
    * 
    * @deprecated use WorkspaceContainerFacade instead of using container
    *             directly
    */
   @Deprecated
   public ExoContainer getContainer()
   {
      return container;
   }

   /**
    * {@inheritDoc}
    */
   public String getId()
   {
      return id;
   }

   /**
    * {@inheritDoc}
    */
   public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws

   PathNotFoundException, ConstraintViolationException, VersionException, RepositoryException
   {
      checkLive();
      NodeImpl node = (NodeImpl)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,
         new ItemDataKeeperAdapter(getTransientNodesManager()), getTransientNodesManager(), nodeTypeManager,
         getLocationFactory(), getValueFactory(), getWorkspace().getNamespaceRegistry(), getAccessManager(), userState,
         context, (RepositoryImpl)getRepository(), getWorkspace().getName());
   }

   /**
    * {@inheritDoc}
    */
   public ItemImpl getItem(String absPath) throws PathNotFoundException, RepositoryException
   {
      checkLive();
      JCRPath loc = locationFactory.parseAbsPath(absPath);

      ItemImpl item = dataManager.getItem(loc.getInternalPath(), true);
      if (item != null)
      {
         return item;
      }

      throw new PathNotFoundException("Item not found " + absPath + " in workspace " + workspaceName);
   }

   public long getLastAccessTime()
   {
      return lastAccessTime;
   }

   /**
    * {@inheritDoc}
    */
   public LocationFactory getLocationFactory()
   {
      return locationFactory;
   }

   public SessionLockManager getLockManager()
   {
      return lockManager;
   }

   /**
    * {@inheritDoc}
    */
   public String[] getLockTokens()
   {
      return getLockManager().getLockTokens();
   }

   /**
    * {@inheritDoc}
    */
   public String getNamespacePrefix(String uri) throws NamespaceException, RepositoryException
   {
      if (prefixes.containsKey(uri))
      {
         return prefixes.get(uri);
      }
      return workspace.getNamespaceRegistry().getPrefix(uri);
   }

   /**
    * {@inheritDoc}
    */
   public String getNamespacePrefixByURI(String uri) throws NamespaceException, RepositoryException
   {
      return getNamespacePrefix(uri);
   }

   /**
    * {@inheritDoc}
    */
   public String[] getNamespacePrefixes() throws RepositoryException
   {
      Collection<String> allPrefixes = new LinkedList<String>();
      allPrefixes.addAll(namespaces.keySet());

      String[] permanentPrefixes = workspace.getNamespaceRegistry().getPrefixes();

      for (int i = 0; i < permanentPrefixes.length; i++)
      {
         if (!prefixes.containsKey(workspace.getNamespaceRegistry().getURI(permanentPrefixes[i])))
         {
            allPrefixes.add(permanentPrefixes[i]);
         }
      }
      return allPrefixes.toArray(new String[allPrefixes.size()]);
   }

   /**
    * {@inheritDoc}
    */
   public String getNamespaceURI(String prefix) throws NamespaceException, RepositoryException
   {
      String uri = null;
      // look in session first
      if (namespaces.size() > 0)
      {
         uri = namespaces.get(prefix);
         if (uri != null)
         {
            return uri;
         }
      }

      return workspace.getNamespaceRegistry().getURI(prefix);
   }

   /**
    * {@inheritDoc}
    */
   public String getNamespaceURIByPrefix(String prefix) throws NamespaceException, RepositoryException
   {
      return getNamespaceURI(prefix);
   }

   /**
    * {@inheritDoc}
    */
   public Node getNodeByIdentifier(String identifier) throws ItemNotFoundException, RepositoryException
   {
      checkLive();
      Item item = dataManager.getItemByIdentifier(identifier, true);
      if (item != null && item.isNode())
      {
         return (Node)item;
      }

      throw new ItemNotFoundException("Node not found " + identifier + " at " + workspaceName);
   }
   
   /**
    * {@inheritDoc}
    */
   public Property getPropertyByIdentifier(String identifier) throws ItemNotFoundException, RepositoryException
   {
      checkLive();
      Item item = dataManager.getItemByIdentifier(identifier, true);
      if (item != null && item instanceof Property)
      {
         return (Property)item;
      }

      throw new ItemNotFoundException("Property not found " + identifier + " at " + workspaceName);
   }

   /**
    * {@inheritDoc}
    */
   public Node getNodeByUUID(String uuid) throws ItemNotFoundException, RepositoryException
   {
      checkLive();
      Item item = dataManager.getItemByIdentifier(uuid, true);

      if (item != null && item.isNode())
      {
         NodeImpl node = (NodeImpl)item;
         node.getUUID(); // throws exception
         return node;
      }

      throw new ItemNotFoundException("Node not found " + uuid + " at " + workspaceName);

   }

   /**
    * {@inheritDoc}
    */
   public Repository getRepository()
   {
      return repository;
   }

   /**
    * {@inheritDoc}
    */
   public Node getRootNode() throws RepositoryException
   {
      return getRootNode(true);
   }

   private Node getRootNode(boolean apiRead) throws RepositoryException
   {
      checkLive();
      Item item = dataManager.getItemByIdentifier(Constants.ROOT_UUID, true, apiRead);
      if (item != null && item.isNode())
      {
         return (NodeImpl)item;
      }

      throw new ItemNotFoundException("Node not found " + JCRPath.ROOT_PATH + " at " + workspaceName);
   }

   public SessionDataManager getTransientNodesManager()
   {
      return this.dataManager;
   }

   /**
    * {@inheritDoc}
    */
   public String getUserID()
   {
      return userState.getIdentity().getUserId();
   }

   /**
    * {@inheritDoc}
    */
   public ValueFactoryImpl getValueFactory() throws UnsupportedRepositoryOperationException, RepositoryException
   {
      return valueFactory;
   }

   /**
    * {@inheritDoc}
    */
   public WorkspaceImpl getWorkspace()
   {
      return workspace;
   }

   /**
    * {@inheritDoc}
    */
   public boolean hasPendingChanges() throws RepositoryException
   {
      return dataManager.hasPendingChanges(Constants.ROOT_PATH);
   }

   /**
    * {@inheritDoc}
    */
   public Session impersonate(Credentials credentials) throws LoginException, RepositoryException
   {

      String name;
      if (credentials instanceof CredentialsImpl)
      {
         name = ((CredentialsImpl)credentials).getUserID();
      }
      else if (credentials instanceof SimpleCredentials)
      {
         name = ((SimpleCredentials)credentials).getUserID();
      }
      else
      {
         throw new LoginException(
            "Credentials for the authentication should be CredentialsImpl or SimpleCredentials type");
      }

      SessionFactory sessionFactory = (SessionFactory)container.getComponentInstanceOfType(SessionFactory.class);

      ConversationState newState =
         new ConversationState(new Identity(name, userState.getIdentity().getMemberships(), userState.getIdentity()
            .getRoles()));
      return sessionFactory.createSession(newState);

   }

   /**
    * {@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.ExtendedSession#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
   {
      checkLive();
      NodeImpl node = (NodeImpl)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,
            new ItemDataKeeperAdapter(getTransientNodesManager()), getTransientNodesManager(), nodeTypeManager,
            getLocationFactory(), getValueFactory(), getWorkspace().getNamespaceRegistry(), getAccessManager(),
            userState, context, (RepositoryImpl)getRepository(), getWorkspace().getName());
      importer.importStream(in);
   }

   /**
    * {@inheritDoc}
    */
   public boolean isLive()
   {
      return live;
   }

   /**
    * Checks if session is alive. Currently only logs a warning, but newer JCR versions will throw an exception. 
    * @throws RepositoryException
    */
   public void checkLive() throws RepositoryException
   {
      if (!live && !ALLOW_CLOSED_SESSION_USAGE)
      {
         throw new RepositoryException("This kind of operation is forbidden after a session.logout().",
            closedByCallStack);
      }
   }

   /**
    * {@inheritDoc}
    */
   public boolean itemExists(String absPath)
   {
      try
      {
         if (getItem(absPath) != null)
         {
            return true;
         }
      }
      catch (RepositoryException e)
      {
         if (LOG.isTraceEnabled())
         {
            LOG.trace("An exception occurred: " + e.getMessage());
         }
      }
      return false;
   }

   /**
    * {@inheritDoc}
    */
   public void logout()
   {
      for (int i = 0, length = lifecycleListeners.size(); i < length; i++)
      {
         lifecycleListeners.get(i).onCloseSession(this);
      }
      this.sessionRegistry.unregisterSession(getId());
      this.live = false;
      if (!ALLOW_CLOSED_SESSION_USAGE && PropertyManager.isDevelopping())
      {
         this.closedByCallStack = new Exception("The session has been closed by the following call stack");
      }
   }

   /**
    * Makes the current session expired. This will automatically logout the session and set the expired
    * flag to true.
    */
   public void expire()
   {
      this.expired = true;
      logout();
   }

   /**
    * {@inheritDoc}
    */
   public void move(String srcAbsPath, String destAbsPath) throws ItemExistsException, PathNotFoundException,
      VersionException, LockException, RepositoryException
   {
      // In this particular case we rely on the default configuration
      move(srcAbsPath, destAbsPath, triggerEventsForDescendantsOnRename, triggerEventsForDescendantsOnMove);
   }

   /**
    * {@inheritDoc}
    */
   public void move(String srcAbsPath, String destAbsPath, boolean triggerEventsForDescendants)
      throws ItemExistsException, PathNotFoundException, VersionException, LockException, RepositoryException
   {
      // In this particular case we decide what to do whether it is a rename or a real move
      move(srcAbsPath, destAbsPath, triggerEventsForDescendants, triggerEventsForDescendants);
   }

   /**
    * We need this new method for backward compatibility in case we decide to disable the trigger in case
    * of a rename but not for a move, which was possible before so we need to make sure that it is still possible
    */
   private void move(String srcAbsPath, String destAbsPath, Boolean triggerEventsForDescendantsOnRename,
      Boolean triggerEventsForDescendantsOnMove) throws ItemExistsException, PathNotFoundException, VersionException,
      LockException, RepositoryException
   {
      checkLive();
      // get destination node
      JCRPath destNodePath = 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 = getLocationFactory().parseAbsPath(srcAbsPath);

      NodeImpl srcNode = (NodeImpl)dataManager.getItem(srcNodePath.getInternalPath(), false);

      // get dst parent node
      NodeImpl destParentNode = (NodeImpl)dataManager.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)dataManager.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 = triggerEventsForDescendantsOnRename;
      }
      else
      {
         srcParentNode = srcNode.parent();
         triggerEventsForDescendants = triggerEventsForDescendantsOnMove;
      }
      // Check if versionable ancestor is not checked-in
      if (!srcParentNode.checkedOut())
      {
         throw new VersionException("Parent or source Node or its nearest ancestor is checked-in");
      }

      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, getTransientNodesManager(), true,
            triggerEventsForDescendants, maxDescendantNodesAllowed);

      getTransientNodesManager().move((NodeData)srcNode.getData(), initializer);

      this.getActionHandler().postMove(srcNode, (NodeImpl) dataManager.
         readItem(initializer.getItemMoveState().getData(), destParentNode.nodeData(), false, false));
   }

   /**
    * {@inheritDoc}
    */
   public void refresh(boolean keepChanges) throws RepositoryException
   {
      getRootNode(false).refresh(keepChanges);
   }

   /**
    * {@inheritDoc}
    */
   public void registerLifecycleListener(SessionLifecycleListener listener)
   {
      this.lifecycleListeners.add(listener);
   }

   /**
    * {@inheritDoc}
    */
   public void removeLockToken(String lt)
   {
      getLockManager().removeLockToken(lt);
   }

   /**
    * {@inheritDoc}
    */
   public void save() throws AccessDeniedException, LockException, ConstraintViolationException,
      InvalidItemStateException, RepositoryException
   {
      getRootNode(false).save();
   }

   /**
    * {@inheritDoc}
    */
   public void setNamespacePrefix(String prefix, String uri) throws NamespaceException, RepositoryException
   {
      checkLive();
      NamespaceRegistryImpl nrg = (NamespaceRegistryImpl)workspace.getNamespaceRegistry();
      if (!nrg.isUriRegistered(uri))
      {
         throw new NamespaceException("The specified uri:" + uri + " is not among "
            + "those registered in the NamespaceRegistry");
      }
      if (nrg.isPrefixMaped(prefix))
      {
         throw new NamespaceException("A prefix '" + prefix + "' is currently already mapped to " + nrg.getURI(prefix)
            + " URI persistently in the repository NamespaceRegistry "
            + "and cannot be remapped to a new URI using this method, since this would make any "
            + "content stored using the old URI unreadable.");
      }
      if (namespaces.containsKey(prefix))
      {
         throw new NamespaceException("A prefix '" + prefix + "' is currently already mapped to "
            + namespaces.get(prefix) + " URI transiently within this Session and cannot be "
            + "remapped to a new URI using this method, since this would make any "
            + "content stored using the old URI unreadable.");
      }
      nrg.validateNamespace(prefix, uri);
      namespaces.put(prefix, uri);
      prefixes.put(uri, prefix);
   }

   public void updateLastAccessTime()
   {
      lastAccessTime = System.currentTimeMillis();
   }

   LocationFactory getSystemLocationFactory()
   {
      return systemLocationFactory;
   }

   public ConversationState getUserState()
   {
      return this.userState;
   }

   int getLazyReadThreshold()
   {
      return lazyReadThreshold;
   }

   int getLazyNodeIteratorPageSize()
   {
      return lazyNodeIteatorPageSize;
   }

   @Override
   public String toString()
   {
      return String.format("Session {\n id: %s;\n userId: %s;\n workspace: %s/%s ;\n alive: %b\n}", id, getUserID(),
         repository.getName(), workspaceName, live);
   }

   /**
    * Checks if a global Tx has been started if so the session and its change will be dynamically enrolled
    * @param statesLog the changes to enlist in case a Global Tx has been started 
    * @return <code>true</code> if a global Tx has been started and the session could
    * be enrolled successfully, <code>false</code> otherwise
    */
   public boolean canEnrollChangeToGlobalTx(PlainChangesLog statesLog)
   {
      return txResourceManager.canEnrollChangeToGlobalTx(this, statesLog);
   }

   /**
    * Gives the local timeout of the session
    * 
    * @return the timeout the local timeout expressed in milliseconds
    */
   public long getTimeout()
   {
      return timeout;
   }

   /**
    * Sets the local timeout of the session
    * 
    * @param timeout the new local timeout any value lower or equals to 0 will disable the timeout,
    * the expected value is expressed in milliseconds 
    */
   public void setTimeout(long timeout)
   {
      this.timeout = (timeout <= 0 ? 0 : timeout);
   }

   /**
    * {@inheritDoc}
    */
   public XAResource getXAResource()
   {
      return txResourceManager;
   }

   /**
    * {@inheritDoc}
    */
   public boolean hasExpired()
   {
      return expired;
   }
}
