Project: * Clean-up (Eclipse cruft, unused files, etc...) * Git-specific changes * Maven POMs clean-up and changes for the build system * Version set to 1.0.0-0 in the development branches * Maven plug-ins updated to latest versions * Very partial dev. documentation added

This commit is contained in:
Emmanuel BENOîT 2011-12-09 08:07:33 +01:00
parent c74e30d5ba
commit 0665a760de
1439 changed files with 1020 additions and 1649 deletions

View file

@ -0,0 +1,106 @@
package com.deepclone.lw.beans.sys;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.deepclone.lw.interfaces.sys.ConstantDefinition;
import com.deepclone.lw.interfaces.sys.ConstantsAdministration;
import com.deepclone.lw.interfaces.sys.InvalidConstantValue;
import com.deepclone.lw.interfaces.sys.UnknownConstantError;
import com.deepclone.lw.sqld.sys.Constant;
/**
* The constants administration implementation mostly serves as a session-specific proxy for the
* share {@link ConstantsData} instance.
*
* @author tseeker
*/
class ConstantsAdministrationImpl
implements ConstantsAdministration
{
/** Shared data storage instance */
private ConstantsData data;
private int admin;
/**
* Copies the required references.
*
* @param data
* shared data storage instance
* @param logger
* session-specific logger
*/
ConstantsAdministrationImpl( ConstantsData data , int admin )
{
this.data = data;
this.admin = admin;
}
/* Documented in ConstantsAdministration interface */
@Override
public Set< String > getCategories( )
{
return this.data.getCategories( );
}
/* Documented in ConstantsAdministration interface */
@Override
public Collection< ConstantDefinition > getConstants( String category )
{
List< Constant > constants = this.data.getCategory( category );
List< ConstantDefinition > defs = new LinkedList< ConstantDefinition >( );
if ( constants == null ) {
return defs;
}
// Convert data to immutable records
for ( Constant constant : constants ) {
ConstantDefinition def;
if ( constant.getMaxValue( ) == null && constant.getMinValue( ) == null ) {
def = new ConstantDefinition( constant.getName( ) , category , constant.getDescription( ) , constant
.getValue( ) );
} else if ( constant.getMaxValue( ) == null || constant.getMinValue( ) == null ) {
boolean isMin = ( constant.getMaxValue( ) == null );
Double val = isMin ? constant.getMinValue( ) : constant.getMaxValue( );
def = new ConstantDefinition( constant.getName( ) , category , constant.getDescription( ) , constant
.getValue( ) , val , isMin );
} else {
if ( constant.getMaxValue( ) == null && constant.getMinValue( ) == null ) {
def = new ConstantDefinition( constant.getName( ) , category , constant.getDescription( ) ,
constant.getValue( ) );
} else if ( constant.getMaxValue( ) == null ) {
def = new ConstantDefinition( constant.getName( ) , category , constant.getDescription( ) ,
constant.getValue( ) , constant.getMinValue( ) , true );
} else if ( constant.getMinValue( ) == null ) {
def = new ConstantDefinition( constant.getName( ) , category , constant.getDescription( ) ,
constant.getValue( ) , constant.getMaxValue( ) , false );
} else {
def = new ConstantDefinition( constant.getName( ) , category , constant.getDescription( ) , constant
.getValue( ) , constant.getMinValue( ) , constant.getMaxValue( ) );
}
}
defs.add( def );
}
return defs;
}
/* Documented in ConstantsAdministration interface */
@Override
public void setConstant( String name , Double value )
throws UnknownConstantError , InvalidConstantValue
{
this.data.setValue( name , value , this.admin );
}
}

View file

