From 75c5245764e97cbe09632d8309b9f7b96a939cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Sat, 30 Jun 2012 14:52:55 +0200 Subject: [PATCH] Event definition loader Implemented the ImportEvents command line tool, which allows event definitions to be imported. In all cases the tool will try to import all definitions; if an error occurs, the process continues but the transaction is rolled back. It skips existing definitions rather than taking the risk of doing something inappropriate (e.g. deletion of existing events). --- build/post-build.d/20-import-tools.sh | 1 + .../com/deepclone/lw/cli/ImportEvents.java | 715 ++++++++++++++++++ .../cli/xmlimport/EventDefinitionLoader.java | 123 +++ .../data/evdef/BooleanEventField.java | 26 + .../data/evdef/EntityEventField.java | 39 + .../xmlimport/data/evdef/EventDefinition.java | 101 +++ .../data/evdef/EventDefinitions.java | 58 ++ .../xmlimport/data/evdef/EventFieldBase.java | 52 ++ .../data/evdef/EventFieldEntityType.java | 21 + .../xmlimport/data/evdef/EventFieldType.java | 25 + .../xmlimport/data/evdef/I18NEventField.java | 26 + .../data/evdef/IntegerEventField.java | 47 ++ .../xmlimport/data/evdef/RealEventField.java | 48 ++ .../xmlimport/data/evdef/TextEventField.java | 50 ++ legacyworlds/doc/local-deployment.txt | 2 + 15 files changed, 1334 insertions(+) create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportEvents.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/EventDefinitionLoader.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/BooleanEventField.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/I18NEventField.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java create mode 100644 legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java diff --git a/build/post-build.d/20-import-tools.sh b/build/post-build.d/20-import-tools.sh index de69c46..5a09127 100644 --- a/build/post-build.d/20-import-tools.sh +++ b/build/post-build.d/20-import-tools.sh @@ -31,6 +31,7 @@ cat > data-source.xml < + * Check the command line options, setting the definitions file accordingly. + */ + @Override + public boolean setOptions( String... options ) + { + if ( options.length != 1 ) { + return false; + } + this.file = new File( options[ 0 ] ); + if ( ! ( this.file.isFile( ) && this.file.canRead( ) ) ) { + return false; + } + return true; + } + + + /** + * Run the event definitions import tool + * + *

+ * Loads the data file, then connects to the database and creates all missing definitions. + */ + @Override + public void run( ) + { + EventDefinitions data; + try { + data = new EventDefinitionLoader( this.file ).load( ); + } catch ( DataImportException e ) { + this.logger.error( "Error while loading event definitions" , e ); + return; + } + + AbstractApplicationContext ctx = this.createContext( ); + this.createTemplates( ctx ); + this.executeImportTransaction( data ); + ToolBase.destroyContext( ctx ); + } + + + /** + * Create the Spring context + * + *

+ * Load the definition of the data source as a Spring context, then adds the transaction + * management component. + * + * @return the Spring application context + */ + private ClassPathXmlApplicationContext createContext( ) + { + FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext( new String[] { + this.getDataSource( ) + } ); + ctx.refresh( ); + + return new ClassPathXmlApplicationContext( new String[] { + "configuration/transactions.xml" + } , true , ctx ); + } + + + /** + * Create database access templates + * + *

+ * Initialise the transaction template and the four stored procedure definitions. + * + * @param ctx + * the Spring application context + */ + private void createTemplates( ApplicationContext ctx ) + { + DataSource dSource = ctx.getBean( DataSource.class ); + PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class ); + + this.fEvdefStart = new StoredProc( dSource , "events" , "evdef_start" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_prio" , java.sql.Types.INTEGER ) + .addParameter( "_adj" , java.sql.Types.BOOLEAN ).addParameter( "_i18n" , java.sql.Types.VARCHAR ) + .addOutput( "_error" , java.sql.Types.OTHER ); + + this.fEvdefFinalise = new StoredProc( dSource , "events" , "evdef_finalise" ).addOutput( "_error" , + java.sql.Types.OTHER ).addOutput( "_fld" , java.sql.Types.VARCHAR ); + + this.fEvdefAddfldBool = new StoredProc( dSource , "events" , "evdef_addfld_bool" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addOutput( "_error" , java.sql.Types.OTHER ); + + this.fEvdefAddfldI18N = new StoredProc( dSource , "events" , "evdef_addfld_i18n" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addOutput( "_error" , java.sql.Types.OTHER ); + + this.fEvdefAddfldEntity = new StoredProc( dSource , "events" , "evdef_addfld_entity" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_etype" , "events.entity_field_type" ).addOutput( "_error" , java.sql.Types.OTHER ); + + this.fEvdefAddfldInt = new StoredProc( dSource , "events" , "evdef_addfld_int" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldIntMin = new StoredProc( dSource , "events" , "evdef_addfld_int_min" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_min" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldIntMax = new StoredProc( dSource , "events" , "evdef_addfld_int_max" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_max" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldIntRange = new StoredProc( dSource , "events" , "evdef_addfld_int_range" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_min" , java.sql.Types.INTEGER ).addParameter( "_max" , java.sql.Types.INTEGER ) + .addOutput( "_error" , java.sql.Types.OTHER ); + + this.fEvdefAddfldReal = new StoredProc( dSource , "events" , "evdef_addfld_real" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldRealMin = new StoredProc( dSource , "events" , "evdef_addfld_real_min" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_min" , java.sql.Types.DOUBLE ).addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldRealMax = new StoredProc( dSource , "events" , "evdef_addfld_real_max" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_max" , java.sql.Types.DOUBLE ).addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldRealRange = new StoredProc( dSource , "events" , "evdef_addfld_real_range" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_min" , java.sql.Types.DOUBLE ).addParameter( "_max" , java.sql.Types.DOUBLE ) + .addOutput( "_error" , java.sql.Types.OTHER ); + + this.fEvdefAddfldText = new StoredProc( dSource , "events" , "evdef_addfld_text" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldTextMin = new StoredProc( dSource , "events" , "evdef_addfld_text_min" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_min" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldTextMax = new StoredProc( dSource , "events" , "evdef_addfld_text_max" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_max" , java.sql.Types.INTEGER ).addOutput( "_error" , java.sql.Types.OTHER ); + this.fEvdefAddfldTextRange = new StoredProc( dSource , "events" , "evdef_addfld_text_range" ) + .addParameter( "_id" , java.sql.Types.VARCHAR ).addParameter( "_optional" , java.sql.Types.BOOLEAN ) + .addParameter( "_min" , java.sql.Types.INTEGER ).addParameter( "_max" , java.sql.Types.INTEGER ) + .addOutput( "_error" , java.sql.Types.OTHER ); + + this.tTemplate = new TransactionTemplate( tManager ); + } + + + /** + * Execute the event definitions importation transaction + * + *

+ * Run a transaction and execute the importation code inside it. Roll back if anything goes + * wrong. + * + * @param data + * the event definitions top-level instance + */ + private void executeImportTransaction( final EventDefinitions data ) + { + boolean rv = this.tTemplate.execute( new TransactionCallback< Boolean >( ) { + @Override + public Boolean doInTransaction( TransactionStatus status ) + { + boolean rv = ImportEvents.this.doTransaction( data ); + if ( !rv ) { + status.setRollbackOnly( ); + } + return rv; + } + } ); + + if ( rv ) { + this.logger.info( "Import complete; " + this.successful + " new type(s), " + this.skipped + " skipped" ); + } else { + this.logger.error( this.errors + " error(s) occurred, no changes were made" ); + } + } + + + /** + * Import transaction body + * + *

+ * Import all definitions and handle exceptions. + * + * @param data + * the event definitions top-level instance + * @return true on success, false otherwise + */ + private boolean doTransaction( EventDefinitions data ) + { + try { + this.importDefinitions( data ); + return this.errors == 0; + } catch ( RuntimeException e ) { + this.logger.error( "Caught runtime exception" , e ); + this.errors ++; + } + return false; + } + + + /** + * Extract the error code from a stored procedure's return value + * + *

+ * This method is used to extract the error code from the maps returned by a definition stored + * procedure's execution. + * + * @param result + * the map returned by the stored procedure's execution + * + * @return the error code + */ + private SPErrorCode getSPResult( Map< String , Object > result ) + { + return SPErrorCode.valueOf( ( (PGobject) result.get( "_error" ) ).getValue( ) ); + } + + + /** + * Import all event definitions from the top-level importable instance + * + *

+ * This method iterates through all of the definitions, trying to import each of them. + * + * @param data + * the top-level importable instance + */ + private void importDefinitions( EventDefinitions data ) + { + for ( EventDefinition evdef : data ) { + this.importDefinition( evdef ); + } + } + + + /** + * Try to import an event definition + * + *

+ * This method will attempt to import an event definition. It will increase the state counters ( + * {@link #errors}, {@link #skipped} and {@link #successful}) depending on the results. + * + * @param evdef + * the event definition to import + */ + private void importDefinition( EventDefinition evdef ) + { + boolean ok; + + // Start the import + try { + this.startImport( evdef ); + ok = true; + } catch ( DataImportException e ) { + this.logger.error( e.getMessage( ) ); + ok = false; + } + + // Import all fields + if ( ok ) { + for ( EventFieldBase field : evdef ) { + ok = ok && this.importField( evdef , field ); + if ( !ok ) { + break; + } + } + } + + // Finalise the operation + boolean wasNew = false; + try { + wasNew = this.endImport( evdef ); + } catch ( DataImportException e ) { + if ( ok ) { + this.logger.error( e.getMessage( ) ); + ok = false; + } + } + + // Update counters and print to log + if ( ok ) { + if ( wasNew ) { + this.logger.info( evdef.getIdentifier( ) + ": new event definition imported" ); + this.successful++; + } else { + this.logger.warn( evdef.getIdentifier( ) + ": event definition exists (skipped)" ); + this.skipped++; + } + } else { + this.errors++; + } + } + + + /** + * Start importing an event definition + * + *

+ * This method calls the events.evdef_start() stored procedure to initialise the + * event definition registration, and handles its return value. + * + * @param evdef + * the event definition instance from the XML definition + * + * @throws DataImportException + * if the definition's identifier is invalid or if one of the internationalised + * strings is missing + */ + private void startImport( EventDefinition evdef ) + throws DataImportException + { + SPErrorCode ec; + ec = this.getSPResult( this.fEvdefStart.execute( evdef.getIdentifier( ) , evdef.getPriority( ) , + evdef.isAdjustable( ) , evdef.getI18NStrings( ) ) ); + + switch ( ec ) { + case OK: + break; + + case BAD_ID: + throw new DataImportException( evdef.getIdentifier( ) + ": bad event type identifier" ); + + case BAD_STRINGS: + throw new DataImportException( evdef.getIdentifier( ) + ": I18N strings not found" ); + + default: + throw new RuntimeException( "Unsupported return value for events.evdef_start(): " + ec.toString( ) ); + } + } + + + /** + * Import a field definition + * + *

+ * Call the appropriate field definition importation method depending on the field's type. + * + * @param evdef + * the event definition + * @param field + * the field definition + * + * @return true on success, false if something went wrong + */ + private boolean importField( EventDefinition evdef , EventFieldBase field ) + { + SPErrorCode ec; + switch ( field.getType( ) ) { + case BOOLEAN: + ec = this.importBooleanField( field ); + break; + + case I18N: + ec = this.importI18NField( field ); + break; + + case ENTITY: + ec = this.importEntityField( (EntityEventField) field ); + break; + + case INTEGER: + ec = this.importIntegerField( (IntegerEventField) field ); + break; + + case REAL: + ec = this.importRealField( (RealEventField) field ); + break; + + case TEXT: + ec = this.importTextField( (TextEventField) field ); + break; + + default: + throw new RuntimeException( "Unsupported field type " + field.getType( ).toString( ) ); + } + + switch ( ec ) { + case OK: + return true; + + case BAD_ID: + this.logger.error( evdef.getIdentifier( ) + ": bad field identifier '" + field.getIdentifier( ) + "'" ); + break; + + default: + throw new RuntimeException( "Unsupported return value for field definition procedure: " + ec.toString( ) ); + } + return false; + } + + + /** + * Import a boolean field + * + *

+ * This method calls events.evdef_addfld_bool to register a new boolean field. + * + * @param field + * the field's definition + * + * @return the stored procedure's error code + */ + private SPErrorCode importBooleanField( EventFieldBase field ) + { + return this.getSPResult( this.fEvdefAddfldBool.execute( field.getIdentifier( ) , !field.isRequired( ) ) ); + } + + + /** + * Import an I18N reference field + * + *

+ * This method calls events.evdef_addfld_i18n to register a new I18N reference + * field. + * + * @param field + * the field's definition + * + * @return the stored procedure's error code + */ + private SPErrorCode importI18NField( EventFieldBase field ) + { + return this.getSPResult( this.fEvdefAddfldI18N.execute( field.getIdentifier( ) , !field.isRequired( ) ) ); + } + + + /** + * Import an entity reference field + * + *

+ * This method calls events.evdef_addfld_entity to register a new entity reference + * field. + * + * @param field + * the field's definition + * + * @return the stored procedure's error code + */ + private SPErrorCode importEntityField( EntityEventField field ) + { + return this.getSPResult( this.fEvdefAddfldEntity.execute( field.getIdentifier( ) , !field.isRequired( ) , + field.getEntityType( ) ) ); + } + + + /** + * Import an integer field definition + * + *

+ * This method calls one of the integer field definitions stored procedures, depending on the + * definition's parameters. + * + * @param field + * the field's definition + * + * @return the stored procedure's error code + */ + private SPErrorCode importIntegerField( IntegerEventField field ) + { + String id = field.getIdentifier( ); + boolean opt = !field.isRequired( ); + Integer min = field.getMin( ); + Integer max = field.getMax( ); + + Map< String , Object > result; + if ( min == null && max == null ) { + result = this.fEvdefAddfldInt.execute( id , opt ); + } else if ( min == null ) { + result = this.fEvdefAddfldIntMax.execute( id , opt , max ); + } else if ( max == null ) { + result = this.fEvdefAddfldIntMin.execute( id , opt , min ); + } else { + result = this.fEvdefAddfldIntRange.execute( id , opt , min , max ); + } + + return this.getSPResult( result ); + } + + + /** + * Import a double precision field definition + * + *

+ * This method calls one of the double precision field definitions stored procedures, depending + * on the definition's parameters. + * + * @param field + * the field's definition + * + * @return the stored procedure's error code + */ + private SPErrorCode importRealField( RealEventField field ) + { + String id = field.getIdentifier( ); + boolean opt = !field.isRequired( ); + Double min = field.getMin( ); + Double max = field.getMax( ); + + Map< String , Object > result; + if ( min == null && max == null ) { + result = this.fEvdefAddfldReal.execute( id , opt ); + } else if ( min == null ) { + result = this.fEvdefAddfldRealMax.execute( id , opt , max ); + } else if ( max == null ) { + result = this.fEvdefAddfldRealMin.execute( id , opt , min ); + } else { + result = this.fEvdefAddfldRealRange.execute( id , opt , min , max ); + } + + return this.getSPResult( result ); + } + + + /** + * Import a text field definition + * + *

+ * This method calls one of the text field definitions stored procedures, depending on the + * definition's parameters. + * + * @param field + * the field's definition + * + * @return the stored procedure's error code + */ + private SPErrorCode importTextField( TextEventField field ) + { + String id = field.getIdentifier( ); + boolean opt = !field.isRequired( ); + Integer min = field.getMinLength( ); + Integer max = field.getMaxLength( ); + + Map< String , Object > result; + if ( min == null && max == null ) { + result = this.fEvdefAddfldText.execute( id , opt ); + } else if ( min == null ) { + result = this.fEvdefAddfldTextMax.execute( id , opt , max ); + } else if ( max == null ) { + result = this.fEvdefAddfldTextMin.execute( id , opt , min ); + } else { + result = this.fEvdefAddfldTextRange.execute( id , opt , min , max ); + } + + return this.getSPResult( result ); + } + + + /** + * Finalise the current event definition + * + *

+ * This method calls the events.evdef_finalise() stored procedure, and handles its + * return values. + * + * @param evdef + * the event definition from the XML file + * + * @return true if the definition was finalised or if it had no effect (except for + * cleanup) due to a previous error, false if the definition already + * existed and was skipped. + * + * @throws DataImportException + * if the event's specification is invalid, if a field name is duplicated, or if a + * field's specification is invalid. + */ + private boolean endImport( EventDefinition evdef ) + throws DataImportException + { + Map< String , Object > result = this.fEvdefFinalise.execute( ); + SPErrorCode ec = this.getSPResult( result ); + String field = (String) result.get( "_fld" ); + + switch ( ec ) { + case OK: + case NO_DATA: + return true; + + case BAD_SPEC: + if ( field == null ) { + throw new DataImportException( evdef.getIdentifier( ) + ": bad event type specification" ); + } + throw new DataImportException( evdef.getIdentifier( ) + ": bad field specification (" + field + ")" ); + + case DUPLICATE: + if ( field == null ) { + return false; + } + throw new DataImportException( evdef.getIdentifier( ) + ": duplicate field name " + field ); + + default: + throw new RuntimeException( "Unsupported return value for events.evdef_finalise(): " + ec.toString( ) ); + } + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/EventDefinitionLoader.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/EventDefinitionLoader.java new file mode 100644 index 0000000..69ff4d7 --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/EventDefinitionLoader.java @@ -0,0 +1,123 @@ +package com.deepclone.lw.cli.xmlimport; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +import com.deepclone.lw.cli.xmlimport.data.DataImportException; +import com.deepclone.lw.cli.xmlimport.data.evdef.*; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.XStreamException; + + + +/** + * Event definitions loader + * + *

+ * This class implements loading event definitions from XML files. The definitions will be extracted + * and verified. + * + * @author E. Benoît + */ +public class EventDefinitionLoader +{ + /** The file to read the XML from */ + private final File file; + + + /** + * Initialise the loader + * + * @param file + * the XML file that contains the definitions + */ + public EventDefinitionLoader( File file ) + { + this.file = file.getAbsoluteFile( ); + } + + + /** + * Initialise the necessary XStream instance + * + *

+ * Initialise the XStream instance by processing annotations in all event definition importable + * data classes. + * + * @return the XStream instance to use when loading the data + */ + private XStream initXStream( ) + { + XStream xstream = new XStream( ); + xstream.processAnnotations( EventDefinitions.class ); + xstream.processAnnotations( BooleanEventField.class ); + xstream.processAnnotations( IntegerEventField.class ); + xstream.processAnnotations( RealEventField.class ); + xstream.processAnnotations( TextEventField.class ); + xstream.processAnnotations( I18NEventField.class ); + xstream.processAnnotations( EntityEventField.class ); + return xstream; + } + + + /** + * Load the event definitions + * + *

+ * Load the XML file and process the definitions file using XStream. + * + * @return the top-level importable data instance + * + * @throws DataImportException + * if reading from the file or parsing its contents fail + */ + private EventDefinitions loadXMLFile( ) + throws DataImportException + { + FileInputStream fis; + try { + fis = new FileInputStream( this.file ); + } catch ( FileNotFoundException e ) { + throw new DataImportException( "Unable to load event definitions" , e ); + } + + try { + try { + XStream xstream = this.initXStream( ); + return (EventDefinitions) xstream.fromXML( fis ); + } finally { + fis.close( ); + } + } catch ( IOException e ) { + throw new DataImportException( "Input error while loading event definitions" , e ); + } catch ( XStreamException e ) { + throw new DataImportException( "XML error while loading event definitions" , e ); + } + } + + + /** + * Load and process I18N definition + * + *

+ * Attempt to load all I18N definitions, make sure they are valid, then set the original file's + * path. + * + * @return the top-level importable data instance + * + * @throws DataImportException + * if loading or verifying the data fails + */ + public EventDefinitions load( ) + throws DataImportException + { + EventDefinitions events = this.loadXMLFile( ); + events.verifyData( ); + events.setReadFrom( this.file ); + return events; + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/BooleanEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/BooleanEventField.java new file mode 100644 index 0000000..24d134b --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/BooleanEventField.java @@ -0,0 +1,26 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import com.thoughtworks.xstream.annotations.XStreamAlias; + + + +/** + * A boolean field definition + * + * @author E. Benoît + */ +@XStreamAlias( "boolean" ) +@SuppressWarnings( "serial" ) +public class BooleanEventField + extends EventFieldBase +{ + + /** @return {@link EventFieldType#BOOLEAN} */ + @Override + public EventFieldType getType( ) + { + return EventFieldType.BOOLEAN; + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java new file mode 100644 index 0000000..3cb893a --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EntityEventField.java @@ -0,0 +1,39 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; + + + +/** + * An entity reference field definition + * + * @author E. Benoît + */ +@XStreamAlias( "entity" ) +@SuppressWarnings( "serial" ) +public class EntityEventField + extends EventFieldBase +{ + + /** The type of entities this field references */ + @XStreamAsAttribute + @XStreamAlias( "entity-type" ) + private EventFieldEntityType entityType; + + + public EventFieldEntityType getEntityType( ) + { + return this.entityType; + } + + + /** @return {@link EntityFieldType#ENTITY} */ + @Override + public EventFieldType getType( ) + { + return EventFieldType.ENTITY; + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java new file mode 100644 index 0000000..f2d6148 --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinition.java @@ -0,0 +1,101 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import com.deepclone.lw.cli.xmlimport.data.DataImportException; +import com.deepclone.lw.cli.xmlimport.data.ImportableData; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; +import com.thoughtworks.xstream.annotations.XStreamImplicit; + + + +/** + * Event definition + * + *

+ * This class represents an event definition entry from the data file. An event definition consists + * in a few properties, and a potentially empty list of event field definitions. + * + * @author E. Benoît + * + */ +@SuppressWarnings( "serial" ) +public class EventDefinition + extends ImportableData + implements Iterable< EventFieldBase > +{ + + /** The event type's identifier */ + @XStreamAsAttribute + @XStreamAlias( "id" ) + private String identifier; + + /** The default priority */ + @XStreamAsAttribute + private Integer priority; + + /** Whether users can override the events' priority */ + @XStreamAsAttribute + private Boolean adjustable; + + /** Prefix of the name and template strings */ + @XStreamAsAttribute + @XStreamAlias( "i18n-strings" ) + private String i18nStrings; + + /** The list of fields */ + @XStreamImplicit + private List< EventFieldBase > fields = new ArrayList< EventFieldBase >( ); + + + public String getIdentifier( ) + { + return this.identifier; + } + + + public int getPriority( ) + { + return ( this.priority == null ) ? 2 : this.priority; + } + + + public boolean isAdjustable( ) + { + return ( this.adjustable == null ) ? true : this.adjustable; + } + + + public String getI18NStrings( ) + { + return this.i18nStrings; + } + + + @Override + public Iterator< EventFieldBase > iterator( ) + { + return Collections.unmodifiableList( this.fields ).iterator( ); + } + + + @Override + public void verifyData( ) + throws DataImportException + { + super.verifyData( ); + if ( this.fields == null ) { + this.fields = new ArrayList< EventFieldBase >( ); + } else { + for ( EventFieldBase f : this.fields ) { + f.verifyData( ); + } + } + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java new file mode 100644 index 0000000..9e67ce4 --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventDefinitions.java @@ -0,0 +1,58 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.deepclone.lw.cli.xmlimport.data.DataImportException; +import com.deepclone.lw.cli.xmlimport.data.ImportableData; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamImplicit; + + + +/** + * Event type definitions + * + *

+ * This class represents the list of event type definitions as it is found in the appropriate data + * file. + * + * @author E. Benoît + */ +@SuppressWarnings( "serial" ) +@XStreamAlias( "lw-event-definitions" ) +public class EventDefinitions + extends ImportableData + implements Iterable< EventDefinition > +{ + + /** The list of event definitions */ + @XStreamImplicit( itemFieldName = "evdef" ) + private List< EventDefinition > definitions = new LinkedList< EventDefinition >( ); + + + /** Iterate over event definitions */ + @Override + public Iterator< EventDefinition > iterator( ) + { + return Collections.unmodifiableList( this.definitions ).iterator( ); + } + + + @Override + public void verifyData( ) + throws DataImportException + { + super.verifyData( ); + if ( this.definitions == null ) { + throw new DataImportException( "No definitions in this file" ); + } + for ( EventDefinition def : this.definitions ) { + def.verifyData( ); + } + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java new file mode 100644 index 0000000..ea6ac1b --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldBase.java @@ -0,0 +1,52 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import com.deepclone.lw.cli.xmlimport.data.ImportableData; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; + + + +/** + * Common event field definition + * + *

+ * This abstract class is used as the base for event field definitions. + * + * @author E. Benoît + */ +@SuppressWarnings( "serial" ) +public abstract class EventFieldBase + extends ImportableData +{ + + /** The field's identifier */ + @XStreamAsAttribute + @XStreamAlias( "id" ) + private String identifier; + + /** Whether the field is required or optional */ + @XStreamAsAttribute + private Boolean required; + + + /** + * Obtain the type of the field + * + * @return the field's type. + */ + public abstract EventFieldType getType( ); + + + public String getIdentifier( ) + { + return this.identifier; + } + + + public boolean isRequired( ) + { + return this.required == null ? true : this.required; + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java new file mode 100644 index 0000000..362e02f --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldEntityType.java @@ -0,0 +1,21 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +/** Types of entities which can be referenced through entity fields */ +public enum EventFieldEntityType { + + /** Empires */ + EMP , + /** Planets */ + PLN , + /** Fleets */ + FLT , + /** Alliances */ + ALL , + /** Battles */ + BAT , + /** Administrators */ + ADM , + /** Bug reports */ + BUG +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java new file mode 100644 index 0000000..0eb450e --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/EventFieldType.java @@ -0,0 +1,25 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +/** Types of event fields as they are stored in the definition file */ +public enum EventFieldType { + + /** An integer field */ + INTEGER , + + /** An double precision real number field */ + REAL , + + /** A string field */ + TEXT , + + /** A boolean field */ + BOOLEAN , + + /** A field that references an internationalised string */ + I18N , + + /** A field that references some game entity */ + ENTITY + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/I18NEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/I18NEventField.java new file mode 100644 index 0000000..689eb67 --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/I18NEventField.java @@ -0,0 +1,26 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import com.thoughtworks.xstream.annotations.XStreamAlias; + + + +/** + * Definition of a field containing an I18N reference + * + * @author E. Benoît + */ +@XStreamAlias( "i18n" ) +@SuppressWarnings( "serial" ) +public class I18NEventField + extends EventFieldBase +{ + + /** @return {@link EventFieldType#I18N} */ + @Override + public EventFieldType getType( ) + { + return EventFieldType.I18N; + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java new file mode 100644 index 0000000..9e89c7d --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/IntegerEventField.java @@ -0,0 +1,47 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; + + + +/** + * An integer event field definition + * + * @author E. Benoît + */ +@XStreamAlias( "integer" ) +@SuppressWarnings( "serial" ) +public class IntegerEventField + extends EventFieldBase +{ + /** Minimal value allowed for the field */ + @XStreamAsAttribute + private Integer min; + + /** Maximal value allowed for the field */ + @XStreamAsAttribute + private Integer max; + + + public Integer getMin( ) + { + return this.min; + } + + + public Integer getMax( ) + { + return this.max; + } + + + /** @return {@link EventFieldType#INTEGER} */ + @Override + public EventFieldType getType( ) + { + return EventFieldType.INTEGER; + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java new file mode 100644 index 0000000..da3bf10 --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/RealEventField.java @@ -0,0 +1,48 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; + + + +/** + * A real field definition + * + * @author E. Benoît + */ +@XStreamAlias( "real" ) +@SuppressWarnings( "serial" ) +public class RealEventField + extends EventFieldBase +{ + + /** Minimal value for the field */ + @XStreamAsAttribute + private Double min; + + /** Maximal value for the field */ + @XStreamAsAttribute + private Double max; + + + public Double getMin( ) + { + return this.min; + } + + + public Double getMax( ) + { + return this.max; + } + + + /** @return {@link EventFieldType#REAL} */ + @Override + public EventFieldType getType( ) + { + return EventFieldType.REAL; + } + +} diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java new file mode 100644 index 0000000..80f6577 --- /dev/null +++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/xmlimport/data/evdef/TextEventField.java @@ -0,0 +1,50 @@ +package com.deepclone.lw.cli.xmlimport.data.evdef; + + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; + + + +/** + * A text event field definition + * + * @author E. Benoît + */ +@XStreamAlias( "text" ) +@SuppressWarnings( "serial" ) +public class TextEventField + extends EventFieldBase +{ + + /** The field's minimum length */ + @XStreamAsAttribute + @XStreamAlias( "min-length" ) + private Integer minLength; + + /** The field's maximum length */ + @XStreamAsAttribute + @XStreamAlias( "max-length" ) + private Integer maxLength; + + + public Integer getMinLength( ) + { + return this.minLength; + } + + + public Integer getMaxLength( ) + { + return this.maxLength; + } + + + /** @return {@link EventFieldType#TEXT} */ + @Override + public EventFieldType getType( ) + { + return EventFieldType.TEXT; + } + +} diff --git a/legacyworlds/doc/local-deployment.txt b/legacyworlds/doc/local-deployment.txt index a2a278d..245e281 100644 --- a/legacyworlds/doc/local-deployment.txt +++ b/legacyworlds/doc/local-deployment.txt @@ -39,6 +39,8 @@ from the root of the server's distribution: java -jar legacyworlds-server-main-1.0.0-0.jar \ --run-tool ImportText data/i18n-text.xml + java -jar legacyworlds-server-main-1.0.0-0.jar \ + --run-tool ImportEvents data/event-definitions.xml java -jar legacyworlds-server-main-1.0.0-0.jar \ --run-tool ImportTechGraph data/tech-graph.xml java -jar legacyworlds-server-main-1.0.0-0.jar \