Game updates improvements
* Added a set of tables which define game updates and their targets. These definitions replace the old enumerate type. Added a set of triggers which automatically create specific update tables, insert missing entries, etc... when game update types are being manipulated. * Removed manual insertion of game updates from empire creation function and universe generator. * Added registration of core update targets (i.e. planets and empires), updated all existing game update processing functions and added type registrations * Created Maven project for game updates control components, moved existing components from the -simple project, rewritten most of what they contained, added new components for server-side update batch processing
This commit is contained in:
parent
ba6a1e2b41
commit
56eddcc4f0
93 changed files with 4004 additions and 578 deletions
legacyworlds-server-beans-updates/src
0
legacyworlds-server-beans-updates/src/main/java/.empty
Normal file
0
legacyworlds-server-beans-updates/src/main/java/.empty
Normal file
|
@ -0,0 +1,238 @@
|
|||
package com.deepclone.lw.beans.updates;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
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.game.updates.GameUpdateProcessor;
|
||||
import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
|
||||
import com.deepclone.lw.interfaces.sys.MaintenanceStatusException;
|
||||
import com.deepclone.lw.interfaces.sys.SystemStatus;
|
||||
import com.deepclone.lw.interfaces.sys.TickStatusException;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Game update processor component
|
||||
*
|
||||
* <p>
|
||||
* This component implements game updates processing. It will update the system status as necessary,
|
||||
* checking for maintenance mode, and of course implement the necessary operations for game updates.
|
||||
*
|
||||
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
|
||||
*/
|
||||
class GameUpdateProcessorBean
|
||||
implements GameUpdateProcessor
|
||||
{
|
||||
/** System state management component */
|
||||
private SystemStatus systemStatus;
|
||||
|
||||
/** Logger used by this component */
|
||||
private SystemLogger logger;
|
||||
|
||||
/** Transaction template used to run update batches */
|
||||
private TransactionTemplate tTemplate;
|
||||
|
||||
/** Update processing database interface */
|
||||
private UpdatesDAO updatesDao;
|
||||
|
||||
/** Server-level update processors registry */
|
||||
private ServerProcessorRegistry registry;
|
||||
|
||||
/** Game update processor lock */
|
||||
private boolean executing;
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injector that sets the sytstem state management component
|
||||
*
|
||||
* @param systemStatus
|
||||
* the sytstem state management component
|
||||
*/
|
||||
@Autowired( required = true )
|
||||
public void setSystemStatus( SystemStatus systemStatus )
|
||||
{
|
||||
this.systemStatus = systemStatus;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injector that initialises the logger from the logging component
|
||||
*
|
||||
* @param logger
|
||||
* the logging component
|
||||
*/
|
||||
@Autowired( required = true )
|
||||
public void setLogger( Logger logger )
|
||||
{
|
||||
this.logger = logger.getSystemLogger( "GameUpdate" );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injector that initialises the transaction template from the transaction manager
|
||||
*
|
||||
* @param transactionManager
|
||||
* the transaction manager
|
||||
*/
|
||||
@Autowired( required = true )
|
||||
public void setTransactionManager( PlatformTransactionManager transactionManager )
|
||||
{
|
||||
this.tTemplate = new TransactionTemplate( transactionManager );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injector that sets the database access component for game updates
|
||||
*
|
||||
* @param updatesDao
|
||||
* the database access component for game updates
|
||||
*/
|
||||
@Autowired( required = true )
|
||||
public void setUpdatesDAO( UpdatesDAO updatesDao )
|
||||
{
|
||||
this.updatesDao = updatesDao;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injector that sets the processor registry
|
||||
*
|
||||
* @param registry
|
||||
* the processor registry
|
||||
*/
|
||||
@Autowired( required = true )
|
||||
public void setRegistry( ServerProcessorRegistry registry )
|
||||
{
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try and lock the processor
|
||||
*
|
||||
* <p>
|
||||
* Test the {@link #executing} field, setting it to <code>true</code> and returning
|
||||
* <code>true</code> if it wasn't or returning <code>false</code> if it was.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean tryLock( )
|
||||
{
|
||||
if ( this.executing ) {
|
||||
return false;
|
||||
}
|
||||
this.executing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unlock the processor
|
||||
*
|
||||
* <p>
|
||||
* Set {@link #executing} back to <code>false</code>. If it was already set to
|
||||
* <code>false</code>, throw an {@link IllegalStateException}.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void unlock( )
|
||||
throws IllegalStateException
|
||||
{
|
||||
if ( !this.executing ) {
|
||||
throw new IllegalStateException( "Trying to unlock game update processor, but it isn't locked" );
|
||||
}
|
||||
this.executing = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for an unfinished update cycle and terminate it
|
||||
*
|
||||
* <p>
|
||||
* Check for a "stuck" update cycle. If there is one, issue a warning and process it through the
|
||||
* {@link #executeTick(long)} method.
|
||||
*/
|
||||
@Override
|
||||
public boolean endPreviousCycle( )
|
||||
{
|
||||
Long currentTick;
|
||||
try {
|
||||
currentTick = this.systemStatus.checkStuckTick( );
|
||||
} catch ( MaintenanceStatusException e ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( currentTick == null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log( LogLevel.WARNING , "Tick " + currentTick + " restarted" ).flush( );
|
||||
this.executeTick( currentTick );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute an update cycle
|
||||
*
|
||||
* <p>
|
||||
* Start by checking for, and executing if necessary, a "stuck" update cycle. If one is found,
|
||||
* return. Otherwise, start a new tick through the system state manager, and call
|
||||
* {@link #executeTick(long)} to process it.
|
||||
*
|
||||
* <p>
|
||||
* If maintenance mode is active, the new tick will not be started.
|
||||
*/
|
||||
@Override
|
||||
public void executeUpdateCycle( )
|
||||
{
|
||||
// Attempt to end the previous tick, if e.g. maintenance mode was initiated while it was
|
||||
// being processed
|
||||
if ( this.endPreviousCycle( ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initiate next tick
|
||||
long tickId;
|
||||
try {
|
||||
tickId = this.systemStatus.startTick( );
|
||||
} catch ( TickStatusException e ) {
|
||||
throw new RuntimeException( "tick initiated while previous tick still being processed" , e );
|
||||
} catch ( MaintenanceStatusException e ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute tick
|
||||
this.logger.log( LogLevel.DEBUG , "Tick " + tickId + " started" ).flush( );
|
||||
this.executeTick( tickId );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process all remaining update batches for an update cycle
|
||||
*
|
||||
* <p>
|
||||
* This method will try and process all update batches using a {@link GameUpdateTransaction}
|
||||
* instance. It loops until the transaction indicates that no further updates are to be
|
||||
* processed. However, the loop will exit if maintenance mode becomes active between update
|
||||
* batches.
|
||||
*
|
||||
* @param tickId
|
||||
* the game update cycle's identifier
|
||||
*/
|
||||
private void executeTick( final long tickId )
|
||||
{
|
||||
GameUpdateTransaction transaction = new GameUpdateTransaction( this.updatesDao , this.registry , tickId );
|
||||
while ( !this.tTemplate.execute( transaction ) ) {
|
||||
if ( this.systemStatus.checkMaintenance( ) != null ) {
|
||||
this.logger.log( LogLevel.INFO , "Tick " + tickId + " interrupted by system maintenance" ).flush( );
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.logger.log( LogLevel.TRACE , "Tick " + tickId + " completed" ).flush( );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package com.deepclone.lw.beans.updates;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.deepclone.lw.interfaces.game.updates.GameUpdateProcessor;
|
||||
import com.deepclone.lw.interfaces.sys.Ticker;
|
||||
import com.deepclone.lw.interfaces.sys.Ticker.Frequency;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Game update ticker task
|
||||
*
|
||||
* <p>
|
||||
* This component will register itself as a ticker task to be executed once per minute. When it
|
||||
* runs, it will try and execute an update cycle.
|
||||
*
|
||||
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
|
||||
*/
|
||||
class GameUpdateTaskBean
|
||||
implements InitializingBean , Runnable
|
||||
{
|
||||
/** The ticker component */
|
||||
private Ticker ticker;
|
||||
|
||||
/** The game updates processor */
|
||||
private GameUpdateProcessor gameUpdateProcessor;
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injector that sets the ticker component
|
||||
*
|
||||
* @param ticker
|
||||
* the ticker component
|
||||
*/
|
||||
@Autowired( required = true )
|
||||
public void setTicker( Ticker ticker )
|
||||
{
|
||||
this.ticker = ticker;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injector that sets the game update processor
|
||||
*
|
||||
* @param gameUpdateProcessor
|
||||
* the game update processor
|
||||
*/
|
||||
@Autowired( required = true )
|
||||
public void setGameUpdateProcessor( GameUpdateProcessor gameUpdateProcessor )
|
||||
{
|
||||
this.gameUpdateProcessor = gameUpdateProcessor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finish a previous update cycle if there is one, then register the component to the ticker
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet( )
|
||||
{
|
||||
if ( this.gameUpdateProcessor.tryLock( ) ) {
|
||||
|
||||
try {
|
||||
this.gameUpdateProcessor.endPreviousCycle( );
|
||||
} finally {
|
||||
this.gameUpdateProcessor.unlock( );
|
||||
}
|
||||
|
||||
}
|
||||
this.ticker.registerTask( Frequency.MINUTE , "Game update" , this );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run a game update cycle
|
||||
*
|
||||
* <p>
|
||||
* Try locking the game update processor. On success, execute an update cycle, then unlock the
|
||||
* processor.
|
||||
*/
|
||||
@Override
|
||||
public void run( )
|
||||
{
|
||||
if ( !this.gameUpdateProcessor.tryLock( ) ) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.gameUpdateProcessor.executeUpdateCycle( );
|
||||
} finally {
|
||||
this.gameUpdateProcessor.unlock( );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package com.deepclone.lw.beans.updates;
|
||||
|
||||
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
|
||||
import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
|
||||
import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
|
||||
import com.deepclone.lw.sqld.sys.GameUpdateResult;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Game update transaction
|
||||
*
|
||||
* <p>
|
||||
* This class is responsible for executing the necessary operations during a game update
|
||||
* transaction. It is used by the {@link GameUpdateProcessorBean} component to implement its main
|
||||
* processing loop.
|
||||
*
|
||||
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
|
||||
*/
|
||||
class GameUpdateTransaction
|
||||
implements TransactionCallback< Boolean >
|
||||
{
|
||||
/** The game updates database access interface */
|
||||
private final UpdatesDAO updatesDao;
|
||||
|
||||
/** The registry of server-side update batch processors */
|
||||
private final ServerProcessorRegistry registry;
|
||||
|
||||
/** The identifier of the current update cycle */
|
||||
private final long tickId;
|
||||
|
||||
|
||||
/**
|
||||
* Initialise the various dependencies and set the identifier of the current cycle
|
||||
*
|
||||
* @param updatesDao
|
||||
* the game updates database access interface
|
||||
* @param registry
|
||||
* the registry of server-side update batch processors
|
||||
* @param tickId
|
||||
* the identifier of the current update cycle
|
||||
*/
|
||||
GameUpdateTransaction( UpdatesDAO updatesDao , ServerProcessorRegistry registry , long tickId )
|
||||
{
|
||||
this.updatesDao = updatesDao;
|
||||
this.registry = registry;
|
||||
this.tickId = tickId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update batch processing transaction
|
||||
*
|
||||
* <p>
|
||||
* Call the database's game update processing function, returning immediately if the function
|
||||
* indicates that the cycle has been completed. Otherwise, execute a server-side processor if
|
||||
* necessary.
|
||||
*
|
||||
* <p>
|
||||
* If the database requests server-side processing but no processor is found, an
|
||||
* {@link UnsupportedUpdateException} will be thrown.
|
||||
*
|
||||
* @return <code>true</code> if the current update cycle has been completed, <code>false</code>
|
||||
* if more updates are needed.
|
||||
*/
|
||||
@Override
|
||||
public Boolean doInTransaction( TransactionStatus status )
|
||||
{
|
||||
GameUpdateResult result = this.updatesDao.processUpdates( this.tickId );
|
||||
String local = result.getLocalExecution( );
|
||||
if ( result.isFinished( ) ) {
|
||||
return true;
|
||||
} else if ( local == null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateBatchProcessor processor = this.registry.getProcessorFor( local );
|
||||
if ( processor == null ) {
|
||||
throw new UnsupportedUpdateException( local );
|
||||
}
|
||||
|
||||
processor.processBatch( this.tickId );
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.deepclone.lw.beans.updates;
|
||||
|
||||
|
||||
import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Server-side update processor registry
|
||||
*
|
||||
* <p>
|
||||
* This interface allows server-side game update batch processors to be looked up depending on the
|
||||
* type of the updates to process.
|
||||
*
|
||||
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
|
||||
*
|
||||
*/
|
||||
interface ServerProcessorRegistry
|
||||
{
|
||||
|
||||
/**
|
||||
* Get a batch processor for some type of update
|
||||
*
|
||||
* @param type
|
||||
* the type of updates to process, as stored in the <code>sys.update_types</code>
|
||||
* database table
|
||||
*
|
||||
* @return <code>null</code> if no processor for the specified type has been registered, or the
|
||||
* processor to use.
|
||||
*/
|
||||
public UpdateBatchProcessor getProcessorFor( String type );
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package com.deepclone.lw.beans.updates;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
|
||||
import com.deepclone.lw.interfaces.game.updates.UpdateBatchProcessor;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Server-side update processor registry component
|
||||
*
|
||||
* <p>
|
||||
* This component will look through registered Spring components for update processors, and use the
|
||||
* results as the registry's contents.
|
||||
*
|
||||
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
|
||||
*/
|
||||
class ServerProcessorRegistryBean
|
||||
implements ServerProcessorRegistry , BeanPostProcessor
|
||||
{
|
||||
|
||||
/** Known server-side update batch processors */
|
||||
private final Map< String , UpdateBatchProcessor > processors = new HashMap< String , UpdateBatchProcessor >( );
|
||||
|
||||
|
||||
/** Do nothing - we're not interested in uninitialised components */
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization( Object bean , String beanName )
|
||||
{
|
||||
return bean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If an initialised component implements the {@link UpdateBatchProcessor} interface, add it to
|
||||
* the {@link #processors} map. A {@link BeanInitializationException} will be thrown if two
|
||||
* components seem to process the same update type.
|
||||
*/
|
||||
@Override
|
||||
public Object postProcessAfterInitialization( Object bean , String beanName )
|
||||
throws BeansException
|
||||
{
|
||||
if ( bean instanceof UpdateBatchProcessor ) {
|
||||
UpdateBatchProcessor processor = (UpdateBatchProcessor) bean;
|
||||
String updType = processor.getUpdateType( );
|
||||
if ( this.processors.containsKey( updType ) ) {
|
||||
throw new BeanInitializationException( "Duplicate update processor for update type " + updType );
|
||||
}
|
||||
this.processors.put( updType , processor );
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
|
||||
/** Obtain the processor from the {@link #processors} map */
|
||||
@Override
|
||||
public UpdateBatchProcessor getProcessorFor( String type )
|
||||
{
|
||||
return this.processors.get( type );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.deepclone.lw.beans.updates;
|
||||
|
||||
|
||||
/**
|
||||
* Unsupported update exception
|
||||
*
|
||||
* <p>
|
||||
* This exception is thrown by the {@link GameUpdateTransaction} when there is no server-side
|
||||
* processor for some update type.
|
||||
*
|
||||
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
|
||||
*/
|
||||
@SuppressWarnings( "serial" )
|
||||
public class UnsupportedUpdateException
|
||||
extends RuntimeException
|
||||
{
|
||||
|
||||
/**
|
||||
* Initialise the exception
|
||||
*
|
||||
* @param name
|
||||
* the name of the update type for which no processor is registered.
|
||||
*/
|
||||
UnsupportedUpdateException( String name )
|
||||
{
|
||||
super( "No processor found for update type " + name );
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package com.deepclone.lw.beans.updates;
|
||||
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.deepclone.lw.interfaces.game.updates.UpdatesDAO;
|
||||
import com.deepclone.lw.sqld.sys.GameUpdateResult;
|
||||
import com.deepclone.lw.utils.StoredProc;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Updates data access component
|
||||
*
|
||||
* <p>
|
||||
* This component accesses the database procedures and views which constitute the game update
|
||||
* system.
|
||||
*
|
||||
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
|
||||
*/
|
||||
class UpdatesDAOBean
|
||||
implements UpdatesDAO
|
||||
{
|
||||
|
||||
/** Game update processor stored procedure */
|
||||
private StoredProc process;
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injector for the data source
|
||||
*
|
||||
* <p>
|
||||
* When Spring injects the data source component, initialise the stored procedures used by the
|
||||
* component.
|
||||
*
|
||||
* @param dataSource
|
||||
* the data source component
|
||||
*/
|
||||
@Autowired( required = true )
|
||||
public void setDataSource( DataSource dataSource )
|
||||
{
|
||||
this.process = new StoredProc( dataSource , "sys" , "process_updates" ).addParameter( "tick_id" , Types.BIGINT )
|
||||
.addOutput( "_has_more" , Types.BOOLEAN ).addOutput( "_process_externally" , Types.VARCHAR );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute the processor's stored procedure
|
||||
*
|
||||
* <p>
|
||||
* This implementation simply runs the database's <code>sys.process_updates()</code> stored
|
||||
* procedure, and converts the two values it returns into a {@link GameUpdateResult} instance.
|
||||
*/
|
||||
@Override
|
||||
public GameUpdateResult processUpdates( long tickId )
|
||||
{
|
||||
Map< String , Object > m = this.process.execute( tickId );
|
||||
if ( !(Boolean) m.get( "_has_more" ) ) {
|
||||
return new GameUpdateResult( );
|
||||
}
|
||||
return new GameUpdateResult( (String) m.get( "_process_externally" ) );
|
||||
}
|
||||
|
||||
}
|
|
@ -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.1.xsd">
|
||||
|
||||
<import resource="game/updates.xml" />
|
||||
|
||||
</beans>
|
|
@ -0,0 +1,13 @@
|
|||
<?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="gameUpdateDAO" class="com.deepclone.lw.beans.updates.UpdatesDAOBean" />
|
||||
<bean id="gameUpdateProcessor" class="com.deepclone.lw.beans.updates.GameUpdateProcessorBean" />
|
||||
<bean id="gameUpdateRegistry"
|
||||
class="com.deepclone.lw.beans.updates.ServerProcessorRegistryBean" />
|
||||
<bean id="gameUpdateTask" class="com.deepclone.lw.beans.updates.GameUpdateTaskBean" />
|
||||
|
||||
</beans>
|
0
legacyworlds-server-beans-updates/src/test/java/.empty
Normal file
0
legacyworlds-server-beans-updates/src/test/java/.empty
Normal file
Reference in a new issue