@ -0,0 +1,630 @@
package com.deepclone.lw.beans.sys;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.cmd.admin.logs.LogLevel;
import com.deepclone.lw.interfaces.eventlog.Logger;
import com.deepclone.lw.interfaces.eventlog.SystemLogger;
import com.deepclone.lw.interfaces.sys.ConstantDefinition;
import com.deepclone.lw.interfaces.sys.ConstantsUser;
import com.deepclone.lw.interfaces.sys.InvalidConstantValue;
import com.deepclone.lw.interfaces.sys.UnknownConstantError;
import com.deepclone.lw.sqld.sys.Constant;
import com.deepclone.lw.utils.StoredProc;
/**
* This class defines the shared data structures used by both the constants manager bean and its
* administrative session instances. It also encapsulates all of the code used by both.
*
* @author tseeker
*/
class ConstantsData
{
/** Database interface */
private SimpleJdbcTemplate dTemplate;
/** Transaction manager interface */
private TransactionTemplate tTemplate;
/** System logger instance for the ConstantsManager component */
private SystemLogger sysLog;
/** All registered constants, by name */
private Map< String , Constant > registeredConstants;
/** Constants users that have received their initial notification */
private Set< ConstantsUser > notifiedUsers = new HashSet< ConstantsUser >( );
/** Constants needed by each user */
private Map< ConstantsUser , Set< String >> userConstants = new HashMap< ConstantsUser , Set< String > >( );
/** Users listening to each constant */
private Map< String , Set< ConstantsUser >> constantUsers = new HashMap< String , Set< ConstantsUser > >( );
/** Constants by category */
private Map< String , List< Constant >> categories;
private SimpleJdbcCall uocConstantNoBounds;
private SimpleJdbcCall uocConstantSingleBound;
private SimpleJdbcCall uocConstantTwoBounds;
private StoredProc fSetConstant;
/**
* Initialises the various references, creates the system logger used by the instance, then
* loads all existing constants.
*
* @param dataSource
* database interface
* @param tTemplate
* transaction manager interface
* @param logger
* logger bean interface
*/
ConstantsData( DataSource dataSource , TransactionTemplate tTemplate , Logger logger )
{
this.dTemplate = new SimpleJdbcTemplate( dataSource );
this.uocConstantNoBounds = new SimpleJdbcCall( dataSource );
this.uocConstantNoBounds.withCatalogName( "sys" ).withFunctionName( "uoc_constant" );
this.uocConstantNoBounds.withoutProcedureColumnMetaDataAccess( );
this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "cname" , Types.VARCHAR ) );
this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "cdesc" , Types.VARCHAR ) );
this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "catname" , Types.VARCHAR ) );
this.uocConstantNoBounds.addDeclaredParameter( new SqlParameter( "value" , Types.REAL ) );
this.uocConstantSingleBound = new SimpleJdbcCall( dataSource );
this.uocConstantSingleBound.withCatalogName( "sys" ).withFunctionName( "uoc_constant" );
this.uocConstantSingleBound.withoutProcedureColumnMetaDataAccess( );
this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "cname" , Types.VARCHAR ) );
this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "cdesc" , Types.VARCHAR ) );
this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "catname" , Types.VARCHAR ) );
this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "value" , Types.REAL ) );
this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "boundary" , Types.REAL ) );
this.uocConstantSingleBound.addDeclaredParameter( new SqlParameter( "is_min" , Types.BOOLEAN ) );
this.uocConstantTwoBounds = new SimpleJdbcCall( dataSource );
this.uocConstantTwoBounds.withCatalogName( "sys" ).withFunctionName( "uoc_constant" );
this.uocConstantTwoBounds.withoutProcedureColumnMetaDataAccess( );
this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "cname" , Types.VARCHAR ) );
this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "cdesc" , Types.VARCHAR ) );
this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "catname" , Types.VARCHAR ) );
this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "value" , Types.REAL ) );
this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "min" , Types.REAL ) );
this.uocConstantTwoBounds.addDeclaredParameter( new SqlParameter( "max" , Types.REAL ) );
this.fSetConstant = new StoredProc( dataSource , "sys" , "set_constant" );
this.fSetConstant.addParameter( "cname" , Types.VARCHAR );
this.fSetConstant.addParameter( "value" , Types.REAL );
this.fSetConstant.addParameter( "admin" , Types.INTEGER );
this.tTemplate = tTemplate;
this.sysLog = logger.getSystemLogger( "ConstantsManager" );
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
loadConstants( );
}
} );
}
private void loadConstants( )
{
String sql = "SELECT category , name , description , value , min , max FROM sys.constants_view";
RowMapper< Constant > mapper = new RowMapper< Constant >( ) {
@Override
public Constant mapRow( ResultSet rs , int rowNum )
throws SQLException
{
Constant c = new Constant( );
c.setCategory( rs.getString( "category" ) );
c.setName( rs.getString( "name" ) );
c.setDescription( rs.getString( "description" ) );
c.setValue( rs.getDouble( "value" ) );
c.setMinValue( (Float) rs.getObject( "min" ) );
c.setMaxValue( (Float) rs.getObject( "max" ) );
return c;
}
};
this.registeredConstants = new HashMap< String , Constant >( );
this.categories = new HashMap< String , List< Constant > >( );
for ( Constant cnst : this.dTemplate.query( sql , mapper ) ) {
this.registeredConstants.put( cnst.getName( ) , cnst );
List< Constant > category = this.categories.get( cnst.getCategory( ) );
if ( category == null ) {
category = new LinkedList< Constant >( );
this.categories.put( cnst.getCategory( ) , category );
}
category.add( cnst );
}
}
private void updateConstants( List< ConstantDefinition > toUpdate )
{
for ( ConstantDefinition cDef : toUpdate ) {
if ( cDef.maxValue == null && cDef.minValue == null ) {
this.uocConstantNoBounds.execute( cDef.name , cDef.description , cDef.category , cDef.defaultValue );
} else if ( cDef.maxValue == null ) {
this.uocConstantSingleBound.execute( cDef.name , cDef.description , cDef.category , cDef.defaultValue ,
cDef.minValue , true );
} else if ( cDef.minValue == null ) {
this.uocConstantSingleBound.execute( cDef.name , cDef.description , cDef.category , cDef.defaultValue ,
cDef.maxValue , false );
} else {
this.uocConstantTwoBounds.execute( cDef.name , cDef.description , cDef.category , cDef.defaultValue ,
cDef.minValue , cDef.maxValue );
}
}
}
/**
* Checks that the descriptive parts of a constant data instance match the corresponding fields
* in a constant definition instance.
*
* @param constant
* the constant data instance
* @param def
* the definition
* @return <code>true</code> if the descriptive parts are a match
*/
private boolean compareConstantDescription( Constant constant , ConstantDefinition def )
{
return constant.getCategory( ).equals( def.category ) && constant.getDescription( ).equals( def.description );
}
/**
* Checks that the bounds defined in a constant data instance are identical to the bounds from a
* constant definition.
*
* @param constant
* the constant data instance
* @param def
* the definition
* @return <code>true</code> if the bounds match
*/
private boolean compareConstantBounds( Constant constant , ConstantDefinition def )
{
return constant.getMinValue( ) == def.minValue && constant.getMaxValue( ) == def.maxValue;
}
/**
* Updates a constant's value after bounds changed.
*
* This method, called when a constant data instance's bounds have been changed, verifies that
* the constant's current value matches the new bounds and modifies it if that is required.
*
* @param constant
* the constant data instance
* @param def
* the definition
* @return <code>true</code> if the value was updated
*/
private boolean updateValue( Constant constant , ConstantDefinition def )
{
if ( def.minValue != null && def.minValue > constant.getValue( ) ) {
constant.setValue( def.minValue );
return true;
} else if ( def.maxValue != null && def.maxValue < constant.getValue( ) ) {
constant.setValue( def.maxValue );
return true;
}
return false;
}
/**
* Notifies constant users that a specific constant value has been modified.
*
* @param changed
* the constant's name
*
* @see #notify(Set)
*/
private void notify( String changed )
{
Set< String > temp = new HashSet< String >( );
temp.add( changed );
this.notify( temp );
}
/**
* Notifies constant users that a set of constants has changed.
*
* Users of each specified constant are identified, and separated in two groups: users that have
* not received their initial notification and for which initial notification must be attempted,
* and already initialised users which will only receive updates on constants that actually
* changed.
*
* @param changed
* the names of the constants that have been changed.
*
* @see #notifyUpdate(ConstantsUser, Set)
* @see #attemptFirstNotification(ConstantsUser)
*/
private void notify( Set< String > changed )
{
Map< ConstantsUser , Set< String >> notifyChange = new HashMap< ConstantsUser , Set< String > >( );
Set< ConstantsUser > firstNotification = new HashSet< ConstantsUser >( );
// Build a map of users <-> notifications and a set of first notifications to attempt
for ( String ccn : changed ) {
Set< ConstantsUser > users = this.constantUsers.get( ccn );
if ( users == null ) {
continue;
}
for ( ConstantsUser user : users ) {
if ( this.notifiedUsers.contains( user ) ) {
Set< String > userChange = notifyChange.get( user );
if ( userChange == null ) {
userChange = new HashSet< String >( );
notifyChange.put( user , userChange );
}
userChange.add( ccn );
} else {
firstNotification.add( user );
}
}
}
// Attempt first notifications
for ( ConstantsUser user : firstNotification ) {
this.attemptFirstNotification( user );
}
// Send updates to the others
for ( ConstantsUser user : notifyChange.keySet( ) ) {
this.notifyUpdate( user , notifyChange.get( user ) );
}
}
/**
* Notifies a constant user of changes.
*
* This method retrieves the values of the listed constants, then notifies the specified user
* that they have changed. If an exception occurs in the user's notification method, an error is
* written to the server's log.
*
* @param user
* the constants user instance.
* @param changed
* the names of the constants for which a notification is to be sent.
*/
private void notifyUpdate( ConstantsUser user , Set< String > changed )
{
Map< String , Double > values = new HashMap< String , Double >( );
// Find updated values
for ( String cn : changed ) {
values.put( cn , this.registeredConstants.get( cn ).getValue( ) );
}
// Send notification
try {
user.setConstants( false , values );
} catch ( Throwable t ) {
this.sysLog.log( LogLevel.ERROR , "updating constant values caused an exception" , t ).flush( );
}
}
/**
* Attempts initial notification of constants values to a constants user.
*
* This method is called when constants registered to a not-yet-initialised constants user
* change. It attempts to retrieve the required values, returning directly if a constant is
* missing. If all values exist, the initial notification is sent. Should an exception occur, it
* is logged as an error and the method ends; otherwise the user is added to the set of
* initialised users.
*
* @param user
* the constants user to notify
*/
private void attemptFirstNotification( ConstantsUser user )
{
Map< String , Double > values = new HashMap< String , Double >( );
// Try to find all required values
for ( String cn : this.userConstants.get( user ) ) {
Constant cst = this.registeredConstants.get( cn );
if ( cst == null ) {
return;
}
values.put( cn , cst.getValue( ) );
}
// Send initial notification
try {
user.setConstants( true , values );
} catch ( Throwable t ) {
this.sysLog.log( LogLevel.ERROR , "registering initial constant values caused an exception" , t ).flush( );
return;
}
this.notifiedUsers.add( user );
}
/**
* Updates the list of categories from the list of constants.
*
* This method updates the list of categories by going through all known constants, creating an
* entry in the categories map for each category and associating it with the list of constants
* it contains.
*/
private void updateCategories( )
{
if ( this.categories != null ) {
return;
}
this.categories = new HashMap< String , List< Constant > >( );
for ( Constant c : this.registeredConstants.values( ) ) {
String cat = c.getCategory( );
List< Constant > cContents;
cContents = this.categories.get( cat );
if ( cContents == null ) {
cContents = new LinkedList< Constant >( );
this.categories.put( cat , cContents );
}
cContents.add( c );
}
}
/**
* Constants registration method.
*
* This method is responsible for updating the in-base constants data when new definitions are
* registered. It will add new constants or update existing one. Finally, if some of the updated
* constants are used by any constants user instance, it will send update notifications.
*
* @param definitions
* the constants to register
*/
synchronized void registerConstants( Collection< ConstantDefinition > definitions )
{
Set< String > toNotify = new HashSet< String >( );
final List< ConstantDefinition > toUpdate = new LinkedList< ConstantDefinition >( );
for ( ConstantDefinition def : definitions ) {
// Handle new definitions
Constant constant = this.registeredConstants.get( def.name );
if ( constant == null ) {
toUpdate.add( def );
toNotify.add( def.name );
this.sysLog.log( LogLevel.DEBUG , "registering constant " + def.name );
continue;
}
boolean sameDescription = this.compareConstantDescription( constant , def );
boolean sameBounds = this.compareConstantBounds( constant , def );
if ( sameDescription && sameBounds ) {
continue;
}
// Make sure the value is correct if the bounds have been changed
if ( !sameBounds && this.updateValue( constant , def ) ) {
toNotify.add( def.name );
}
toUpdate.add( def );
this.sysLog.log( LogLevel.DEBUG , "updating definition of constant " + def.name );
}
// Apply updates and notify users
if ( !toUpdate.isEmpty( ) ) {
this.sysLog.flush( );
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
updateConstants( toUpdate );
loadConstants( );
}
} );
if ( !toNotify.isEmpty( ) ) {
this.notify( toNotify );
}
}
}
/**
* Constants user registration
*
* This method registers (or re-registers) a constants user, indicating the constants used by
* the instance and for which update notifications should be sent. Once the user has been
* registered, an attempt at sending the initial notification is made.
*
* @param user
* the constants user to register
* @param constants
* the names of the constants for which notifications are required
*/
synchronized void registerUser( ConstantsUser user , Set< String > constants )
{
// If this user was already registered, unregister it
if ( this.userConstants.containsKey( user ) ) {
this.unregisterUser( user );
}
// Add user <-> constant maps
this.userConstants.put( user , new HashSet< String >( constants ) );
for ( String cn : constants ) {
Set< ConstantsUser > users;
users = this.constantUsers.get( cn );
if ( users == null ) {
users = new HashSet< ConstantsUser >( );
this.constantUsers.put( cn , users );
}
users.add( user );
}
// Try sending the user its initial notification
this.attemptFirstNotification( user );
}
/**
* Unregisters a constants user.
*
* This method removes a constants user from the manager, preventing further notifications to be
* sent.
*
* @param user
* the constants user to unregister
*/
synchronized void unregisterUser( ConstantsUser user )
{
Set< String > constants = this.userConstants.get( user );
if ( constants == null ) {
return;
}
this.userConstants.remove( user );
this.notifiedUsers.remove( user );
for ( String constant : constants ) {
Set< ConstantsUser > users = this.constantUsers.get( constant );
users.remove( user );
if ( users.isEmpty( ) ) {
this.constantUsers.remove( constant );
}
}
}
/**
* Gets the names of the constant categories.
*
* This method will make sure the constant categories map is up-to-date, then return a copy of
* its keys.
*
* @return the names of all constant categories
*/
synchronized Set< String > getCategories( )
{
this.updateCategories( );
return new HashSet< String >( this.categories.keySet( ) );
}
/**
* Accesses the constant data instances from a category.
*
* This method will make sure the constant categories map is up-to-date, then return a copy of
* the list corresponding to the specified category. If the category is not present in the map,
* <code>null</code> will be returned.
*
* @param cat
* the category's name
* @return the list of constant data instances, or <code>null</code> if the category is
* undefined
*/
synchronized List< Constant > getCategory( String cat )
{
this.updateCategories( );
List< Constant > listCst = this.categories.get( cat );
if ( listCst != null ) {
return new LinkedList< Constant >( listCst );
}
return null;
}
/**
* Modifies the value of a single constant.
*
* This method is called by administrative sessions in order to modify a single constant. It
* checks that the constant exists and that its new value is valid, then updates the database
* entry. Once this is done, it notifies the constant's users and returns the previous value.
*
* @param name
* the name of the constant to modify
* @param value
* the constant's new value
* @return the constant's previous value
* @throws UnknownConstantError
* if the specified constant does not exist
* @throws InvalidConstantValue
* if the specified value is out of bounds
*/
synchronized Double setValue( final String name , final Double value , final int admin )
throws UnknownConstantError , InvalidConstantValue
{
// Check name
Constant c = this.registeredConstants.get( name );
if ( c == null ) {
throw new UnknownConstantError( name );
}
// Check value
if ( c.getMinValue( ) != null && value < c.getMinValue( ) || c.getMaxValue( ) != null
&& value > c.getMaxValue( ) ) {
throw new InvalidConstantValue( );
}
// Store constant
try {
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
fSetConstant.execute( name , value.floatValue( ) , admin );
}
} );
} catch ( RuntimeException t ) {
this.sysLog.log( LogLevel.ERROR , "could not update constant " + c.getName( ) , t ).flush( );
throw t;
}
// Update in-memory copy
Double old = c.getValue( );
c.setValue( value );
// Notify constant users
this.notify( c.getName( ) );
return old;
}
}

View file

