Technology definitions loader

* Added "dummy" data file for technologies (for now it simply copies the
old, line-based technologies) and corresponding XML schema

* Added missing SQL stored procedure to clear all dependencies and
reverse dependencies from a technology

* Added import classes, loader and import tool for the technology graph

* Added tech graph import tool to post-build tests
This commit is contained in:
Emmanuel BENOîT 2012-02-27 20:04:02 +01:00
parent c5464212bc
commit 1f3c7a9202
24 changed files with 1731 additions and 43 deletions

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<lw-tech-graph xmlns="http://www.deepclone.com/lw/b6/m2/tech-graph"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.deepclone.com/lw/b6/m2/tech-graph tech-graph.xsd">
<!-- Military technologies (cruisers, battle cruisers, dreadnoughts) -->
<technology name="cruisersTech" category="milTech"
description="cruisersTechDescription" discovery="cruisersTechDescription"
points="25000" cost="10000" />
<technology name="bCruisersTech" category="milTech"
description="bCruisersTechDescription" discovery="bCruisersTechDescription"
points="900000" cost="400000">
<depends-on>cruisersTech</depends-on>
</technology>
<technology name="dreadnoughtsTech" category="milTech"
description="dreadnoughtsTechDescription" discovery="dreadnoughtsTechDescription"
points="2250000" cost="1012500">
<depends-on>bCruisersTech</depends-on>
</technology>
<!-- Civilian technologies (industrial factories, corpse re-animation, bio-turrets -->
<technology name="indFactTech" category="civTech"
description="indFactTechDescription" discovery="indFactTechDescription"
points="10000" cost="5000" />
<technology name="reanimationTech" category="civTech"
description="reanimationTechDescription" discovery="reanimationTechDescription"
points="562500" cost="281250">
<depends-on>indFactTech</depends-on>
</technology>
<technology name="superTurretTech" category="civTech"
description="superTurretTechDescription" discovery="superTurretTechDescription"
points="1350000" cost="607500">
<depends-on>reanimationTech</depends-on>
</technology>
</lw-tech-graph>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="http://www.deepclone.com/lw/b6/m2/tech-graph"
targetNamespace="http://www.deepclone.com/lw/b6/m2/tech-graph"
xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="lw-tech-graph">
<xs:complexType>
<xs:sequence>
<xs:element name="technology" type="technology-description"
minOccurs="1" maxOccurs="unbounded">
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:simpleType name="positive-long">
<xs:restriction base="xs:long">
<xs:minExclusive value="0"></xs:minExclusive>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="technology-description">
<xs:sequence>
<xs:element name="depends-on" type="xs:token" minOccurs="0"
maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="name" use="required" type="xs:token" />
<xs:attribute name="category" use="required" type="xs:token" />
<xs:attribute name="description" use="required" type="xs:token" />
<xs:attribute name="discovery" use="required" type="xs:token" />
<xs:attribute name="cost" use="required" type="positive-long" />
<xs:attribute name="points" use="required" type="positive-long" />
</xs:complexType>
</xs:schema>

View file

@ -0,0 +1,326 @@
package com.deepclone.lw.cli;
import java.io.File;
import java.util.HashMap;
import java.util.List;
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.TechnologyLoader;
import com.deepclone.lw.cli.xmlimport.data.DataImportException;
import com.deepclone.lw.cli.xmlimport.data.techs.Technologies;
import com.deepclone.lw.cli.xmlimport.data.techs.Technology;
import com.deepclone.lw.cli.xmlimport.data.techs.TechnologyDefinitionResult;
import com.deepclone.lw.utils.StoredProc;
/**
* Technology graph import tool
*
* <p>
* This class defines the body of the technology graph import tool. It loads the data file specified
* on the command line, validates it and then loads the technology definitions and dependencies into
* the database.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class ImportTechGraph
extends CLITool
{
/** Logging system */
private final Logger logger = Logger.getLogger( ImportTechGraph.class );
/** File to read the definitions from */
private File file;
/** Spring transaction template */
private TransactionTemplate tTemplate;
/** Technology creation or update stored procedure */
private StoredProc fUocTechnology;
/** Stored procedure that removes all dependencies from a technology */
private StoredProc fTechdepClear;
/** Stored procedure that adds a dependency to a technology */
private StoredProc fTechdepAdd;
/**
* 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 stored procedure wrappers.
*
* @param ctx
* the Spring application context
*/
private void createTemplates( ApplicationContext ctx )
{
DataSource dSource = ctx.getBean( DataSource.class );
PlatformTransactionManager tManager = ctx.getBean( PlatformTransactionManager.class );
this.fUocTechnology = new StoredProc( dSource , "defs" , "uoc_technology" )
.addParameter( "_name" , java.sql.Types.VARCHAR ).addParameter( "_category" , java.sql.Types.VARCHAR )
.addParameter( "_discovery" , java.sql.Types.VARCHAR )
.addParameter( "_description" , java.sql.Types.VARCHAR )
.addParameter( "_price" , java.sql.Types.BIGINT ).addParameter( "_points" , java.sql.Types.BIGINT )
.addOutput( "_result" , java.sql.Types.OTHER );
this.fTechdepClear = new StoredProc( dSource , "defs" , "techdep_clear" ).addParameter( "_tech" ,
java.sql.Types.VARCHAR );
this.fTechdepAdd = new StoredProc( dSource , "defs" , "techdep_add" )
.addParameter( "_dependent" , java.sql.Types.VARCHAR )
.addParameter( "_dependency" , java.sql.Types.VARCHAR ).addOutput( "_result" , java.sql.Types.OTHER );
this.tTemplate = new TransactionTemplate( tManager );
}
/**
* Import all technology definitions
*
* <p>
* Import all technology definitions from the top-level {@link Technologies}, creating a map of
* dependencies. Once all definitions have been imported, add all dependencies.
*
* @param data
* the top level {@link Technologies} data instance
*
* @throws DataImportException
* when some technology definition or dependency cannot be imported
*/
private void importTechnologies( Technologies data )
throws DataImportException
{
Map< String , List< String > > dependencies = new HashMap< String , List< String > >( );
for ( Technology technology : data ) {
this.importTechnologyDefinition( technology );
dependencies.put( technology.getName( ) , technology.getDependencies( ) );
}
for ( Map.Entry< String , List< String > > techDeps : dependencies.entrySet( ) ) {
for ( String dependency : techDeps.getValue( ) ) {
this.importDependency( techDeps.getKey( ) , dependency );
}
}
}
/**
* Import a technology definition
*
* <p>
* Import the definition, and if there was already a definition by that name, clear its
* dependencies.
*
* @param technology
* the definition to import
* @throws DataImportException
* when the update or creation stored procedure returns anything other than
* <code>CREATED</code> or <code>UPDATED</code>
*/
private void importTechnologyDefinition( Technology technology )
throws DataImportException
{
this.logger.info( "Importing technology '" + technology.getName( ) + "'" );
TechnologyDefinitionResult result = this.getResultOf( this.fUocTechnology.execute( technology.getName( ) ,
technology.getCategory( ) , technology.getDiscovery( ) , technology.getDescription( ) ,
technology.getCost( ) , technology.getPoints( ) ) );
if ( result == TechnologyDefinitionResult.UPDATED ) {
this.fTechdepClear.execute( technology.getName( ) );
} else if ( result != TechnologyDefinitionResult.CREATED ) {
throw new DataImportException( "Error while importing technology '" + technology.getName( ) + "': "
+ result );
}
}
/**
* Import a dependency between two technologies
*
* <p>
* Attempt to add a dependency from a technology to another.
*
* @param dependent
* the dependent technology
* @param dependency
* the technology to depend on
*
* @throws DataImportException
* if dependency creation fails
*/
private void importDependency( String dependent , String dependency )
throws DataImportException
{
this.logger.info( "Importing dependency from " + dependent + " to " + dependency );
TechnologyDefinitionResult result = this.getResultOf( this.fTechdepAdd.execute( dependent , dependency ) );
switch ( result ) {
case CREATED:
return;
case REDUNDANT:
throw new DataImportException( "( " + dependent + " -> " + dependency + " ) is redundant" );
case BAD_STRINGS:
throw new DataImportException( "( " + dependent + " -> " + dependency + " ): undefined technology" );
case CYCLE:
throw new DataImportException( "( " + dependent + " -> " + dependency + " ) would create a cycle" );
default:
throw new DataImportException( "( " + dependent + " -> " + dependency + " ): unexpected error code "
+ result );
}
}
/**
* Helper method for stored procedure results
*
* <p>
* This method converts the map returned by one of the definition stored procedures into a
* {@link TechnologyDefinitionResult} value.
*
* @param spResult
* the return value of {@link StoredProc#execute(Object...)}
*
* @return the converted value of the _result field
*/
private TechnologyDefinitionResult getResultOf( Map< String , Object > spResult )
{
return TechnologyDefinitionResult.valueOf( ( (PGobject) spResult.get( "_result" ) ).getValue( ) );
}
/**
* Run the technology definitions import tool
*
* <p>
* Loads the data file, the connects to the database and creates or updates all definitions.
*/
@Override
public void run( )
{
Technologies data;
try {
data = new TechnologyLoader( this.file ).load( );
} catch ( DataImportException e ) {
this.logger.error( "Error while loading technology definitions" , e );
return;
}
AbstractApplicationContext ctx = this.createContext( );
this.createTemplates( ctx );
this.executeImportTransaction( data );
ToolBase.destroyContext( ctx );
}
/**
* Execute the technology definitions importation transaction
*
* <p>
* Run a transaction and execute the importation code inside it. Roll back if anything goes
* wrong.
*
* @param data
* the {@link Technologies} definitions instance
*/
private void executeImportTransaction( final Technologies data )
{
boolean rv = this.tTemplate.execute( new TransactionCallback< Boolean >( ) {
@Override
public Boolean doInTransaction( TransactionStatus status )
{
boolean rv = ImportTechGraph.this.doTransaction( data );
if ( !rv ) {
status.setRollbackOnly( );
}
return rv;
}
} );
if ( rv ) {
this.logger.info( "Technology import successful" );
}
}
/**
* Import transaction body
*
* <p>
* Import all definitions and handle exceptions.
*
* @param data
* the {@link Technologies} definitions instance
* @return
*/
private boolean doTransaction( Technologies data )
{
try {
this.importTechnologies( data );
return true;
} catch ( RuntimeException e ) {
this.logger.error( "Caught runtime exception" , e );
} catch ( DataImportException e ) {
this.logger.error( e.getMessage( ) );
}
return false;
}
/**
* 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;
}
}

View file

@ -0,0 +1,116 @@
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.techs.Technologies;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
/**
* Technology Loader
*
* <p>
* This class can be used to load all technology definitions. It extracts them from the XML file and
* verifies them.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public class TechnologyLoader
{
/** The file to read the XML from */
private final File file;
/**
* Initialise the loader
*
* @param file
* the XML file that contains the definitions
*/
public TechnologyLoader( File file )
{
this.file = file.getAbsoluteFile( );
}
/**
* Initialise the necessary XStream instance
*
* <p>
* Initialise the XStream instance by processing the annotations of all the technology
* importable data classes.
*
* @return the XStream instance to use when loading the data
*/
private XStream initXStream( )
{
XStream xstream = new XStream( );
xstream.processAnnotations( Technologies.class );
return xstream;
}
/**
* Load the technology definitions
*
* <p>
* Load the XML file and process the definitions using XStream.
*
* @return the top-level importable data instance
*
* @throws DataImportException
* if reading from the file or parsing its contents fail
*/
private Technologies loadXMLFile( )
throws DataImportException
{
FileInputStream fis;
try {
fis = new FileInputStream( this.file );
} catch ( FileNotFoundException e ) {
throw new DataImportException( "Unable to load technology definitions" , e );
}
try {
try {
XStream xstream = this.initXStream( );
return (Technologies) xstream.fromXML( fis );
} finally {
fis.close( );
}
} catch ( IOException e ) {
throw new DataImportException( "Input error while loading technology definitions" , e );
} catch ( XStreamException e ) {
throw new DataImportException( "XML error while loading technology definitions" , e );
}
}
/**
* Load and process technology definitions
*
* <p>
* Attempt to load all technology definitions, ensure 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 Technologies load( )
throws DataImportException
{
Technologies techs = this.loadXMLFile( );
techs.verifyData( );
techs.setReadFrom( this.file );
return techs;
}
}

View file

@ -64,9 +64,9 @@ public class BasicResource
throw new DataImportException( "Missing name string" );
}
if ( this.description == null || "".equals( this.description.trim( ) ) ) {
throw new DataImportException( "Missing name string" );
throw new DataImportException( "Missing description string" );
}
if ( this.category != null && "".equals( this.description.trim( ) ) ) {
if ( this.category != null && "".equals( this.category.trim( ) ) ) {
throw new DataImportException( "Category invalid" );
}
if ( this.weight == null ) {

View file

@ -0,0 +1,98 @@
package com.deepclone.lw.cli.xmlimport.data.techs;
import java.util.Collections;
import java.util.HashSet;
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;
/**
* Technology definitions data
*
* <p>
* This class represents the contents of a technology definition XML file. It contains a list of
* technology definitions.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@SuppressWarnings( "serial" )
@XStreamAlias( "lw-tech-graph" )
public class Technologies
extends ImportableData
implements Iterable< Technology >
{
/** All present technology definitions */
@XStreamImplicit( itemFieldName = "technology" )
private final List< Technology > technologies = new LinkedList< Technology >( );
/**
* Check the technology data
*
* <p>
* Check whether there is a list of technologies, then check each definition to make sure that
* it is valid and that its name is unique.
*/
@Override
public void verifyData( )
throws DataImportException
{
if ( this.technologies == null ) {
throw new DataImportException( "No technology definitions" );
}
HashSet< String > names = new HashSet< String >( );
for ( Technology tech : this.technologies ) {
tech.verifyData( );
this.checkUniqueItem( names , tech.getName( ) );
}
}
/**
* Checks that the name of a technology is unique
*
* <p>
* This helper method is used by {@link #verifyData()} to make sure technology names are unique.
*
* @param existing
* the existing set of names
* @param value
* the name to check
*
* @throws DataImportException
* if the name is already present in the set of existing names
*/
public void checkUniqueItem( HashSet< String > existing , String value )
throws DataImportException
{
if ( existing.contains( value ) ) {
throw new DataImportException( "Duplicate technology name '" + value + "'" );
}
existing.add( value );
}
/**
* Technology definition iterator
*
* <p>
* Grant access to the list of technologies in read-only mode.
*
* @return a read-only iterator on the list of technologies.
*/
@Override
public Iterator< Technology > iterator( )
{
return Collections.unmodifiableList( this.technologies ).iterator( );
}
}

