/*******************************************************************************
* Copyright (c) 2002, 2008 Innoopract Informationssysteme GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
******************************************************************************/
package org.eclipse.rwt;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.rwt.internal.service.ContextProvider;
import org.eclipse.rwt.internal.service.IServiceStateInfo;
import org.eclipse.rwt.service.ISessionStore;
/**
* <p>Subclasses of <code>SessionSingletonBase</code> provide access to a
* unique instance of their type with session scope. This means that in the
* context of one user session <code>getInstance(Class)</code> will always return
* the same object, but for different user sessions the returned instances
* will be different.</p>
*
* <p>usage:
* <pre>
* public class FooSingleton extends SessionSingletonBase {
*
* private FooSingleton() {}
*
* public static FooSingleton getInstance() {
* return ( FooSingleton )getInstance( FooSingleton.class );
* }
* }
* </pre>
* </p>
*
* @since 1.0
*/
public abstract class SessionSingletonBase {
/**
* <b>IMPORTANT:</b> This constant is <em>not</em> part of the RWT
* public API. It is marked public only so that it can be shared
* within the packages provided by RWT. It should never be
* referenced from application code.
*/
public static final String LOCK
= SessionSingletonBase.class.getName() + ".Lock";
/**
* This is used as prefix for the key under which the instance
* is stored as session attribute. The key consists of the prefix
* and the fully qualified classname of the singleton type.
*/
private final static String PREFIX = "com_w4t_session_singleton_";
private static final String LOCK_POSTFIX = "#typeLock";
private final static Class[] EMPTY_PARAMS = new Class[ 0 ];
private final static Map instanceKeyMap = new Hashtable();
private final static Map lockKeyMap = new Hashtable();
/**
* Returns the singleton instance of the specified type that is stored
* in the current session context. If no instance exists yet, a new
* one will be created. Therefore the specified type should have
* an parameterless default constructor.
*
* @param type specifies the session singleton instance type.
* @return the unique instance of the specified type that is associated
* with the current user session context.
*/
public static Object getInstance( final Class type ) {
// Note [fappel]: Since this code is performance critical, don't change
// anything without checking it against a profiler.
IServiceStateInfo stateInfo = ContextProvider.getStateInfo();
Object result = null;
if( stateInfo != null ) {
result = stateInfo.getAttribute( getInstanceKey( type ) );
}
if( result == null ) {
synchronized( getInstanceLock( type ) ) {
result = getInstanceInternal( type );
}
if( stateInfo != null ) {
stateInfo.setAttribute( getInstanceKey( type ), result );
}
}
return result;
}
//////////////////
// helping methods
private static Object getInstanceLock( final Class type ) {
// create a lock per session instance to avoid deadlocks
ISessionStore session = ContextProvider.getSession();
Object result = null;
synchronized( session.getAttribute( LOCK ) ) {
result = session.getAttribute( getLockKey( type ) );
if( result == null ) {
result = new Object();
session.setAttribute( getLockKey( type ), result );
}
}
return result;
}
private static String getInstanceKey( final Class type ) {
// Note [fappel]: Since this code is performance critical, don't change
// anything without checking it against a profiler.
String name = type.getName();
String result = ( String )instanceKeyMap.get( name );
if( result == null ) {
StringBuffer key = new StringBuffer( PREFIX );
key.append( name );
instanceKeyMap.put( name, key.toString() );
}
return result;
}
private static String getLockKey( final Class type ) {
// Note [fappel]: Since this code is performance critical, don't change
// anything without checking it against a profiler.
String name = type.getName();
String result = ( String )lockKeyMap.get( name );
if( result == null ) {
StringBuffer key = new StringBuffer( PREFIX );
key.append( name );
key.append( LOCK_POSTFIX );
lockKeyMap.put( name, key.toString() );
}
return result;
}
private static Object getInstanceInternal( final Class type ) {
Object result = getAttribute( getInstanceKey( type ) );
if( result == null ) {
try {
Constructor constructor = type.getDeclaredConstructor( EMPTY_PARAMS );
if( constructor.isAccessible() ) {
result = type.newInstance();
} else {
constructor.setAccessible( true );
result = constructor.newInstance( null );
}
} catch( final SecurityException ex ) {
String msg = "Could not created the session singleton instance of '"
+ type.getName()
+ "' due to security restrictions that probably do "
+ "not allow reflection.";
throw new RuntimeException( msg, ex );
} catch( final IllegalArgumentException iae ) {
String msg = "Could not create the session singleton instance of '"
+ type.getName()
+ "'. Probably there is no parameterless constructor.";
throw new RuntimeException( msg, iae );
} catch( final NoSuchMethodException nsme ) {
String msg = "Could not create the session singleton instance of '"
+ type.getName()
+ "'. Probably there is no parameterless constructor.";
throw new RuntimeException( msg, nsme );
} catch( final InstantiationException ise ) {
String msg = "Could not create the session singleton instance of '"
+ type.getName()
+ "'. Unable to create an instance.";
throw new RuntimeException( msg, ise );
} catch( final IllegalAccessException iae ) {
String msg = "Could not create the session singleton instance of '"
+ type.getName()
+ "'. Not allowed to access the constructor "
+ "for unknown reasons.";
throw new RuntimeException( msg, iae );
} catch( final InvocationTargetException ite ) {
String msg = "Could not create the session singleton instance of '"
+ type.getName()
+ "' because an Exception was thrown by the constructor:\n"
+ ite.getCause().getMessage()
+ ".";
throw new RuntimeException( msg, ite );
}
setAttribute( getInstanceKey( type ), result );
}
return result;
}
private static Object getAttribute( final String name ) {
return ContextProvider.getSession().getAttribute( name );
}
private static void setAttribute( final String name, final Object object ) {
ContextProvider.getSession().setAttribute( name, object );
}
}