@ -0,0 +1,115 @@
package com.deepclone.lw.beans.sys;
import java.util.Collection;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.interfaces.eventlog.Logger;
import com.deepclone.lw.interfaces.sys.ConstantDefinition;
import com.deepclone.lw.interfaces.sys.ConstantsAdministration;
import com.deepclone.lw.interfaces.sys.ConstantsManager;
import com.deepclone.lw.interfaces.sys.ConstantsUser;
/**
* The constants manager bean initialises a {@link ConstantsData} instance, to which it delegates
* all operations.
*
* @author tseeker
*/
public class ConstantsManagerBean
implements ConstantsManager , InitializingBean
{
/** Transaction manager interface */
private TransactionTemplate tTemplate;
/** Logger bean */
private Logger logger;
/** Shared constants data instance */
private ConstantsData data;
private DataSource dataSource;
@Autowired( required = true )
public void setDataSource( DataSource dataSource )
{
this.dataSource = dataSource;
}
/**
* Sets the transaction manager interface (DI)
*
* @param transactionManager
* the transaction manager
*/
@Autowired( required = true )
public void setTransactionManager( PlatformTransactionManager transactionManager )
{
this.tTemplate = new TransactionTemplate( transactionManager );
}
/**
* Sets the logger bean interface (DI)
*
* @param logger
* the logger bean
*/
@Autowired( required = true )
public void setLogger( Logger logger )
{
this.logger = logger;
}
/** Creates the data storage and manipulation instance once the bean is ready */
@Override
public void afterPropertiesSet( )
{
this.data = new ConstantsData( this.dataSource , this.tTemplate , this.logger );
}
/* Documented in ConstantsManager interface */
@Override
public void registerConstants( Collection< ConstantDefinition > definitions )
{
this.data.registerConstants( definitions );
}
/* Documented in ConstantsManager interface */
@Override
public void registerUser( ConstantsUser user , Set< String > constants )
{
this.data.registerUser( user , constants );
}
/* Documented in ConstantsManager interface */
@Override
public void unregisterUser( ConstantsUser user )
{
this.data.unregisterUser( user );
}
/* Documented in ConstantsManager interface */
@Override
public ConstantsAdministration getAdminSession( int admin )
{
return new ConstantsAdministrationImpl( this.data , admin );
}
}

View file

@ -0,0 +1,202 @@
package com.deepclone.lw.beans.sys;
import java.util.LinkedList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import com.deepclone.lw.interfaces.sys.ConstantDefinition;
import com.deepclone.lw.interfaces.sys.ConstantsManager;
public class ConstantsRegistrarBean
{
@Autowired( required = true )
public void setConstantsManager( ConstantsManager cm )
{
List< ConstantDefinition > defs = new LinkedList< ConstantDefinition >( );
String cDesc;
// Default maximal age for log entries = 1 week
double oneWeek = 60 * 60 * 24 * 7;
// Initial values
cDesc = "Initial cash for new empires.";
defs.add( new ConstantDefinition( "game.initialCash" , "Initial values" , cDesc , 2000.0 , 0.0 , true ) );
cDesc = "Initial credits for new accounts.";
defs.add( new ConstantDefinition( "game.initialCredits" , "Initial values" , cDesc , 0.0 , 0.0 , true ) );
// Misc. game-related values
cDesc = "Game updates - batch size.";
defs.add( new ConstantDefinition( "game.batchSize" , "Game (misc)" , cDesc , 20.0 , 1.0 , true ) );
cDesc = "Population growth factor.";
defs.add( new ConstantDefinition( "game.growthFactor" , "Game (misc)" , cDesc , 50.0 , 1.0 , true ) );
cDesc = "Increase to the population growth factor caused by reanimation centres.";
defs.add( new ConstantDefinition( "game.growthFactor.rCentre" , "Game (misc)" , cDesc , 10.0 , 1.0 , true ) );
cDesc = "Time to abandon a planet (in ticks).";
defs.add( new ConstantDefinition( "game.timeToAbandon" , "Game (misc)" , cDesc , 1440.0 , 1.0 , true ) );
cDesc = "Fleet damage ratio per day when debt and upkeep are equal.";
defs.add( new ConstantDefinition( "game.debt.fleet" , "Game (misc)" , cDesc , 0.3 , 0.001 , 0.999 ) );
cDesc = "Buildings damage ratio per day when debt and upkeep are equal.";
defs.add( new ConstantDefinition( "game.debt.buildings" , "Game (misc)" , cDesc , 0.1 , 0.001 , 0.999 ) );
// Universe
cDesc = "Ratio of free planets below which universe expansion is triggered.";
defs.add( new ConstantDefinition( "game.universe.minFreeRatio" , "Universe" , cDesc , 0.1 , 0.02 , 0.98 ) );
cDesc = "Amount of pictures available to generate planets.";
defs.add( new ConstantDefinition( "game.universe.pictures" , "Universe" , cDesc , 200.0 , 1.0 , true ) );
cDesc = "Initial population on first-generation planets.";
defs.add( new ConstantDefinition( "game.universe.initialPopulation" , "Universe" , cDesc , 2000.0 , 1000.0 ,
true ) );
cDesc = "Initial universe size (offset relative to the centre).";
defs.add( new ConstantDefinition( "game.universe.initialSize" , "Universe" , cDesc , 1.0 , 1.0 , true ) );
// Happiness
String[] hcNames = {
"noEmployment" , "employmentLimit" , "noDefence" , "defenceLimit" , "popPerDefencePoint" ,
"idealEmpireSize" , "smallEmpire" , "eSizeLimit" , "strike" , "relativeChange" , "maxAbsoluteChange"
};
for ( int i = 0 ; i < hcNames.length ; i++ ) {
hcNames[ i ] = "game.happiness." + hcNames[ i ];
}
String cat = "Happiness";
cDesc = "Employment happiness level when there is no employment.";
defs.add( new ConstantDefinition( hcNames[ 0 ] , cat , cDesc , 0.7 , 0.05 , 0.99 ) );
cDesc = "Employment ratio beyond which things get tough.";
defs.add( new ConstantDefinition( hcNames[ 1 ] , cat , cDesc , 2.0 , 1.1 , true ) );
cDesc = "Defence happiness level when there is no defence.";
defs.add( new ConstantDefinition( hcNames[ 2 ] , cat , cDesc , 0.5 , 0.05 , 0.99 ) );
cDesc = "Defence ratio beyond which things get tough.";
defs.add( new ConstantDefinition( hcNames[ 3 ] , cat , cDesc , 4.0 , 1.1 , true ) );
cDesc = "Population covered by a single defence power point.";
defs.add( new ConstantDefinition( hcNames[ 4 ] , cat , cDesc , 5.0 , 1.0 , true ) );
cDesc = "Ideal empire size.";
defs.add( new ConstantDefinition( hcNames[ 5 ] , cat , cDesc , 10.0 , 2.0 , true ) );
cDesc = "Happiness level for 1-planet empires.";
defs.add( new ConstantDefinition( hcNames[ 6 ] , cat , cDesc , 0.9 , 0.5 , true ) );
cDesc = "Empire size limit (relative to the ideal size) beyond which things get tough.";
defs.add( new ConstantDefinition( hcNames[ 7 ] , cat , cDesc , 2.0 , 1.1 , true ) );
cDesc = "Happiness level below which strikes begin.";
defs.add( new ConstantDefinition( hcNames[ 8 ] , cat , cDesc , 0.25 , 0.0 , 1.0 ) );
cDesc = "Happiness change at each update, relative to the size of the population.";
defs.add( new ConstantDefinition( hcNames[ 9 ] , cat , cDesc , 0.001 , 0.00001 , 0.99999 ) );
cDesc = "Maximal population units for which happiness will change.";
defs.add( new ConstantDefinition( hcNames[ 10 ] , cat , cDesc , 1000.0 , 1.0 , true ) );
// Work and income
String[] wcNames = {
"population" , "factory" , "strikeEffect" , "wuPerPopUnit" , "destructionRecovery" , "destructionWork" ,
"rpPerPopUnit" , "cancelRecovery"
};
for ( int i = 0 ; i < wcNames.length ; i++ ) {
wcNames[ i ] = "game.work." + wcNames[ i ];
}
cat = "Work & income";
cDesc = "Daily income per population unit.";
defs.add( new ConstantDefinition( wcNames[ 0 ] , cat , cDesc , 0.2 , 0.01 , true ) );
cDesc = "Daily income per factory production unit.";
defs.add( new ConstantDefinition( wcNames[ 1 ] , cat , cDesc , 250.0 , 0.01 , true ) );
cDesc = "Proportion of the base income that disappears if the whole population is on strike.";
defs.add( new ConstantDefinition( wcNames[ 2 ] , cat , cDesc , 1.0 , 0.0 , 1.0 ) );
cDesc = "Work units generated by each population unit.";
defs.add( new ConstantDefinition( wcNames[ 3 ] , cat , cDesc , 0.01 , 0.001 , true ) );
cDesc = "Proportion of a building's cost that is recovered when it is destroyed.";
defs.add( new ConstantDefinition( wcNames[ 4 ] , cat , cDesc , 0.1 , 0.01 , 0.99 ) );
cDesc = "Proportion of a building's construction work units required to destroy it";
defs.add( new ConstantDefinition( wcNames[ 5 ] , cat , cDesc , 0.25 , 0.01 , 1.0 ) );
cDesc = "Research points per population unit.";
defs.add( new ConstantDefinition( wcNames[ 6 ] , cat , cDesc , 0.50 , 0.01 , true ) );
cDesc = "Proportion of queue investments that is recovered when flushing the queue.";
defs.add( new ConstantDefinition( wcNames[ 7 ] , cat , cDesc , 0.1 , 0.01 , 1.0 ) );
// Vacation mode
cDesc = "Initial vacation credits.";
defs.add( new ConstantDefinition( "vacation.initial" , "Vacation mode" , cDesc , 4320.0 , 0.0 , true ) );
cDesc = "Delay before vacation mode is activated (seconds).";
defs.add( new ConstantDefinition( "vacation.delay" , "Vacation mode" , cDesc , 21600.0 , 3600.0 , true ) );
cDesc = "Maximal vacation credits.";
defs.add( new ConstantDefinition( "vacation.max" , "Vacation mode" , cDesc , 259200.0 , 10000.0 , true ) );
cDesc = "Vacation cost (credits / minute).";
defs.add( new ConstantDefinition( "vacation.cost" , "Vacation mode" , cDesc , 3.0 , 2.0 , 20.0 ) );
cDesc = "Income/upkeep divider used when vacation mode is active.";
defs.add( new ConstantDefinition( "vacation.cashDivider" , "Vacation mode" , cDesc , 3.0 , 1.1 , 10.0 ) );
cDesc = "Research points divider used when vacation mode is active.";
defs.add( new ConstantDefinition( "vacation.researchDivider" , "Vacation mode" , cDesc , 10.0 , 1.1 , 50.0 ) );
// Map names
cDesc = "Minimal delay between map object renaming.";
defs.add( new ConstantDefinition( "map.names.minDelay" , "Map names" , cDesc , 15.0 , 1.0 , true ) );
cDesc = "Units for the minimal delay between map object renaming (seconds).";
defs.add( new ConstantDefinition( "map.names.minDelay.units" , "Map names" , cDesc , 86400.0 , 1.0 , true ) );
// Log clean-up
cDesc = "Maximal age of administration log entries, in seconds.";
defs.add( new ConstantDefinition( "log.maxAge.admin" , "Logs" , cDesc , oneWeek , 1.0 , true ) );
cDesc = "Maximal age of account log entries, in seconds.";
defs.add( new ConstantDefinition( "log.maxAge.users" , "Logs" , cDesc , oneWeek , 1.0 , true ) );
cDesc = "Maximal age of system log entries, in seconds.";
defs.add( new ConstantDefinition( "log.maxAge.sys" , "Logs" , cDesc , oneWeek , 1.0 , true ) );
// Battles
cDesc = "Amount of ticks before a battle reaches full intensity.";
defs.add( new ConstantDefinition( "game.battle.timeToFullIntensity" , "Battles" , cDesc , 30.0 , 0.0 , true ) );
cDesc = "Initial intensity of a battle.";
defs.add( new ConstantDefinition( "game.battle.initialIntensity" , "Battles" , cDesc , 0.25 , 0.0 , 1.0 ) );
cDesc = "Defence bonus.";
defs.add( new ConstantDefinition( "game.battle.defenceBonus" , "Battles" , cDesc , 0.1 , 0.0 , true ) );
cDesc = "Damage / power unit / minute";
defs.add( new ConstantDefinition( "game.battle.damage" , "Battles" , cDesc , 0.01 , 0.001 , true ) );
cDesc = "Proportion of random damage variation.";
defs.add( new ConstantDefinition( "game.battle.randomDamage" , "Battles" , cDesc , 0.05 , 0.0 , 0.9 ) );
// Ticker
cDesc = "Interval between ticks with the highest frequency, in milliseconds.";
defs.add( new ConstantDefinition( "ticker.interval" , "Ticker" , cDesc , 5000.0 , 1000.0 , true ) );
// Accounts
cDesc = "Minimal interval between address change requests (seconds)";
defs.add( new ConstantDefinition( "accounts.acrDelay" , "Accounts" , cDesc , 14400.0 , 1.0 , true ) );
cDesc = "Minimal interval between password recovery requests (seconds)";
defs.add( new ConstantDefinition( "accounts.prrDelay" , "Accounts" , cDesc , 8800.0 , 1.0 , true ) );
cDesc = "Time before a new or reactivated account is deleted / disabled (seconds)";
defs.add( new ConstantDefinition( "accounts.cacDelay" , "Accounts" , cDesc , 86400.0 , 3600.0 , true ) );
cDesc = "Delay between clicking the 'Quit' button and account de-activation (seconds)";
defs.add( new ConstantDefinition( "accounts.quitDelay" , "Accounts" , cDesc , 86400.0 , 3600.0 , true ) );
cDesc = "Delay between a ban and the deletion of the player's empire (seconds)";
defs.add( new ConstantDefinition( "accounts.banDelay" , "Accounts" , cDesc , 178000.0 , 3600.0 , true ) );
cDesc = "Delay before a ban request expires (seconds)";
defs.add( new ConstantDefinition( "accounts.banExpiration" , "Accounts" , cDesc , oneWeek , 3600.0 , true ) );
// Accounts - warnings
cDesc = "Amount of warnings that triggers an automatic ban request.";
defs.add( new ConstantDefinition( "accounts.warnings.autoBan" , "Accounts - Warnings" , cDesc , 3.0 , 1.0 , true ) );
cDesc = "Period after a warning is received during which additional warnings will be ignored (seconds).";
defs.add( new ConstantDefinition( "accounts.warnings.grace" , "Accounts - Warnings" , cDesc , 7200.0 , 60.0 , true ) );
cDesc = "Time after which warnings are decreased (expressed in units as defined by a.w.expiration.units).";
defs.add( new ConstantDefinition( "accounts.warnings.expiration" , "Accounts - Warnings" , cDesc , 60.0 , 1.0 , true ) );
cDesc = "Units used to express warning expiration time (seconds).";
defs.add( new ConstantDefinition( "accounts.warnings.expiration.units" , "Accounts - Warnings" , cDesc , 86400.0 , 1.0 , true ) );
// Account inactivity
cDesc = "Time units (seconds)";
defs.add( new ConstantDefinition( "accounts.inactivity.units" , "Accounts - Inactivity" , cDesc , oneWeek , 3600.0 , true ) );
cDesc = "Time after which the inactivity warning e-mail is to be sent, expressed using units defined by a.i.units.";
defs.add( new ConstantDefinition( "accounts.inactivity.warningMail" , "Accounts - Inactivity" , cDesc , 3.0 , 1.0 , true ) );
cDesc = "Time between the inactivity warning e-mail and actual account deletion, expressed using units defined by a.i.units.";
defs.add( new ConstantDefinition( "accounts.inactivity.deletion" , "Accounts - Inactivity" , cDesc , 1.0 , 1.0 , true ) );
// Bug reports
cDesc = "Amount of credits granted for low priority bug reports.";
defs.add( new ConstantDefinition( "bugtracker.lowCredits" , "Bug tracking system" , cDesc , 1.0 , 1.0 , true ) );
cDesc = "Amount of credits granted for normal bug reports.";
defs.add( new ConstantDefinition( "bugtracker.mediumCredits" , "Bug tracking system" , cDesc , 2.0 , 1.0 , true ) );
cDesc = "Amount of credits granted for critical bug reports.";
defs.add( new ConstantDefinition( "bugtracker.highCredits" , "Bug tracking system" , cDesc , 3.0 , 1.0 , true ) );
cm.registerConstants( defs );
}
}

