/*
 * 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.ext.common;

import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.DynamicIdentity;
import org.exoplatform.services.jcr.core.ExtendedSession;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.core.SessionLifecycleListener;
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.exoplatform.services.security.IdentityConstants;
import org.exoplatform.services.security.MembershipEntry;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import javax.jcr.LoginException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

/**
 * Created by The eXo Platform SAS<br>
 * Provides JCR Session for client program. Usually it is per client thread object Session creates
 * with Repository.login(..) method and then can be stored in some cache if neccessary.
 * 
 * @author <a href="mailto:gennady.azarenkov@exoplatform.com">Gennady Azarenkov</a>
 * @version $Id: SessionProvider.java 34445 2009-07-24 07:51:18Z dkatayev $
 * @LevelAPI Platform
 */

public class SessionProvider implements SessionLifecycleListener
{

   protected static final Log LOG = ExoLogger.getLogger("exo.jcr.component.ext.SessionProvider");

   /**
    * Constant for handlers.
    */
   public final static String SESSION_PROVIDER = "JCRsessionProvider";

   /**
    * Sessions cache.
    */
   private final Map<String, ExtendedSession> cache;

   /**
    * System session marker.
    */
   private boolean isSystem;

   private ManageableRepository currentRepository;

   private String currentWorkspace;

   private boolean closed;
   
   private ConversationState conversationState;

   /**
    * Creates SessionProvider for certain identity.
    * 
    * @param userState
    */
   public SessionProvider(ConversationState userState)
   {
      this(false);
      if (userState.getAttribute(SESSION_PROVIDER) == null)
         userState.setAttribute(SESSION_PROVIDER, this);
   }

   /**
    * Creates SessionProvider for a dynamic identity.
    * 
    * @param membershipEntries the expected memberships
    */
   private SessionProvider(HashSet<MembershipEntry> membershipEntries)
   {
      this(false);
      Identity id = new Identity(DynamicIdentity.DYNAMIC, membershipEntries);
      this.conversationState = new ConversationState(id);
   }

   /**
    * Internal constructor.
    * 
    * @param isSystem
    */
   private SessionProvider(boolean isSystem)
   {
      this.isSystem = isSystem;
      this.cache = new HashMap<String, ExtendedSession>();
      this.closed = false;
   }

   /**
    * Helper for creating System session provider.
    * 
    * @return a system session provider
    */
   public static SessionProvider createSystemProvider()
   {
      return new SessionProvider(true);
   }

   /**
    * Helper for creating Anonymous session provider.
    * 
    * @return an anonymous session provider
    */
   public static SessionProvider createAnonimProvider()
   {
      Identity id = new Identity(IdentityConstants.ANONIM, new HashSet<MembershipEntry>());
      return new SessionProvider(new ConversationState(id));
   }

   /**
     * Gives a {@link SessionProvider} for a given list of {@link AccessControlEntry}.
     *
     * @param accessList list of {@link AccessControlEntry}
     * @return a {@link SessionProvider} allowing to provide sessions with the
     * corresponding ACL.
     */
   public static SessionProvider createProvider(List<AccessControlEntry> accessList)
   {
      if (accessList == null || accessList.isEmpty())
      {
         return createAnonimProvider();
      }
      else
      {
         HashSet<MembershipEntry> membershipEntries = new HashSet<MembershipEntry>();

         for (AccessControlEntry ace : accessList)
         {
            membershipEntries.add(ace.getMembershipEntry());
         }
         return new SessionProvider(membershipEntries);
      }

   }

   /**
    * Gets the session from an internal cache if a similar session has already been used
    * or creates a new session and puts it into the internal cache.
    * 
    * @param workspaceName the workspace name
    * @param repository the repository instance
    * @return a session corresponding to the given repository and workspace
    * @throws LoginException if an error occurs while trying to login to the workspace
    * @throws NoSuchWorkspaceException if the requested workspace doesn't exist
    * @throws RepositoryException if any error occurs
    */
   public synchronized Session getSession(String workspaceName, ManageableRepository repository) throws LoginException,
      NoSuchWorkspaceException, RepositoryException
   {

      if (closed)
      {
         throw new IllegalStateException("Session provider already closed");
      }

      if (workspaceName == null)
      {
         throw new IllegalArgumentException("Workspace Name is null");
      }

      ExtendedSession session = cache.get(key(repository, workspaceName));
      // create and cache new session

      if (session == null)
      {
         if (conversationState != null)
         {
            session =
                     (ExtendedSession) repository.getDynamicSession(workspaceName, conversationState.getIdentity()
                              .getMemberships());
         }
         else if (!isSystem)
         {
            session = (ExtendedSession)repository.login(workspaceName);
         }
         else
         {
            session = (ExtendedSession)repository.getSystemSession(workspaceName);
         }

         session.registerLifecycleListener(this);

         cache.put(key(repository, workspaceName), session);
      }

      return session;
   }

   /**
    * Calls logout() method for all cached sessions.
    * 
    * Session will be removed from cache by the listener (this provider) via
    * ExtendedSession.logout().
    */
   public synchronized void close()
   {

      if (closed) {
        if (LOG.isTraceEnabled()) {
          LOG.trace(new IllegalStateException("Session is already closed"));
        } else {
          LOG.debug("Session is already closed");
        }
        return;
      }

      closed = true;

      for (ExtendedSession session : (ExtendedSession[])cache.values().toArray(
         new ExtendedSession[cache.values().size()]))
         session.logout();

      // the cache already empty (logout listener work, see onCloseSession())
      // just to be sure
      cache.clear();
   }

   /*
    * (non-Javadoc)
    * @see
    * org.exoplatform.services.jcr.core.SessionLifecycleListener#onCloseSession(org.exoplatform.services
    * .jcr.core.ExtendedSession)
    */
   public synchronized void onCloseSession(ExtendedSession session)
   {
      this.cache.remove(key((ManageableRepository)session.getRepository(), session.getWorkspace().getName()));
   }

   /**
    * Key generator for sessions cache.
    * 
    * @param repository the repository instance
    * @param workspaceName the workspace name
    * @return
    */
   private String key(ManageableRepository repository, String workspaceName)
   {
      String repositoryName = repository.getConfiguration().getName();
      return repositoryName + workspaceName;
   }

   /**
     * @return returns the current Repository
     */
   public ManageableRepository getCurrentRepository()
   {
      return currentRepository;
   }

   /**
     * @return returns the current Workspace
     */
   public String getCurrentWorkspace()
   {
      return currentWorkspace;
   }

   /**
     * Sets the current repository Repository.
     * @param  currentRepository the current repository
     */
   public void setCurrentRepository(ManageableRepository currentRepository)
   {
      this.currentRepository = currentRepository;
   }

   /**
     * Sets the current Workspace
     * @param  currentWorkspace the current workspace
     */
   public void setCurrentWorkspace(String currentWorkspace)
   {
      this.currentWorkspace = currentWorkspace;
   }

}
