/*
 Copyright (c) 2012 GFT Appverse, S.L., Sociedad Unipersonal.

 This Source Code Form is subject to the terms of the Appverse Public License
 Version 2.0 (“APL v2.0”). If a copy of the APL was not distributed with this
 file, You can obtain one at http://www.appverse.mobi/licenses/apl_v2.0.pdf. [^]

 Redistribution and use in source and binary forms, with or without modification,
 are permitted provided that the conditions of the AppVerse Public License v2.0
 are met.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. EXCEPT IN CASE OF WILLFUL MISCONDUCT OR GROSS NEGLIGENCE, IN NO EVENT
 SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 */
package org.appverse.web.framework.backend.ecm.cmis.managers.impl.live;

import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.chemistry.opencmis.client.api.Session;
import org.appverse.web.framework.backend.core.enterprise.log.AutowiredLogger;
import org.appverse.web.framework.backend.ecm.cmis.factories.CmisSessionFactory;
import org.appverse.web.framework.backend.ecm.cmis.managers.CmisSessionManager;
import org.slf4j.Logger;

/**
 * CmisSessionManagerImpl implements a CMIS Session Manager that allows session reuse.
 * Take into account that Open CMIS Session object is thread-safe and it is recommended to reuse as much as possible
 * for performance reasons. The Open CMIS Session object holds the user caches. So it is recommended to use the
 * same Session per user and repository.
 * This implementation allows to reuse sessions and configure a "buffer" limiting the number of existing sessions.
 * When the maximum capacity is reached, the Session Manager will remove the session that shows less activity.
 * In order to create new sessions, the session manager delegates to the CmisSessionFactory.
 */
public class CmisSessionManagerImpl implements CmisSessionManager {

    // TODO: An improvement would be that the CmisSessionManager proactively expired sessions based on
    // an expiration time parameter and session inactivity

    @AutowiredLogger
    private static Logger logger;

    private CmisSessionFactory cmisSessionFactory;

    // Collection of sessions
    private HashMap<String, CmisSessionWrapper> cmisSessionsMap;
    private SortedSet<CmisSessionWrapper> orderedCmisSessionsList;

    // Default number of sessions hold is 10
    private int maxNumberOfSessions = 10;

    public CmisSessionManagerImpl(){
        cmisSessionsMap = new HashMap<String, CmisSessionWrapper>();
        orderedCmisSessionsList = new TreeSet<CmisSessionWrapper>(new sortByLastRequestedDateComparator());
    }

    private Session lookUpSession(String repositoryId, String username){
        CmisSessionWrapper cmisSessionWrapper = cmisSessionsMap.get(username + "-" + repositoryId);
        if (cmisSessionWrapper != null){
            cmisSessionWrapper.setLastRequested(new Date());
            return cmisSessionWrapper.getSession();
        }
        else return null;
    }

    private void registerSession(Session session, String repositoryId, String username){
        CmisSessionWrapper cmisSessionWrapper = new CmisSessionWrapper();
        cmisSessionWrapper.setSession(session);
        cmisSessionWrapper.setRepositoryId(repositoryId);
        cmisSessionWrapper.setUser(username);
        cmisSessionWrapper.setCreated(new Date());
        cmisSessionWrapper.setLastRequested(new Date());

        if (orderedCmisSessionsList.size() >= maxNumberOfSessions){
            // Remove the session that shows more inactivity
            CmisSessionWrapper cmisSessionWrapperToRemove = orderedCmisSessionsList.first();
            orderedCmisSessionsList.remove(cmisSessionWrapperToRemove);
            cmisSessionsMap.remove(cmisSessionWrapperToRemove.getKey());
            logger.debug("Removing CMIS session from manager buffer: " + cmisSessionWrapperToRemove.getSession().toString() + " Buffer size: " + maxNumberOfSessions);
        }
        cmisSessionsMap.put(cmisSessionWrapper.getKey(), cmisSessionWrapper);
        orderedCmisSessionsList.add(cmisSessionWrapper);

        logger.debug("Created and registered new open CMIS session in buffer: " + cmisSessionWrapper.getSession().toString() +
                "Overall number of sessions is: " + orderedCmisSessionsList.size());
    }

    @Override
    public Session getCmisSession(String repositoryId, String username, String password) throws Exception {
        Session session = lookUpSession(repositoryId, username);
        if (session == null){
            // Create an register a new session
            session = cmisSessionFactory.createCmisSession(repositoryId, username, password);
            registerSession(session, repositoryId, username);
        }
        return session;
    }

    @Override
    public Session getCmisSession(String username, String password) throws Exception {
        Session session = lookUpSession(cmisSessionFactory.getRepositoryId(), username);
        if (session == null){
            // Create an register a new session
            session = cmisSessionFactory.createCmisSession(username, password);
            registerSession(session, cmisSessionFactory.getRepositoryId(), username);
        }
        return session;
    }

    @Override
    public Session getCmisSession() throws Exception {
        Session session = lookUpSession(cmisSessionFactory.getRepositoryId(), cmisSessionFactory.getUser());
        if (session == null){
            // Create an register a new session
            session = cmisSessionFactory.createCmisSession();
            registerSession(session, cmisSessionFactory.getRepositoryId(), cmisSessionFactory.getUser());
        }
        return session;
    }

    public int getMaxNumberOfSessions() {
        return maxNumberOfSessions;
    }

    public void setMaxNumberOfSessions(int maxNumberOfSessions) {
        this.maxNumberOfSessions = maxNumberOfSessions;
    }

    public CmisSessionFactory getCmisSessionFactory() throws Exception {
        return cmisSessionFactory;
    }

    public void setCmisSessionFactory(CmisSessionFactory cmisSessionFactory) throws Exception {
        this.cmisSessionFactory = cmisSessionFactory;
    }

    // Comparator to sort the TreeSet by CmisSessionManager lastRequestedDate.
    // Even though this seems to be the best criteria to order sessions in the Collection, in the future
    // might be provided, if necessary, to change the ordering criteria
    private class sortByLastRequestedDateComparator implements Comparator<CmisSessionWrapper>
    {
        @Override
        public int compare(CmisSessionWrapper o1, CmisSessionWrapper o2) {
            return o1.getLastRequested().compareTo(o2.getLastRequested());
        }
    }
}