View file

@ -0,0 +1,251 @@
package com.deepclone.lw.beans.sys;
import java.net.InetAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.deepclone.lw.cmd.admin.users.SessionTerminationType;
import com.deepclone.lw.interfaces.session.ServerSession;
import com.deepclone.lw.interfaces.session.SessionTypeDefiner;
import com.deepclone.lw.session.Command;
import com.deepclone.lw.session.CommandResponse;
import com.deepclone.lw.session.SessionCommandException;
import com.deepclone.lw.session.SessionIdentifierException;
import com.deepclone.lw.session.SessionReference;
import com.deepclone.lw.session.SessionStateException;
import com.deepclone.lw.utils.RandomStringGenerator;
/**
* Session data.
*
* @author tseeker
*/
class ServerSessionData
implements ServerSession
{
/** Data store */
private Map< String , Object > storage = new HashMap< String , Object >( );
/** Session identifier */
private String identifier;
/** Definer for the session's type */
private SessionTypeDefiner definer;
/** Authentication challenge generator */
private RandomStringGenerator challengeGenerator;
/** Last authentication challenge */
private String lastChallenge;
/** Session expiration date */
private Date expire;
/** Termination reason */
SessionTerminationType termination = null;
/** IPv4 address of the client */
private InetAddress address;
/** Client type identifier */
private String client;
/**
* Initialises a new session.
*
* @param generator
* authentication challenge generator
* @param definer
* session type definer
* @param client
* type of the client interface initiating the session
* @param address
* IP address the session is being initiated from
*/
public ServerSessionData( RandomStringGenerator generator , SessionTypeDefiner definer , String client ,
InetAddress address )
{
this.challengeGenerator = generator;
this.definer = definer;
this.expire = new Date( new Date( ).getTime( ) + 5000L );
this.definer.initialise( this );
this.client = client;
this.address = address;
}
/**
* Sets the session's identifier
*
* @param id
* the session's identifier
*/
public void setIdentifier( String id )
{
this.identifier = id;
}
/* Documented in ServerSession interface */
@Override
public String getIdentifier( )
{
return this.identifier;
}
/* Documented in ServerSession interface */
@Override
public String getClient( )
{
return this.client;
}
/* Documented in ServerSession interface */
@Override
public InetAddress getAddress( )
{
return this.address;
}
/* Documented in ServerSession interface */
@Override
public String getChallenge( )
{
return this.lastChallenge;
}
/* Documented in ServerSession interface */
@Override
public void setExpirationDate( Date date )
{
this.expire = date;
}
/** @return the session's expiration date */
public Date getExpirationDate( )
{
return this.expire;
}
/**
* Sets the termination type and handles termination
*
* @param termination
* type of session termination
*/
synchronized public void handleTermination( SessionTerminationType termination )
{
if ( this.termination == null ) {
this.termination = termination;
this.definer.terminate( this , termination );
}
}
/* Documented in ServerSession interface */
@Override
public SessionTerminationType getTerminationType( )
{
return this.termination;
}
/* Documented in ServerSession interface */
@Override
public void terminate( )
{
this.handleTermination( SessionTerminationType.MANUAL );
}
@Override
public void terminate( SessionTerminationType reason )
{
this.handleTermination( reason );
}
/* Documented in ServerSession interface */
@Override
public void put( String key , Object value )
{
if ( value == null ) {
this.storage.remove( key );
} else {
this.storage.put( key , value );
}
}
/* Documented in ServerSession interface */
@Override
public < T > T get( String key , Class< T > type )
{
Object obj = this.storage.get( key );
try {
return ( obj == null ) ? null : type.cast( obj );
} catch ( ClassCastException e ) {
return null;
}
}
/**
* Generates a {@link SessionReference} from the session's current state.
*
* @return the new session reference
*/
synchronized public SessionReference toSessionReference( )
{
if ( this.termination != null ) {
return null;
}
boolean auth = this.definer.isAuthenticated( this );
String extra;
if ( auth ) {
extra = this.definer.getState( this );
} else {
extra = this.challengeGenerator.generate( );
this.lastChallenge = extra;
}
return new SessionReference( this.identifier , this.definer.getName( ) , auth , extra );
}
/** Attempts to authenticate the session */
synchronized public void authenticate( String identifier , String sha1Hash , String md5Hash )
throws SessionStateException , SessionIdentifierException
{
if ( this.termination != null ) {
throw new SessionIdentifierException( this.identifier );
}
this.definer.authenticate( this , identifier , sha1Hash , md5Hash );
}
/**
* Attempts to execute a command on the session
*/
synchronized public CommandResponse execute( Command command )
throws SessionStateException , SessionCommandException , SessionIdentifierException
{
if ( this.termination != null ) {
throw new SessionIdentifierException( this.identifier );
}
return this.definer.execute( this , command );
}
}