View file

@ -0,0 +1,183 @@
package com.deepclone.lw.cli.xmlimport.data.techs;
import java.util.Collections;
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.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
/**
* A technology definition
*
* <p>
* This class represents a technology definition that can be imported from the data file. It
* contains all required technology attributes, as well as a list of strings for the dependencies.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
@SuppressWarnings( "serial" )
public class Technology
extends ImportableData
{
/** Identifier of the name string */
@XStreamAsAttribute
private String name;
/** Identifier of the category string */
@XStreamAsAttribute
private String category;
/** Identifier of the description string */
@XStreamAsAttribute
private String description;
/** Identifier of the string to display on technology discovery */
@XStreamAsAttribute
private String discovery;
/** Monetary cost of the technology */
@XStreamAsAttribute
private Long cost;
/** Required amount of research points */
@XStreamAsAttribute
private Long points;
/** List of technology name string identifiers describing the dependencies */
@XStreamImplicit( itemFieldName = "depends-on" )
private final List< String > dependencies = new LinkedList< String >( );
/**
* Technology definition verifier
*
* <p>
* Make sure that all technology definition fields are both present and valid. If dependencies
* exist, also verify the names' validity.
*/
@Override
public void verifyData( )
throws DataImportException
{
if ( this.name == null || "".equals( this.name.trim( ) ) ) {
throw new DataImportException( "Missing name string" );
}
if ( this.category == null || "".equals( this.category.trim( ) ) ) {
throw new DataImportException( "Missing category string in " + this.name );
}
if ( this.description == null || "".equals( this.description.trim( ) ) ) {
throw new DataImportException( "Missing description string in " + this.name );
}
if ( this.discovery == null || "".equals( this.discovery.trim( ) ) ) {
throw new DataImportException( "Missing discovery string in " + this.name );
}
if ( this.points == null ) {
throw new DataImportException( "Missing research points in " + this.name );
} else if ( this.points <= 0 ) {
throw new DataImportException( "Invalid research points in " + this.name );
}
if ( this.cost == null ) {
throw new DataImportException( "Missing cost in " + this.name );
} else if ( this.cost <= 0 ) {
throw new DataImportException( "Invalid cost in " + this.name );
}
if ( this.dependencies != null ) {
for ( String dep : this.dependencies ) {
if ( dep == null || "".equals( dep.trim( ) ) ) {
throw new DataImportException( "Empty dependency name in " + this.name );
}
}
}
}
/**
* Gets the identifier of the name string.
*
* @return the identifier of the name string
*/
public String getName( )
{
return this.name;
}
/**
* Gets the identifier of the category string.
*
* @return the identifier of the category string
*/
public String getCategory( )
{
return this.category;
}
/**
* Gets the identifier of the description string.
*
* @return the identifier of the description string
*/
public String getDescription( )
{
return this.description;
}
/**
* Gets the identifier of the string to display on technology discovery.
*
* @return the identifier of the string to display on technology discovery
*/
public String getDiscovery( )
{
return this.discovery;
}
/**
* Gets the monetary cost of the technology.
*
* @return the monetary cost of the technology
*/
public Long getCost( )
{
return this.cost;
}
/**
* Gets the required amount of research points.
*
* @return the required amount of research points
*/
public Long getPoints( )
{
return this.points;
}
/**
* Gets the list of technology name string identifiers describing the dependencies.
*
* @return the list of technology name string identifiers describing the dependencies
*/
public List< String > getDependencies( )
{
if ( this.dependencies == null ) {
return Collections.emptyList( );
}
return Collections.unmodifiableList( this.dependencies );
}
}

View file

@ -0,0 +1,47 @@
package com.deepclone.lw.cli.xmlimport.data.techs;
/**
* Return codes for technology definition manipulation stored procedures
*
* <p>
* This enumeration represents the various values which can be returned by the stored procedures
* which manipulate technology definitions and technology dependencies.
*
* @author <a href="mailto:tseeker@legacyworlds.com">E. Benoît</a>
*/
public enum TechnologyDefinitionResult {
/** The technology definition or dependency was created */
CREATED ,
/** The technology definition was updated */
UPDATED ,
/** The dependency was deleted */
DELETED ,
/** The specified dependency does not exist */
MISSING ,
/** One (or more) of the numeric parameters is invalid */
BAD_VALUE ,
/**
* The name, description, discovery or category string identifiers were not valid string
* identifiers.
*/
BAD_STRINGS ,
/**
* The specified description and/or discovery string was in use by another technology.
*/
DUP_STRING ,
/** The dependency would cause a cycle */
CYCLE ,
/** The dependency would be redundant */
REDUNDANT
}