/*
* Copyright 2010 Martin Grotzke
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package de.javakaffee.web.msm;
import static de.javakaffee.web.msm.Statistics.StatsType.ATTRIBUTES_SERIALIZATION;
import static de.javakaffee.web.msm.Statistics.StatsType.CACHED_DATA_SIZE;
import static de.javakaffee.web.msm.Statistics.StatsType.LOAD_FROM_MEMCACHED;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import de.javakaffee.web.msm.BackupSessionService.SimpleFuture;
import de.javakaffee.web.msm.BackupSessionTask.BackupResult;
import de.javakaffee.web.msm.MemcachedNodesManager.StorageClientCallback;
import de.javakaffee.web.msm.storage.StorageClient;
/**
* This {@link MemcachedSessionService} can be used for debugging session
* <em>deserialization</em> - to see if serialized session data actually can be
* deserialized. Session data is serialized at the end of the request as normal (stored
* in a simple map), and deserialized when a following request is asking for the session.
* The deserialization is done like this (instead of directly at the end of the request
* when it is serialized) to perform deserialization at the same point in the lifecycle
* as it would happen in the real failover case (there might be difference in respect
* to initialized ThreadLocals or other stuff).
* <p>
* The memcached configuration (<code>memcachedNodes</code>, <code>failoverNode</code>) is
* not used to create a memcached client, so serialized session data will <strong>not</strong>
* be sent to memcached - and therefore no running memcacheds are required. Though, the
* <code>memcachedNodes</code> attribute is still required (use some dummy values).
* </p>
*
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
* @version $Id$
*/
public class DummyMemcachedSessionService<T extends MemcachedSessionService.SessionManager> extends MemcachedSessionService {
private final Map<String,byte[]> _sessionData = new ConcurrentHashMap<String, byte[]>();
private final ExecutorService _executorService = Executors.newSingleThreadExecutor(new NamedThreadFactory("dummy-msm"));
public DummyMemcachedSessionService( final T manager ) {
super( manager );
}
@Override
protected StorageClient createStorageClient(final MemcachedNodesManager memcachedNodesManager,
final Statistics statistics ) {
return null;
}
@Override
protected StorageClientCallback createStorageClientCallback() {
return new StorageClientCallback() {
@Override
public byte[] get(final String key) {
return null;
}
};
}
@Override
protected void deleteFromMemcached(final String sessionId) {
// no memcached access
}
/**
* Store the provided session in memcached if the session was modified
* or if the session needs to be relocated.
*
* @param session
* the session to save
* @param sessionRelocationRequired
* specifies, if the session id was changed due to a memcached failover or tomcat failover.
* @return the {@link BackupResultStatus}
*/
public Future<BackupResult> backupSession( final String sessionId, final boolean sessionIdChanged, final String requestId ) {
final MemcachedBackupSession session = _manager.getSessionInternal( sessionId );
if ( session == null ) {
if(_log.isDebugEnabled())
_log.debug( "No session found in session map for " + sessionId );
return new SimpleFuture<BackupResult>( BackupResult.SKIPPED );
}
_log.info( "Serializing session data for session " + session.getIdInternal() );
final long startSerialization = System.currentTimeMillis();
final byte[] data = _transcoderService.serializeAttributes( (MemcachedBackupSession) session, ((MemcachedBackupSession) session).getAttributesFiltered() );
_log.info( String.format( "Serializing %1$,.3f kb session data for session %2$s took %3$d ms.",
(double)data.length / 1000, session.getIdInternal(), System.currentTimeMillis() - startSerialization ) );
_sessionData.put( session.getIdInternal(), data );
_statistics.registerSince( ATTRIBUTES_SERIALIZATION, startSerialization );
_statistics.register( CACHED_DATA_SIZE, data.length );
return new SimpleFuture<BackupResult>( new BackupResult( BackupResultStatus.SUCCESS ) );
}
@Override
public MemcachedBackupSession findSession( final String id ) throws IOException {
final MemcachedBackupSession result = super.findSession( id );
if ( result != null ) {
final byte[] data = _sessionData.remove( id );
if ( data != null ) {
_executorService.submit( new SessionDeserialization( id, data ) );
}
}
return result;
}
@Override
protected MemcachedBackupSession loadFromMemcachedWithCheck( final String sessionId ) {
return null;
}
@Override
protected void updateExpirationInMemcached() {
}
private final class SessionDeserialization implements Callable<Void> {
private final String _id;
private final byte[] _data;
private SessionDeserialization( final String id, final byte[] data ) {
_id = id;
_data = data;
}
@Override
public Void call() throws Exception {
_log.info( String.format( "Deserializing %1$,.3f kb session data for session %2$s (asynchronously).", (double)_data.length / 1000, _id ) );
final long startDeserialization = System.currentTimeMillis();
try {
_transcoderService.deserializeAttributes( _data );
} catch( final Exception e ) {
_log.warn( "Could not deserialize session data.", e );
}
_log.info( String.format( "Deserializing %1$,.3f kb session data for session %2$s took %3$d ms.",
(double)_data.length / 1000, _id, System.currentTimeMillis() - startDeserialization ) );
_statistics.registerSince( LOAD_FROM_MEMCACHED, startDeserialization );
return null;
}
}
}