View file

@ -0,0 +1,396 @@
package com.deepclone.lw.beans.sys;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.deepclone.lw.cmd.admin.logs.LogLevel;
import com.deepclone.lw.cmd.admin.users.SessionTerminationType;
import com.deepclone.lw.interfaces.eventlog.Logger;
import com.deepclone.lw.interfaces.eventlog.SystemLogger;
import com.deepclone.lw.interfaces.session.*;
import com.deepclone.lw.interfaces.sys.Ticker;
import com.deepclone.lw.session.*;
import com.deepclone.lw.utils.RandomStringGenerator;
/**
* Session management bean implementation.
*
* <p>
* This class is the implementation of the session management bean. It is responsible for creating
* sessions, using its collection of type definers, for accessing the sessions, and for making them
* expire as required.
*
* @author tseeker
*/
public class SessionManagerBean
implements SessionManager , DisposableBean
{
/** Session type definers, by name */
private final Map< String , WeakReference< SessionTypeDefiner > > types = new HashMap< String , WeakReference< SessionTypeDefiner > >( );
/** Sessions, by identifier */
private final Map< String , ServerSessionData > sessions = new HashMap< String , ServerSessionData >( );
/** Random string generator for session identifiers */
private RandomStringGenerator sessionIDGenerator;
/** Random string generator for authentication challenges */
private RandomStringGenerator challengeGenerator;
/** Session clean-up task, registered to the {@link Ticker} */
private Runnable cleanupTask;
/** System logger for the session manager */
private SystemLogger logger;
/**
* Sets the generator for session identifiers (DI)
*
* @param rsg
* reference to the random string generator to use for session identifiers
*/
@Autowired( required = true )
@Qualifier( "sessionIdentifiers" )
public void setSessionIDGenerator( RandomStringGenerator rsg )
{
this.sessionIDGenerator = rsg;
}
/**
* Sets the generator for authentication challenges (DI)
*
* @param rsg
* reference to the random string generator to use for authentication challenges
*/
@Autowired( required = true )
@Qualifier( "authChallenges" )
public void setChallangeGenerator( RandomStringGenerator rsg )
{
this.challengeGenerator = rsg;
}
/**
* Initialises the session clean-up task (DI)
*
* @param ticker
* reference to the ticker bean
*/
@Autowired( required = true )
public void setTicker( Ticker ticker )
{
this.cleanupTask = new Runnable( ) {
@Override
public void run( )
{
purgeSessions( );
}
};
ticker.registerTask( Ticker.Frequency.HIGH , "Sessions clean-up" , this.cleanupTask );
}
/**
* Initialises the system logger (DI)
*
* @param logger
* reference to the logger bean
*/
@Autowired( required = true )
public void setLogger( Logger logger )
{
this.logger = logger.getSystemLogger( "SessionManager" );
}
/**
* Removes the reference to the clean-up task.
*/
@Override
public void destroy( )
{
this.cleanupTask = null;
synchronized ( this.sessions ) {
for ( ServerSessionData data : this.sessions.values( ) ) {
try {
this.logger.log( LogLevel.DEBUG , "expiring '" + data.getIdentifier( ) + "'" ).flush( );
data.handleTermination( SessionTerminationType.EXPIRED );
} catch ( RuntimeException e ) {
this.logger.log( LogLevel.ERROR , "error while terminating '" + data.getIdentifier( ) + "'" , e )
.flush( );
}
}
}
}
/**
* Finds the session type definer corresponding to a type name.
*
* @param type
* the name of the session type
* @return the session type definer, or <code>null</code> if it doesn't exist
*/
private SessionTypeDefiner getTypeDefiner( String type )
{
SessionTypeDefiner definer = null;
synchronized ( this.types ) {
WeakReference< SessionTypeDefiner > ref = this.types.get( type );
if ( ref != null ) {
definer = ref.get( );
if ( definer == null ) {
this.types.remove( type );
}
}
}
return definer;
}
/**
* Allocates a session identifier.
*
* <p>
* This method generates an unique session identifier for the specified session, setting the
* session's identifier and storing it in the {@link #sessions} map.
*
* @param data
* the session that needs to be initialised
*/
private void allocateSessionIdentifier( ServerSessionData data )
{
synchronized ( this.sessions ) {
String id;
do {
id = this.sessionIDGenerator.generate( );
} while ( this.sessions.containsKey( id ) );
data.setIdentifier( id );
this.sessions.put( id , data );
}
}
/**
* Retrieves a session from an identifier.
*
* <p>
* This method attempts to retrieve a session from the {@link #sessions} map. If the session
* exists but is expired, it will be removed and the method will behave as if it didn't exist.
*
* @param id
* the session's identifier
* @return the session's data
* @throws SessionIdentifierException
* if the session doesn't exist or has expired.
*/
private ServerSessionData getSession( String id )
throws SessionIdentifierException
{
ServerSessionData data;
synchronized ( this.sessions ) {
data = this.sessions.get( id );
if ( data == null ) {
this.logger.log( LogLevel.INFO , "session '" + id + "' not found" ).flush( );
throw new SessionIdentifierException( id );
}
if ( data.getExpirationDate( ).before( new Date( ) ) ) {
this.logger.log( LogLevel.INFO , "session '" + id + "' found but expired" ).flush( );
ServerSessionData session = this.sessions.remove( id );
session.handleTermination( SessionTerminationType.EXPIRED );
throw new SessionIdentifierException( id );
}
}
return data;
}
/**
* Removes expired sessions.
*
* <p>
* This method goes through all registered sessions, removing them from the {@link #sessions}
* map if they have expired.
*/
private void purgeSessions( )
{
Date now = new Date( );
List< ServerSessionData > toRemove = new LinkedList< ServerSessionData >( );
synchronized ( this.sessions ) {
for ( ServerSessionData session : this.sessions.values( ) ) {
if ( session.getExpirationDate( ).before( now ) ) {
toRemove.add( session );
}
}
for ( ServerSessionData session : toRemove ) {
this.sessions.remove( session.getIdentifier( ) );
}
}
for ( ServerSessionData session : toRemove ) {
this.handleTermination( session , SessionTerminationType.EXPIRED );
}
if ( !toRemove.isEmpty( ) ) {
this.logger.log( LogLevel.TRACE , toRemove.size( ) + " session(s) removed" ).flush( );
}
}
private void handleTermination( ServerSessionData session , SessionTerminationType reason )
{
try {
session.handleTermination( reason );
} catch ( RuntimeException e ) {
this.logger.log( LogLevel.ERROR , "error while terminating '" + session.getIdentifier( ) + "'" , e )
.flush( );
}
}
/* Documented in SessionManager interface */
@Override
public void registerSessionType( SessionTypeDefiner definer )
{
synchronized ( this.types ) {
this.types.put( definer.getName( ) , new WeakReference< SessionTypeDefiner >( definer ) );
}
this.logger.log( LogLevel.INFO , "registered session type definer '" + definer.getName( ) + "'" ).flush( );
}
/* Documented in SessionAccessor interface */
@Override
public SessionReference create( String type , String client , InetAddress address )
throws SessionInternalException
{
SessionTypeDefiner definer = this.getTypeDefiner( type );
if ( definer == null ) {
this.logger.log( LogLevel.WARNING , "no such session type '" + type + "'" ).flush( );
return null;
}
ServerSessionData data;
try {
data = new ServerSessionData( this.challengeGenerator , definer , client , address );
} catch ( RuntimeException e ) {
this.logger.log( LogLevel.ERROR , "error while creating session with type '" + type + "'" , e ).flush( );
throw new SessionInternalException( false , e );
}
this.allocateSessionIdentifier( data );
this.logger.log( LogLevel.INFO , "session " + data.getIdentifier( ) + " created (type " + type + ")" ).flush( );
return data.toSessionReference( );
}
/* Documented in SessionAccessor interface */
@Override
public SessionReference authenticate( String session , String identifier , String sha1Hash , String md5Hash )
throws SessionIdentifierException , SessionStateException , SessionInternalException
{
ServerSessionData data = this.getSession( session );
this.logger.log( LogLevel.TRACE , "attempting to authenticate session '" + session + "'" ).flush( );
try {
data.authenticate( identifier , sha1Hash , md5Hash );
} catch ( SessionIdentifierException e ) {
synchronized ( this.sessions ) {
this.sessions.remove( session );
}
throw e;
} catch ( RuntimeException e ) {
this.logger.log( LogLevel.ERROR , "error while authenticating session '" + session + "'" , e ).flush( );
throw new SessionInternalException( false , e );
}
if ( data.getTerminationType( ) != null ) {
synchronized ( this.sessions ) {
this.logger.log( LogLevel.INFO , "terminated '" + session + "' during authentication" ).flush( );
this.sessions.remove( session );
}
}
return data.toSessionReference( );
}
/* Documented in SessionAccessor interface */
@Override
public SessionResponse executeCommand( String session , Command command )
throws SessionIdentifierException , SessionStateException , SessionCommandException ,
SessionInternalException
{
ServerSessionData data = this.getSession( session );
this.logger.log( LogLevel.DEBUG , "executing command " + command.getClass( ) + " on '" + session + "'" )
.flush( );
CommandResponse response;
try {
response = data.execute( command );
} catch ( SessionIdentifierException e ) {
synchronized ( this.sessions ) {
this.sessions.remove( session );
}
throw e;
} catch ( RuntimeException e ) {
this.logger.log( LogLevel.ERROR , "error while executing command on '" + session + "'" , e ).flush( );
throw new SessionInternalException( false , e );
}
if ( data.getTerminationType( ) != null ) {
this.logger.log( LogLevel.INFO , "terminated '" + session + "' during command execution" ).flush( );
synchronized ( this.sessions ) {
this.sessions.remove( session );
}
}
return new SessionResponse( data.toSessionReference( ) , response );
}
/* Documented in SessionAccessor interface */
@Override
public void terminate( String session )
throws SessionIdentifierException , SessionInternalException
{
ServerSessionData data;
synchronized ( this.sessions ) {
data = this.sessions.remove( session );
}
if ( data == null ) {
throw new SessionIdentifierException( session );
} else if ( data.getExpirationDate( ).before( new Date( ) ) ) {
data.handleTermination( SessionTerminationType.EXPIRED );
throw new SessionIdentifierException( session );
}
try {
data.handleTermination( SessionTerminationType.MANUAL );
this.logger.log( LogLevel.INFO , "terminated '" + session + "'" ).flush( );
} catch ( RuntimeException e ) {
this.logger.log( LogLevel.ERROR , "error while terminating '" + session + "'" , e ).flush( );
throw new SessionInternalException( false , e );
}
}
}

