From 75c5245764e97cbe09632d8309b9f7b96a939cba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
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 <<EOF
 EOF
 
 java legacyworlds-server-main-*.jar --run-tool ImportText data/i18n-text.xml || exit 1
+java legacyworlds-server-main-*.jar --run-tool ImportEvents data/event-definitions.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportResources data/resources.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportTechs data/techs.xml || exit 1
 java legacyworlds-server-main-*.jar --run-tool ImportTechGraph data/tech-graph.xml || exit 1
diff --git a/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportEvents.java b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportEvents.java
new file mode 100644
index 0000000..92401fd
--- /dev/null
+++ b/legacyworlds-server-main/src/main/java/com/deepclone/lw/cli/ImportEvents.java
@@ -0,0 +1,715 @@
+package com.deepclone.lw.cli;
+
+
+import java.io.File;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.log4j.Logger;
+import org.postgresql.util.PGobject;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.FileSystemXmlApplicationContext;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import com.deepclone.lw.cli.xmlimport.EventDefinitionLoader;
+import com.deepclone.lw.cli.xmlimport.data.DataImportException;
+import com.deepclone.lw.cli.xmlimport.data.evdef.EntityEventField;
+import com.deepclone.lw.cli.xmlimport.data.evdef.EventDefinition;
+import com.deepclone.lw.cli.xmlimport.data.evdef.EventDefinitions;
+import com.deepclone.lw.cli.xmlimport.data.evdef.EventFieldBase;
+import com.deepclone.lw.cli.xmlimport.data.evdef.IntegerEventField;
+import com.deepclone.lw.cli.xmlimport.data.evdef.RealEventField;
+import com.deepclone.lw.cli.xmlimport.data.evdef.TextEventField;
+import com.deepclone.lw.utils.StoredProc;
+
+
+
+public class ImportEvents
+		extends CLITool
+{
+
+	/**
+	 * Error codes that may be returned by the various import stored procedures; see the
+	 * corresponding SQL code for details about each value.
+	 */
+	private static enum SPErrorCode {
+		OK ,
+		BAD_ID ,
+		BAD_STRINGS ,
+		DUPLICATE ,
+		BAD_SPEC ,
+		NO_DATA
+	};
+
+	/** Logging system */
+	private final Logger logger = Logger.getLogger( ImportResources.class );
+
+	/** File to read the definitions from */
+	private File file;
+
+	/** Spring transaction template */
+	private TransactionTemplate tTemplate;
+
+	/** Error counter */
+	private int errors = 0;
+
+	/** Skipped definitions counter */
+	private int skipped = 0;
+
+	/** Successful imports counter */
+	private int successful = 0;
+
+	/** Stored procedure that starts recording an event definition */
+	private StoredProc fEvdefStart;
+
+	/** Stored procedure that finalises an event definition's recording */
+	private StoredProc fEvdefFinalise;
+
+	/** Stored procedure that defines a boolean field */
+	private StoredProc fEvdefAddfldBool;
+
+	/** Stored procedure that defines an I18N reference field */
+	private StoredProc fEvdefAddfldI18N;
+
+	/** Stored procedure that defines an entity reference field */
+	private StoredProc fEvdefAddfldEntity;
+
+	/** Unbounded integer field definition procedure */
+	private StoredProc fEvdefAddfldInt;
+
+	/** Stored procedure that defines an integer field with a lower bound */
+	private StoredProc fEvdefAddfldIntMin;
+
+	/** Stored procedure that defines an integer field with a higher bound */
+	private StoredProc fEvdefAddfldIntMax;
+
+	/** Stored procedure that defines a bounded integer field */
+	private StoredProc fEvdefAddfldIntRange;
+
+	/** Unbounded double precision field definition procedure */
+	private StoredProc fEvdefAddfldReal;
+
+	/** Stored procedure that defines a double precision field with a lower bound */
+	private StoredProc fEvdefAddfldRealMin;
+
+	/** Stored procedure that defines a double precision field with a higher bound */
+	private StoredProc fEvdefAddfldRealMax;
+
+	/** Stored procedure that defines a bounded double precision field */
+	private StoredProc fEvdefAddfldRealRange;
+
+	/** Unbounded text field definition procedure */
+	private StoredProc fEvdefAddfldText;
+
+	/** Stored procedure that defines a text field with a minimum length */
+	private StoredProc fEvdefAddfldTextMin;
+
+	/** Stored procedure that defines a text field with a maximal length */
+	private StoredProc fEvdefAddfldTextMax;
+
+	/** Stored procedure that defines a bounded text field */
+	private StoredProc fEvdefAddfldTextRange;
+
+
+	/**
+	 * Obtain the name of the definitions file
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * Import all definitions and handle exceptions.
+	 * 
+	 * @param data
+	 *            the event definitions top-level instance
+	 * @return <code>true</code> on success, <code>false</code> 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * This method calls the <code>events.evdef_start()</code> 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
+	 * 
+	 * <p>
+	 * Call the appropriate field definition importation method depending on the field's type.
+	 * 
+	 * @param evdef
+	 *            the event definition
+	 * @param field
+	 *            the field definition
+	 * 
+	 * @return <code>true</code> on success, <code>false</code> 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
+	 * 
+	 * <p>
+	 * This method calls <code>events.evdef_addfld_bool</code> 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
+	 * 
+	 * <p>
+	 * This method calls <code>events.evdef_addfld_i18n</code> 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
+	 * 
+	 * <p>
+	 * This method calls <code>events.evdef_addfld_entity</code> 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * This method calls the <code>events.evdef_finalise()</code> stored procedure, and handles its
+	 * return values.
+	 * 
+	 * @param evdef
+	 *            the event definition from the XML file
+	 * 
+	 * @return <code>true</code> if the definition was finalised or if it had no effect (except for
+	 *         cleanup) due to a previous error, <code>false</code> 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
+ * 
+ * <p>
+ * This class implements loading event definitions from XML files. The definitions will be extracted
+ * and verified.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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
+	 * 
+	 * <p>
+	 * 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 <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@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 <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@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
+ * 
+ * <p>
+ * 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 <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ * 
+ */
+@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
+ * 
+ * <p>
+ * This class represents the list of event type definitions as it is found in the appropriate data
+ * file.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@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
+ * 
+ * <p>
+ * This abstract class is used as the base for event field definitions.
+ * 
+ * @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@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 <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@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 <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@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 <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@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 <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
+ */
+@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 \