/*
 * 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 java.io.IOException;
import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.sip.Address;
import javax.servlet.sip.TimerListener;
import com.ericsson.ssa.sip.DialogSet;
import com.ericsson.ssa.sip.PathNode.Type;
import com.ericsson.ssa.sip.PersistentSipSessionManagerBase;
import com.ericsson.ssa.sip.SipApplicationSessionImpl;
import com.ericsson.ssa.sip.SipSessionDialogImpl;
import com.ericsson.ssa.sip.timer.ServletTimerImpl;
import com.sun.appserv.ha.spi.BackingStore;
import com.sun.appserv.ha.spi.BackingStoreException;
import com.sun.appserv.ha.spi.BackingStoreFactory;
import com.sun.appserv.ha.spi.BackingStoreRegistry;
import com.sun.appserv.ha.spi.SimpleMetadata;
import com.sun.ejb.containers.util.cache.BaseCache;
import com.sun.enterprise.ee.web.sessmgmt.EEPersistenceTypeResolver;
import com.sun.enterprise.ee.web.sessmgmt.JxtaBackingStoreFactory;
import com.sun.enterprise.ee.web.sessmgmt.JxtaBackingStoreImpl;
import com.sun.enterprise.ee.web.sessmgmt.JxtaReplicationReceiver;
import com.sun.enterprise.ee.web.sessmgmt.JxtaReplicationSender;
import com.sun.enterprise.ee.web.sessmgmt.ReplicationHealthChecker;
import com.sun.enterprise.ee.web.sessmgmt.ReplicationManager;
import com.sun.enterprise.ee.web.sessmgmt.ReplicationMessageRouter;
import com.sun.enterprise.ee.web.sessmgmt.ReplicationState;
import com.sun.enterprise.ee.web.sessmgmt.ReplicationStateQueryResponse;
import com.sun.enterprise.ee.web.sessmgmt.StorePool;
import com.sun.enterprise.ee.web.sessmgmt.StorePoolElement;
import com.sun.logging.LogDomains;
import java.lang.reflect.InvocationTargetException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.security.SecurityUtil;

public class SipTransactionPersistentManager 
        extends PersistentSipSessionManagerBase
        implements ReplicationManager {

    private static final Logger _logger = Logger.getLogger("SipContainer");
    protected static final String MODE_SIP = ReplicationState.MODE_SIP;
    final static String DUPLICATE_IDS_SEMANTICS_PROPERTY 
        = ReplicationState.DUPLICATE_IDS_SEMANTICS_PROPERTY; 
    final static String COMMAND_MAP = "commandmap";
    private static final Level TRACE_LEVEL = Level.FINE;
    protected static int _maxBaseCacheSize = 4096;
    protected static float _loadFactor = 0.75f;
    
    private static final Map sipApplicationSessionCommandMap = new HashMap();
    private static final Map sipSessionCommandMap = new HashMap();
    private static final Map servletTimerCommandMap = new HashMap();
    
    public final static String SAVE_COMMAND 
        = ReplicationState.SAVE_COMMAND;
    public final static String VALVE_SAVE_COMMAND 
        = ReplicationState.VALVE_SAVE_COMMAND;    
    public final static String REMOVE_COMMAND 
        = ReplicationState.REMOVE_COMMAND;    
    public final static String UNDEPLOY_COMMAND 
        = ReplicationState.UNDEPLOY_COMMAND;
    public final static String REMOVE_EXPIRED_COMMAND 
        = ReplicationState.REMOVE_EXPIRED_COMMAND;
    public final static String UPDATE_LAST_ACCESS_TIME_COMMAND 
        = ReplicationState.UPDATE_LAST_ACCESS_TIME_COMMAND;
    public final static String MESSAGE_BROADCAST_QUERY 
        = ReplicationState.MESSAGE_BROADCAST_QUERY;
    public final static String MESSAGE_BROADCAST_LOAD_RECEIVED
        = ReplicationState.MESSAGE_BROADCAST_LOAD_RECEIVED;
    
    public final static String SAVE_SAS_COMMAND = "savesas";
    public final static String REMOVE_SAS_COMMAND = "removesas";    
    public final static String UPDATE_LAST_ACCESS_TIME_SAS_COMMAND 
        = "updatelastaccesstimesas";
    public final static String MESSAGE_BROADCAST_QUERY_SAS 
        = "broadcastfindsas";
    public final static String MESSAGE_BROADCAST_LOAD_RECEIVED_SAS
        = "broadcastloadreceivedsas";
    
    public final static String SAVE_SIP_SESSION_COMMAND = "savesipsession";
    public final static String REMOVE_SIP_SESSION_COMMAND = "removesipsession";    
    public final static String UPDATE_LAST_ACCESS_TIME_SIP_SESSION_COMMAND 
        = "updatelastaccesstimesipsession";
    public final static String MESSAGE_BROADCAST_QUERY_SIP_SESSION 
        = "broadcastfindsipsession"; 
    public final static String MESSAGE_BROADCAST_LOAD_RECEIVED_SIP_SESSION
        = "broadcastloadreceivedsipsession";    
    
    public final static String SAVE_SERVLET_TIMER_COMMAND 
        = "saveservlettimer";
    public final static String REMOVE_SERVLET_TIMER_COMMAND 
        = "removeservlettimer";    
    public final static String UPDATE_LAST_ACCESS_TIME_SERVLET_TIMER_COMMAND 
        = "updatelastaccesstimeservlettimer";
    public final static String MESSAGE_BROADCAST_QUERY_SERVLET_TIMER 
        = "broadcastfindservlettimer";
    public final static String MESSAGE_BROADCAST_LOAD_RECEIVED_SERVLET_TIMER
        = "broadcastloadreceivedservlettimer";    
    
    static {
        checkSessionCacheProperties();
        initializeCommandMaps();
    }

    private SessionFactory sessionFactory;
    private StorePool sipApplicationSessionStorePool;
    private StorePool sipSessionStorePool;
    private StorePool servletTimerStorePool;

    /**
     * Our cache of replicated SipApplicationSession ReplicationState objects
     * keyed by id
     */
    protected BaseCache replicatedSipApplicationSessions = new BaseCache();

    /**
     * Our cache of replicated SipSession ReplicationState objects
     * keyed by id
     */
    protected BaseCache replicatedSipSessions = new BaseCache();
    
    /**
     * Our cache of replicated ServletTimer ReplicationState objects
     * keyed by id
     */
    protected BaseCache replicatedServletTimers = new BaseCache();    

    /**
     * Our cache of replicated SipApplicationSession ReplicationStateUpdate
     * objects
     * keyed by id
     */
    protected BaseCache replicatedSipApplicationSessionUpdates = null;

    /**
     * Our cache of replicated SipSession ReplicationStateUpdate objects
     * keyed by id
     */
    protected BaseCache replicatedSipSessionUpdates = null;
    
    /**
     * Our cache of replicated ServletTimer ReplicationStateUpdate objects
     * keyed by id
     */
    protected BaseCache replicatedServletTimerUpdates = null;
    
    /**
    * Our Replicator instance for SipApplicationSessions (for SimpleMetadata)
    */
    protected BackingStore sipApplicationSessionBackingStore = null; 
    
    /**
    * Our Replicator instance SipSessions (for SimpleMetadata)
    */
    protected BackingStore sipSessionBackingStore = null; 
    
    /**
    * Our Replicator instance for ServletTimers (for SimpleMetadata)
    */
    protected BackingStore servletTimerBackingStore = null;     
    
    protected String _passedInPersistenceType = null;
    protected boolean _duplicateIdsSemanticsAllowed = false;     

    /** Creates a new instance of ReplicationManagerBase */
    public SipTransactionPersistentManager() {
        super();
        //initialize replicated sessions cache
        replicatedSipApplicationSessions = new BaseCache();
        replicatedSipApplicationSessions.init(_maxBaseCacheSize, _loadFactor,
                                              null);
        replicatedSipSessions = new BaseCache();
        replicatedSipSessions.init(_maxBaseCacheSize, _loadFactor, null);
        replicatedServletTimers = new BaseCache();
        replicatedServletTimers.init(_maxBaseCacheSize, _loadFactor, null);
        
        replicatedSipApplicationSessionUpdates = new BaseCache();
        replicatedSipApplicationSessionUpdates.init(_maxBaseCacheSize,
                                                    _loadFactor, null); 
        replicatedSipSessionUpdates = new BaseCache();
        replicatedSipSessionUpdates.init(_maxBaseCacheSize, _loadFactor, null); 
        replicatedServletTimerUpdates = new BaseCache();
        replicatedServletTimerUpdates.init(_maxBaseCacheSize, _loadFactor, null); 
    }  

    protected static boolean checkSessionCacheProperties() {
        boolean result = false;

        try {
            Properties props = System.getProperties();
            String cacheSize = props.getProperty(
                    "HTTP_SESSION_CACHE_MAX_BASE_CACHE_SIZE");

            if (null != cacheSize) {
                _maxBaseCacheSize = (new Integer(cacheSize).intValue());
            }

            String loadFactor = props.getProperty(
                    "HTTP_SESSION_CACHE_MAX_BASE_LOAD_FACTOR");

            if (null != loadFactor) {
                _loadFactor = (new Float(loadFactor).floatValue());
            }
        } catch (Exception e) {
            //do nothing accept defaults
        }

        return result;
    }
    
    protected static void initializeCommandMaps() {
        sipApplicationSessionCommandMap.put(SAVE_COMMAND, SAVE_SAS_COMMAND);
        sipSessionCommandMap.put(SAVE_COMMAND, SAVE_SIP_SESSION_COMMAND);
        servletTimerCommandMap.put(SAVE_COMMAND, SAVE_SERVLET_TIMER_COMMAND); 

        sipApplicationSessionCommandMap.put(VALVE_SAVE_COMMAND, SAVE_SAS_COMMAND);
        sipSessionCommandMap.put(VALVE_SAVE_COMMAND, SAVE_SIP_SESSION_COMMAND);
        servletTimerCommandMap.put(VALVE_SAVE_COMMAND, SAVE_SERVLET_TIMER_COMMAND);        
        
        sipApplicationSessionCommandMap.put(REMOVE_COMMAND, REMOVE_SAS_COMMAND);
        sipSessionCommandMap.put(REMOVE_COMMAND, REMOVE_SIP_SESSION_COMMAND);
        servletTimerCommandMap.put(REMOVE_COMMAND, REMOVE_SERVLET_TIMER_COMMAND);  
        
        sipApplicationSessionCommandMap.put(UPDATE_LAST_ACCESS_TIME_COMMAND, UPDATE_LAST_ACCESS_TIME_SAS_COMMAND);
        sipSessionCommandMap.put(UPDATE_LAST_ACCESS_TIME_COMMAND, UPDATE_LAST_ACCESS_TIME_SIP_SESSION_COMMAND);
        servletTimerCommandMap.put(UPDATE_LAST_ACCESS_TIME_COMMAND, UPDATE_LAST_ACCESS_TIME_SERVLET_TIMER_COMMAND);
        
        sipApplicationSessionCommandMap.put(MESSAGE_BROADCAST_QUERY, MESSAGE_BROADCAST_QUERY_SAS);
        sipSessionCommandMap.put(MESSAGE_BROADCAST_QUERY, MESSAGE_BROADCAST_QUERY_SIP_SESSION);
        servletTimerCommandMap.put(MESSAGE_BROADCAST_QUERY, MESSAGE_BROADCAST_QUERY_SERVLET_TIMER);        
    }     
    
    public void start() throws LifecycleException {
        super.start();
        registerWithMessageRouter();        
    }
    
    protected void registerWithMessageRouter() {
        ReplicationMessageRouter router = ReplicationMessageRouter.createInstance();
        if(router != null) {
            router.addReplicationManager(this.getApplicationId(), (ReplicationManager)this);
        }
    }  
    
    /**
    * get the backingStore for SipApplicationSession
    */ 
    public BackingStore getSipApplicationSessionBackingStore() {
        if(sipApplicationSessionBackingStore == null) {
            sipApplicationSessionBackingStore = this.createBackingStore(sipApplicationSessionCommandMap);
        }
        return sipApplicationSessionBackingStore;
    }
    
    /**
    * get the backingStore for SipSession
    */ 
    public BackingStore getSipSessionBackingStore() {
        if(sipSessionBackingStore == null) {
            sipSessionBackingStore = this.createBackingStore(sipSessionCommandMap);
        }
        return sipSessionBackingStore;
    } 
    
    /**
    * get the backingStore for ServletTimer
    */ 
    public BackingStore getServletTimerBackingStore() {
        if(servletTimerBackingStore == null) {
            servletTimerBackingStore = this.createBackingStore(servletTimerCommandMap);
        }
        return servletTimerBackingStore;
    }
    
    /**
    * create and set the backing store
    * @param commandMap map used to translate commands
    */     
    BackingStore createBackingStore(Map commandMap) {
        BackingStoreFactory storeFactory = new JxtaBackingStoreFactory();
        BackingStoreRegistry backingStoreRegistry 
            = BackingStoreRegistry.getInstance();
        Properties inputEnv 
            = backingStoreRegistry.getFactoryClassEnv(getPassedInPersistenceType());
        Properties env = (Properties)inputEnv.clone();        
        //does this manager & backing store support duplicate id semantics
        //for batch replication usage
        env.put(DUPLICATE_IDS_SEMANTICS_PROPERTY, Boolean.valueOf(this.isDuplicateIdsSemanticsAllowed()));
        env.put(COMMAND_MAP, commandMap);
        BackingStore backingStore = null;
        try {
            backingStore = storeFactory.createBackingStore(
                        SimpleMetadata.class,     //type
                        this.getApplicationId(), //appid
                        env);
        } catch (BackingStoreException ex) {
            //deliberate no-op
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("backingStore: " + backingStore);
        }         
        if(backingStore != null) {
            if(backingStore instanceof JxtaBackingStoreImpl) {
                ((JxtaBackingStoreImpl)backingStore).setMode(MODE_SIP);
            }        
        }
        return backingStore;
    }        
    
    public boolean isDuplicateIdsSemanticsAllowed() {
        return _duplicateIdsSemanticsAllowed;
    }    
    
    public void setDuplicateIdsSemanticsAllowed(boolean value) {
        _duplicateIdsSemanticsAllowed = value;
    }   
    
    public String getPassedInPersistenceType() {
        return _passedInPersistenceType;
    }    
    
    public void setPassedInPersistenceType(String persistenceType) {
        _passedInPersistenceType = persistenceType;
    }    
    
    //SAS cache methods

    /**
     * get the replicated SipApplicationSessions cache
     */ 
    public BaseCache getReplicatedSipApplicationSessions() {
        return replicatedSipApplicationSessions;
    }
    
    /**
     * set the replicated SipApplicationSessions cache
     * @param sesstable
     */ 
    public void setReplicatedSipApplicationSessions(BaseCache sesstable) {
        replicatedSipApplicationSessions = sesstable;
    } 
    
    /**
     * Put session State in SipApplicationSession replica cache
     * @param sessionState
     */    
    protected synchronized void putInSipApplicationSessionReplicationCache(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName()
                         + ">>putInSipApplicationSessionReplicationCache id: "
                         + sessionState.getId());
        } 
        String id = (String)sessionState.getId();
        if(id == null) {
            return;
        }
        
        ReplicationState currentState = (ReplicationState)replicatedSipApplicationSessions.get(id);
        if(_logger.isLoggable(Level.FINE)) {
            if(currentState != null) {
                _logger.fine("currentVersion: " + currentState.getVersion()
                             + " newVersion: " + sessionState.getVersion());
            }
        }
        if((currentState != null)
                && currentState.getVersion() > sessionState.getVersion()) {
            return;
        }
        replicatedSipApplicationSessions.put(sessionState.getId(), sessionState);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, "in " + this.getClass().getName()
                + ">>putInSipApplicationSessionReplicationCache complete id: "
                + sessionState.getId() + "[ver:" + sessionState.getVersion()
                + "]");
        }        
    }
    
    /**
     * get SipApplicationSession State from replica cache based on the id
     * @param id
     */    
    protected ReplicationState getFromSipApplicationSessionReplicationCache(String id) {
        ReplicationState returnState 
            = (ReplicationState)replicatedSipApplicationSessions.get(id);
        if(returnState != null) {
            ReplicationStateUpdate returnStateUpdate
                = this.getFromSipApplicationSessionReplicationUpdateCache(id);
            //apply update if it exists and has higher version
            if(returnStateUpdate != null) {
                if(returnStateUpdate.getVersion() > returnState.getVersion()) {
                    returnState.setVersion(returnStateUpdate.getVersion());
                    returnState.setLastAccess(returnStateUpdate.getLastAccess());
                }
                this.removeFromSipApplicationSessionReplicationUpdateCache(returnStateUpdate.getId());
            }            
        }
        return returnState;
    }
    
    /**
     * remove session State from replica cache based on the id 
     * @param id
     */    
    protected ReplicationState removeFromSipApplicationSessionReplicationCache(String id) {
        if(id == null) {
            return null;
        }
        removeFromSipApplicationSessionReplicationUpdateCache(id);
        return (ReplicationState)replicatedSipApplicationSessions.remove(id);        
    }
    
    //SS cache methods
    
    /**
    * get the replicated SipSessions cache
    */ 
    public BaseCache getReplicatedSipSessions() {
        return replicatedSipSessions;
    }
    
    /**
    * set the replicated SipSessions cache
    * @param sesstable
    */ 
    public void setReplicatedSipSessions(BaseCache sesstable) {
        replicatedSipSessions = sesstable;
    }
    
    /**
     * Put session State in SipApplicationSession replica cache
     * @param sessionState
     */    
    protected synchronized void putInSipSessionReplicationCache(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName()
                         + ">>putInSipSessionReplicationCache id: "
                         + sessionState.getId());
        } 
        String id = (String)sessionState.getId();
        if(id == null) {
            return;
        }
        
        ReplicationState currentState = (ReplicationState)replicatedSipSessions.get(id);
        if(_logger.isLoggable(Level.FINE)) {
            if(currentState != null) {
                _logger.fine("currentVersion: " + currentState.getVersion()
                             + " newVersion: " + sessionState.getVersion());
            }
        }
        if((currentState != null) && currentState.getVersion() > sessionState.getVersion()) {
            return;
        }
        replicatedSipSessions.put(sessionState.getId(), sessionState);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, "in " + this.getClass().getName()
                        + ">>putInSipSessionReplicationCache complete id: "
                        + sessionState.getId() + "[ver:"
                        + sessionState.getVersion() + "]");
        }        
    } 
    
    /**
     * get SipSession State from replica cache based on the id
     * @param id
     */    
    protected ReplicationState getFromSipSessionReplicationCache(String id) {
        ReplicationState returnState 
            = (ReplicationState)replicatedSipSessions.get(id);
        if(returnState != null) {
            ReplicationStateUpdate returnStateUpdate
                = this.getFromSipSessionReplicationUpdateCache(id);
            //apply update if it exists and has higher version
            if(returnStateUpdate != null) {
                if(returnStateUpdate.getVersion() > returnState.getVersion()) {
                    returnState.setVersion(returnStateUpdate.getVersion());
                    returnState.setLastAccess(returnStateUpdate.getLastAccess());
                }
                this.removeFromSipSessionReplicationUpdateCache(returnStateUpdate.getId());
            }            
        }
        return returnState;
    }
    
    /**
     * remove session State from replica cache based on the id 
     * @param id
     */    
    protected ReplicationState removeFromSipSessionReplicationCache(String id) {
        if(id == null) {
            return null;
        }
        removeFromSipSessionReplicationUpdateCache(id);
        return (ReplicationState)replicatedSipSessions.remove(id);        
    }    
    
 //ServletTimer cache methods
    
    /**
    * get the replicated ServletTimers cache
    */ 
    public BaseCache getReplicatedServletTimers() {
        return replicatedServletTimers;
    }
    
    /**
    * set the replicated ServletTimers cache
    * @param sesstable
    */ 
    public void setReplicatedServletTimers(BaseCache sesstable) {
        replicatedServletTimers = sesstable;
    }
    
    /**
     * Put session timer State in ServletTimers replica cache
     * @param sessionState
     */    
    protected synchronized void putInServletTimerReplicationCache(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName()
                         + ">>putInServletTimerReplicationCache id: "
                         + sessionState.getId());
        } 
        String id = (String)sessionState.getId();
        if(id == null) {
            return;
        }
        
        ReplicationState currentState = (ReplicationState)replicatedServletTimers.get(id);
        if(_logger.isLoggable(Level.FINE)) {
            if(currentState != null) {
                _logger.fine("currentVersion: " + currentState.getVersion()
                             + " newVersion: " + sessionState.getVersion());
            }
        }
        if((currentState != null) && currentState.getVersion() > sessionState.getVersion()) {
            return;
        }
        replicatedServletTimers.put(sessionState.getId(), sessionState);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, "in " + this.getClass().getName()
                        + ">>putInSipSessionReplicationCache complete id: "
                        + sessionState.getId() + "[ver:"
                        + sessionState.getVersion() + "]");
        }        
    } 
    
    /**
     * get ServletTimer State from replica cache based on the id
     * @param id
     */    
    protected ReplicationState getFromServletTimerReplicationCache(String id) {
        ReplicationState returnState 
            = (ReplicationState)replicatedSipSessions.get(id);
        if(returnState != null) {
            ReplicationStateUpdate returnStateUpdate
                = this.getFromServletTimerReplicationUpdateCache(id);
            //apply update if it exists and has higher version
            if(returnStateUpdate != null) {
                if(returnStateUpdate.getVersion() > returnState.getVersion()) {
                    returnState.setVersion(returnStateUpdate.getVersion());
                    returnState.setLastAccess(returnStateUpdate.getLastAccess());
                }
                this.removeFromServletTimerReplicationUpdateCache(returnStateUpdate.getId());
            }            
        }
        return returnState;
    }
    
    /**
     * remove session State from replica cache based on the id 
     * @param id
     */    
    protected ReplicationState removeFromServletTimerReplicationCache(String id) {
        if(id == null) {
            return null;
        }
        removeFromServletTimerReplicationUpdateCache(id);
        return (ReplicationState)replicatedServletTimers.remove(id);        
    }    
    
    //SAS updates cache methods    
    
    /**
     * Put SipApplicationSession State updates in replica cache
     * @param sessionState
     */    
    protected synchronized void putInSipApplicationSessionReplicationUpdateCache(ReplicationStateUpdate sessionStateUpdate) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName()
                         + ">>putInSipApplicationSessionUpdateCache id: "
                         + sessionStateUpdate.getId());
        }
        if(sessionStateUpdate == null) {
            return;
        }
        String id = (String)sessionStateUpdate.getId();
        if(id == null) {
            return;
        }
        
        ReplicationStateUpdate currentStateUpdate = this.getFromSipApplicationSessionReplicationUpdateCache(id);
        if(_logger.isLoggable(Level.FINE)) {
            if(currentStateUpdate != null) {
                _logger.fine("currentVersion: "
                             + currentStateUpdate.getVersion()
                             + " newVersion: "
                             + sessionStateUpdate.getVersion());
            }
        }
        if((currentStateUpdate != null) && currentStateUpdate.getVersion() > sessionStateUpdate.getVersion()) {
            return;
        }
        replicatedSipApplicationSessionUpdates.put(sessionStateUpdate.getId(), sessionStateUpdate);
    }
    
    /**
     * get SipApplicationSession State update from replica cache based on the
     * id
     *
     * @param id
     */    
    protected ReplicationStateUpdate getFromSipApplicationSessionReplicationUpdateCache(String id) {    
        return (ReplicationStateUpdate)replicatedSipApplicationSessionUpdates.get(id);
    }
    
    /**
     * remove SipApplicationSession State update from replica cache based on
     * the id of sessionStateUpdate
     * @param id
     */    
    protected void removeFromSipApplicationSessionReplicationUpdateCache(String id) {
        if(id == null) {
            return;
        }
        replicatedSipApplicationSessionUpdates.remove(id);
    }
    
    //SS updates cache methods
    
    /**
     * Put SipSession State updates in replica cache
     * @param sessionState
     */    
    protected synchronized void putInSipSessionReplicationUpdateCache(ReplicationStateUpdate sessionStateUpdate) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName()
                         + ">>putInSipSessionReplicationUpdateCache id: "
                         + sessionStateUpdate.getId());
        }
        if(sessionStateUpdate == null) {
            return;
        }
        String id = (String)sessionStateUpdate.getId();
        if(id == null) {
            return;
        }
        
        ReplicationStateUpdate currentStateUpdate = this.getFromSipSessionReplicationUpdateCache(id);
        if(_logger.isLoggable(Level.FINE)) {
            if(currentStateUpdate != null) {
                _logger.fine("currentVersion: "
                             + currentStateUpdate.getVersion()
                             + " newVersion: "
                             + sessionStateUpdate.getVersion());
            }
        }
        if((currentStateUpdate != null)
                && currentStateUpdate.getVersion() > sessionStateUpdate.getVersion()) {
            return;
        }
        replicatedSipSessionUpdates.put(sessionStateUpdate.getId(),
                                        sessionStateUpdate);
    }
    
    /**
     * get SipSession State update from replica cache based on the id
     * @param id
     */    
    protected ReplicationStateUpdate getFromSipSessionReplicationUpdateCache(String id) {    
        return (ReplicationStateUpdate)replicatedSipSessionUpdates.get(id);
    }
    
    /**
     * remove SipSession State update from replica cache based on the id of
     * sessionStateUpdate
     * @param id
     */    
    protected void removeFromSipSessionReplicationUpdateCache(String id) {
        if(id == null) {
            return;
        }
        replicatedSipSessionUpdates.remove(id);
    }   
    
    //ServletTimer updates cache methods
    
    /**
     * Put ServletTimer State updates in replica cache
     * @param sessionState
     */    
    protected synchronized void putInServletTimerReplicationUpdateCache(ReplicationStateUpdate sessionStateUpdate) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName()
                         + ">>putInServletTimerReplicationUpdateCache id: "
                         + sessionStateUpdate.getId());
        }
        if(sessionStateUpdate == null) {
            return;
        }
        String id = (String)sessionStateUpdate.getId();
        if(id == null) {
            return;
        }
        
        ReplicationStateUpdate currentStateUpdate = this.getFromServletTimerReplicationUpdateCache(id);
        if(_logger.isLoggable(Level.FINE)) {
            if(currentStateUpdate != null) {
                _logger.fine("currentVersion: "
                             + currentStateUpdate.getVersion()
                             + " newVersion: "
                             + sessionStateUpdate.getVersion());
            }
        }
        if((currentStateUpdate != null)
                && currentStateUpdate.getVersion() > sessionStateUpdate.getVersion()) {
            return;
        }
        replicatedServletTimerUpdates.put(sessionStateUpdate.getId(),
                                        sessionStateUpdate);
    }
    
    /**
     * get ServletTimer State update from replica cache based on the id
     * @param id
     */    
    protected ReplicationStateUpdate getFromServletTimerReplicationUpdateCache(String id) {    
        return (ReplicationStateUpdate)replicatedServletTimerUpdates.get(id);
    }
    
    /**
     * remove ServletTimer State update from replica cache based on the id of
     * sessionStateUpdate
     * @param id
     */    
    protected void removeFromServletTimerReplicationUpdateCache(String id) {
        if(id == null) {
            return;
        }
        replicatedServletTimerUpdates.remove(id);
    }   
    
    //end cache methods
    
    //start receiver side process methods
    
    //SipApplicationSession process methods

    /**
     * process the save of a SipApplicationSession
     * @param sessionState - contains the save command
     */     
    public void processSavesas(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processSavesas");
        }        
        this.putInSipApplicationSessionReplicationCache(sessionState);
    }    

    /**
     * process the removal of a SipApplicationSession
     * @param sessionState - contains the remove command
     */     
    public void processRemovesas(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processRemovesas");
        } 
        if(sessionState == null) {
            return;
        }
        replicatedSipApplicationSessions.remove(sessionState.getId());
        removeFromSipApplicationSessionReplicationUpdateCache((String)sessionState.getId());      
    }

    /**
     * process the update of a SipApplicationSession
     * @param sessionState - contains the updatelastaccesstime command
     */    
    public void processUpdatelastaccesstimesas(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processUpdatelastaccesstimesas");
            _logger.fine("processUpdatelastaccesstimesas:version:" + sessionState.getVersion());
        }
        String id = (String)sessionState.getId();
        ReplicationState state = this.getFromSipApplicationSessionReplicationCache(id);
        if(state != null) {
            state.setLastAccess(sessionState.getLastAccess());
            state.setVersion(sessionState.getVersion());
            this.putInSipApplicationSessionReplicationCache(state);
        } else {
            if(_logger.isLoggable(Level.FINE)) {                
                _logger.fine("processUpdatelastaccesstimesas: attempting to update a session not yet stored:id:" + sessionState.getId());
            }
            this.putInSipApplicationSessionReplicationUpdateCache(new ReplicationStateUpdate(id, sessionState.getVersion(), sessionState.getLastAccess()) );
        }        
    }
    
    /**
     * process the broadcastfindsas for SipApplicationSession
     * @param queryState
     */     
    public ReplicationState processBroadcastfindsas(ReplicationState queryState) {
        //complete query and send back response
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindsas:instance: " + getInstanceName());
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindsas:id=" + queryState.getId());                        
        } 
        
        //look in active cache
        HASipApplicationSession haSipApplicationSession = null;
        SipApplicationSessionImpl sipApplicationSession 
            = findSipApplicationSessionFromCacheOnly((String)queryState.getId());
        if(sipApplicationSession instanceof HASipApplicationSession) {
            haSipApplicationSession = (HASipApplicationSession)sipApplicationSession;
        }        
        
        //look in replica cache
        ReplicationState replicaState
            = getFromSipApplicationSessionReplicationCache((String)queryState.getId());
        ReplicationState returnState 
            = getBestSipApplicationSession(haSipApplicationSession, replicaState, queryState);          
        clearFromSipApplicationSessionManagerCache((String)queryState.getId());
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindsas:returnState=" + returnState);
        }
        return returnState;
    }
    
    public void processBroadcastloadreceivedsas(ReplicationState queryState) {
        //load is acknowledged safe to remove replica now
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processbroadcastloadreceivedsas:instance: " + getInstanceName());
            _logger.fine("in " + this.getClass().getName() + ">>processbroadcastloadreceivedsas:id=" + queryState.getId());                        
        }
        if(queryState == null || queryState.getId() == null) {
            return;
        }
        String id = (String)queryState.getId();
        //remove active sip application session if present and not locked
        SipApplicationSessionImpl sipApplicationSession 
            = findSipApplicationSessionFromCacheOnly(id);
        if(sipApplicationSession != null 
                && !sipApplicationSession.isForegroundLocked()) {
            this.removeSipApplicationSessionFromCache(sipApplicationSession);
        } 
        //only safe to remove replica if we are not the replica partner
        //of the sending instance
        ReplicationHealthChecker healthChecker 
            = ReplicationHealthChecker.getInstance();
        String replicatedFromInstanceName 
            = healthChecker.getReshapeReplicateFromInstanceName();        
        if(replicatedFromInstanceName != queryState.getInstanceName()) {            
            removeFromSipApplicationSessionReplicationCache(id);           
        }
    }   
    
    /**
     * find the best version of SipApplicationSession
     * removing any stale versions and return query result
     * @param activeSipApplicationSession SipApplicationSession 
     *      from active cache
     * @param replicaSipApplicationSession SipApplicationSession 
     *      from replica cache
     * @param queryVersion version requested in query (-1 means 
     *      version unaware
     */     
    private ReplicationState getBestSipApplicationSession(HASipApplicationSession activeSipApplicationSession, 
        ReplicationState replicaSipApplicationSession, ReplicationState queryState) {
        ReplicationState bestResult = null;
        long queryVersion = queryState.getVersion();
        
        //first check for none found in either active or replica caches
        if(replicaSipApplicationSession == null && activeSipApplicationSession == null) {
            //return nack
            return ReplicationState.createQueryResponseFrom(queryState, true);
        }
        
        //next check for artifacts found in both active and replica caches
        if(replicaSipApplicationSession != null && activeSipApplicationSession != null) { 
            //compare and remove the lesser version
            //keeping the higher version as (tentative) best
            if(replicaSipApplicationSession.getVersion() < activeSipApplicationSession.getVersion()) {
                //remove stale replica - work with active
                removeFromSipApplicationSessionReplicationCache((String)replicaSipApplicationSession.getId());
                //create appropriate response from active
                bestResult 
                    = createSipApplicationSessionResponseFrom(activeSipApplicationSession, queryState);
            } else {
                //remove stale active - work with replica
                clearFromSipApplicationSessionManagerCache(activeSipApplicationSession.getId());               
                //create appropriate response from replica
                bestResult 
                    = createSipApplicationSessionResponseFrom(replicaSipApplicationSession, queryState);                                
            }
        } else {
            //either replica or active is null and other is non-null
            //replica is null and active is not null
            if(replicaSipApplicationSession == null) {                
                //create appropriate response from active
                bestResult 
                    = createSipApplicationSessionResponseFrom(activeSipApplicationSession, queryState);                               
            } else {
                //active is null & replica is not null
                //create appropriate response from replica
                bestResult 
                    = createSipApplicationSessionResponseFrom(replicaSipApplicationSession, queryState);                                 
            }
        }
        return bestResult;
    }
    
    private ReplicationState createSipApplicationSessionResponseFrom(ReplicationState replicaSipApplicationSession, 
            ReplicationState queryState) {        
        //create appropriate response from replica
        ReplicationState result = null;
        long queryVersion = queryState.getVersion();        
        if(queryVersion != -1 && replicaSipApplicationSession.getVersion() < queryVersion) {
            //return nack & clear stale replica
            removeFromSipApplicationSessionReplicationCache((String)replicaSipApplicationSession.getId());
            result = ReplicationState.createQueryResponseFrom(queryState, true);
        } else {
            //return real response based on replica
            result = ReplicationState.createQueryResponseFrom(replicaSipApplicationSession); 
        } 
        return result;
    }

    private ReplicationState createSipApplicationSessionResponseFrom(HASipApplicationSession activeSipApplicationSession, 
            ReplicationState queryState) {     
        //create appropriate response from active
        ReplicationState result = null;
        long queryVersion = queryState.getVersion();        
        if(queryVersion != -1 && activeSipApplicationSession.getVersion() < queryVersion) {
            //return nack & clear stale active
            clearFromSipApplicationSessionManagerCache(activeSipApplicationSession.getId());
            result = ReplicationState.createQueryResponseFrom(queryState, true);
        } else {
            //return real response based on active SAS
            try {
                result = createQueryResponseFrom(activeSipApplicationSession);
            } catch (IOException ioe) {
                _logger.warning("Failed load: Unable to serialize " +
                                activeSipApplicationSession);
                // We've been unable to serialize the given active
                // SipApplicationSession.
                // Clear it from the active cache and return a nack instead
                clearFromSipApplicationSessionManagerCache(
                    activeSipApplicationSession.getId());
                result = ReplicationState.createQueryResponseFrom(queryState,
                                                                  true);
            }
        }

        return result;
    }
    
    /**
     * Converts the given SipApplicationSession to a ReplicationState.
     *
     * @param sas The SipApplicationSession to be converted
     *
     * @return The ReplicationState corresponding to the given 
     * SipApplicationSession
     */ 
    private ReplicationState createQueryResponseFrom(
            HASipApplicationSession sas) throws IOException {
        return new ReplicationState(
                MODE_SIP,
                sas.getId(),
                getApplicationId(),  
                sas.getVersion(),
                0L,
                0L,
                null,
                null,
                null,
                ReplicationState.RETURN_BROADCAST_MSG_COMMAND,
                getSingletonSipApplicationSessionStore().getByteArray(sas),    
                null);
    }

    protected void clearFromSipApplicationSessionManagerCache(String id) {
        SipApplicationSessionImpl sess 
            = this.findSipApplicationSessionFromCacheOnly(id);
        if(sess != null) {                               
            this.removeSipApplicationSessionFromCache(sess);
        } 
    }    
    
    //SipSession process methods
    
    /**
     * process the save of a SipSession
     * @param sessionState - contains the save command
     */     
    public void processSavesipsession(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processSavess");
        }        
        this.putInSipSessionReplicationCache(sessionState);
    }    
    
    /**
     * process the removal of a Sip Session
     * @param sessionState - contains the remove command
     */     
    public void processRemovesipsession(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processRemovess");
        } 
        if(sessionState == null) {
            return;
        }
        replicatedSipSessions.remove(sessionState.getId());
        removeFromSipSessionReplicationUpdateCache((String)sessionState.getId());
    }
    
    /**
     * process the update of a SipSession
     * @param sessionState - contains the updatelastaccesstime command
     */    
    public void processUpdatelastaccesstimesipsession(ReplicationState sessionState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processUpdatelastaccesstimess");
            _logger.fine("processUpdatelastaccesstimess:version:" + sessionState.getVersion());
        }
        String id = (String)sessionState.getId();
        ReplicationState state = this.getFromSipSessionReplicationCache(id);
        if(state != null) {
            state.setLastAccess(sessionState.getLastAccess());
            state.setVersion(sessionState.getVersion());
            this.putInSipSessionReplicationCache(state);
        } else {
            if(_logger.isLoggable(Level.FINE)) {                
                _logger.fine("processUpdatelastaccesstimess: attempting to update a session not yet stored:id:" + sessionState.getId());
            }
            this.putInSipSessionReplicationUpdateCache(new ReplicationStateUpdate(id, sessionState.getVersion(), sessionState.getLastAccess()) );
        }        
    }

    /**
     * process the broadcastfindsipsession for SipSession
     * @param queryState
     */     
    public ReplicationState processBroadcastfindsipsession(ReplicationState queryState) {
        //complete query and send back response
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindsipsession:instance: " + getInstanceName());
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindsipsession:id=" + queryState.getId());                        
        } 
        
        //look in active cache
        HASipSession haSipSession = null;
        SipSessionDialogImpl sipSession 
            = findSipSessionFromCacheOnly((String)queryState.getId());
        if(sipSession instanceof HASipSession) {
            haSipSession = (HASipSession)sipSession;
        }        
        
        //look in replica cache
        ReplicationState replicaState
            = getFromSipSessionReplicationCache((String)queryState.getId());
        ReplicationState returnState 
            = getBestSipSession(haSipSession, replicaState, queryState);          
        clearFromSipSessionManagerCache((String)queryState.getId());
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindsipsession:returnState=" + returnState);
        }
        return returnState;
    }
    
    public void processBroadcastloadreceivedsipsession(ReplicationState queryState) {
        //load is acknowledged safe to remove replica now
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processbroadcastloadreceivedsipsession:instance: " + getInstanceName());
            _logger.fine("in " + this.getClass().getName() + ">>processbroadcastloadreceivedsipsession:id=" + queryState.getId());                        
        }
        if(queryState == null || queryState.getId() == null) {
            return;
        }
        String id = (String)queryState.getId();
        //remove active sip session if present and is not locked
        SipSessionDialogImpl sipSession = findSipSessionFromCacheOnly(id);
        if(sipSession != null 
                && !sipSession.isForegroundLocked()) {        
            this.removeSipSessionFromCache(sipSession);
        } 
        //only safe to remove replica if we are not the replica partner
        //of the sending instance
        ReplicationHealthChecker healthChecker 
            = ReplicationHealthChecker.getInstance();
        String replicatedFromInstanceName 
            = healthChecker.getReshapeReplicateFromInstanceName();        
        if(replicatedFromInstanceName != queryState.getInstanceName()) {            
            removeFromSipSessionReplicationCache(id);           
        }
    }    

    /**
     * find the best version of SipSession
     * removing any stale versions and return query result
     * @param activeSipSession SipSession from active cache
     * @param replicaSas replica SipSession from replica cache
     * @param queryVersion version requested in query (-1 means 
     *      version unaware
     */     
    private ReplicationState getBestSipSession(HASipSession activeSipSession, 
        ReplicationState replicaSipSession, ReplicationState queryState) {
        ReplicationState bestResult = null;
        long queryVersion = queryState.getVersion();
        
        //first check for none found in either active or replica caches
        if(replicaSipSession == null && activeSipSession == null) {
            //return nack
            return ReplicationState.createQueryResponseFrom(queryState, true);
        }
        
        //next check for artifacts found in both active and replica caches
        if(replicaSipSession != null && activeSipSession != null) { 
            //compare and remove the lesser version
            //keeping the higher version as (tentative) best
            if(replicaSipSession.getVersion() < activeSipSession.getVersion()) {
                //remove stale replica - work with active
                removeFromSipSessionReplicationCache((String)replicaSipSession.getId());
                //create appropriate response from active
                bestResult 
                    = createSipSessionResponseFrom(activeSipSession, queryState);
            } else {
                //remove stale active - work with replica
                this.clearFromSipSessionManagerCache(activeSipSession.getId());                
                //create appropriate response from replica
                bestResult 
                    = createSipSessionResponseFrom(replicaSipSession, queryState);                                
            }
        } else {
            //either replica or active is null and other is non-null
            //replica is null and active is not null
            if(replicaSipSession == null) {                
                //create appropriate response from active
                bestResult 
                    = createSipSessionResponseFrom(activeSipSession, queryState);                               
            } else {
                //active is null & replica is not null
                //create appropriate response from replica
                bestResult 
                    = createSipSessionResponseFrom(replicaSipSession, queryState);                                 
            }
        }
        return bestResult;
    }
    
    private ReplicationState createSipSessionResponseFrom(ReplicationState replicaSipSession, 
            ReplicationState queryState) {        
        //create appropriate response from replica
        ReplicationState result = null;
        long queryVersion = queryState.getVersion();        
        if(queryVersion != -1 && replicaSipSession.getVersion() < queryVersion) {
            //return nack & clear stale replica
            removeFromSipSessionReplicationCache((String)replicaSipSession.getId());
            result = ReplicationState.createQueryResponseFrom(queryState, true);
        } else {
            //return real response based on replica
            result = ReplicationState.createQueryResponseFrom(replicaSipSession); 
        } 
        return result;
    }

    private ReplicationState createSipSessionResponseFrom(HASipSession activeSipSession, 
            ReplicationState queryState) {     
        //create appropriate response from active
        ReplicationState result = null;
        long queryVersion = queryState.getVersion();        
        if(queryVersion != -1 && activeSipSession.getVersion() < queryVersion) {
            //return nack & clear stale active
            clearFromSipSessionManagerCache(activeSipSession.getId());
            result = ReplicationState.createQueryResponseFrom(queryState, true);
        } else {
            //return real response based on active SipSession
            try {
                result = createQueryResponseFrom(activeSipSession);
            } catch (IOException ioe) {
                _logger.warning("Failed load: Unable to serialize " +
                                activeSipSession);
                // We've been unable to serialize the given active SipSession.
                // Clear it from the active cache and return a nack instead
                clearFromSipSessionManagerCache(activeSipSession.getId());
                result = ReplicationState.createQueryResponseFrom(queryState,
                                                                  true);
            }
        }
        return result;
    }
    
    /**
     * Converts the given SipSession to a ReplicationState.
     *
     * @param ss The SipSession to be converted
     *
     * @return The ReplicationState corresponding to the given SipSession
     */ 
    private ReplicationState createQueryResponseFrom(HASipSession ss)
            throws IOException {
        return new ReplicationState(
                MODE_SIP,
                ss.getId(),
                getApplicationId(),  
                ss.getVersion(),
                0L,
                0L,
                null,
                null,
                null,
                ReplicationState.RETURN_BROADCAST_MSG_COMMAND,
                getSingletonSipSessionStore().getByteArray(ss),
                null);
    }
    
    protected void clearFromSipSessionManagerCache(String id) {
        SipSessionDialogImpl sess 
            = this.findSipSessionFromCacheOnly(id);
        if(sess != null) {                               
            this.removeSipSessionFromCache(sess);
        } 
    }    
    
    //ServletTimer process methods
    
    /**
     * process the save of a Servlet Timer
     * @param timerState - contains the save command
     */     
    public void processSaveservlettimer(ReplicationState timerState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processSaveservlettimer");
        }        
        this.putInServletTimerReplicationCache(timerState);
    }     
    
    /**
     * process the removal of a Servlet Timer
     * @param timerState - contains the remove command
     */     
    public void processRemoveservlettimer(ReplicationState timerState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processRemoveservlettimer");
        } 
        if(timerState == null) {
            return;
        }
        replicatedServletTimers.remove(timerState.getId());
        removeFromServletTimerReplicationUpdateCache((String)timerState.getId());
    }
    
    /**
     * process the update of a Servlet Timer
     * @param timerState - contains the updatelastaccesstime command
     */    
    public void processUpdatelastaccesstimeservlettimer(ReplicationState timerState) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("IN" + this.getClass().getName() + ">>processUpdatelastaccesstimeservlettimer");
            _logger.fine("processUpdatelastaccesstimeservlettimer:version:" + timerState.getVersion());
        }
        String id = (String)timerState.getId();
        ReplicationState state = this.getFromServletTimerReplicationCache(id);
        if(state != null) {
            state.setLastAccess(timerState.getLastAccess());
            state.setVersion(timerState.getVersion());
            this.putInServletTimerReplicationCache(state);
        } else {
            if(_logger.isLoggable(Level.FINE)) {                
                _logger.fine("processUpdatelastaccesstimeservlettimer: attempting to update a session not yet stored:id:" + timerState.getId());
            }
            this.putInServletTimerReplicationUpdateCache(new ReplicationStateUpdate(id, timerState.getVersion(), timerState.getLastAccess()) );
        }        
    }
    
    /**
     * process the broadcastfindservlettimer for ServletTimer
     * @param queryState
     */     
    public ReplicationState processBroadcastfindservlettimer(ReplicationState queryState) {
        //complete query and send back response
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindservlettimer:instance: " + getInstanceName());
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindservlettimer:id=" + queryState.getId());                        
        } 
        
        //look in active cache
        HAServletTimer haServletTimer = null;
        ServletTimerImpl servletTimer 
            = findServletTimerFromCacheOnly((String)queryState.getId());
        if(servletTimer instanceof HAServletTimer) {
            haServletTimer = (HAServletTimer)servletTimer;
        }        
        
        //look in replica cache
        ReplicationState replicaState
            = getFromServletTimerReplicationCache((String)queryState.getId());
        ReplicationState returnState 
            = getBestServletTimer(haServletTimer, replicaState, queryState);          
        clearFromServletTimerManagerCache((String)queryState.getId());
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processBroadcastfindservlettimer:returnState=" + returnState);
        }
        return returnState;
    }
    
    public void processBroadcastloadreceivedservlettimer(ReplicationState queryState) {
        //load is acknowledged safe to remove replica now
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processbroadcastloadreceivedservlettimer:instance: " + getInstanceName());
            _logger.fine("in " + this.getClass().getName() + ">>processbroadcastloadreceivedservlettimer:id=" + queryState.getId());                        
        }
        if(queryState == null || queryState.getId() == null) {
            return;
        }
        String id = (String)queryState.getId();
        //remove active timer if present and not locked
        ServletTimerImpl timer = findServletTimerFromCacheOnly(id);
        if(timer != null) {
        /* FIXME should be these 2 following lines
         * need to add lock to timer first
        if(timer != null 
                && !timer.isForegroundLocked()) { 
         */        
            this.removeServletTimerFromCache(timer);
        } 
        //only safe to remove replica if we are not the replica partner
        //of the sending instance
        ReplicationHealthChecker healthChecker 
            = ReplicationHealthChecker.getInstance();
        String replicatedFromInstanceName 
            = healthChecker.getReshapeReplicateFromInstanceName();        
        if(replicatedFromInstanceName != queryState.getInstanceName()) {            
            removeFromServletTimerReplicationCache(id);           
        }
    }    
    
    /**
     * find the best version of ServletTimer
     * removing any stale versions and return query result
     * @param activeServletTimer ServletTimer 
     *      from active cache
     * @param replicaSipApplicationSession ServletTimer replica 
     *      from replica cache
     * @param queryVersion version requested in query (-1 means 
     *      version unaware
     */     
    private ReplicationState getBestServletTimer(HAServletTimer activeServletTimer, 
        ReplicationState replicaServletTimer, ReplicationState queryState) {
        ReplicationState bestResult = null;
        long queryVersion = queryState.getVersion();
        
        //first check for none found in either active or replica caches
        if(replicaServletTimer == null && activeServletTimer == null) {
            //return nack
            return ReplicationState.createQueryResponseFrom(queryState, true);
        }
        
        //next check for artifacts found in both active and replica caches
        if(replicaServletTimer != null && activeServletTimer != null) { 
            //compare and remove the lesser version
            //keeping the higher version as (tentative) best
            if(replicaServletTimer.getVersion() < activeServletTimer.getVersion()) {
                //remove stale replica - work with active
                removeFromServletTimerReplicationCache((String)replicaServletTimer.getId());
                //create appropriate response from active
                bestResult 
                    = createServletTimerResponseFrom(activeServletTimer, queryState);
            } else {
                //remove stale active - work with replica
                clearFromServletTimerManagerCache(activeServletTimer.getId());               
                //create appropriate response from replica
                bestResult 
                    = createServletTimerResponseFrom(replicaServletTimer, queryState);                                
            }
        } else {
            //either replica or active is null and other is non-null
            //replica is null and active is not null
            if(replicaServletTimer == null) {                
                //create appropriate response from active
                bestResult 
                    = createServletTimerResponseFrom(activeServletTimer, queryState);                               
            } else {
                //active is null & replica is not null
                //create appropriate response from replica
                bestResult 
                    = createServletTimerResponseFrom(replicaServletTimer, queryState);                                 
            }
        }
        return bestResult;
    }
    
    private ReplicationState createServletTimerResponseFrom(ReplicationState replicaServletTimer, 
            ReplicationState queryState) {        
        //create appropriate response from replica
        ReplicationState result = null;
        long queryVersion = queryState.getVersion();        
        if(queryVersion != -1 && replicaServletTimer.getVersion() < queryVersion) {
            //return nack & clear stale replica
            removeFromServletTimerReplicationCache((String)replicaServletTimer.getId());
            result = ReplicationState.createQueryResponseFrom(queryState, true);
        } else {
            //return real response based on replica
            result = ReplicationState.createQueryResponseFrom(replicaServletTimer); 
        } 
        return result;
    }

    private ReplicationState createServletTimerResponseFrom(HAServletTimer activeServletTimer, 
            ReplicationState queryState) {     
        //create appropriate response from active
        ReplicationState result = null;
        long queryVersion = queryState.getVersion();        
        if(queryVersion != -1 && activeServletTimer.getVersion() < queryVersion) {
            //return nack & clear stale active
            clearFromServletTimerManagerCache(activeServletTimer.getId());
            result = ReplicationState.createQueryResponseFrom(queryState, true);
        } else {
            //return real response based on active ServletTimer
            try {
                result = createQueryResponseFrom(activeServletTimer);
            } catch (IOException ioe) {
                _logger.warning("Failed load: Unable to serialize " +
                                activeServletTimer);
                // We've been unable to serialize the given active
                // ServletTimer.
                // Clear it from the active cache and return a nack instead
                clearFromServletTimerManagerCache(activeServletTimer.getId());
                result = ReplicationState.createQueryResponseFrom(queryState,
                                                                  true);
            }
        }
        return result;
    }
    
    /**
     * Converts the given ServletTimer to a ReplicationState.
     *
     * @param timer The ServletTimer to be converted
     *
     * @return The ReplicationState corresponding to the given ServletTimer
     */ 
    private ReplicationState createQueryResponseFrom(
            HAServletTimer timer) throws IOException {
        return new ReplicationState(
                MODE_SIP,
                timer.getId(),
                getApplicationId(),  
                timer.getVersion(),
                0L,
                0L,
                null,
                null,
                null,
                ReplicationState.RETURN_BROADCAST_MSG_COMMAND,
                getSingletonServletTimerStore().getByteArray(timer),
                null);
    }

    protected void clearFromServletTimerManagerCache(String id) {
        ServletTimerImpl timer 
            = this.findServletTimerFromCacheOnly(id);
        if(timer != null) {                               
            this.removeServletTimerFromCache(timer);
        } 
    }    
    
    //end receiver side process methods
    
    //start expiration related methods
    
    void processExpiredReplicas() {
        //FIXME needs optimization
        //both scale of task and coupling of expiration logic
        processExpiredSipApplicationSessionReplicas();
        processExpiredSipSessionReplicas();
        processExpiredServletTimerReplicas();
    }
    
    void processExpiredSipApplicationSessionReplicas() {
        //FIXME 
        //code assumes that isExpired works for this type of ReplicationState
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processExpiredSipApplicationSessionReplicas");            
        }
        ArrayList expiredReplicas = new ArrayList(30);
        BaseCache replicasCache = this.getReplicatedSipApplicationSessions();
        for(Iterator it = replicasCache.values(); it.hasNext();) {
            ReplicationState nextState = (ReplicationState)it.next();
            if(_logger.isLoggable(Level.FINE)) {
                _logger.fine("in " + this.getClass().getName() + "nextState=" + nextState);            
            } 
            if(isExpiredSipApplicationSessionReplica(nextState)) {
                expiredReplicas.add(nextState);
            }
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("processExpiredSipApplicationSessionReplicas:expiredReplicas.size=" + expiredReplicas.size());            
        }        
        for(int i=0; i<expiredReplicas.size(); i++) {
            ReplicationState nextState = 
                (ReplicationState)expiredReplicas.get(i);
            this.removeFromSipApplicationSessionReplicationCache((String)nextState.getId());
        }
    }
    
    protected boolean isExpiredSipApplicationSessionReplica(ReplicationState state) {
        //FIXME
        return false;
    }
    
    void processExpiredSipSessionReplicas() {
        //FIXME 
        //code assumes that isExpired works for this type of ReplicationState        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processExpiredSipSessionReplicas");            
        }
        ArrayList expiredReplicas = new ArrayList(30);
        BaseCache replicasCache = this.getReplicatedSipSessions();
        for(Iterator it = replicasCache.values(); it.hasNext();) {
            ReplicationState nextState = (ReplicationState)it.next();
            if(_logger.isLoggable(Level.FINE)) {
                _logger.fine("in " + this.getClass().getName() + "nextState=" + nextState);            
            }            
            if(isExpiredSipSessionReplica(nextState)) {
                expiredReplicas.add(nextState);
            }
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("processExpiredSipSessionReplicas:expiredReplicas.size=" + expiredReplicas.size());            
        }        
        for(int i=0; i<expiredReplicas.size(); i++) {
            ReplicationState nextState = 
                (ReplicationState)expiredReplicas.get(i);
            this.removeFromSipSessionReplicationCache((String)nextState.getId());
        }
    } 
    
    protected boolean isExpiredSipSessionReplica(ReplicationState state) {
        //FIXME
        return false;
    }    
    
    void processExpiredServletTimerReplicas() {
        //FIXME 
        //code assumes that isExpired works for this type of ReplicationState        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processExpiredServletTimerReplicas");            
        }
        ArrayList expiredReplicas = new ArrayList(30);
        BaseCache replicasCache = this.getReplicatedServletTimers();
        for(Iterator it = replicasCache.values(); it.hasNext();) {
            ReplicationState nextState = (ReplicationState)it.next();
            if(_logger.isLoggable(Level.FINE)) {
                _logger.fine("in " + this.getClass().getName() + "nextState=" + nextState);            
            }            
            if(isExpiredServletTimerReplica(nextState)) {
                expiredReplicas.add(nextState);
            }
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("processExpiredServletTimerReplicas:expiredReplicas.size=" + expiredReplicas.size());            
        }        
        for(int i=0; i<expiredReplicas.size(); i++) {
            ReplicationState nextState = 
                (ReplicationState)expiredReplicas.get(i);
            this.removeFromServletTimerReplicationCache((String)nextState.getId());
        }
    } 
    
    protected boolean isExpiredServletTimerReplica(ReplicationState state) {
        //FIXME
        return false;
    }    
    
    //start expiration related methods

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }    

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    
    public StorePool getSipApplicationSessionStorePool() {
        return sipApplicationSessionStorePool;
    }
    
    public void setSipApplicationSessionStorePool(StorePool sipApplicationSessionStorePool) {
        this.sipApplicationSessionStorePool = sipApplicationSessionStorePool;
    }
    
    public StorePool getSipSessionStorePool() {
        return sipSessionStorePool;
    }
    
    public void setSipSessionStorePool(StorePool sipSessionStorePool) {
        this.sipSessionStorePool = sipSessionStorePool;
    }           

    public StorePool getServletTimerStorePool() {
        return servletTimerStorePool;
    }
    
    public void setServletTimerStorePool(StorePool servletTimerStorePool) {
        this.servletTimerStorePool = servletTimerStorePool;
    }           
    
    /**
     * Creates a new SipSession.
     *
     * @param set
     * @param to
     * @param appSession
     * @param handler
     * @type type
     *
     * @return The new SipSession
     */
    protected SipSessionDialogImpl createNewSipSession(
            DialogSet set, Address to, SipApplicationSessionImpl appSession,
            String handler, Type type) {

        return getSessionFactory().createSipSession(this, set, to, appSession,
                                                    handler, type);
    }


    /**
     * Creates a new SipApplicationSession.
     *
     * @param id The id of the new SipApplicationSession
     *
     * @return The new SipApplicationSession
     */
    protected SipApplicationSessionImpl createNewSipApplicationSession(String id) {

        return getSessionFactory().createSipApplicationSession(this, id);
    }


    /**
     * Creates a new ServletTimer.
     *
     * @return The new ServletTimer
     */
    protected ServletTimerImpl createNewServletTimer(
            SipApplicationSessionImpl sas, Serializable info, long delay,
            TimerListener listener) {
        return new HAServletTimer(sas, info, delay, listener);
    }


    /**
     * Creates a new ServletTimer.
     *
     * @return The new ServletTimer
     */
    protected ServletTimerImpl createNewServletTimer(
            SipApplicationSessionImpl sas, Serializable info, long delay,
            boolean fixedDelay, long period, TimerListener listener) {
        return new HAServletTimer(sas, info, delay, fixedDelay, period,
                                  listener);
    }


    /**
     * Gets the persistence type of this session manager.
     */
    public String getPersistenceType() {
        return EEPersistenceTypeResolver.REPLICATED_TYPE;
    }
    
    //begin send-side methods
    
    /**
     * Persists the given SipApplicationSession.
     *
     * @param sas The SipApplicationSession to be persisted
     */
    public void saveSipApplicationSession(SipApplicationSessionImpl sas)
            throws IOException {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "saveSipApplicationSession");
        }        
        SipApplicationSessionStoreImpl store = null;
        try {
            store = getSipApplicationSessionStore();            
            store.save(sas);
        }
        finally {
            this.putSipApplicationSessionStore(store);
        }                 
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "saveSipApplicationSession");
        }
    } 
    
    /**
     * Removes the given SipApplicationSession from both the active cache
     * and the persistent session store of this session manager,
     *
     * @param sas The SipApplicationSession to be removed
     */
    public void removeSipApplicationSession(SipApplicationSessionImpl sas) {
        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "removeSipApplicationSession");
        } 
        super.removeSipApplicationSessionFromCache(sas);
        SipApplicationSessionStoreImpl store = null;
        try {
            store = getSipApplicationSessionStore();            
            store.remove(sas.getId());
        } catch (IOException ex) {
            //FIXME
        } finally {
            this.putSipApplicationSessionStore(store);
        }                 
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "removeSipApplicationSession");
        }
    } 
    
    /**
     * Look for a SipApplicationSession in the Store and, if found, restore
     * it in the Manager's list of active sessions if appropriate.
     * The session will be removed from the Store after swapping
     * in, but will not be added to the active session list if it
     * is invalid or past its expiration.
     * @param id the id of the SAS
     * @param version - the version of the SAS
     */
    protected SipApplicationSessionImpl swapInSipApplicationSession(String id, String version) throws IOException {
        SipApplicationSessionStoreImpl store = null;
        try {
            store = this.getSipApplicationSessionStore();
            if (store == null)
                return null;

            SipApplicationSessionImpl session = null;
            try {
                if (SecurityUtil.isPackageProtectionEnabled()){
                    try{
                        session = (SipApplicationSessionImpl) AccessController.doPrivileged(new PrivilegedStoreLoadSipApplicationSession(id, version, store));
                    }catch(PrivilegedActionException ex){
                        Exception exception = ex.getException();
                        if (exception instanceof IOException){
                            throw (IOException)exception;
                        } else if (exception instanceof ClassNotFoundException) {
                            throw (ClassNotFoundException)exception;
                        }
                    }
                } else {
                    if (version != null) {
                         session = store.load(id, version);
                    } else {
                         session = store.load(id);
                    }
                }   
            } catch (ClassNotFoundException e) {
                IOException ex1 = 
                    (IOException) new IOException("Error during swapInSipApplicationSession: " + e.getMessage()).initCause(e);
                throw ex1;                 
            }

            if (session == null)
                return (null);

            if (!session.isValid()) {
                if(_logger.isLoggable(Level.INFO)) {
                    _logger.log(Level.INFO, "session swapped in is invalid or expired");
                }
                return (null);
            }
            // make sure the listeners know about it.
            //FIXME invoke listeners
            addSipApplicationSession(session);
            return (session);
        } finally {
            this.putSipApplicationSessionStore(store);
        }
    }
    
    /**
     * Persists the given SipSession.
     *
     * @param sipSession The SipSession to be persisted
     */
    public void saveSipSession(SipSessionDialogImpl sipSession)
            throws IOException {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "saveSipSession");
        }        
        SipSessionStoreImpl store = null;
        try {
            store = getSipSessionStore();            
            store.save(sipSession); 
        }
        finally {
            this.putSipSessionStore(store);
        }                 
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "saveSipSession");
        }
    }
    
    /**
     * Removes the given SipSession from both the active cache and the
     * persistent session store of this session manager,
     *
     * @param sipSession The SipSession to be removed
     */
    public void removeSipSession(SipSessionDialogImpl sipSession) {
        
        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "removeSipSession");
        }
        removeSipSessionFromCache(sipSession);
        SipSessionStoreImpl store = null;
        try {
            store = getSipSessionStore();            
            store.remove(sipSession.getId()); 
        } catch (IOException ex) {
            //FIXME
        } finally {
            this.putSipSessionStore(store);
        }                 
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "removeSipSession");
        }
    } 
    
    /**
     * Look for a SipSession in the Store and, if found, restore
     * it in the Manager's list of active sessions if appropriate.
     * The session will be removed from the Store after swapping
     * in, but will not be added to the active session list if it
     * is invalid or past its expiration.
     * @param id the id of the SipSession
     * @param version - the version of the SipSession
     */
    protected SipSessionDialogImpl swapInSipSession(String id, String version) throws IOException {
        SipSessionStoreImpl store = null;
        try {
            store = this.getSipSessionStore();
            if (store == null)
                return null;

            SipSessionDialogImpl session = null;
            try {
                if (SecurityUtil.isPackageProtectionEnabled()){
                    try{
                        session = (SipSessionDialogImpl) AccessController.doPrivileged(new PrivilegedStoreLoadSipSession(id, version, store));
                    }catch(PrivilegedActionException ex){
                        Exception exception = ex.getException();
                        if (exception instanceof IOException){
                            throw (IOException)exception;
                        } else if (exception instanceof ClassNotFoundException) {
                            throw (ClassNotFoundException)exception;
                        }
                    }
                } else {
                    if (version != null) {
                         session = store.load(id, version);
                    } else {
                         session = store.load(id);
                    }
                }   
            } catch (ClassNotFoundException e) {
                IOException ex1 = 
                    (IOException) new IOException("Error during swapInSipSession: " + e.getMessage()).initCause(e);
                throw ex1; 
            }

            if (session == null)
                return (null);

            if (!session.isValid()) {
                if(_logger.isLoggable(Level.INFO)) {
                    _logger.log(Level.INFO, "session swapped in is invalid or expired");
                }
                return (null);
            }
            // make sure the listeners know about it.
            //FIXME invoke listeners
            addSipSession(session);
            return (session);
        } finally {
            this.putSipSessionStore(store);
        }
    }    
    
    /**
     * Persists the given ServletTimer.
     *
     * @param servletTimer The ServletTimer to be persisted
     */
    public void saveServletTimer(ServletTimerImpl servletTimer)
            throws IOException {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "saveServletTimer");
        }        
        ServletTimerStoreImpl store = null;
        try {
            store = getServletTimerStore();            
            store.save(servletTimer);
        }
        finally {
            this.putServletTimerStore(store);
        }                 
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "saveServletTimer");
        }       
    }
    
    /**
     * Removes the given ServletTimer from both the active cache and the
     * persistent session store of this session manager,
     *
     * @param servletTimer The ServletTimer to be removed
     */
    public void removeServletTimer(ServletTimerImpl servletTimer) {

        removeServletTimerFromCache(servletTimer);

        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "removeServletTimer");
        }        
        ServletTimerStoreImpl store = null;
        try {
            store = getServletTimerStore();            
            store.remove(servletTimer.getId());
        } catch (IOException ex) {
            //FIXME
        } finally {
            this.putServletTimerStore(store);
        }                 
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "removeServletTimer");
        }
    }
    
    /**
     * Look for a ServletTimer in the Store and, if found, restore
     * it in the Manager's list of active sessions if appropriate.
     * The session will be removed from the Store after swapping
     * in, but will not be added to the active session list if it
     * is invalid or past its expiration.
     * @param id the id of the ServletTimer
     * @param version - the version of the ServletTimer
     */
    protected ServletTimerImpl swapInServletTimer(String id, String version) throws IOException {
        ServletTimerStoreImpl store = null;
        try {
            store = this.getServletTimerStore();
            if (store == null)
                return null;

            ServletTimerImpl timer = null;
            try {
                if (SecurityUtil.isPackageProtectionEnabled()){
                    try{
                        timer = (ServletTimerImpl) AccessController.doPrivileged(new PrivilegedStoreLoadServletTimer(id, version, store));
                    }catch(PrivilegedActionException ex){
                        Exception exception = ex.getException();
                        if (exception instanceof IOException){
                            throw (IOException)exception;
                        } else if (exception instanceof ClassNotFoundException) {
                            throw (ClassNotFoundException)exception;
                        }
                    }
                } else {
                    if (version != null) {
                         timer = store.load(id, version);
                    } else {
                         timer = store.load(id);
                    }
                }   
            } catch (ClassNotFoundException e) {
                 IOException ex1 = 
                    (IOException) new IOException("Error during swapInServletTimer: " + e.getMessage()).initCause(e);
                throw ex1; 
            }
            if (timer == null)
                return (null);

            // make sure the listeners know about it - are there listeners
            //for timers?
            //FIXME invoke listeners
            this.addServletTimer(timer);
            return (timer);
        } finally {
            this.putServletTimerStore(store);
        }
    }      
    
    /** Returns a store from the pool 
     * This method intializes the store with right parameters
     * @return returns SipApplicationSessionStoreImpl
     */
    public SipApplicationSessionStoreImpl getSipApplicationSessionStore() {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "getSipApplicationSessionStore");
        }
        SipApplicationSessionStoreImpl store = null;
        try {
            store = (SipApplicationSessionStoreImpl) sipApplicationSessionStorePool.take();
            store.setSipSessionManager(this);
            if(_logger.isLoggable(Level.FINEST)) {
                _logger.log(Level.FINEST,
                    "SipTransactionPersistentManager.getSipApplicationSessionStore returning   " + store);
            }
            return store;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "getSipApplicationSessionStore", store);
        }
        return store;
    } 
    
    /** 
     *  Returns (puts) a store back to the pool
     */
    private void putSipApplicationSessionStore(StorePoolElement store) {    
        ( (SipApplicationSessionStoreImpl) store).setSipSessionManager(null);
        if (store != null) {
            try {
                if(sipApplicationSessionStorePool != null) {
                    sipApplicationSessionStorePool.put(store);
                }
            }
            catch (InterruptedException ex1) {
                //FIXME: log this
                ex1.printStackTrace();
            }
        }
    }
    
    /** Returns the singleton store 
     * This method intializes the store with right parameters
     * @return returns SipApplicationSessionStoreImpl
     */
    public SipApplicationSessionStoreImpl getSingletonSipApplicationSessionStore() { 
        return (SipApplicationSessionStoreImpl)super.getSipApplicationSessionStore(); 
    }     
    
    /** Returns a store from the pool.
     * This method intializes the store with right parameters
     * @return returns SipSessionStoreImpl
     */
    public SipSessionStoreImpl getSipSessionStore() {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "getSipSessionStore");
        }
        SipSessionStoreImpl store = null;
        try {
            store = (SipSessionStoreImpl) sipSessionStorePool.take();
            store.setSipSessionManager(this);
            if(_logger.isLoggable(Level.FINEST)) {
                _logger.log(Level.FINEST,
                    "SipTransactionPersistentManager.getSipSessionStore returning   " + store);
            }
            return store;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "getSipSessionStore", store);
        }
        return store;
    }
    
    /** 
     *  Returns (puts) a store back to the pool
     */
    private void putSipSessionStore(StorePoolElement store) {    
        ( (SipSessionStoreImpl) store).setSipSessionManager(null);
        if (store != null) {
            try {
                if(sipSessionStorePool != null) {
                    sipSessionStorePool.put(store);
                }
            }
            catch (InterruptedException ex1) {
                //FIXME: log this
                ex1.printStackTrace();
            }
        }
    }
    
    /** Returns the singleton store 
     * This method intializes the store with right parameters
     * @return returns SipSessionStoreImpl
     */
    public SipSessionStoreImpl getSingletonSipSessionStore() { 
        return (SipSessionStoreImpl)super.getSipSessionStore();
    }    
    
    /** Returns a store from the pool 
     * This method intializes the store with right parameters
     * @return returns ServletTimerStoreImpl
     */
    public ServletTimerStoreImpl getServletTimerStore() {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.entering("SipTransactionPersistentManager", "getServletTimerStore");
        }
        ServletTimerStoreImpl store = null;
        try {
            store = (ServletTimerStoreImpl) servletTimerStorePool.take();
            store.setSipSessionManager(this);
            if(_logger.isLoggable(Level.FINEST)) {
                _logger.log(Level.FINEST,
                    "SipTransactionPersistentManager.getServletTimerStore returning   " + store);
            }
            return store;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        if(_logger.isLoggable(Level.FINE)) {
            _logger.exiting("SipTransactionPersistentManager", "getServletTimerStore", store);
        }
        return store;
    }
    
    /** 
     *  Returns (puts) a store back to the pool
     */
    private void putServletTimerStore(StorePoolElement store) {    
        ( (ServletTimerStoreImpl) store).setSipSessionManager(null);
        if (store != null) {
            try {
                if(servletTimerStorePool != null) {
                    servletTimerStorePool.put(store);
                }
            }
            catch (InterruptedException ex1) {
                //FIXME: log this
                ex1.printStackTrace();
            }
        }
    } 
    
    /** Returns the singleton store 
     * This method intializes the store with right parameters
     * @return returns ServletTimerStoreImpl
     */
    public ServletTimerStoreImpl getSingletonServletTimerStore() { 
        return (ServletTimerStoreImpl)super.getServletTimerStore();
    }     
    
    //end send-side methods
    
    //begin implement ReplicationManager
                
    public void processMessage(ReplicationState message) {
        //handle broadcast methods
        if(ReplicationState.isBroadcastState(message)) {
            processBroadcastMessage(message);
            return;
        }
        
        //handle non-void methods
        ReplicationStateQueryResponse queryResult = null;
        //do process non-void message (and cannot be response either)
        if(!message.isResponseState() && !message.isVoidMethodReturnState()) {
            //do non-void processing including sending response
            queryResult = this.doProcessQueryMessage(message);
            ReplicationState qResponse = queryResult.getState();
            if(qResponse != null) {
                //sourceInstanceName is preserved in the response
                ReplicationState response = 
                    ReplicationState.createResponseFrom(qResponse);
                if(_logger.isLoggable(Level.FINE)) {
                    _logger.fine("RepMgrBase:responseState=" + response);
                }                
                this.doSendResponse(response);
            }
            return;
        }
        //end do process non-void message        

        boolean isResponse = this.doProcessMessage(message);
        
    }
    
    //return true if message is processResponse
    public boolean doProcessMessage(ReplicationState message) {
        boolean result = false;
        String methodName = getProcessMethodName(message);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>doProcessMessageName=" + methodName);
        }
        try {
            Class myClass = this.getClass();
            myClass.getMethod(
                methodName,
                    new Class[]{ message.getClass() }).invoke(
                        this, new Object[]{ message });            
        } catch (Throwable t) {
           _logger.log(
               Level.SEVERE,
               "Unable to process replication message with methodName=" +
               methodName, t);
        } 
        if(methodName.equals("processResponse")) {
            result = true;
        }
        return result;
    }    
    
    /**
    * send the response
    *
    * @param sessionState 
    *   The replication state response
    */    
    public void doSendResponse(ReplicationState sessionState) {
               
        JxtaReplicationReceiver replicationReceiver = 
                JxtaReplicationReceiver.createInstance();
        ReplicationState resultState = 
                replicationReceiver.sendReplicationStateResponse(sessionState); 
    }        
    
    public void processBroadcastMessage(ReplicationState message) {
        ReplicationStateQueryResponse response = this.doProcessQueryMessage(message);
        boolean isResponse = response.isResponse();
        ReplicationState responseState = response.getState();
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("processBroadcastMessage:after doProcessQueryMessage:response=" + isResponse);
            _logger.fine("processBroadcastMessage:after doProcessQueryMessage:responseState=" + responseState);
            _logger.fine("processBroadcastMessage:after doProcessQueryMessage:responseStateTrunk=" + responseState.getTrunkState());
            _logger.fine("processBroadcastMessage:after doProcessQueryMessage:responseStateAttr=" + responseState.getState());
            _logger.fine("processBroadcastMessage:after doProcessQueryMessage:responseStateVer=" + responseState.getVersion());
        }

        //don't send a response to a response
        if(!isResponse) {
            //point-to-point response back to sender
            //System.out.println("processBroadcastMessage - need to send back result");
            //doSendQueryResponse(responseState, this.getInstanceName());
            doSendQueryResponse(responseState, message.getInstanceName());
        }
    }
    
    /**
    * send the response
    *
    * @param sessionState 
    *   The replication state response
    * @param instanceName  the name of the target instance
    */    
    public void doSendQueryResponse(ReplicationState sessionState, String instanceName) {
        JxtaReplicationSender replicationSender = 
                JxtaReplicationSender.createInstance();
        ReplicationState resultState = 
                replicationSender.sendReplicationStateQueryResponse(sessionState, instanceName); 
    }        
    
    //return true if message is processQueryResponse
    public ReplicationStateQueryResponse doProcessQueryMessage(ReplicationState message) {
        ReplicationState resultState = null;
        String methodName = getProcessMethodName(message);
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>doProcessQueryMessage:methodName=" + methodName);
            _logger.fine("in " + this.getClass().getName() + ">>doProcessQueryMessage:thisInstance=" + getInstanceName() + "SASEreturnInstance=" + message.getInstanceName() );
        }         
        try {
            Class myClass = this.getClass();
            resultState = (ReplicationState) myClass.getMethod(
                methodName,
                    new Class[]{ message.getClass() }).invoke(
                        this, new Object[]{ message });            
        } catch (Throwable t) {
           _logger.log(
               Level.SEVERE,
               "Unable to process replication message with methodName=" +
               methodName, t);
        } 
        boolean isResponse = methodName.equals("processBroadcastresponse");
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>doProcessQueryMessage:resultState=" + resultState);
        }        
        //System.out.println("in " + this.getClass().getName() + ">>doProcessQueryMessage:resultState=" + resultState);
        return new ReplicationStateQueryResponse(resultState, isResponse);
    }
    
    private String getProcessMethodName(ReplicationState message) {
        String command = message.getCommand();
        return "process" + camelCase(command);
    }
    
    /**
     * this method strips out all non-alpha characters; camelCases the result
     *
     * @param inputString
     */     
    private String camelCase(String inputString) {
        String strippedString = stripNonAlphas(inputString);
        String firstLetter = (strippedString.substring(0, 1)).toUpperCase();
        String remainingPart = 
            (strippedString.substring(1, strippedString.length())).toLowerCase();
        return firstLetter + remainingPart;
    }
    
    /**
     * this method strips out all non-alpha characters
     *
     * @param inputString
     */     
    private String stripNonAlphas(String inputString) {
        StringBuilder sb = new StringBuilder(inputString.length());
        for(int i=0; i<inputString.length(); i++) {
            char nextChar = inputString.charAt(i);
            if(Character.isLetter(nextChar)) {
                sb.append(nextChar);
            }
        }
        return sb.toString();
    }
    
    public void processQueryMessage(ReplicationState message, String returnInstance) {
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("in " + this.getClass().getName() + ">>processQueryMessage:returnInstance= " + returnInstance);
        }         
        ReplicationStateQueryResponse response = this.doProcessQueryMessage(message);
        boolean isResponse = response.isResponse();
        ReplicationState responseState = response.getState();
        if(_logger.isLoggable(Level.FINE)) {
            _logger.fine("processQueryMessage:after doProcessQueryMessage:response=" + isResponse);
            _logger.fine("processQueryMessage:after doProcessQueryMessage:responseState=" + responseState);            
        }        
        //don't send a response to a response
        if(!isResponse && responseState != null) {
            //point-to-point response back to sender
            if(_logger.isLoggable(Level.FINE)) {
                _logger.fine("processQueryMessage - need to send back result to " + returnInstance);            
            }             
            doSendQueryResponse(responseState, returnInstance);
        }
    }    
    
    public void repair(long repairStartTime) {
       if (!isRepairDuringFailure()) {
           return;
       }        
        //FIXME
    }     
    
    public void repair(long repairStartTime, boolean checkForStopping) {
       if (!isRepairDuringFailure()) {
           return;
       }         
        //FIXME
    } 
    
    public void respondToFailure(String failedInstanceName, boolean checkForStopping) {
        //only do timer wakeup if failedInstanceName was replicating
        //to us
        ReplicationHealthChecker healthChecker 
            = ReplicationHealthChecker.getInstance();
        String replicatedFromInstanceName 
            = healthChecker.getReshapeReplicateFromInstanceName();
        if(!failedInstanceName.equalsIgnoreCase(replicatedFromInstanceName)) {
            return;
        }
        //FIXME do the timer wakeup 
        
    }   
    
    private class ReplicationStateUpdate {

        private String _id = null;
        private long _lastAccess = 0L;
        private long _version = -1L;
        
        String getId() {
            return _id;
        }        
        
        long getLastAccess() {
            return _lastAccess;
        }
        
        long getVersion() {
            return _version;
        }       
        
        public ReplicationStateUpdate(String id, long version, long lastAccess) {
            _id = id;
            _version = version;
            _lastAccess = lastAccess;
        }
        
    } 
    
    private class PrivilegedStoreLoadSipApplicationSession
        implements PrivilegedExceptionAction {

        private String id;
        private String version;
        private SipApplicationSessionStoreImpl store;
            
        PrivilegedStoreLoadSipApplicationSession(String id, String version, SipApplicationSessionStoreImpl store) {     
            this.id = id;
            this.version = version;
            this.store = store;
        }

        public Object run() throws Exception{
           return store.load(id, version);
        }                       
    }
    
    private class PrivilegedStoreLoadSipSession
        implements PrivilegedExceptionAction {

        private String id;
        private String version;
        private SipSessionStoreImpl store;
            
        PrivilegedStoreLoadSipSession(String id, String version, SipSessionStoreImpl store) {     
            this.id = id;
            this.version = version;
            this.store = store;
        }

        public Object run() throws Exception{
           return store.load(id, version);
        }                       
    }
    
    private class PrivilegedStoreLoadServletTimer
        implements PrivilegedExceptionAction {

        private String id;
        private String version;
        private ServletTimerStoreImpl store;
            
        PrivilegedStoreLoadServletTimer(String id, String version, ServletTimerStoreImpl store) {     
            this.id = id;
            this.version = version;
            this.store = store;
        }

        public Object run() throws Exception{
           return store.load(id, version);
        }                       
    }    

}