View file

@ -0,0 +1,302 @@
package com.deepclone.lw.beans.sys;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.interfaces.sys.MaintenanceData;
import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
import com.deepclone.lw.interfaces.sys.SystemStatus;
import com.deepclone.lw.interfaces.sys.TickStatusException;
import com.deepclone.lw.sqld.sys.Status;
import com.deepclone.lw.utils.StoredProc;
public class SystemStatusBean
implements SystemStatus
{
/** Database interface */
private SimpleJdbcTemplate dTemplate;
/** Transaction template */
private TransactionTemplate tTemplate;
/** System status record */
private Status status = null;
/** Current maintenance mode record */
private MaintenanceData maintenance = null;
private SimpleJdbcCall doStartTick;
private SimpleJdbcCall doCheckTick;
private StoredProc fEnterMaintenanceMode;
private StoredProc fExtendMaintenanceMode;
private StoredProc fExitMaintenanceMode;
@Autowired( required = true )
public void setDataSource( DataSource dataSource )
{
this.dTemplate = new SimpleJdbcTemplate( dataSource );
this.fEnterMaintenanceMode = new StoredProc( dataSource , "sys" , "enter_maintenance_mode" );
this.fEnterMaintenanceMode.addParameter( "admin_id" , Types.INTEGER );
this.fEnterMaintenanceMode.addParameter( "reason" , Types.VARCHAR );
this.fEnterMaintenanceMode.addParameter( "duration" , Types.INTEGER );
this.fEnterMaintenanceMode.addOutput( "success" , Types.BOOLEAN );
this.fExtendMaintenanceMode = new StoredProc( dataSource , "sys" , "extend_maintenance_mode" );
this.fExtendMaintenanceMode.addParameter( "admin_id" , Types.INTEGER );
this.fExtendMaintenanceMode.addParameter( "duration" , Types.INTEGER );
this.fExtendMaintenanceMode.addOutput( "success" , Types.BOOLEAN );
this.fExitMaintenanceMode = new StoredProc( dataSource , "sys" , "exit_maintenance_mode" );
this.fExitMaintenanceMode.addParameter( "admin_id" , Types.INTEGER );
this.fExitMaintenanceMode.addOutput( "success" , Types.BOOLEAN );
this.doStartTick = new SimpleJdbcCall( dataSource );
this.doStartTick.withCatalogName( "sys" ).withFunctionName( "start_tick" );
this.doStartTick.withoutProcedureColumnMetaDataAccess( );
this.doStartTick.addDeclaredParameter( new SqlOutParameter( "tick_id" , Types.BIGINT ) );
this.doCheckTick = new SimpleJdbcCall( dataSource );
this.doCheckTick.withCatalogName( "sys" ).withFunctionName( "check_stuck_tick" );
this.doCheckTick.withoutProcedureColumnMetaDataAccess( );
this.doCheckTick.addDeclaredParameter( new SqlOutParameter( "tick_id" , Types.BIGINT ) );
}
/**
* Sets the transaction manager interface (DI)
*
* @param transactionManager
* the transaction manager
*/
@Autowired( required = true )
public void setTransactionManager( PlatformTransactionManager transactionManager )
{
this.tTemplate = new TransactionTemplate( transactionManager );
}
private void loadStatus( )
{
String sql = "SELECT * FROM sys.status";
RowMapper< Status > mapper = new RowMapper< Status >( ) {
@Override
public Status mapRow( ResultSet rs , int rowNum )
throws SQLException
{
Status s = new Status( );
s.setNextTickIdentifier( rs.getLong( "next_tick" ) );
s.setCurrentTick( rs.getLong( "current_tick" ) );
s.setLastMsgRecap( rs.getTimestamp( "last_msg_recap" ) );
s.setLastAdminRecap( rs.getTimestamp( "last_admin_recap" ) );
s.setLastErrorCheck( rs.getTimestamp( "last_error_recap" ) );
s.setMaintenanceStart( rs.getTimestamp( "maintenance_start" ) );
s.setMaintenanceEnd( rs.getTimestamp( "maintenance_end" ) );
s.setMaintenanceReason( rs.getString( "maintenance_text" ) );
return s;
}
};
this.status = this.dTemplate.queryForObject( sql , mapper );
// Update maintenance status
if ( this.status.getMaintenanceReason( ) != null ) {
this.maintenance = new MaintenanceData( this.status.getMaintenanceStart( ) , this.status
.getMaintenanceEnd( ) , this.status.getMaintenanceReason( ) );
} else {
this.maintenance = null;
}
}
/**
* Initialises the system's status.
*
* <p>
* This method attempts to read the system's previous status from the database. If the entry
* doesn't exist, it is created. Some of the record's data is then stored locally and the bean
* is marked as initialised.
*/
private void initialise( )
{
if ( this.status != null ) {
return;
}
// Load status
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
loadStatus( );
}
} );
}
/* Documented in interface */
@Override
synchronized public MaintenanceData checkMaintenance( )
{
this.initialise( );
return this.maintenance;
}
/* Documented in interface */
@Override
synchronized public void startMaintenance( final int adminId , final String reason , final int duration )
throws MaintenanceStatusException
{
if ( duration <= 0 || reason == null ) {
throw new IllegalArgumentException( );
}
boolean s = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
@Override
public Boolean doInTransaction( TransactionStatus status )
{
Map< String , Object > m = fEnterMaintenanceMode.execute( adminId , reason , duration );
loadStatus( );
return (Boolean) m.get( "success" );
}
} );
if ( !s ) {
throw new MaintenanceStatusException( this.maintenance );
}
}
/* Documented in interface */
@Override
synchronized public void updateMaintenance( final int adminId , final int durationFromNow )
throws MaintenanceStatusException
{
if ( durationFromNow <= 0 ) {
throw new IllegalArgumentException( );
}
boolean s = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
@Override
public Boolean doInTransaction( TransactionStatus status )
{
Map< String , Object > m = fExtendMaintenanceMode.execute( adminId , durationFromNow );
loadStatus( );
return (Boolean) m.get( "success" );
}
} );
if ( !s ) {
throw new MaintenanceStatusException( );
}
}
/* Documented in interface */
@Override
synchronized public void endMaintenance( final int adminId )
throws MaintenanceStatusException
{
boolean s = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
@Override
public Boolean doInTransaction( TransactionStatus status )
{
Map< String , Object > m = fExitMaintenanceMode.execute( adminId );
loadStatus( );
return (Boolean) m.get( "success" );
}
} );
if ( !s ) {
throw new MaintenanceStatusException( );
}
}
/* Documented in interface */
@Override
synchronized public long startTick( )
throws TickStatusException , MaintenanceStatusException
{
Long tid = this.tTemplate.execute( new TransactionCallback< Long >( ) {
@Override
public Long doInTransaction( TransactionStatus status )
{
Map< String , Object > m = doStartTick.execute( );
loadStatus( );
return (Long) m.get( "tick_id" );
}
} );
if ( tid == null ) {
if ( this.maintenance != null ) {
throw new MaintenanceStatusException( this.maintenance );
} else {
throw new TickStatusException( this.status.getCurrentTick( ) );
}
}
return tid;
}
/* Documented in interface */
@Override
public Long checkStuckTick( )
throws MaintenanceStatusException
{
Long tid = this.tTemplate.execute( new TransactionCallback< Long >( ) {
@Override
public Long doInTransaction( TransactionStatus status )
{
Map< String , Object > m = doCheckTick.execute( );
loadStatus( );
return (Long) m.get( "tick_id" );
}
} );
if ( tid == null && this.maintenance != null ) {
throw new MaintenanceStatusException( this.maintenance );
}
return tid;
}
}

View file

@ -0,0 +1,160 @@
package com.deepclone.lw.beans.sys;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import com.deepclone.lw.cmd.admin.logs.LogLevel;
import com.deepclone.lw.interfaces.eventlog.Logger;
import com.deepclone.lw.interfaces.eventlog.SystemLogger;
import com.deepclone.lw.interfaces.sys.ConstantsManager;
import com.deepclone.lw.interfaces.sys.Ticker;
/**
* Task scheduler bean.
*
* <p>
* This class implements Legacy Worlds' task scheduler, used for most repetitive actions such as
* updating the game's state, cleaning logs or de-activating accounts.
*
* <ul>
* <li>Tasks with {@link Frequency#HIGH} are executed once every time the period is over.</li>
* <li>Tasks with {@link Frequency#MEDIUM} are executed once every 6 periods.</li>
* <li>Tasks with {@link Frequency#MINUTE} are executed once every 12 periods.</li>
* <li>Tasks with {@link Frequency#LOW} are executed once every 60 periods.</li>
* </ul>
*
* <p>
* <em>ticker.interval</em> defaults to 5 seconds.
*
* @author tseeker
*/
public class TickerBean
implements Ticker , InitializingBean , DisposableBean
{
/** System logger for the bean */
private SystemLogger logger;
/** Constants manager bean */
private ConstantsManager constantsManager;
/** Main thread for ticker control */
private TickerThread mainThread;
private TickerTaskStatusHandler tickerManager;
/**
* Sets the system logger (DI)
*
* @param logger
* reference to the logger bean
*/
@Autowired( required = true )
public void setLogger( Logger logger )
{
this.logger = logger.getSystemLogger( "Ticker" );
}
/**
* Sets the constants manager (DI)
*
* @param manager
* reference to the constants manager bean
*/
@Autowired( required = true )
public void setConstantsManager( ConstantsManager manager )
{
this.constantsManager = manager;
}
@Autowired( required = true )
public void setManager( TickerTaskStatusHandler manager )
{
this.tickerManager = manager;
}
/**
* Initialises the bean.
*
* <p>
* When all dependencies have been set, the bean will register the <em>ticker.interval</em>
* system constant. The main control thread will then be created and registered as an user of
* the constant (which causes the control thread to schedule itself using the timer).
*/
@Override
public void afterPropertiesSet( )
{
this.logger.log( LogLevel.INFO , "Initialisation" ).flush( );
// Create thread
this.mainThread = new TickerThread( this.logger , this.tickerManager );
// Register thread as a constants user
Set< String > use = new HashSet< String >( );
use.add( "ticker.interval" );
this.constantsManager.registerUser( this.mainThread , use );
}
/**
* Destroys the bean.
*
* This method will abort the main control thread's execution, and unregister it from the
* constants manager.
*/
@Override
public void destroy( )
{
this.logger.log( LogLevel.INFO , "Destruction" ).flush( );
this.mainThread.terminate( );
this.constantsManager.unregisterUser( this.mainThread );
this.constantsManager = null;
}
/* Documented in Ticker interface */
@Override
public void registerTask( Ticker.Frequency frequency , String name , Runnable task )
{
this.logger.log( LogLevel.DEBUG , "Registering task " + name + " at frequency " + frequency ).flush( );
int id = this.tickerManager.registerTask( name );
this.mainThread.registerTask( id , frequency , name , task );
}
/* Documented in Ticker interface */
@Override
public void pause( )
throws IllegalStateException
{
this.mainThread.pause( );
}
/* Documented in Ticker interface */
@Override
public void unpause( )
throws IllegalStateException
{
this.mainThread.unpause( );
}
/* Documented in Ticker interface */
@Override
public boolean isActive( )
{
return this.mainThread.isActive( );
}
}

