All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.javakaffee.web.msm.BackupSessionTask Maven / Gradle / Ivy

There is a newer version: 2.3.2
Show newest version
/*
 * Copyright 2009 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.MemcachedUtil.toMemcachedExpiration;
import static de.javakaffee.web.msm.Statistics.StatsType.ATTRIBUTES_SERIALIZATION;
import static de.javakaffee.web.msm.Statistics.StatsType.BACKUP;
import static de.javakaffee.web.msm.Statistics.StatsType.MEMCACHED_UPDATE;
import static de.javakaffee.web.msm.Statistics.StatsType.RELEASE_LOCK;

import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

import de.javakaffee.web.msm.BackupSessionTask.BackupResult;
import de.javakaffee.web.msm.storage.StorageClient;

/**
 * Stores the provided session in memcached if the session was modified
 * or if the session needs to be relocated (set force to true).
 *
 * @author Martin Grotzke
 */
public class BackupSessionTask implements Callable {

    private static final Log _log = LogFactory.getLog( BackupSessionTask.class );

    private final MemcachedBackupSession _session;
    private final boolean _force;
    private final TranscoderService _transcoderService;
    private final boolean _sessionBackupAsync;
    private final int _sessionBackupTimeout;
    private final StorageClient _storage;
    private final MemcachedNodesManager _memcachedNodesManager;
    private final Statistics _statistics;

