/*
 * 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.SipSessionManager;
import com.ericsson.ssa.sip.timer.ServletTimerImpl;
import com.ericsson.ssa.sip.timer.ServletTimerStore;

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.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;

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

/**
 * @author lwhite
 */
public class ServletTimerStoreImpl extends SipStoreBase
    implements StorePoolElement, ServletTimerStore {
    
    private static final Logger _logger = Logger.getLogger("SipContainer");
    protected final static String MODE_SIP = ReplicationState.MODE_SIP;
    
    /**
     * gets the BackingStore used for replicating ServletTimers
     *
     * @return the BackingStore initialized for ServletTimers
     *
     */    
    protected BackingStore getBackingStore() {
        SipTransactionPersistentManager mgr
            = (SipTransactionPersistentManager)this.getSipSessionManager();
        return mgr.getServletTimerBackingStore(); 
    }     
    
    /**
     * Loads and returns the ServletTimer with the given id from
     * this store.
     *
     * @param id The id of the ServletTimer to load
     *
     * @return The ServletTimer with the given id, or null if not
     * found
     *
     * @throws IOException
     */
    public ServletTimerImpl load(String id) throws IOException {
        return load(id, null);
    };
    
    /**
     * Loads and returns the ServletTimer with the given id from
     * this store.
     *
     * @param id The id of the ServletTimer to load
     * @param version The version of the ServletTimer to load
     *
     * @return The ServletTimer with the given id, or null if not
     * found
     *
     * @throws IOException
     */
    public ServletTimerImpl load(String id, String version) throws IOException {
        ServletTimerImpl result = null;
        if(id == null) {
            return result;
        }
        SipTransactionPersistentManager repMgr 
            = (SipTransactionPersistentManager)this.getSipSessionManager();
        ReplicationState localCachedState 
            = repMgr.removeFromServletTimerReplicationCache(id);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("ServletTimerStoreImpl>>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.getServletTimer(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("ServletTimerStoreImpl>>load:broadcastResultState from broadcast=" + broadcastResultState);                       
        }        
        ReplicationState bestState 
            = ReplicationState.getBestResult(localCachedState, broadcastResultState);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("ServletTimerStoreImpl>>load:bestState=" + bestState);                       
        }
        if(bestState != null) {
            if(_logger.isLoggable(Level.FINE)) {
                _logger.fine("ServletTimerStoreImpl>>load:bestStateVERSION=" + bestState.getVersion());
                _logger.fine("ServletTimerStoreImpl>>load:bestStateSTATE=" + bestState.getState());
            }            
        }

        if(bestState != null && bestState.getState() != null) {
            if(_logger.isLoggable(Level.FINE)) {
                _logger.fine("ServletTimerStoreImpl>>load:before deserializing bestState:ver=" + bestState.getVersion());                       
            }            
            try {
                result = this.getServletTimer(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("ServletTimerStoreImpl>>load:after deserializing session:ver=" + ((ServletTimerImpl)result).getVersion());                       
            }            
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("ServletTimerStoreImpl>>load:id " + id + " session: " + result);                       
        }         
        return result;
    }; 
    
    private ReplicationState findSessionViaBroadcast(String id, String version) 
        throws IOException {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("ServletTimerStoreImpl>>findSessionViaBroadcast");                       
        }        
        BackingStore replicator = this.getBackingStore();
        JxtaBackingStoreImpl jxtaReplicator = null;
        if(replicator instanceof JxtaBackingStoreImpl) {
            jxtaReplicator = (JxtaBackingStoreImpl)replicator;
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("ServletTimerStoreImpl>>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 ServletTimer for the given data, and associated
    *   with this Manager
    */
    protected ServletTimerImpl getServletTimer(ReplicationState replicationState) 
        throws IOException, ClassNotFoundException 
    {
        byte[] state = replicationState.getState();
        ServletTimerImpl _timer = 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 {
                _timer = (ServletTimerImpl)ois.readObject();
            } 
            finally {
                if (ois != null) {
                    try {
                        ois.close();
                        bis = null;
                    }
                    catch (IOException e) {
                    }
                }
            }
        }     
        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("ServletTimerStoreImpl>>getServletTimer:expires=" + expirationTime);                                  
        } 
        //currentOwnerInstanceName is in the manager already
        //time to live
        //_timer.setExpires(expirationTime);
        //FIXME
        ((HAServletTimer)_timer).setVersion(version);
        ((HAServletTimer)_timer).setDirty(false);
        //((ServletTimerImpl)_timer).setPersistent(false);        
        return _timer;
    }          

    /**
     * Saves the given ServletTimer to this store.
     *
     * If a ServletTimer with the same id already exists in this
     * store, it will be replaced.
     *
     * @param sas The ServletTimer to be saved
     *
     * @exception IOException
     */
    public void save(ServletTimerImpl timer) throws IOException {
        byte[] timerState = this.getByteArray(timer);
        BackingStore replicator = this.getBackingStore();
        SimpleMetadata simpleMetadata =        
            new SimpleMetadata(
                ((HAServletTimer) timer).getVersion(),
                0L, //lastAccessedTime
                0L, //maxinactiveinterval
                timerState,
                null,
                ((HAServletTimer) timer).getExtraParameters()
            );

        try {        
            replicator.save(timer.getId(), //id
                    simpleMetadata);  //SimpleMetadata 
        } catch (BackingStoreException ex) {
            IOException ex1 = 
                (IOException) new IOException("Error during save: " + ex.getMessage()).initCause(ex);
            throw ex1;
        } 
    }

    /**
     * Removes the ServletTimer with the given id from this store.
     *
     * @param id The id of the ServletTimer to be removed
     *
     * @exception IOException
     */
    public void remove(String id) throws IOException {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("ServletTimerStoreImpl>>remove" + " id: " + id);                       
        } 
        BackingStore replicator = this.getBackingStore();
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("ServletTimerStoreImpl>>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 ServletTimer into this Store.
     *
     * @param timer ServletTimerImpl to be saved
     *
     * @exception IOException if an input/output error occurs
     */    
    public void updateLastAccessTime(ServletTimerImpl timer) 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("ServletTimerStoreImpl>>updateLastAccessTime: replicator: " + replicator);                       
        }         
        try {
            replicator.updateLastAccessTime(timer.getId(), //id
                    //temp FIXME
                    0L,
                    //timer.getLastAccessedTime(), //lastaccesstime
                    ((HAServletTimer)timer).getVersion()); //version
        } catch (BackingStoreException ex) {
            IOException ex1 = 
                (IOException) new IOException("Error during updateLastAccessTime: " + ex.getMessage()).initCause(ex);
            throw ex1;
        }
    }     

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

    /**
     * Cleans up any resources that were used by this instance.
     */
    public void cleanup() {
        //FIXME        
    }
}