View file

@ -0,0 +1,269 @@
package com.deepclone.lw.beans.sys;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.deepclone.lw.cmd.admin.tick.TickerTaskInfo;
import com.deepclone.lw.cmd.admin.tick.TickerTaskStatus;
import com.deepclone.lw.interfaces.sys.TickerManager;
import com.deepclone.lw.sqld.sys.TickerTaskRecord;
import com.deepclone.lw.utils.StoredProc;
public class TickerManagerBean
implements TickerManager , TickerTaskStatusHandler , InitializingBean
{
private final Map< Integer , TickerTaskRecord > tasks = new HashMap< Integer , TickerTaskRecord >( );
private final Map< String , Integer > taskIds = new HashMap< String , Integer >( );
private final Set< Integer > registered = new HashSet< Integer >( );
private TransactionTemplate tTemplate;
private SimpleJdbcTemplate dTemplate;
private final RowMapper< TickerTaskRecord > mTask;
private StoredProc fRegisterTask;
private StoredProc fSetTaskStarted;
private StoredProc fSetTaskRunning;
private StoredProc fScheduleTask;
public TickerManagerBean( )
{
this.mTask = new RowMapper< TickerTaskRecord >( ) {
@Override
public TickerTaskRecord mapRow( ResultSet rs , int rowNum )
throws SQLException
{
TickerTaskRecord task = new TickerTaskRecord( );
task.setId( rs.getInt( "id" ) );
task.setName( rs.getString( "task_name" ) );
task.setStatus( rs.getString( "status" ) );
task.setTimestamp( rs.getTimestamp( "auto_start" ) );
return task;
}
};
}
@Autowired( required = true )
public void setTransactionManager( PlatformTransactionManager manager )
{
this.tTemplate = new TransactionTemplate( manager );
}
@Autowired( required = true )
public void setDataSource( DataSource dataSource )
{
this.dTemplate = new SimpleJdbcTemplate( dataSource );
this.fRegisterTask = new StoredProc( dataSource , "sys" , "register_ticker_task" );
this.fRegisterTask.addParameter( "task_name" , Types.VARCHAR );
this.fRegisterTask.addOutput( "id" , Types.INTEGER );
this.fSetTaskStarted = new StoredProc( dataSource , "sys" , "set_task_started" );
this.fSetTaskStarted.addParameter( "id" , Types.INTEGER );
this.fSetTaskRunning = new StoredProc( dataSource , "sys" , "set_task_running" );
this.fSetTaskRunning.addParameter( "admin_id" , Types.INTEGER );
this.fSetTaskRunning.addParameter( "task_id" , Types.INTEGER );
this.fSetTaskRunning.addParameter( "running" , Types.BOOLEAN );
this.fScheduleTask = new StoredProc( dataSource , "sys" , "schedule_task" );
this.fScheduleTask.addParameter( "admin_id" , Types.INTEGER );
this.fScheduleTask.addParameter( "task_id" , Types.INTEGER );
this.fScheduleTask.addParameter( "time_to_start" , Types.BIGINT );
this.fScheduleTask.addOutput( "start_at" , Types.TIMESTAMP );
}
private List< TickerTaskRecord > getDBTasks( )
{
return this.tTemplate.execute( new TransactionCallback< List< TickerTaskRecord > >( ) {
@Override
public List< TickerTaskRecord > doInTransaction( TransactionStatus status )
{
return dTemplate.query( "SELECT * FROM sys.ticker" , mTask );
}
} );
}
private void checkAutoStart( final TickerTaskRecord record )
{
if ( record.getStatus( ).equals( "AUTO" ) ) {
Timestamp start = record.getTimestamp( );
if ( start.before( new Date( ) ) ) {
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
fSetTaskStarted.execute( record.getId( ) );
}
} );
record.setStatus( "RUNNING" );
record.setTimestamp( null );
}
}
}
private void setTaskRunning( final int administrator , final TickerTaskRecord record , final boolean running )
{
this.tTemplate.execute( new TransactionCallbackWithoutResult( ) {
@Override
protected void doInTransactionWithoutResult( TransactionStatus status )
{
fSetTaskRunning.execute( administrator , record.getId( ) , running );
}
} );
record.setStatus( running ? "RUNNING" : "STOPPED" );
record.setTimestamp( null );
}
@Override
public void afterPropertiesSet( )
{
List< TickerTaskRecord > rTasks = this.getDBTasks( );
for ( TickerTaskRecord task : rTasks ) {
this.tasks.put( task.getId( ) , task );
this.taskIds.put( task.getName( ) , task.getId( ) );
}
}
@Override
synchronized public List< TickerTaskInfo > getTasks( )
{
List< TickerTaskInfo > result = new LinkedList< TickerTaskInfo >( );
long now = new Date( ).getTime( );
for ( Integer id : this.registered ) {
TickerTaskRecord record = this.tasks.get( id );
this.checkAutoStart( record );
TickerTaskInfo info = new TickerTaskInfo( );
info.setId( record.getId( ) );
info.setName( record.getName( ) );
info.setStatus( TickerTaskStatus.valueOf( record.getStatus( ) ) );
if ( info.getStatus( ) == TickerTaskStatus.AUTO ) {
info.setStart( record.getTimestamp( ) );
info.setTimeToStart( ( record.getTimestamp( ).getTime( ) - now ) / 1000 );
}
result.add( info );
}
return result;
}
@Override
synchronized public void startTask( int administrator , int id )
{
TickerTaskRecord record = this.tasks.get( id );
if ( record == null || record.getStatus( ).equals( "RUNNING" ) ) {
return;
}
this.setTaskRunning( administrator , record , true );
}
@Override
synchronized public void stopTask( int administrator , int id )
{
TickerTaskRecord record = this.tasks.get( id );
if ( record == null || record.getStatus( ).equals( "STOPPED" ) ) {
return;
}
this.setTaskRunning( administrator , record , false );
}
@Override
synchronized public void setTaskStart( final int administrator , final int id , final long time )
{
if ( time <= 0 ) {
this.startTask( administrator , id );
return;
}
TickerTaskRecord record = this.tasks.get( id );
if ( record == null ) {
return;
}
Timestamp startAt = this.tTemplate.execute( new TransactionCallback< Timestamp >( ) {
@Override
public Timestamp doInTransaction( TransactionStatus status )
{
return (Timestamp) fScheduleTask.execute( administrator , id , time ).get( "start_at" );
}
} );
record.setStatus( "AUTO" );
record.setTimestamp( startAt );
}
@Override
synchronized public int registerTask( final String name )
{
Integer eId = this.taskIds.get( name );
if ( eId != null ) {
this.registered.add( eId );
return eId;
}
// Register new task
eId = this.tTemplate.execute( new TransactionCallback< Integer >( ) {
@Override
public Integer doInTransaction( TransactionStatus status )
{
return (Integer) fRegisterTask.execute( name ).get( "id" );
}
} );
TickerTaskRecord record = new TickerTaskRecord( );
record.setId( eId );
record.setName( name );
record.setStatus( "RUNNING" );
record.setTimestamp( null );
this.tasks.put( eId , record );
this.taskIds.put( name , eId );
return eId;
}
@Override
synchronized public boolean isTaskRunning( int id )
{
TickerTaskRecord record = this.tasks.get( id );
this.checkAutoStart( record );
return ( record.getStatus( ).equals( "RUNNING" ) );
}
}

View file

