/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.web.tomcat.service.session.distributedcache.ispn;
import java.util.HashMap;
import java.util.Map;
import javax.transaction.TransactionManager;
import org.infinispan.Cache;
import org.infinispan.atomic.AtomicMap;
import org.infinispan.config.CacheLoaderManagerConfig;
import org.infinispan.config.Configuration;
import org.infinispan.context.Flag;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.lifecycle.ComponentStatus;
import org.infinispan.manager.CacheContainer;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryActivated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
import org.infinispan.notifications.cachelistener.event.CacheEntryActivatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
import org.infinispan.transaction.tm.BatchModeTransactionManager;
import org.jboss.ha.ispn.CacheContainerRegistry;
import org.jboss.ha.ispn.atomic.AtomicMapFactory;
import org.jboss.ha.ispn.invoker.CacheInvoker;
import org.jboss.logging.Logger;
import org.jboss.web.tomcat.service.session.distributedcache.impl.BatchingManagerImpl;
import org.jboss.web.tomcat.service.session.distributedcache.impl.IncomingDistributableSessionDataImpl;
import org.jboss.web.tomcat.service.session.distributedcache.spi.BatchingManager;
import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata;
import org.jboss.web.tomcat.service.session.distributedcache.spi.IncomingDistributableSessionData;
import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager;
import org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingDistributableSessionData;
import org.jboss.web.tomcat.service.session.distributedcache.spi.SessionOwnershipSupport;
/**
* Distributed cache manager implementation using Infinispan.
* @author Paul Ferraro
*/
@Listener
public class DistributedCacheManager<T extends OutgoingDistributableSessionData> implements org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager<T>
{
static String mask(String sessionId)
{
if (sessionId == null) return null;
int length = sessionId.length();
if (length <= 8) return sessionId;
return sessionId.substring(0, 2) + "****" + sessionId.substring(length - 6, length);
}
static RuntimeException getRuntimeException(String message, Exception e)
{
if (e instanceof RuntimeException) return (RuntimeException) e;
return new RuntimeException(message != null ? message : e.toString(), e);
}
private static final Logger log = Logger.getLogger(DistributedCacheManager.class);
private final LocalDistributableSessionManager manager;
private final SessionAttributeStorage<T> attributeStorage;
private final CacheInvoker invoker;
private final CacheContainerRegistry registry;
final AtomicMapFactory atomicMapFactory;
private Cache<String, AtomicMap<Object, Object>> cache;
private BatchingManagerImpl batchingManager;
private boolean passivationEnabled = false;
public DistributedCacheManager(LocalDistributableSessionManager manager, CacheContainerRegistry registry, SessionAttributeStorage<T> attributeStorage, CacheInvoker invoker, AtomicMapFactory atomicMapFactory)
{
this.manager = manager;
this.registry = registry;
this.attributeStorage = attributeStorage;
this.invoker = invoker;
this.atomicMapFactory = atomicMapFactory;
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#start()
*/
@Override
public void start()
{
String templateCacheName = this.manager.getReplicationConfig().getCacheName();
String containerName = null;
if (templateCacheName != null)
{
String[] parts = templateCacheName.split(":");
if (parts.length == 2)
{
containerName = parts[0];
templateCacheName = parts[1];
}
}
CacheContainer container = this.registry.getCacheContainer(containerName);
String hostName = this.manager.getHostName();
String host = (hostName == null) || hostName.isEmpty() ? "localhost" : hostName;
String context = this.manager.getContextName();
String path = context.isEmpty() || context.equals("/") ? "ROOT" : context.startsWith("/") ? context.substring(1) : context;
String cacheName = host + "/" + path;
EmbeddedCacheManager manager = (EmbeddedCacheManager) container.getCache().getCacheManager();
manager.defineConfiguration(cacheName, templateCacheName, new Configuration());
this.cache = manager.getCache(cacheName);
if (this.cache.getStatus() != ComponentStatus.RUNNING)
{
this.cache.start();
}
TransactionManager tm = this.cache.getAdvancedCache().getTransactionManager();
if (!(tm instanceof BatchModeTransactionManager))
{
throw new IllegalStateException("Unexpected transaction manager type: " + ((tm != null) ? tm.getClass().getName() : "null"));
}
this.batchingManager = new BatchingManagerImpl(tm);
this.cache.addListener(this);
CacheLoaderManagerConfig loaderManagerConfig = this.cache.getConfiguration().getCacheLoaderManagerConfig();
this.passivationEnabled = (loaderManagerConfig != null) ? loaderManagerConfig.isPassivation().booleanValue() && !loaderManagerConfig.isShared().booleanValue() : false;
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#stop()
*/
@Override
public void stop()
{
this.cache.removeListener(this);
this.cache.stop();
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#getBatchingManager()
*/
@Override
public BatchingManager getBatchingManager()
{
return this.batchingManager;
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#sessionCreated(java.lang.String)
*/
@Override
public void sessionCreated(String sessionId)
{
// Do nothing
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#storeSessionData(org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingDistributableSessionData)
*/
@Override
public void storeSessionData(T sessionData)
{
final String sessionId = sessionData.getRealId();
if (log.isTraceEnabled())
{
log.trace("putSession(): putting session " + mask(sessionId));
}
Operation<AtomicMap<Object, Object>> operation = new Operation<AtomicMap<Object, Object>>()
{
@Override
public AtomicMap<Object, Object> invoke(Cache<String, AtomicMap<Object, Object>> cache)
{
return DistributedCacheManager.this.atomicMapFactory.getAtomicMap(cache, sessionId, true);
}
};
AtomicMap<Object, Object> data = this.invoker.invoke(this.cache, operation);
AtomicMapEntry.VERSION.put(data, Integer.valueOf(sessionData.getVersion()));
AtomicMapEntry.METADATA.put(data, sessionData.getMetadata());
AtomicMapEntry.TIMESTAMP.put(data, sessionData.getTimestamp());
this.attributeStorage.store(data, sessionData);
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#getSessionData(java.lang.String, boolean)
*/
@Override
public IncomingDistributableSessionData getSessionData(String sessionId, boolean initialLoad)
{
return this.getData(sessionId, true);
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#getSessionData(java.lang.String, java.lang.String, boolean)
*/
@Override
public IncomingDistributableSessionData getSessionData(final String sessionId, String dataOwner, boolean includeAttributes)
{
return (dataOwner == null) ? this.getData(sessionId, includeAttributes) : null;
}
private IncomingDistributableSessionData getData(final String sessionId, boolean includeAttributes)
{
Operation<AtomicMap<Object, Object>> operation = new Operation<AtomicMap<Object, Object>>()
{
@Override
public AtomicMap<Object, Object> invoke(Cache<String, AtomicMap<Object, Object>> cache)
{
return DistributedCacheManager.this.atomicMapFactory.getAtomicMap(cache, sessionId, false);
}
};
AtomicMap<Object, Object> data = this.invoker.invoke(this.cache, operation);
// If requested session is no longer in the cache; return null
if (data == null) return null;
try
{
Integer version = AtomicMapEntry.VERSION.get(data);
Long timestamp = AtomicMapEntry.TIMESTAMP.get(data);
DistributableSessionMetadata metadata = AtomicMapEntry.METADATA.get(data);
IncomingDistributableSessionDataImpl result = new IncomingDistributableSessionDataImpl(version, timestamp, metadata);
if (includeAttributes)
{
try
{
result.setSessionAttributes(this.attributeStorage.load(data));
}
catch (Exception e)
{
throw getRuntimeException("Failed to load session attributes for session: " + mask(sessionId), e);
}
}
return result;
}
catch (Exception e)
{
String message = String.format("Problem accessing session [%s]: %s", mask(sessionId), e.toString());
log.warn(message);
log.debug(message, e);
// Clean up
this.removeSessionLocal(sessionId);
return null;
}
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#removeSession(java.lang.String)
*/
@Override
public void removeSession(final String sessionId)
{
this.removeSession(sessionId, false);
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#removeSessionLocal(java.lang.String)
*/
@Override
public void removeSessionLocal(final String sessionId)
{
this.removeSession(sessionId, true);
}
private void removeSession(final String sessionId, final boolean local)
{
Operation<AtomicMap<Object, Object>> operation = new Operation<AtomicMap<Object, Object>>()
{
@Override
public AtomicMap<Object, Object> invoke(Cache<String, AtomicMap<Object, Object>> cache)
{
if (local)
{
cache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL);
}
return cache.remove(sessionId);
}
};
this.invoker.invoke(this.cache, operation);
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#removeSessionLocal(java.lang.String, java.lang.String)
*/
@Override
public void removeSessionLocal(String sessionId, String dataOwner)
{
if (dataOwner == null)
{
this.removeSession(sessionId, true);
}
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#evictSession(java.lang.String)
*/
@Override
public void evictSession(final String sessionId)
{
Operation<Void> operation = new Operation<Void>()
{
@Override
public Void invoke(Cache<String, AtomicMap<Object, Object>> cache)
{
cache.evict(sessionId);
return null;
}
};
this.invoker.invoke(this.cache, operation);
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#evictSession(java.lang.String, java.lang.String)
*/
@Override
public void evictSession(String sessionId, String dataOwner)
{
if (dataOwner == null)
{
this.evictSession(sessionId);
}
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#getSessionIds()
*/
@Override
public Map<String, String> getSessionIds()
{
Map<String, String> result = new HashMap<String, String>();
for (String sessionId: this.cache.keySet())
{
result.put(sessionId, null);
}
return result;
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#isPassivationEnabled()
*/
@Override
public boolean isPassivationEnabled()
{
return this.passivationEnabled;
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#setForceSynchronous(boolean)
*/
@Override
public void setForceSynchronous(boolean forceSynchronous)
{
this.invoker.setForceSynchronous(forceSynchronous);
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#getSessionOwnershipSupport()
*/
@Override
public SessionOwnershipSupport getSessionOwnershipSupport()
{
return null;
}
/**
* {@inheritDoc}
* @see org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManager#isLocal(java.lang.String)
*/
@Override
public boolean isLocal(String realId)
{
DistributionManager manager = this.cache.getAdvancedCache().getDistributionManager();
return (manager != null) ? manager.isLocal(realId) : true;
}
@CacheEntryRemoved
public void removed(CacheEntryRemovedEvent event)
{
if (event.isPre() || event.isOriginLocal()) return;
this.manager.notifyRemoteInvalidation((String) event.getKey());
}
@CacheEntryModified
public void modified(CacheEntryModifiedEvent event)
{
if (event.isPre() || event.isOriginLocal()) return;
String sessionId = (String) event.getKey();
@SuppressWarnings("unchecked")
AtomicMap<Object, Object> data = (AtomicMap<Object, Object>) event.getValue();
Integer version = AtomicMapEntry.VERSION.get(data);
Long timestamp = AtomicMapEntry.TIMESTAMP.get(data);
DistributableSessionMetadata metadata = AtomicMapEntry.METADATA.get(data);
if (timestamp == null)
{
log.warn(String.format("No timestamp attribute found in node modification event for session %s", mask(sessionId)));
return;
}
boolean updated = this.manager.sessionChangedInDistributedCache(sessionId, null, version.intValue(), timestamp.longValue(), metadata);
if (!updated)
{
log.warn(String.format("Possible concurrency problem: Replicated version id %d is less than or equal to in-memory version for session %s", version, mask(sessionId)));
}
}
@CacheEntryActivated
public void activated(CacheEntryActivatedEvent event)
{
if (event.isPre()) return;
if (this.manager.isPassivationEnabled())
{
this.manager.sessionActivated();
}
}
// Simplified CacheInvoker.Operation using assigned key/value types
static interface Operation<R> extends CacheInvoker.Operation<String, AtomicMap<Object, Object>, R>
{
}
}