/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, 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/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.jvnet.glassfish.comms.replication.sessmgmt;
import com.ericsson.ssa.sip.RemoteLockException;
import com.ericsson.ssa.sip.SipApplicationSessionUtil;
import com.ericsson.ssa.sip.SipSessionDialogImpl;
import com.ericsson.ssa.sip.SipSessionManager;
import com.ericsson.ssa.sip.SipSessionStore;
import com.sun.appserv.ha.spi.BackingStore;
import com.sun.appserv.ha.spi.BackingStoreException;
import com.sun.appserv.ha.util.SimpleMetadata;
import com.sun.appserv.ha.util.SimpleMetadataFactory;
import com.sun.appserv.ha.uow.ReplicableEntity;
import com.sun.appserv.util.cache.BaseCache;
import com.sun.enterprise.web.ServerConfigLookup;
import com.sun.enterprise.ee.web.sessmgmt.ExpatListElement;
import com.sun.enterprise.ee.web.sessmgmt.JxtaBackingStoreImpl;
import com.sun.enterprise.ee.web.sessmgmt.JxtaReplicationSender;
import com.sun.enterprise.ee.web.sessmgmt.ReplicationUtil;
import com.sun.enterprise.ee.web.sessmgmt.ReplicationState;
import com.sun.enterprise.ee.web.sessmgmt.StorePoolElement;
import com.sun.enterprise.ee.web.sessmgmt.SessionStoreInterface;
import java.util.logging.Level;
import java.util.zip.GZIPInputStream;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.io.*;
import org.apache.catalina.*;
import org.apache.catalina.session.IOUtilsCaller;
/**
*
* @author Larry White
*/
public class SipSessionStoreImpl extends SipStoreBase
implements StorePoolElement, SessionStoreInterface, SipSessionStore {
public final static String BEKEY
= ReplicationState.BEKEY;
private static boolean useReplicationUnicastLoadBatching = false;
static {
ServerConfigLookup lookup = new ServerConfigLookup();
useReplicationUnicastLoadBatching
= lookup.isReplicationUnicastLoadBatchingEnabled();
}
/** Creates a new instance of SipSessionStoreImpl */
public SipSessionStoreImpl() {
}
/**
* gets the BackingStore used for replicating SipSessions
*
* @return the BackingStore initialized for SipSessions
*
*/
protected BackingStore getBackingStore() {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)this.getSipSessionManager();
return mgr.getSipSessionBackingStore();
}
/**
* Loads and returns the SipSession with the given id from this session
* store.
*
* @param id The id of the SipSession to load
*
* @return The SipSession with the given id, or null if not found
*
* @throws IOException
* @throws RemoteLockException
*/
public SipSessionDialogImpl load(String id, boolean loadDependencies)
throws IOException, RemoteLockException {
return load(id, null, loadDependencies);
}
public SipSessionDialogImpl load(String id, String version,
boolean loadDependencies)
throws IOException, RemoteLockException {
try {
ExpatListElement expat = this.getExpatListElementFor(id);
if (expat != null && expat.isFromActive()) {
/**
* Irrespective of the backing store implementation, if the session
* is activated in the remote instance, then we should load it from there.
*/
return loadFromRemoteActiveCache(id, expat, loadDependencies);
} else {
return loadFromBackingStore(id, version, loadDependencies);
}
} catch(BackingStoreException ex) {
IOException ex1 =
(IOException) new IOException("Error during load: " + ex.getMessage()).initCause(ex);
throw ex1;
}
}
/**
* This will use the Jxta channel to load the session from remote instance's
* active cache.
*/
private HASipSession loadFromRemoteActiveCache(String id,
ExpatListElement expat,
boolean loadDependecies)
throws IOException, BackingStoreException, RemoteLockException {
/**
* For the in-memory implementation, the restarted instance might
* have the session in its replica cache, if the load-factor is used.
*/
BaseCache replicaCache = getReplicaCache();
if(replicaCache != null) {
replicaCache.remove(id);
}
ReplicationState sessionState = loadSessionFromRemoteActiveCache(
id, String.valueOf(expat.getVersion()), expat.getInstanceName());
if (_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>loadFromActiveCache:id="
+ id + ", sessionState=" + sessionState);
}
HASipSession session = getSipSession(sessionState);
validateAndSave(session, loadDependecies);
return session;
}
private HASipSession loadFromBackingStore(String id, String version, boolean loadDependencies)
throws IOException, BackingStoreException, RemoteLockException {
SimpleMetadata metaData = (SimpleMetadata) getBackingStore().load(id, version);
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>loadFromBackingStore:id=" +
id + ", metaData=" + metaData);
}
HASipSession session = getSipSession(id, metaData);
validateAndSave(session, loadDependencies);
return session;
}
private HASipSession validateAndSave(HASipSession session, boolean loadDependencies)
throws IOException, RemoteLockException {
if(session != null) {
boolean isLoadedFromRemote =
__removeFromRemotelyLoadedSessionIds(session.getId());
if (session.isValid()) {
// For SipSession, this sending of load received ack
// has been postponed to this point so we can check the
// deserialized session to see if it was locked on the remote
// side (this is done inside validate(), which may throw a
// RemoteLockedException).
// For most other artifacts the load received ack is done
// immediately in JxtaBackingStoreImpl
session.validate(loadDependencies);
if(isLoadedFromRemote) { // no need to do immediate save if it is loaded from third party backing store.
if (_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>validateAndSave saving " +
"the sip session after loading it. SipSession=" + session);
}
//save session - save will reset dirty to false
session.setDirty(true, false);
save(session);
}
}
}
return session;
}
/**
* Loads and returns the SipSession with the given id from this session
* store.
*
* @param id The id of the SipSession to load
* @param version the version of the SipSession to load
*
* @return The SipSession with the given id, or null if not found
*
* @throws IOException
* @throws RemoteLockException
*/
public SimpleMetadata __load(String id, String version)
throws BackingStoreException {
SimpleMetadata result = null;
if(id == null) {
return result;
}
SipTransactionPersistentManager repMgr
= (SipTransactionPersistentManager)this.getSipSessionManager();
ReplicationState localCachedState
= repMgr.removeFromSipSessionReplicationCache(id);
//check if we got a hit from our own replica cache
//and check if we can trust it. If so save and return it immediately
boolean trustCachedState = canTrustLocallyCachedState(id, version, localCachedState);
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>__load:id= " + id + ", localCachedState=" +
localCachedState + ", trustCachedState=" + trustCachedState);
}
ReplicationState bestState = null;
if(trustCachedState) {
bestState = localCachedState;
} else {
ReplicationState broadcastResultState =
findSessionViaBroadcastOrUnicast(id, version);
bestState = ReplicationState.getBestResult(localCachedState, broadcastResultState);
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>__load:id=" + id + ", broadcastResultState " +
"from broadcast or unicast=" + broadcastResultState + ", bestState = " + bestState);
}
}
if(bestState != null && bestState.getState() != null) {
__addToRemotelyLoadedSessionIds(id);
}
result = ReplicationState.createSimpleMetadataFrom(
bestState, isReplicationCompressionEnabled());
return result;
}
protected ExpatListElement getExpatListElementFor(String id) {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
if(mgr.isExpectingExpatIdsMap()) {
//in this period do not trust expatIdsMap
return null;
} else {
return mgr.getSipSessionExpatListElement(id);
}
}
protected ExpatListElement removeExpatListElementFor(String id) {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
return mgr.removeSipSessionExpatListElement(id);
}
private ReplicationState findSessionViaBroadcastOrUnicast(String id, String version)
throws BackingStoreException {
try {
ExpatListElement expat = this.getExpatListElementFor(id);
if(expat == null || expat.getInstanceName() == null) {
return findSessionViaBroadcast(id, version);
} else {
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("doing unicast load id = " + id + " version = " + version + "to instance: " + expat.getInstanceName());
}
return findSessionViaUnicast(id, version, expat.getInstanceName());
}
} finally {
removeExpatListElementFor(id);
}
}
private ReplicationState findSessionViaBroadcast(String id, String version)
throws BackingStoreException {
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>findSessionViaBroadcast");
}
BackingStore replicator = this.getBackingStore();
JxtaBackingStoreImpl jxtaReplicator = null;
if(replicator instanceof JxtaBackingStoreImpl) {
jxtaReplicator = (JxtaBackingStoreImpl)replicator;
}
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>findSessionViaBroadcast: replicator: " + replicator);
}
ReplicationState queryResult = jxtaReplicator != null ?
jxtaReplicator.__load(id, version) : null;
return queryResult;
}
private ReplicationState loadSessionFromRemoteActiveCache(String id,
String version,
String instanceName)
throws BackingStoreException {
ReplicationState returnState = findSessionViaUnicast(id, version, instanceName);
if (returnState != null && returnState.getState() != null) {
__addToRemotelyLoadedSessionIds(id);
removeExpatListElementFor(id);
}
return returnState;
}
private ReplicationState findSessionViaUnicast(String id, String version, String instanceName)
throws BackingStoreException {
if(ReplicationUtil.getInstanceName().equalsIgnoreCase(instanceName)) {
return null;
}
return sendUnicastLoadQuery(id, version, instanceName);
}
/**
* Given a byte[] containing session data, return a session
* object
* if isReplicationCompressionEnabled() returns true the
* input byte[] will be assumed compressed and treated
* accordingly
*
* @param replicationState
* The byte[] with the session data
*
* @return
* A newly created SipSession for the given session data, and associated
* with this Manager
*/
protected HASipSession getSipSession(ReplicationState replicationState)
throws IOException {
if (replicationState == null || replicationState.getState() == null) {
return null;
} else {
try {
return getSipSession((String) replicationState.getId(),
replicationState.getState(),
replicationState.getVersion(),
replicationState.getContainerExtraParamsState(), null);
} catch(RuntimeException ex) {
throw new RuntimeException("ss id = " + replicationState.getId(), ex);
}
}
}
protected HASipSession getSipSession(String id, SimpleMetadata metadata)
throws IOException {
if (metadata == null || metadata.getState() == null) {
return null;
} else {
return getSipSession(id, metadata.getState(),
metadata.getVersion(), null,
(SipSessionExtraParams) metadata.getExtraParam());
}
}
protected HASipSession getSipSession(String id, byte[] state, long version, byte[] extraParamState, SipSessionExtraParams extraParams)
throws IOException
{
HASipSession _session = null;
InputStream is = null;
BufferedInputStream bis = null;
ByteArrayInputStream bais = null;
Loader loader = null;
ClassLoader classLoader = null;
ObjectInputStream ois = null;
SipSessionManager manager
= this.getSipSessionManager();
Container container = manager.getContext();
IOUtilsCaller utilsCaller = null;
bais = new ByteArrayInputStream(state);
bis = new BufferedInputStream(bais);
if(isReplicationCompressionEnabled()) {
is = new GZIPInputStream(bis);
} else {
is = bis;
}
if(_logger.isLoggable(Level.FINEST)) {
_logger.finest("loaded session from replicationstore, length = "+state.length);
}
if (container != null) {
loader = container.getLoader();
}
if (loader != null) {
classLoader = loader.getClassLoader();
}
if (classLoader != null) {
if( (utilsCaller = ReplicationUtil.getWebUtilsCaller()) != null) {
try {
ois = utilsCaller.createObjectInputStream(is, true, classLoader);
} catch (Exception ex) {}
}
}
if (ois == null) {
ois = new ObjectInputStream(is);
}
if(ois != null) {
try {
_session = (HASipSession)ois.readObject();
} catch (ClassNotFoundException ex) {
IOException ex1 = (IOException) new IOException(
"Error during deserialization: " + ex.getMessage()).initCause(ex);
throw ex1;
} finally {
if (ois != null) {
try {
ois.close();
bis = null;
}
catch (IOException e) {
}
}
}
}
_session.setId(id);
_session.setSipSessionManager(manager);
_session.update(extraParamState, extraParams);
//currentOwnerInstanceName is in the manager already
_session.setInternalLastAccessedTime(System.currentTimeMillis());
_session.setVersion(version);
_session.setDirty(false);
_session.setReplicated(false);
return _session;
}
/**
* Saves the given SipSession to this store.
*
* If a SipSession with the same id already exists in this session
* store, it will be replaced.
*
* @param session The SipSession to be saved
*
* @exception IOException
*/
public void save(SipSessionDialogImpl session) throws IOException {
HASipSession haSession = (HASipSession)session;
long previousVersion = haSession.getVersion();
haSession.incrementVersion();
haSession.setInternalLastAccessedTime(System.currentTimeMillis());
try {
if( haSession.isReplicated() && !haSession.isDirty() ) {
this.updateContainerExtraParam(haSession);
} else {
haSession.setIsBeingReplicated(true);
this.doSave(haSession);
haSession.setReplicated(true);
}
haSession.setDirty(false);
} catch (IOException ex) {
haSession.setVersion(previousVersion);
throw ex;
} finally {
haSession.setIsBeingReplicated(false);
}
}
boolean isReplicationCompressionEnabled() {
SipTransactionPersistentManager repMgr
= (SipTransactionPersistentManager)this.getSipSessionManager();
return repMgr.isReplicationCompressionEnabled();
}
/**
* Saves the given SipSession to this store.
*
* If a SipSession with the same id already exists in this session
* store, it will be replaced.
*
* @param haSession The SipSession to be saved
*
* @exception IOException
*/
public void doSave(HASipSession haSession) throws IOException {
byte[] sessionState = ReplicationUtil.getByteArray(haSession, isReplicationCompressionEnabled());
BackingStore replicator = this.getBackingStore();
SimpleMetadata simpleMetadata =
SimpleMetadataFactory.createSimpleMetadata(
haSession.getVersion(),
haSession.getInternalLastAccessedTime(),
0L, //maxinactiveinterval
sessionState,
haSession.getExtraParameters() //containerExtraParam
);
if(haSession.getParentSASId() != null) {
String beKey = SipApplicationSessionUtil.getSipApplicationKey(haSession.getParentSASId());
simpleMetadata.setBeKey(beKey);
simpleMetadata.setOwningInstanceName(ReplicationUtil.getInstanceName());
}
try {
replicator.save(haSession.getId(), //id
simpleMetadata, haSession.isReplicated());
} catch (BackingStoreException ex) {
IOException ex1 =
(IOException) new IOException("Error during save: " + ex.getMessage()).initCause(ex);
throw ex1;
}
}
public ReplicationState getTransmitState(HASipSession haSession) throws IOException {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)this.getSipSessionManager();
BackingStore replicator = mgr.getSipSessionBackingStore();
if(!(replicator instanceof JxtaBackingStoreImpl)) {
return null;
}
JxtaBackingStoreImpl jxtaReplicator = (JxtaBackingStoreImpl)replicator;
ReplicationState transmitState = null;
byte[] sessionState = ReplicationUtil.getByteArray(haSession, isReplicationCompressionEnabled());
SimpleMetadata simpleMetadata =
SimpleMetadataFactory.createSimpleMetadata(
haSession.getVersion(),
haSession.getInternalLastAccessedTime(),
0L, //maxinactiveinterval
sessionState,
haSession.getExtraParameters() //containerExtraParam
);
if(haSession.getParentSASId() != null) {
String beKey = SipApplicationSessionUtil.getSipApplicationKey(haSession.getParentSASId());
simpleMetadata.setBeKey(beKey);
simpleMetadata.setOwningInstanceName(ReplicationUtil.getInstanceName());
}
try {
transmitState = jxtaReplicator.getSimpleTransmitState(haSession.getId(), simpleMetadata);
} catch (BackingStoreException ex) {}
return transmitState;
}
/**
* Removes the SipSession with the given id from this store.
*
* @param id The id of the SipSession to be removed
*
* @exception IOException
*/
public void remove(String id) throws IOException {
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>remove" + " id: " + id);
}
BackingStore replicator = this.getBackingStore();
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>remove: replicator: " + replicator);
}
try {
replicator.remove(id);
} catch (BackingStoreException ex) {
IOException ex1 =
(IOException) new IOException("Error during remove: " + ex.getMessage()).initCause(ex);
throw ex1;
}
}
void sendUnicastLoadAcknowledgement(String id, String instanceName)
throws IOException {
//send load received ack to instanceName
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
String theCommand = mgr.MESSAGE_BROADCAST_LOAD_RECEIVED_SIP_SESSION;
ReplicationState loadReceivedState =
ReplicationState.createBroadcastLoadReceivedState(MODE_SIP, id, this.getApplicationId(), 0L, mgr.getInstanceName(), theCommand);
JxtaReplicationSender sender
= JxtaReplicationSender.createInstance();
sender.sendOverPropagatedPipe(loadReceivedState, instanceName, false);
//if we want to batch unicast load acks use next line
//sender.sendReplicationReceivedState(loadReceivedState, instanceName);
}
ReplicationState sendUnicastLoadQuery(String id, String version, String instanceName) {
//send load query to instanceName via unicast
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
String theCommand = mgr.LOAD_SIP_SESSION_COMMAND;
ReplicationState loadState
= ReplicationState.createUnicastLoadState(MODE_SIP, id, this.getApplicationId(), ReplicationUtil.parseLong(version), mgr.getInstanceName(), theCommand);
JxtaReplicationSender sender
= JxtaReplicationSender.createInstance();
ReplicationState returnState
= sender.sendReplicationLoadState(loadState, instanceName, useReplicationUnicastLoadBatching);
return returnState;
}
/**
* update the lastaccess time of the specified SipSession into this Store.
*
* @param sipSession SipSessionDialogImpl to be saved
*
* @exception IOException if an input/output error occurs
*/
public void updateContainerExtraParam(HASipSession sipSession)
throws IOException {
BackingStore replicator = this.getBackingStore();
JxtaBackingStoreImpl jxtaReplicator = null;
if(replicator instanceof JxtaBackingStoreImpl) {
jxtaReplicator = (JxtaBackingStoreImpl)replicator;
}
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>updateContainerExtraParam: replicator: " + replicator);
}
try {
SimpleMetadata smd = SimpleMetadataFactory.createSimpleMetadata(
sipSession.getInternalLastAccessedTime(), //internallastaccesstime
sipSession.getVersion(), //version
sipSession.getExtraParameters());
replicator.save(sipSession.getId(), smd, sipSession.isReplicated()); //containerExtraParams
} catch (BackingStoreException ex) {
IOException ex1 =
(IOException) new IOException("Error during updateContainerExtraParam: " + ex.getMessage()).initCause(ex);
throw ex1;
}
}
/**
* Removes all SipSessions from this store.
*/
public void clear() throws IOException {
//FIXME
}
/**
* clean up resources
*/
public void cleanup() {
//FIXME
}
/**
* Removes any expired SipSessions from this store.
*
* This method is invoked by the background reaper thread.
*/
public void processExpires() {
SipTransactionPersistentManager replicationMgr =
(SipTransactionPersistentManager) this.getSipSessionManager();
// if(!(replicationMgr.isThirdPartyBackingStoreInUse())) {
// replicationMgr.processExpiredSipSessionReplicas();
// } else {
// replicationMgr.processExpiredSipSessionReplicasThirdPartySPI();
// //removeExpiredSessions();
// }
replicationMgr._processExpiredSipSessions();
}
/** This method deletes all the sip
* sessions corresponding to the "appId" that should be expired
* @return number of removed sessions
*/
public int removeExpiredSessions() {
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("IN SipSessionStoreImpl>>removeExpiredSessions");
}
int result = 0;
BackingStore replicator = getBackingStore();
try {
result = replicator.removeExpired();
} catch (BackingStoreException ex) {
_logger.log(Level.WARNING,
"unable to remove expired sip session replicas", ex);
}
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("SipSessionStoreImpl>>removeExpiredSessions():number of expired sessions = " + result);
}
return result;
}
// find the session in local active cache, passivate it and remove from local cache.
public ReplicableEntity findSessionAndPassivate(String id) {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
return mgr.findSipSessionAndPassivate(id, new AtomicBoolean());
}
// get the active cache sessions.
public Map<String, ReplicableEntity> getActiveCache() {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
return mgr.getActiveSipSessions();
}
// put the state in the local replica cache.
public void putInReplicaCache(ReplicationState state) {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
mgr.putInSipSessionReplicationCache(state);
}
// remove the entry from replica cache with given id.
public void removeFromReplicaCache(String id) {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
mgr.removeFromSipSessionReplicationCache(id);
}
public BaseCache getReplicaCache() {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
return mgr.getReplicatedSipSessions();
}
public ReplicableEntity deserialize(ReplicationState state) {
try {
return getSipSession(state);
} catch (IOException ex) {
return null;
}
}
public void activate(ReplicableEntity session) {
SipTransactionPersistentManager mgr
= (SipTransactionPersistentManager)getSipSessionManager();
mgr.activate((HASipSession)session, true);
}
}