    /**
     * @param session
     *            the session to save
     * @param sessionIdChanged
     *            specifies, if the session needs to be saved by all means, e.g.
     *            as it has to be relocated to another memcached
     *            node (the session id had been changed before in this case).
     * @param sessionBackupAsync
     * @param sessionBackupTimeout
     * @param storage
     * @param memcachedNodesManager
     * @param statistics
     */
    public BackupSessionTask( final MemcachedBackupSession session,
            final boolean sessionIdChanged,
            final TranscoderService transcoderService,
            final boolean sessionBackupAsync,
            final int sessionBackupTimeout,
            final StorageClient storage,
            final MemcachedNodesManager memcachedNodesManager,
            final Statistics statistics ) {
        _session = session;
        _force = sessionIdChanged;
        _transcoderService = transcoderService;
        _sessionBackupAsync = sessionBackupAsync;
        _sessionBackupTimeout = sessionBackupTimeout;
        _storage = storage;
        _memcachedNodesManager = memcachedNodesManager;
        _statistics = statistics;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public BackupResult call() throws Exception {
        if ( _log.isDebugEnabled() ) {
            _log.debug( "Starting for session id " + _session.getId() );
        }

        _session.setBackupRunning( true );
        try {

            final long startBackup = System.currentTimeMillis();

            final ConcurrentMap attributes = _session.getAttributesFiltered();
            final byte[] attributesData = serializeAttributes( _session, attributes );
            final int hashCode = Arrays.hashCode( attributesData );
            final BackupResult result;
            if ( _session.getDataHashCode() != hashCode
                    || _force
                    || _session.authenticationChanged() ) {

                _session.setLastBackupTime( System.currentTimeMillis() );
                final byte[] data = _transcoderService.serialize( _session, attributesData );

                result = doBackupSession( _session, data, attributesData );
                if ( result.isSuccess() ) {
                    _session.setDataHashCode( hashCode );
                }
            } else {
                result = new BackupResult( BackupResultStatus.SKIPPED );
            }

            switch ( result.getStatus() ) {
                case FAILURE:
                    _statistics.requestWithBackupFailure();
                    _session.backupFailed();
                    break;
                case SKIPPED:
                    _statistics.requestWithoutSessionModification();
                    _session.storeThisAccessedTimeFromLastBackupCheck();
                    break;
                case SUCCESS:
                    _statistics.registerSince( BACKUP, startBackup );
                    _session.storeThisAccessedTimeFromLastBackupCheck();
                    _session.backupFinished();
                    break;
            }

            if ( _log.isDebugEnabled() ) {
                _log.debug( "Finished for session id " + _session.getId() +
                        ", returning status " + result.getStatus() );
            }

            return result;

        } catch (Exception e) {
            _log.warn("FAILED for session id " + _session.getId(), e);
            throw e;
        }
        finally {
            _session.setBackupRunning( false );
            releaseLock();
        }

    }

    private void releaseLock() {
        if ( _session.isLocked()  ) {
            try {
                if ( _log.isDebugEnabled() ) {
                    _log.debug( "Releasing lock for session " + _session.getIdInternal() );
                }
                final long start = System.currentTimeMillis();
                _storage.delete( _memcachedNodesManager.getSessionIdFormat().createLockName( _session.getIdInternal() ) ).get();
                _statistics.registerSince( RELEASE_LOCK, start );
                _session.releaseLock();
            } catch( final Exception e ) {
                _log.warn( "Caught exception when trying to release lock for session " + _session.getIdInternal(), e );
            }
        }
    }

    private byte[] serializeAttributes( final MemcachedBackupSession session, final ConcurrentMap attributes ) {
        final long start = System.currentTimeMillis();
        final byte[] attributesData = _transcoderService.serializeAttributes( session, attributes );
        _statistics.registerSince( ATTRIBUTES_SERIALIZATION, start );
        return attributesData;
    }

    /**
     * Store the provided session in memcached.
     * @param session the session to backup
     * @param data the serialized session data (session fields and session attributes).
     * @param attributesData just the serialized session attributes.
     *
     * @return the {@link BackupResultStatus}
     */
    BackupResult doBackupSession( final MemcachedBackupSession session, final byte[] data, final byte[] attributesData ) throws InterruptedException {
        if ( _log.isDebugEnabled() ) {
            _log.debug( "Trying to store session in memcached: " + session.getId() );
        }

        try {
            storeSessionInMemcached( session, data );
            return new BackupResult( BackupResultStatus.SUCCESS, data, attributesData );
        } catch (final ExecutionException e) {
            handleException(session, e);
            return new BackupResult(BackupResultStatus.FAILURE, data, null);
        } catch (final TimeoutException e) {
            handleException(session, e);
            return new BackupResult(BackupResultStatus.FAILURE, data, null);
        }
    }

    private void handleException(final MemcachedBackupSession session, final Exception e) {
        //if ( _log.isWarnEnabled() ) {
            String msg = "Could not store session " + session.getId() + " in memcached.";
            if ( _force ) {
                msg += "\nNote that this session was relocated to this node because the" +
                    " original node was not available.";
            }
            _log.warn(msg, e);
        //}
        _memcachedNodesManager.setNodeAvailableForSessionId(session.getId(), false);
    }

    private void storeSessionInMemcached( final MemcachedBackupSession session, final byte[] data) throws InterruptedException, ExecutionException, TimeoutException {

        /* calculate the expiration time (instead of using just maxInactiveInterval), as
         * this is relevant for the update of the expiration time: if we would just use
         * maxInactiveInterval, the session would exist longer in memcached than it would
         * be valid in tomcat
         */
        final int expirationTime = session.getMemcachedExpirationTimeToSet();
        final long start = System.currentTimeMillis();
        try {
            final Future future = _storage.set(
                    _memcachedNodesManager.getStorageKeyFormat().format(session.getId()),
                    toMemcachedExpiration(expirationTime), data );
            if ( !_sessionBackupAsync ) {
                future.get( _sessionBackupTimeout, TimeUnit.MILLISECONDS );
                session.setLastMemcachedExpirationTime( expirationTime );
                session.setLastBackupTime( System.currentTimeMillis() );
            }
            else {
                /* in async mode, we asume the session was stored successfully
                 */
                session.setLastMemcachedExpirationTime( expirationTime );
                session.setLastBackupTime( System.currentTimeMillis() );
            }
        } finally {
            _statistics.registerSince( MEMCACHED_UPDATE, start );
        }
    }

    static final class BackupResult {

        public static final BackupResult SKIPPED = new BackupResult( BackupResultStatus.SKIPPED );
        public static final BackupResult FAILURE = new BackupResult( BackupResultStatus.FAILURE );

        private final BackupResultStatus _status;
        private final byte[] _data;
        private final byte[] _attributesData;
        public BackupResult( @Nonnull final BackupResultStatus status ) {
            this( status, null, null );
        }
        public BackupResult( @Nonnull final BackupResultStatus status, @Nullable final byte[] data, @Nullable final byte[] attributesData ) {
            _status = status;
            _data = data;
            _attributesData = attributesData;
        }
        /**
         * The status/result of the backup operation.
         * @return the status
         */
        @Nonnull
        BackupResultStatus getStatus() {
            return _status;
        }
        /**
         * The serialized session data (session fields and session attributes).
         * This can be null (if {@link #getStatus()} is {@link BackupResultStatus#SKIPPED}).
         *
         * @return the session data
         */
        @CheckForNull
        byte[] getData() {
            return _data;
        }
        /**
         * The serialized attributes that were actually stored in memcached with the
         * full serialized session data. This can be null, e.g. if
         * {@link #getStatus()} is {@link BackupResultStatus#FAILURE} or {@link BackupResultStatus#SKIPPED}.
         *
         * @return the attributesData
         */
        @CheckForNull
        byte[] getAttributesData() {
            return _attributesData;
        }
        /**
         * Specifies if the backup was performed successfully.
         *
         * @return true if the status is {@link BackupResultStatus#SUCCESS},
         * otherwise false.
         */
        public boolean isSuccess() {
            return _status == BackupResultStatus.SUCCESS;
        }
		@Override
		public String toString() {
			return "BackupResult [_status=" + _status + ", _data="
					+ (_data != null ? "byte[" + _data.length + "]" : "null") + ", _attributesData="
					+ (_attributesData != null ? "byte[" + _attributesData.length + "]" : "null") + "]";
		}
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy