/*
 * Copyright (C) 2003-2009 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.cms.link;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessControlException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

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.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.ValueFactory;
import javax.jcr.Workspace;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.version.VersionException;
import javax.transaction.xa.XAResource;

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.impl.core.LocationFactory;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

/**
 * Created by The eXo Platform SAS
 * Author : eXoPlatform
 *          nicolas.filotto@exoplatform.com
 * 4 avr. 2009
 */
public class SessionLinkAware implements ExtendedSession, NamespaceAccessor {

  /**
   * Logger.
   */
  private static final Log LOG  = ExoLogger.getLogger(SessionLinkAware.class.getName());

  private ItemLinkAware itemLA;

  private final String originalWorkspace;

  private volatile ExtendedSession[] sessions;

  SessionLinkAware (ItemLinkAware itemLA) throws RepositoryException {
    this.itemLA = itemLA;
    this.originalWorkspace = itemLA.originalWorkspaceName;
  }

  private ExtendedSession[] getSessions() throws RepositoryException {
    if (sessions == null) {
      synchronized (this) {
        if (sessions == null) {
          Set<ExtendedSession> sSessions = new HashSet<ExtendedSession>(3, 1f);
          sSessions.add(getOriginalSession());
          sSessions.add((ExtendedSession)itemLA.getItemSession());
          sSessions.add(getTargetSession());
          sessions = sSessions.toArray(new ExtendedSession[sSessions.size()]);
        }
      }
    }
    return sessions;
  }

  private ExtendedSession getSession() throws RepositoryException {
    return (ExtendedSession)itemLA.getItemSession();
  }

  private ExtendedSession getOriginalSession() throws RepositoryException {
    return (ExtendedSession)WCMCoreUtils.getUserSessionProvider().
                    getSession(originalWorkspace, WCMCoreUtils.getRepository());
  }

  private ExtendedSession getTargetSession() throws RepositoryException {
    return getTargetSession(itemLA);
  }

  private ExtendedSession getTargetSession(String absPath, Item item) throws RepositoryException {
    return getTargetSession(ItemLinkAware.newInstance(originalWorkspace, absPath, item));
  }

  private ExtendedSession getTargetSession(ItemLinkAware itemLA) throws RepositoryException {
    if (itemLA instanceof NodeLinkAware) {
      return (ExtendedSession) ((NodeLinkAware) itemLA).getTargetNode().getRealNode().getSession();
    }
    return (ExtendedSession) itemLA.getItemSession();
  }

  private ExtendedSession getTargetSession(String absPath) throws RepositoryException {
    Item item = getItem(absPath);
    return getTargetSession(absPath, item);
  }

