com.phloc.scopes.mgr.ScopeSessionManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of phloc-scopes Show documentation
Show all versions of phloc-scopes Show documentation
Library for handling scopes in a generic way
/**
* Copyright (C) 2006-2015 phloc systems
* http://www.phloc.com
* office[at]phloc[dot]com
*
* 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 com.phloc.scopes.mgr;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.ReturnsMutableCopy;
import com.phloc.commons.annotations.UsedViaReflection;
import com.phloc.commons.collections.ContainerHelper;
import com.phloc.commons.state.EChange;
import com.phloc.commons.stats.IStatisticsHandlerCounter;
import com.phloc.commons.stats.StatisticsManager;
import com.phloc.commons.string.StringHelper;
import com.phloc.scopes.domain.ISessionScope;
import com.phloc.scopes.singleton.GlobalSingleton;
import com.phloc.scopes.spi.ScopeSPIManager;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Internal manager class for session scopes.
* This class is only non-final so that the WebScopeSessionManager can be used
* for web scopes!
*
* @author Philip Helger
*/
@ThreadSafe
public class ScopeSessionManager extends GlobalSingleton
{
public static final boolean DEFAULT_DESTROY_ALL_SESSIONS_ON_SCOPE_END = true;
public static final boolean DEFAULT_END_ALL_SESSIONS_ON_SCOPE_END = true;
private static final Logger s_aLogger = LoggerFactory.getLogger (ScopeSessionManager.class);
private static final IStatisticsHandlerCounter s_aUniqueSessionCounter = StatisticsManager.getCounterHandler (ScopeSessionManager.class.getName () +
"$UNIQUE_SESSIONS");
private static volatile ScopeSessionManager s_aInstance = null;
/** All contained session scopes. */
private final ReadWriteLock m_aRWLock = new ReentrantReadWriteLock ();
private final Map m_aSessionScopes = new HashMap ();
private final Set m_aSessionsInDestruction = new HashSet ();
@GuardedBy ("m_aRWLock")
private boolean m_bDestroyAllSessionsOnScopeEnd = DEFAULT_DESTROY_ALL_SESSIONS_ON_SCOPE_END;
@GuardedBy ("m_aRWLock")
private boolean m_bEndAllSessionsOnScopeEnd = DEFAULT_END_ALL_SESSIONS_ON_SCOPE_END;
@Deprecated
@UsedViaReflection
public ScopeSessionManager ()
{}
@Nonnull
public static ScopeSessionManager getInstance ()
{
// This special handling is needed, because this global singleton is
// required upon shutdown of the GlobalWebScope!
if (s_aInstance == null)
s_aInstance = getGlobalSingleton (ScopeSessionManager.class);
return s_aInstance;
}
/**
* Get the session scope with the specified ID. If no such scope exists, no
* further actions are taken.
*
* @param sScopeID
* The ID to be resolved. May be null
.
* @return null
if no such scope exists.
*/
@Nullable
public ISessionScope getSessionScopeOfID (@Nullable final String sScopeID)
{
if (StringHelper.hasNoText (sScopeID))
return null;
m_aRWLock.readLock ().lock ();
try
{
return m_aSessionScopes.get (sScopeID);
}
finally
{
m_aRWLock.readLock ().unlock ();
}
}
/**
* Register the passed session scope in the internal map, call the
* {@link ISessionScope #initScope()} method and finally invoke the SPIs for
* the new scope.
*
* @param aSessionScope
* The session scope that was just created. May not be
* null
.
*/
public void onScopeBegin (@Nonnull final ISessionScope aSessionScope)
{
ValueEnforcer.notNull (aSessionScope, "SessionScope");
final String sSessionID = aSessionScope.getID ();
m_aRWLock.writeLock ().lock ();
try
{
if (m_aSessionScopes.put (sSessionID, aSessionScope) != null)
s_aLogger.error ("Overwriting session scope with ID '" + sSessionID + "'");
}
finally
{
m_aRWLock.writeLock ().unlock ();
}
// Init the scope after it was registered
aSessionScope.initScope ();
// Invoke SPIs
ScopeSPIManager.onSessionScopeBegin (aSessionScope);
// Increment statistics counter
s_aUniqueSessionCounter.increment ();
}
/**
* Close the passed session scope gracefully. Each managed scope is guaranteed
* to be destroyed only once. First the SPI manager is invoked, and afterwards
* the scope is destroyed.
*
* @param aSessionScope
* The session scope to be ended. May not be null
.
*/
public void onScopeEnd (@Nonnull final ISessionScope aSessionScope)
{
ValueEnforcer.notNull (aSessionScope, "SessionScope");
// Only handle scopes that are not yet destructed
if (aSessionScope.isValid ())
{
final String sSessionID = aSessionScope.getID ();
boolean bCanDestroyScope = true;
m_aRWLock.writeLock ().lock ();
try
{
// Only if we're not just in destruction of exactly this session
if (m_aSessionsInDestruction.add (sSessionID))
{
// Remove from map
final ISessionScope aRemovedScope = m_aSessionScopes.remove (sSessionID);
if (aRemovedScope != aSessionScope)
{
s_aLogger.error ("Ending an unknown session with ID '" + sSessionID + "'");
s_aLogger.error (" Scope to be removed: " + aSessionScope);
s_aLogger.error (" Removed scope: " + aRemovedScope);
}
bCanDestroyScope = true;
}
else
s_aLogger.info ("Already destructing session '" + sSessionID + "'");
}
finally
{
m_aRWLock.writeLock ().unlock ();
}
if (bCanDestroyScope)
{
// Destroy scope outside of write lock
try
{
// Invoke SPIs
ScopeSPIManager.onSessionScopeEnd (aSessionScope);
// Destroy the scope
aSessionScope.destroyScope ();
}
finally
{
// Remove from "in destruction" list
m_aRWLock.writeLock ().lock ();
try
{
m_aSessionsInDestruction.remove (sSessionID);
}
finally
{
m_aRWLock.writeLock ().unlock ();
}
}
}
}
}
/**
* @return true
if at least one session is present,
* false
otherwise
*/
public boolean containsAnySession ()
{
m_aRWLock.readLock ().lock ();
try
{
return !m_aSessionScopes.isEmpty ();
}
finally
{
m_aRWLock.readLock ().unlock ();
}
}
/**
* @return The number of managed session scopes. Always ≥ 0.
*/
@Nonnegative
public int getSessionCount ()
{
m_aRWLock.readLock ().lock ();
try
{
return m_aSessionScopes.size ();
}
finally
{
m_aRWLock.readLock ().unlock ();
}
}
/**
* @return A non-null
, mutable copy of all managed session
* scopes.
*/
@Nonnull
@ReturnsMutableCopy
public Collection extends ISessionScope> getAllSessionScopes ()
{
m_aRWLock.readLock ().lock ();
try
{
return ContainerHelper.newList (m_aSessionScopes.values ());
}
finally
{
m_aRWLock.readLock ().unlock ();
}
}
private void _checkIfAnySessionsExist ()
{
if (containsAnySession ())
{
m_aRWLock.writeLock ().lock ();
try
{
s_aLogger.error ("The following " +
m_aSessionScopes.size () +
" session scopes are left over: " +
m_aSessionScopes);
m_aSessionScopes.clear ();
}
finally
{
m_aRWLock.writeLock ().unlock ();
}
}
}
/**
* Destroy all known session scopes. After this method it is ensured that the
* internal session map is empty.
*/
public void destroyAllSessions ()
{
// destroy all session scopes (make a copy, because we're invalidating
// the sessions internally!)
for (final ISessionScope aSessionScope : getAllSessionScopes ())
{
// Unfortunately we need a special handling here
if (aSessionScope.selfDestruct ().isContinue ())
{
// Remove from map
onScopeEnd (aSessionScope);
}
// Else the destruction was already started!
}
// Sanity check in case something went wrong
_checkIfAnySessionsExist ();
}
/**
* Remove all existing session scopes, and invoke the destruction methods on
* the contained objects.
*/
private void _endAllSessionScopes ()
{
// end all session scopes without destroying the underlying sessions (make a
// copy, because we're invalidating the sessions!)
for (final ISessionScope aSessionScope : getAllSessionScopes ())
{
// Remove from map
onScopeEnd (aSessionScope);
}
// Sanity check in case something went wrong
_checkIfAnySessionsExist ();
}
public boolean isDestroyAllSessionsOnScopeEnd ()
{
m_aRWLock.readLock ().lock ();
try
{
return m_bDestroyAllSessionsOnScopeEnd;
}
finally
{
m_aRWLock.readLock ().unlock ();
}
}
@Nonnull
public EChange setDestroyAllSessionsOnScopeEnd (final boolean bDestroyAllSessionsOnScopeEnd)
{
m_aRWLock.writeLock ().lock ();
try
{
if (m_bDestroyAllSessionsOnScopeEnd == bDestroyAllSessionsOnScopeEnd)
return EChange.UNCHANGED;
m_bDestroyAllSessionsOnScopeEnd = bDestroyAllSessionsOnScopeEnd;
return EChange.CHANGED;
}
finally
{
m_aRWLock.writeLock ().unlock ();
}
}
public boolean isEndAllSessionsOnScopeEnd ()
{
m_aRWLock.readLock ().lock ();
try
{
return m_bEndAllSessionsOnScopeEnd;
}
finally
{
m_aRWLock.readLock ().unlock ();
}
}
@Nonnull
public EChange setEndAllSessionsOnScopeEnd (final boolean bEndAllSessionsOnScopeEnd)
{
m_aRWLock.writeLock ().lock ();
try
{
if (m_bEndAllSessionsOnScopeEnd == bEndAllSessionsOnScopeEnd)
return EChange.UNCHANGED;
m_bEndAllSessionsOnScopeEnd = bEndAllSessionsOnScopeEnd;
return EChange.CHANGED;
}
finally
{
m_aRWLock.writeLock ().unlock ();
}
}
@SuppressFBWarnings ("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
@Override
protected void onDestroy ()
{
if (isDestroyAllSessionsOnScopeEnd ())
destroyAllSessions ();
else
if (isEndAllSessionsOnScopeEnd ())
_endAllSessionScopes ();
s_aInstance = null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy