/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 */
package org.jvnet.glassfish.comms.replication.sessmgmt;

import com.ericsson.ssa.sip.SipSessionDialogImpl;
import com.ericsson.ssa.sip.SipSessionManager;
import com.ericsson.ssa.sip.SipSessionStore;
import com.sun.appserv.ha.spi.BackingStore;
import com.sun.appserv.ha.spi.BackingStoreException;
import com.sun.appserv.ha.spi.SimpleMetadata;
import com.sun.enterprise.ee.web.sessmgmt.JxtaBackingStoreImpl;
import com.sun.enterprise.ee.web.sessmgmt.NumberUtil;
import com.sun.enterprise.ee.web.sessmgmt.ReplicationState;
import com.sun.enterprise.ee.web.sessmgmt.StorePoolElement;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.*;

import org.apache.catalina.*;
import org.apache.catalina.session.IOUtilsCaller;


/**
 *
 * @author Larry White
 */
public class SipSessionStoreImpl extends SipStoreBase
    implements StorePoolElement, SipSessionStore {
    
    private static final Logger _logger = Logger.getLogger("SipContainer");
    protected final static String MODE_SIP = ReplicationState.MODE_SIP;    
    
    /** Creates a new instance of SipSessionStoreImpl */
    public SipSessionStoreImpl() {
    }
    
    /**
     * gets the BackingStore used for replicating SipSessions
     *
     * @return the BackingStore initialized for SipSessions
     *
     */    
    protected BackingStore getBackingStore() {
        SipTransactionPersistentManager mgr
            = (SipTransactionPersistentManager)this.getSipSessionManager();
        return mgr.getSipSessionBackingStore(); 
    }      

    /**
     * Loads and returns the SipSession with the given id from this session
     * store.
     *
     * @param id The id of the SipSession to load
     *
     * @return The SipSession with the given id, or null if not found
     *
     * @throws IOException
     */
    public SipSessionDialogImpl load(String id) throws IOException {
        return load(id, null);
    }
    
    /**
     * Loads and returns the SipSession with the given id from this session
     * store.
     *
     * @param id The id of the SipSession to load
     * @param version the version of the SipSession to load
     *
     * @return The SipSession with the given id, or null if not found
     *
     * @throws IOException
     */
    public SipSessionDialogImpl load(String id, String version) throws IOException {
        SipSessionDialogImpl result = null;
        if(id == null) {
            return result;
        }
        SipTransactionPersistentManager repMgr 
            = (SipTransactionPersistentManager)this.getSipSessionManager();
        ReplicationState localCachedState 
            = repMgr.removeFromSipSessionReplicationCache(id);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>load:localCachedState=" + localCachedState);                       
        }
        //check if we got a hit from our own replica cache
        //and if so return it immediately
        if(version != null && localCachedState != null) {
            long versionLong = NumberUtil.parseLong(version);
            try {
                if(localCachedState.getVersion() == versionLong) {
                    return (this.getSipSession(localCachedState));
                } 
            } catch (ClassNotFoundException ex) {
                IOException ex1 = 
                    (IOException) new IOException("Error during load: " + ex.getMessage()).initCause(ex);
                throw ex1;                
            }
        }        
        ReplicationState broadcastResultState = findSessionViaBroadcast(id, version);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>load:broadcastResultState from broadcast=" + broadcastResultState);                       
        }        
        ReplicationState bestState 
            = ReplicationState.getBestResult(localCachedState, broadcastResultState);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>load:bestState=" + bestState);                       
        }
        if(bestState != null) {
            if(_logger.isLoggable(Level.FINE)) {
                _logger.fine("SipSessionStoreImpl>>load:bestStateVERSION=" + bestState.getVersion());
                _logger.fine("SipSessionStoreImpl>>load:bestStateSTATE=" + bestState.getState());
            }            
        }

        if(bestState != null && bestState.getState() != null) {
            if(_logger.isLoggable(Level.FINE)) {
                _logger.fine("SipSessionStoreImpl>>load:before deserializing bestState:ver=" + bestState.getVersion());                       
            }            
            try {
                result = this.getSipSession(bestState); 
            } catch (ClassNotFoundException ex) {
                IOException ex1 = 
                    (IOException) new IOException("Error during load: " + ex.getMessage()).initCause(ex);
                throw ex1;                
            }                        
            if(_logger.isLoggable(Level.FINE)) {
                //FIXME
                //_logger.fine("SipSessionStoreImpl>>load:after deserializing session:ver=" + ((SipApplicationSessionImpl)result).getVersion());                       
            }            
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>load:id " + id + " session: " + result);                       
        }         
        return result;
    } 
    
    private ReplicationState findSessionViaBroadcast(String id, String version) 
        throws IOException {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>findSessionViaBroadcast");                       
        }        
        BackingStore replicator = this.getBackingStore();
        JxtaBackingStoreImpl jxtaReplicator = null;
        if(replicator instanceof JxtaBackingStoreImpl) {
            jxtaReplicator = (JxtaBackingStoreImpl)replicator;
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>findSessionViaBroadcast: replicator: " + replicator);                       
        }
        
        SimpleMetadata queryResult = null;
        try {
            //use version aware load if possible
            if(jxtaReplicator != null && version != null) {
                queryResult = (SimpleMetadata)jxtaReplicator.load(id, version);
            } else {
                queryResult = (SimpleMetadata)replicator.load(id);
            }
        } catch (BackingStoreException ex) {
            IOException ex1 = 
                (IOException) new IOException("Error during load: " + ex.getMessage()).initCause(ex);
            throw ex1;
        }
        if(queryResult == null) {
            return null;
        }
        //queryResult.getExtraParam() here is expiration time
        ReplicationState result 
            = new ReplicationState(MODE_SIP, id, this.getApplicationId(), 
                queryResult.getVersion(), 0L, 0L, null, queryResult.getExtraParam(), 
                null, null, queryResult.getState(), null);
        return result;
    }    
    
    /**
    * Given a byte[] containing session data, return a session
    * object
    *
    * @param state
    *   The byte[] with the session data
    *
    * @return
    *   A newly created SipSession for the given session data, and associated
    *   with this Manager
    */
    protected SipSessionDialogImpl getSipSession(ReplicationState replicationState) 
        throws IOException, ClassNotFoundException 
    {
        byte[] state = replicationState.getState();
        SipSessionDialogImpl _session = null;
        BufferedInputStream bis = null;
        ByteArrayInputStream bais = null;
        Loader loader = null;    
        ClassLoader classLoader = null;
        ObjectInputStream ois = null;
        SipSessionManager manager 
            = this.getSipSessionManager();
        Container container = manager.getContext();
        int expirationTime = -1;  //extraParam
        long version = 0L;
        IOUtilsCaller utilsCaller = null;
            
        bais = new ByteArrayInputStream(state);
        bis = new BufferedInputStream(bais);

        String id = (String)replicationState.getId();
        expirationTime = NumberUtil.parseInt(replicationState.getExtraParam());
        version = replicationState.getVersion();    

        if(_logger.isLoggable(Level.FINEST)) {
            _logger.finest("loaded session from replicationstore, length = "+state.length);
        }
        if (container != null) {
            loader = container.getLoader();
        }

        if (loader != null) {
            classLoader = loader.getClassLoader();
        }                   
        if (classLoader != null) {
            if( (utilsCaller = this.getWebUtilsCaller()) != null) {
                try {
                    ois = utilsCaller.createObjectInputStream(bis, true, classLoader);
                } catch (Exception ex) {}
            }
        }
        if (ois == null) {
            ois = new ObjectInputStream(bis); 
        }
        if(ois != null) {               
            try {
                _session = (SipSessionDialogImpl)ois.readObject();
            } 
            finally {
                if (ois != null) {
                    try {
                        ois.close();
                        bis = null;
                    }
                    catch (IOException e) {
                    }
                }
            }
        }     
        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>getSipSession:expires=" + expirationTime);                                  
        } 
        //currentOwnerInstanceName is in the manager already
        //time to live
        //_session.setExpires(expirationTime);
        //FIXME
        ((HASipSession)_session).setVersion(version);
        ((HASipSession)_session).setDirty(false);
        //((HASipSession)_session).setPersistent(false);        
        return _session;
    }       

    /**
     * Saves the given SipSession to this store.
     *
     * If a SipSession with the same id already exists in this session
     * store, it will be replaced.
     *
     * @param session The SipSession to be saved
     *
     * @exception IOException
     */
    public void save(SipSessionDialogImpl session) throws IOException {
        byte[] sessionState = this.getByteArray(session);
        BackingStore replicator = this.getBackingStore();
        SimpleMetadata simpleMetadata =        
            new SimpleMetadata(
                ((HASipSession) session).getVersion(),
                session.getLastAccessedTime(),
                0L, //maxinactiveinterval
                sessionState,
                null,
                ((HASipSession) session).getExtraParameters()
            );
                
        try {        
            replicator.save(session.getId(), //id
                    simpleMetadata);  //SimpleMetadata 
        } catch (BackingStoreException ex) {
            IOException ex1 = 
                (IOException) new IOException("Error during save: " + ex.getMessage()).initCause(ex);
            throw ex1;
        } 
    }
    
    /**
     * Removes the SipSession with the given id from this store.
     *
     * @param id The id of the SipSession to be removed
     *
     * @exception IOException
     */
    public void remove(String id) throws IOException {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>remove" + " id: " + id);                       
        } 
        BackingStore replicator = this.getBackingStore();
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>remove: replicator: " + replicator);                       
        }        
        try {
            replicator.remove(id);
        } catch (BackingStoreException ex) {
            IOException ex1 = 
                (IOException) new IOException("Error during remove: " + ex.getMessage()).initCause(ex);
            throw ex1;       
        }
    } 
    
    /**
     * update the lastaccess time of the specified SipSession into this Store.
     *
     * @param sipSession SipSessionDialogImpl to be saved
     *
     * @exception IOException if an input/output error occurs
     */    
    public void updateLastAccessTime(SipSessionDialogImpl sipSession) throws IOException {
        //FIXME needs to handle other extraParams
        BackingStore replicator = this.getBackingStore();
        JxtaBackingStoreImpl jxtaReplicator = null;
        if(replicator instanceof JxtaBackingStoreImpl) {
            jxtaReplicator = (JxtaBackingStoreImpl)replicator;
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("SipSessionStoreImpl>>updateLastAccessTime: replicator: " + replicator);                       
        }         
        try {
            replicator.updateLastAccessTime(sipSession.getId(), //id
                    sipSession.getLastAccessedTime(), //lastaccesstime
                    ((HASipSession)sipSession).getVersion()); //version
        } catch (BackingStoreException ex) {
            IOException ex1 = 
                (IOException) new IOException("Error during updateLastAccessTime: " + ex.getMessage()).initCause(ex);
            throw ex1;
        }
    }        

    /**
     * Removes all SipSessions from this store.
     */
    public void clear() throws IOException {
        //FIXME
    }

    /**
     * clean up resources
     */
    public void cleanup() {
        //FIXME        
    }
}