@ -0,0 +1,312 @@
package com.deepclone.lw.beans.sys;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.deepclone.lw.cmd.admin.logs.LogLevel;
import com.deepclone.lw.interfaces.eventlog.SystemLogger;
/**
* Ticker task execution class.
*
* <p>
* This class is responsible for both the execution and the control of ticker tasks.
*
* @author tseeker
*/
class TickerTask
extends Thread
{
/** Possible states of a task execution thread. */
private static enum State {
/** The task thread is being initialised. */
STARTING ,
/** The task thread is waiting for instructions */
WAITING ,
/** The task is currently being executed */
RUNNING ,
/** The task thread is exiting. */
EXITING ,
/** The task thread is finished. */
FINISHED
}
/** Reference to the system logger */
private final SystemLogger logger;
/** Name of the task */
private final String name;
/** Weak reference to the task's code */
private final WeakReference< Runnable > task;
/** Lock used to protect the task thread's state */
private final Lock lock = new ReentrantLock( );
/** Condition triggered whenever the thread's state changes */
private final Condition stateChanged = this.lock.newCondition( );
/** The thread's current state */
private State state = State.STARTING;
/**
* @param name
* the task's name
* @param task
* reference to the task's code
* @param logger
* system logger instance
*/
TickerTask( String name , Runnable task , SystemLogger logger )
{
super( "Ticker thread for '" + name + "'" );
this.name = name;
this.task = new WeakReference< Runnable >( task );
this.logger = logger;
}
/**
* Starts the task execution thread.
*
* <p>
* Starts the thread itself, then waits for the thread's state to change from its default
* {@link State#STARTING} value.
*/
@Override
public void start( )
{
super.start( );
this.lock.lock( );
try {
State state = this.getTaskState( );
while ( state == State.STARTING ) {
state = this.waitTaskState( );
}
} finally {
this.lock.unlock( );
}
}
/**
* Retrieves the task execution thread's state.
*
* <p>
* This method retrieves the task execution thread's state, locking {@link #lock} to prevent
* concurrent access.
*
* @return the task execution thread's current state.
*/
private State getTaskState( )
{
this.lock.lock( );
try {
return this.state;
} finally {
this.lock.unlock( );
}
}
/**
* Changes the task execution thread's state.
*
* <p>
* This method modifies the thread's state, locking {@link #lock} to prevent concurrent access
* and triggering {@link #stateChanged} once it's done.
*
* @param newState
* the thread's new state.
*/
private void setTaskState( State newState )
{
this.lock.lock( );
try {
this.state = newState;
this.stateChanged.signal( );
} finally {
this.lock.unlock( );
}
}
/**
* Waits for a change of state.
*
* <p>
* This method waits for the task's state to change using {@link #stateChanged}, then returns
* the new state.
*
* @return the task execution thread's new state.
*/
private State waitTaskState( )
{
this.lock.lock( );
try {
this.stateChanged.await( );
return this.state;
} catch ( InterruptedException e ) {
return this.state;
} finally {
this.lock.unlock( );
}
}
/**
* Task execution loop.
*
* <p>
* This method makes sure that the task is executed every time the state is set to
* {@link State#RUNNING}, setting it to {@link State#WAITING} when it is inactive.
*
* <p>
* If the thread's state is set to {@link State#EXITING}, or if the task code is no longer
* referenced, the method will exit, setting the state to {@link State#FINISHED}.
*/
@Override
public void run( )
{
while ( true ) {
State state;
this.lock.lock( );
try {
if ( this.getTaskState( ) == State.EXITING ) {
break;
}
this.setTaskState( State.WAITING );
state = this.waitTaskState( );
} finally {
this.lock.unlock( );
}
if ( state == State.EXITING || !this.runTask( ) ) {
break;
}
}
this.setTaskState( State.FINISHED );
}
/**
* Runs the task's code.
*
* <p>
* This method runs the task's code, if it is still referenced. All exceptions will be caught
* and logged, but they will not affect the main loop.
*
* @return <code>true</code> if the task was still referenced, <code>false</code> if it wasn't.
*/
private boolean runTask( )
{
Runnable task = this.task.get( );
if ( task == null ) {
this.logger.log( LogLevel.INFO , "task '" + this.name + "' is no longer referenced, exiting" )
.flush( );
return false;
}
try {
this.logger.log( LogLevel.TRACE , "task '" + this.name + "' started" ).flush( );
task.run( );
this.logger.log( LogLevel.TRACE , "task '" + this.name + "' ended" ).flush( );
} catch ( Throwable t ) {
this.logger.log( LogLevel.ERROR , "task '" + this.name + "' failed due to exception" , t ).flush( );
}
return true;
}
/**
* Triggers the task's execution.
*
* <p>
* This method attempts to trigger the task's execution. It will not do anything if the task
* thread is exiting or finished, but if the task is still running, a warning will be added to
* the log.
*/
void startTask( )
{
this.lock.lock( );
try {
State state = this.getTaskState( );
if ( state == State.EXITING || state == State.FINISHED ) {
return;
} else if ( state == State.RUNNING ) {
this.logger.log( LogLevel.WARNING , "task '" + this.name + "' didn't manage to run in time" )
.flush( );
return;
}
this.setTaskState( State.RUNNING );
} finally {
this.lock.unlock( );
}
}
/**
* Terminates the task execution thread.
*
* <p>
* This method shuts down the task execution thread, waiting until it has completed to return.
*/
void terminate( )
{
this.lock.lock( );
try {
State state = this.getTaskState( );
while ( state != State.FINISHED ) {
this.setTaskState( State.EXITING );
state = this.waitTaskState( );
}
} finally {
this.lock.unlock( );
}
this.logger.log( LogLevel.DEBUG , "task '" + this.name + "' terminated" ).flush( );
}
/**
* Waits until the task is done running.
*
* <p>
* This method waits until the task is no longer running. If the task is not running when the
* method is called, it will return immediately.
*/
void waitForTask( )
{
this.lock.lock( );
try {
State state = this.getTaskState( );
while ( state == State.RUNNING ) {
state = this.waitTaskState( );
}
} finally {
this.lock.unlock( );
}
}
/**
* @return <code>true</code> if the task execution thread is no longer running,
* <code>false</code> otherwise.
*/
boolean isFinished( )
{
return ( this.getTaskState( ) == State.FINISHED );
}
}

View file

@ -0,0 +1,12 @@
package com.deepclone.lw.beans.sys;
public interface TickerTaskStatusHandler
{
public int registerTask( String name );
public boolean isTaskRunning( int id );
}

View file

@ -0,0 +1,251 @@
package com.deepclone.lw.beans.sys;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import com.deepclone.lw.interfaces.eventlog.SystemLogger;
import com.deepclone.lw.interfaces.sys.ConstantsUser;
import com.deepclone.lw.interfaces.sys.Ticker;
/**
* Main ticker control thread.
*
* <p>
* This class implements the ticker bean's main control thread, executed through a Java
* {@link Timer} instance. It supports changing the timer's period when the <em>ticker.interval</em>
* constant is modified, and includes the code that pauses or reactivates the ticker.
*
* @author tseeker
*/
class TickerThread
implements ConstantsUser
{
private static class TaskStat
{
public final int id;
public final TickerTask task;
public final int modulo;
public int counter = 0;
public TaskStat( int id , TickerTask task , int modulo )
{
this.id = id;
this.task = task;
this.task.start( );
this.modulo = modulo;
}
}
private Timer timer;
private long interval = 0;
private boolean paused = false;
private int taskIndex = 0;
private int nTasks = 0;
private TaskStat[] tasks;
private TickerTaskStatusHandler handler;
private SystemLogger logger;
TickerThread( SystemLogger logger , TickerTaskStatusHandler handler )
{
this.timer = null;
this.tasks = new TaskStat[ 5 ];
this.handler = handler;
this.logger = logger;
}
synchronized private void runTasks( )
{
if ( this.paused ) {
return;
}
this.taskIndex = ( this.taskIndex + 1 ) % this.tasks.length;
TaskStat toExecute = this.tasks[ this.taskIndex ];
if ( toExecute == null || !this.handler.isTaskRunning( toExecute.id ) ) {
return;
}
toExecute.counter = ( toExecute.counter + 1 ) % toExecute.modulo;
if ( toExecute.counter != 0 ) {
return;
}
if ( toExecute.task.isFinished( ) ) {
this.tasks[ this.taskIndex ] = null;
this.nTasks--;
return;
}
toExecute.task.startTask( );
}
/**
* Updates the ticker's frequency.
*
* <p>
* When the <em>ticker.interval</em> constant is changed, this method will cancel the current
* scheduling and re-start it using the new interval.
*/
@Override
synchronized public void setConstants( boolean initial , Map< String , Double > values )
{
this.interval = Math.round( values.get( "ticker.interval" ) );
this.reschedule( );
}
private void reschedule( )
{
if ( this.interval == 0 ) {
return;
}
if ( this.timer != null ) {
this.timer.cancel( );
}
TimerTask task = new TimerTask( ) {
@Override
public void run( )
{
runTasks( );
}
};
this.timer = new Timer( "Main ticker thread" );
this.timer.scheduleAtFixedRate( task , 1 , this.interval / this.tasks.length );
}
synchronized void registerTask( int id , Ticker.Frequency frequency , String name , Runnable task )
{
if ( this.nTasks == this.tasks.length ) {
TaskStat[] nState = new TaskStat[ this.tasks.length * 2 ];
for ( int i = 0 ; i < this.tasks.length ; i++ ) {
nState[ i * 2 ] = this.tasks[ i ];
}
this.taskIndex *= 2;
this.tasks = nState;
this.reschedule( );
}
int modulo;
switch ( frequency ) {
case HIGH:
modulo = 1;
break;
case LOW:
modulo = 30;
break;
case MEDIUM:
modulo = 6;
break;
case MINUTE:
modulo = 12;
break;
default:
throw new RuntimeException( "Invalid timer frequency " + frequency );
}
for ( int i = 0 ; i < this.tasks.length ; i++ ) {
if ( this.tasks[ i ] == null ) {
this.tasks[ i ] = new TaskStat( id , new TickerTask( name , task , this.logger ) , modulo );
break;
}
}
this.nTasks ++;
}
/**
* Pauses the ticker.
*
* <p>
* This method implements the bean's pause method. If the ticker is not already paused, it will
* set the {@link #paused} flag, then wait for all running tasks to complete.
*
* @throws IllegalStateException
* if the ticker is already paused.
*/
synchronized void pause( )
throws IllegalStateException
{
if ( this.paused ) {
throw new IllegalStateException( "already paused" );
}
this.paused = true;
for ( int i = 0 ; i < this.tasks.length ; i++ ) {
TaskStat ts = this.tasks[ i ];
if ( ts == null ) {
continue;
}
if ( ts.task.isFinished( ) ) {
this.tasks[ i ] = null;
this.nTasks--;
continue;
}
ts.task.waitForTask( );
}
}
/**
* Restarts the ticker after it's been paused.
*
* @throws IllegalStateException
* if the ticker was not paused.
*/
synchronized void unpause( )
throws IllegalStateException
{
if ( !this.paused ) {
throw new IllegalStateException( "not paused" );
}
this.paused = false;
}
/**
* Terminates all tasks in all task sets, then stops the timer.
*/
synchronized void terminate( )
{
for ( TaskStat ts : this.tasks ) {
if ( ts == null ) {
continue;
}
ts.task.terminate( );
}
this.nTasks = 0;
this.tasks = new TaskStat[ 5 ];
if ( this.timer != null ) {
this.timer.cancel( );
}
}
/**
* @return <code>true</code> the ticker is currently running or <code>false</code> if it has
* been paused.
*/
synchronized boolean isActive( )
{
return !this.paused;
}
}

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<import resource="system/constants-manager-bean.xml" />
<import resource="system/constants-registrar-bean.xml" />
<import resource="system/ticker-bean.xml" />
<import resource="system/session-manager-bean.xml" />
<import resource="system/system-status-bean.xml" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="constantsManager" class="com.deepclone.lw.beans.sys.ConstantsManagerBean" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="constantsRegistrar" class="com.deepclone.lw.beans.sys.ConstantsRegistrarBean" />
</beans>

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="authChallengeGenerator" class="com.deepclone.lw.utils.RandomStringGenerator">
<qualifier value="authChallenges" />
<property name="length" value="100" />
<property name="characterSet" value="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" />
</bean>
<bean id="sessionIDGenerator" class="com.deepclone.lw.utils.RandomStringGenerator">
<qualifier value="sessionIdentifiers" />
<property name="length" value="50" />
<property name="characterSet" value="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" />
</bean>
<bean id="sessionManager" class="com.deepclone.lw.beans.sys.SessionManagerBean" />
</beans>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="systemStatus" class="com.deepclone.lw.beans.sys.SystemStatusBean" />
</beans>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="tickerManager" class="com.deepclone.lw.beans.sys.TickerManagerBean" />
<bean id="ticker" class="com.deepclone.lw.beans.sys.TickerBean" />
</beans>