  /**
   * {@inheritDoc}
   */
  public void addLockToken(String lt) {
    try {
      getTargetSession().addLockToken(lt);
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void checkPermission(String absPath, String actions) throws AccessControlException,
                                                       RepositoryException {
    getTargetSession(absPath).checkPermission(absPath, actions);
  }

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

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

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

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

  /**
   * {@inheritDoc}
   */
  public Object getAttribute(String name) {
    try {
      return getTargetSession().getAttribute(name);
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public String[] getAttributeNames() {
    try {
      return getTargetSession().getAttributeNames();
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException,
                                                                                       ConstraintViolationException,
                                                                                       VersionException,
                                                                                       LockException,
                                                                                       RepositoryException {
    return getTargetSession(parentAbsPath).getImportContentHandler(parentAbsPath, uuidBehavior);
  }

  /**
   * {@inheritDoc}
   */
  public Item getItem(String absPath) throws PathNotFoundException, RepositoryException {
    NodeFinder nodeFinder = LinkUtils.getNodeFinder();
    return nodeFinder.getItem(getOriginalSession(), absPath);
  }

  /**
   * {@inheritDoc}
   */
  public String[] getLockTokens() {
    try {
      return getTargetSession().getLockTokens();
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
    }
    return null;
  }

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

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

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

  /**
   * {@inheritDoc}
   */
  public Node getNodeByUUID(String uuid) throws ItemNotFoundException, RepositoryException {
    ExtendedSession[] sessions = getSessions();
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      try {
        return session.getNodeByUUID(uuid);
      } catch (ItemNotFoundException e) {
        continue;
      }
    }
    throw new ItemNotFoundException("No node with uuid ='" + uuid + "' can be found");
  }

  /**
   * {@inheritDoc}
   */
  public Repository getRepository() {
    try {
      return getSession().getRepository();
    } catch (Exception e) {
      return null;
    }
  }

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

  /**
   * {@inheritDoc}
   */
  public String getUserID() {
    try {
      return getSession().getUserID();
    } catch (Exception e) {
      return null;
    }
  }

  /**
   * {@inheritDoc}
   */
  public ValueFactory getValueFactory() throws UnsupportedRepositoryOperationException,
                                       RepositoryException {
    return getOriginalSession().getValueFactory();
  }

  /**
   * {@inheritDoc}
   */
  public Workspace getWorkspace() {
    try {
      return getOriginalSession().getWorkspace();
    } catch (Exception e) {
      return null;
    }
  }

  /**
   * {@inheritDoc}
   */
  public boolean hasPendingChanges() throws RepositoryException {
    ExtendedSession[] sessions = getSessions();
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      if (session.hasPendingChanges()) {
        return true;
      }
    }
    return false;
  }

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

  /**
   * {@inheritDoc}
   */
  public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException,
                                                                               PathNotFoundException,
                                                                               ItemExistsException,
                                                                               ConstraintViolationException,
                                                                               VersionException,
                                                                               InvalidSerializedDataException,
                                                                               LockException,
                                                                               RepositoryException {
    getTargetSession(parentAbsPath).importXML(parentAbsPath, in, uuidBehavior);
  }

  /**
   * {@inheritDoc}
   */
  public boolean isLive() {
    ExtendedSession[] sessions;
    try {
      sessions = getSessions();
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
      return false;
    }
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      if (session.isLive()) {
        return true;
      }
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */
  public boolean itemExists(String absPath) throws RepositoryException {
    NodeFinder nodeFinder = LinkUtils.getNodeFinder();
    return nodeFinder.itemExists(getOriginalSession(), absPath);
  }

  /**
   * {@inheritDoc}
   */
  public void logout() {
    ExtendedSession[] sessions;
    try {
      sessions = getSessions();
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
      return;
    }
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      session.logout();
    }
  }

  /**
   * {@inheritDoc}
   */
  public void move(String srcAbsPath, String destAbsPath) throws ItemExistsException,
                                                         PathNotFoundException,
                                                         VersionException,
                                                         ConstraintViolationException,
                                                         LockException,
                                                         RepositoryException {
    Item srcItem = getItem(srcAbsPath);
    Session srcSession = getTargetSession(srcAbsPath, srcItem);
    Session destParentSession = getTargetSession(LinkUtils.getParentPath(destAbsPath));
    if (srcSession.getWorkspace().equals(destParentSession.getWorkspace())) {
      srcSession.move(srcAbsPath, srcAbsPath);
    } else {
      destParentSession.getWorkspace().clone(srcSession.getWorkspace().getName(),
                                             srcAbsPath,
                                             destAbsPath,
                                             false);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void refresh(boolean keepChanges) throws RepositoryException {
    ExtendedSession[] sessions = getSessions();
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      session.refresh(keepChanges);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void removeLockToken(String lt) {
    try {
      getTargetSession().removeLockToken(lt);
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void save() throws AccessDeniedException,
                    ItemExistsException,
                    ConstraintViolationException,
                    InvalidItemStateException,
                    VersionException,
                    LockException,
                    NoSuchNodeTypeException,
                    RepositoryException {
    ExtendedSession[] sessions = getSessions();
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      session.save();
    }
  }

  /**
   * {@inheritDoc}
   */
  public void setNamespacePrefix(String prefix, String uri) throws NamespaceException,
                                                          RepositoryException {
    getTargetSession().setNamespacePrefix(prefix, uri);
  }

  /**
   * {@inheritDoc}
   */
  public String getId() {
    try {
      return getTargetSession().getId();
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public LocationFactory getLocationFactory() {
    try {
      return getTargetSession().getLocationFactory();
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */
  public void importXML(String parentAbsPath,
                        InputStream in,
                        int uuidBehavior,
                        Map<String, Object> context) throws IOException,
                                                    PathNotFoundException,
                                                    ItemExistsException,
                                                    ConstraintViolationException,
                                                    InvalidSerializedDataException,
                                                    RepositoryException {
    getTargetSession(parentAbsPath).importXML(parentAbsPath, in, uuidBehavior, context);
  }

  /**
   * {@inheritDoc}
   */
  public void registerLifecycleListener(SessionLifecycleListener listener) {
    try {
      getTargetSession().registerLifecycleListener(listener);
    } catch (RepositoryException e) {
      if (LOG.isErrorEnabled()) {
        LOG.error(e);
      }
    }
  }

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

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

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

  /**
   * {@inheritDoc}}
   */
  public Node getNodeByIdentifier(String identifier) throws ItemNotFoundException, RepositoryException {
    return getTargetSession().getNodeByIdentifier(identifier);
  }

  @Override
  public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse,
      boolean exportChildVersionHisotry) throws IOException, PathNotFoundException, RepositoryException {
    getTargetSession().exportSystemView(absPath, out, skipBinary, noRecurse, exportChildVersionHisotry);
  }

  @Override
  public XAResource getXAResource() {
    try {
      return getTargetSession().getXAResource();
    } catch (RepositoryException e) {
      return null;
    }
  }

  @Override
  public boolean hasExpired() {
    try {
      return getTargetSession().hasExpired();
    } catch (RepositoryException e) {
      return true;
    }
  }

  @Override
  public void setTimeout(long timeout) {
    try {
      getTargetSession().setTimeout(timeout);
    } catch (RepositoryException e) {
      LOG.warn(e.getMessage());
    }
  }

  @Override
  public long getTimeout() {
    try {
      return getTargetSession().getTimeout();
    } catch (RepositoryException e) {
      return 0;
    }
  }

  @Override
  public void move(String srcAbsPath,
                   String destAbsPath,
                   boolean triggerEventsForDescendentsOnRename) throws ItemExistsException,
                                                               PathNotFoundException,
                                                               VersionException,
                                                               ConstraintViolationException,
                                                               LockException,
                                                               RepositoryException {
    Item srcItem = getItem(srcAbsPath);
    Session srcSession = getTargetSession(srcAbsPath, srcItem);
    Session destParentSession = getTargetSession(LinkUtils.getParentPath(destAbsPath));
    if (srcSession.getWorkspace().equals(destParentSession.getWorkspace())) {
      ((SessionImpl)srcSession).move(srcAbsPath, srcAbsPath, triggerEventsForDescendentsOnRename);
    } else {
      destParentSession.getWorkspace().clone(srcSession.getWorkspace().getName(),
                                             srcAbsPath,
                                             destAbsPath,
                                             false);
    }
  }
}
