/* * Copyright 2005 The Codehaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.mojo.castor; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.compiler.util.scan.InclusionScanException; import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner; import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping; import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; import org.codehaus.plexus.util.FileUtils; import org.exolab.castor.util.Version; /** * A mojo that uses Castor to generate a collection of javabeans from an XSD. Detailed explanations of many of these can * be found in the details for the Castor <a href="http://castor.codehaus.org/sourcegen.html">SourceGenerator</a>. * * @goal generate * @phase generate-sources * @description Castor plugin * @author brozow <brozow@opennms.org> * @author jesse <jesse.mcconnell@gmail.com> */ public class GenerateMojo extends AbstractMojo { /** * Output message to indicate that class descriptor generation is disabled. */ private static final String DISABLE_DESCRIPTORS_MSG = "Disabling generation of Class descriptors"; /** * Output message to indicate that generation of marshaling framework methods is disabled. */ private static final String DISABLE_MARSHALL_MSG = "Disabling generation of Marshalling framework methods (marshall, unmarshall, validate)."; /** * Default location of castorbuilder.properties file. */ private static final String DEFAULT_PROPERTY_FILE_LOCATION = "src/main/castor/castorbuilder.properties"; /** * The binding file to use for mapping xml to java. * * @parameter expression="${basedir}/src/main/castor/bindings.xml" */ private File bindingfile; /** * A schema file to process. If this is not set then all .xsd files in schemaDirectory will be processed. * * @parameter */ private File schema; /** * The source directory containing *.xsd files * * @parameter expression="${basedir}/src/main/castor" */ private File schemaDirectory; /** * The directory to output the generated sources to * * @parameter expression="${project.build.directory}/generated-sources/castor" * @todo This would be better as outputDirectory but for backward compatibility I left it as dest */ private File dest; /** * The directory to store the processed xsds. The timestamps of these xsds are used to determine if the source for * that xsd need to be regenerated * * @parameter expression="${project.build.directory}/xsds" * @todo timestampDirectory would be a better name for this. Used this name for backward compatibility */ private File tstamp; /** * The granularity in milliseconds of the last modification date for testing whether a source needs recompilation * * @parameter expression="${lastModGranularityMs}" default-value="0" */ private int staleMillis = 0; /** * Castor collection types. Allowable values are 'vector', 'arraylist', 'j2' or 'odmg' 'j2' and 'arraylist' are the * same. * * @parameter default-value="arraylist"; */ private String types = "arraylist"; /** * If true, generate descriptors * * @parameter default-value="true" */ private boolean descriptors = true; /** * Verbose output during generation * * @parameter default-value="false" */ private boolean verbose = false; /** * Enable warning messages * * @parameter default-value="false" */ private boolean warnings = false; /** * if false, don't generate the marshaller * * @parameter default-value="true" */ private boolean marshal = true; /** * The line separator to use in generated source. Can be either win, unix, or mac * * @parameter */ private String lineSeparator; /** * The castorbuilder.properties file to use * * @parameter expression="${basedir}/src/main/castor/castorbuilder.properties" */ private File properties; /** * The package for the generated source * * @parameter */ private String packaging; /** * Whether to generate Java classes from imported XML schemas or not. * * @parameter default-value="false" */ private boolean generateImportedSchemas = false; /** * Set to <tt>true</tt> to generate Castor XML class mappings for the Java classes generated by the XML code * generator from imported XML schemas. * * @parameter default-value="false" */ private boolean generateMappings = false; /** * The method to use for Java class generation; possible values are 'standard' (default) and 'velocity'. * * @parameter default-value="standard" */ private String classGenerationMode = "standard"; /** * The directory to output generated <b>resources</b> files to. * * @parameter expression="${project.build.directory}/generated-sources/castor" */ private File resourceDestination; /** * Whether to generate JDO-specific descriptor classes or not. * * @parameter default-value="false" */ private boolean createJdoDescriptors = false; /** * The Maven project to act upon. * * @parameter expression="${project}" * @required */ private MavenProject project; /** * {@link SourceGenerator} instance used for code generation. * * @see SourceGenerator#generateSource(org.xml.sax.InputSource, String) */ private CastorSourceGenerator sgen; /** * {@inheritDoc} * * @see org.apache.maven.plugin.AbstractMojo#execute() */ public void execute() throws MojoExecutionException { if ( !dest.exists() ) { FileUtils.mkdir( dest.getAbsolutePath() ); } if ( !resourceDestination.exists() ) { FileUtils.mkdir( resourceDestination.getAbsolutePath() ); } Set<File> staleXSDs = computeStaleXSDs(); if ( staleXSDs.isEmpty() ) { getLog().info( "Nothing to process - all xsds are up to date" ); // adding generated sources to Maven project project.addCompileSourceRoot( dest.getAbsolutePath() ); // adding resources directory to Maven project getLog().info( "Adding resource " + getResourceDestination().getAbsolutePath() + " ... " ); Resource resource = new Resource(); resource.setDirectory( getResourceDestination().getAbsolutePath() ); resource.addInclude( "**/*.cdr" ); resource.addExclude( "**/*.java" ); project.addResource( resource ); return; } config(); for ( Iterator<File> i = staleXSDs.iterator(); i.hasNext(); ) { File xsd = (File) i.next(); try { processFile( xsd.getCanonicalPath() ); // copy stale xsd to timestamp directory within the same relative path File timeStampDir = getTimeStampDirectory(); // make sure this is after the actual processing, // otherwise it if fails the computeStaleXSDs will think it completed. FileUtils.copyFileToDirectory( xsd, timeStampDir ); } catch ( Exception e ) { throw new MojoExecutionException( "Castor execution failed", e ); } catch ( Throwable t ) { throw new MojoExecutionException( "Castor execution failed", t ); } } if ( project != null ) { project.addCompileSourceRoot( dest.getAbsolutePath() ); // adding resources directory to Maven project getLog().info( "Adding resource " + getResourceDestination().getAbsolutePath() + " ... " ); Resource resource = new Resource(); resource.setDirectory( getResourceDestination().getAbsolutePath() ); resource.addInclude( "**/*.cdr" ); resource.addExclude( "**/*.java" ); project.addResource( resource ); } } /** * Computes the collection of <i>stale</i> XML schemas for which sources need to be re-generated. * * @return A set of XML schemas for which sources need to be re-generated. * @throws MojoExecutionException If there's a problem with scanning all potential XML schemas. */ private Set<File> computeStaleXSDs() throws MojoExecutionException { Set<File> staleSources = new HashSet<File>(); if ( schema != null && schema.exists() ) { File sourceFile = schema; File targetFile = new File( getTimeStampDirectory(), sourceFile.getName() ); if ( !targetFile.exists() || ( targetFile.lastModified() + staleMillis < sourceFile.lastModified() ) ) { getLog().debug( "Finding XSDs - adding schema " + targetFile ); staleSources.add( sourceFile ); } else { getLog().debug( "Finding XSDs - adding schema " + targetFile ); } } else { SourceMapping mapping = new SuffixMapping( ".xsd", ".xsd" ); File tstampDir = getTimeStampDirectory(); File schemaDir = schemaDirectory; SourceInclusionScanner scanner = getSourceInclusionScanner(); scanner.addSourceMapping( mapping ); try { getLog().debug( "Finding XSDs - adding scanned XSDs from schemaDir " + schemaDir + " tstampDir " + tstampDir ); staleSources.addAll( scanner.getIncludedSources( schemaDir, tstampDir ) ); } catch ( InclusionScanException e ) { throw new MojoExecutionException( "Error scanning source root: \'" + schemaDir + "\' for stale xsds to reprocess.", e ); } } return staleSources; } /** * Returns a {@link SourceInclusionScanner} instance. * * @return A {@link SourceInclusionScanner} instance. */ private SourceInclusionScanner getSourceInclusionScanner() { return new StaleSourceScanner( staleMillis ); } /** * Returns the location where timestamp information is recorded. * * @return the location where timestamp information is recorded. */ private File getTimeStampDirectory() { return tstamp; } /** * Entry point for configuring the underlying Castor XML code generator based upon the plugin configuration. * * @throws MojoExecutionException If there's a problem accessing resources as specified in the plugin configuration. */ private void config() throws MojoExecutionException { sgen = CastorSourceGenerator.createSourceGenerator( types ); if ( getLog().isInfoEnabled() ) { getLog().info( "Successfully created an instance of the Castor XML source generator, release " + Version.VERSION ); } sgen.setLog( getLog() ); sgen.setLineSeparatorStyle( lineSeparator ); sgen.setDestDir( dest.getAbsolutePath() ); callSetterMethodUsingReflection( "setResourceDestination", String.class, getResourceDestination().getAbsolutePath() ); if ( bindingfile != null && bindingfile.exists() ) { sgen.setBindingFile( bindingfile ); } sgen.setVerbose( verbose ); sgen.setSuppressNonFatalWarnings( !warnings ); sgen.setDescriptorCreation( descriptors ); if ( !descriptors ) { log( DISABLE_DESCRIPTORS_MSG ); } sgen.setCreateMarshalMethods( marshal ); if ( !marshal ) { log( DISABLE_MARSHALL_MSG ); } if ( properties != null && properties.exists() ) { sgen.setBuilderProperties( properties ); } else { File defaultPropertyFile = new File( getProject().getBasedir(), DEFAULT_PROPERTY_FILE_LOCATION ); if ( properties != null && !properties.equals( defaultPropertyFile ) ) { getLog().warn( "Cannot find custom builder property file " + properties.getAbsolutePath() ); throw new MojoExecutionException( "Cannot find custom builder property file " + properties.getAbsolutePath() ); } else if ( properties != null ) { getLog().info( "There is no custom builder property file at " + "the default location at " + properties.getAbsolutePath() + ". Continuing code generation without." ); } } if ( createJdoDescriptors == true ) { callSetterMethodUsingReflection( "setJdoDescriptorCreation", boolean.class, new Boolean( createJdoDescriptors ) ); } if ( isGenerateImportedSchemas() == true ) { sgen.setGenerateImportedSchemas( true ); } if ( generateMapping() == true ) { sgen.setGenerateMappingFile( this.generateMappings ); sgen.setDescriptorCreation( false ); } if ( !getClassGenerationMode().equals( "standard" ) ) { callSetterMethodUsingReflection( "setJClassPrinterType", String.class, getClassGenerationMode() ); } } /** * Helper method to invoke a setter method on SourceGenerator that might not be available due to a version issue. * * @param methodName Name of the method * @param parameterType Type of the method parameter. * @param parameterValue Actual parameter value to be used during method invocation. * @throws MojoExecutionException If the method cannot be invoked. */ private void callSetterMethodUsingReflection( final String methodName, final Class<?> parameterType, final Object parameterValue ) throws MojoExecutionException { getLog().info( "About to invoke method >" + methodName + "< on Castor's SourceGenerator class using reflection." ); try { Method method = sgen.getClass().getMethod( methodName, new Class[] { parameterType } ); method.invoke( sgen, new Object[] { parameterValue } ); } catch ( NoSuchMethodException e ) { getLog().info( "Specified method " + methodName + " not available on class SourceGenerator." ); // unable to find method to configure JDO descriptor creation. } catch ( IllegalArgumentException e ) { throw new MojoExecutionException( "Problem calling SourceGenerator." + methodName + ": ", e ); } catch ( IllegalAccessException e ) { throw new MojoExecutionException( "Problem calling SourceGenerator." + methodName + ": ", e ); } catch ( InvocationTargetException e ) { throw new MojoExecutionException( "Problem calling SourceGenerator." + methodName + ": ", e ); } } /** * Run source generation on the {@link File} soecified. * * @param filePath The XML schema file (path) to be processed. * @throws MojoExecutionException If there's a problem related to executing this mojo. */ private void processFile( String filePath ) throws MojoExecutionException { log( "Processing " + filePath ); try { sgen.generateSource( filePath, packaging ); } catch ( FileNotFoundException e ) { String message = "XML Schema file \"" + filePath + "\" not found."; log( message ); throw new MojoExecutionException( message ); } catch ( IOException iox ) { throw new MojoExecutionException( "An IOException occurred processing " + filePath, iox ); } catch ( Exception e ) { throw new MojoExecutionException( "An Exception occurred processing " + filePath, e ); } } /** * Logs a message to the logger. * * @param msg The message ot be logged. */ private void log( final String msg ) { getLog().info( msg ); } /** * Returns the destination directory to used during code generation. * * @return the destination directory to used during code generation. */ public File getDest() { return dest; } /** * Returns the destination directory for resource files generated during code generation. * * @return the destination directory for resource files. */ public File getResourceDestination() { return this.resourceDestination; } /** * Sets the destination directory to used during code generation. * * @param dest the destination directory to used during code generation. */ public void setDest( final File dest ) { this.dest = dest; } /** * Sets the destination directory for resource files generated during code generation. * * @param resourceDestination the destination directory for generated resource files. */ public void setResourceDestination( final File resourceDestination ) { this.resourceDestination = resourceDestination; } /** * Returns the directory to store time stamp information. * * @return the directory to store time stamp information. */ public File getTstamp() { return tstamp; } /** * Sets the directory to store time stamp information. * * @param tstamp the directory to store time stamp information. */ public void setTstamp( final File tstamp ) { this.tstamp = tstamp; } /** * Returns the default package to be used during code generation. * * @return the default package to be used during code generation. */ public String getPackaging() { return packaging; } /** * Sets the default package to be used during code generation. * * @param packaging the default package to be used during code generation. */ public void setPackaging( final String packaging ) { this.packaging = packaging; } /** * Returns the (single) XML schema file to be processed. * * @return the (single) XML schema file to be processed. */ public File getSchema() { return schema; } /** * Sets the (single) XML schema file to be processed. * * @param schema the (single) XML schema file to be processed. */ public void setSchema( final File schema ) { this.schema = schema; } /** * Returns the collection types Castor XML is capable of working with. * * @return the collection types Castor XML is capable of working with. */ public String getTypes() { return types; } /** * Sets the collection types Castor XML is capable of working with. * * @param types the collection types Castor XML is capable of working with. */ public void setTypes( final String types ) { this.types = types; } /** * Sets the Castor XML code generator binding file to be used during code generation. * * @param bindingfile the Castor XML code generator binding file to be used during code generation. */ public void setBindingfile( final File bindingfile ) { this.bindingfile = bindingfile; } /** * Sets the (user-specific) <tt>castorbuilder.properties</tt> file to be used during code generation. * * @param properties the (user-specific) <tt>castorbuilder.properties</tt> file to be used during code generation. */ public void setProperties( final File properties ) { this.properties = properties; } /** * Indicates whether #marshal() methods will be generated during the code generation. * * @return True if #marshal() methods will be generated during the code generation. */ public boolean getMarshal() { return marshal; } /** * Sets whether #marshal() methods will be generated during the code generation. * * @param marshal True if #marshal() methods will be generated during the code generation. */ public void setMarshal( final boolean marshal ) { this.marshal = marshal; } /** * Indicates whether code should be generated for imported XML schemas as well. * * @return True if code should be generated for imported XML schemas as well. */ public boolean isGenerateImportedSchemas() { return generateImportedSchemas; } /** * Sets whether code should be generated for imported XML schemas as well. * * @param generateImportedSchemas True if code should be generated for imported XML schemas as well. */ public void setGenerateImportedSchemas( final boolean generateImportedSchemas ) { this.generateImportedSchemas = generateImportedSchemas; } /** * Returns the {@link MavenProject} instance for which code generation should be executed. * * @return the {@link MavenProject} instance for which code generation should be executed. */ public MavenProject getProject() { return project; } /** * Sets the {@link MavenProject} instance for which code generation should be executed. * * @param project the {@link MavenProject} instance for which code generation should be executed. */ public void setProject( MavenProject project ) { this.project = project; } /** * Indicates whether JDO descriptors should be generated during code generation. * * @return True if JDO descriptors should be generated during code generation. */ public final boolean getCreateJdoDescriptors() { return createJdoDescriptors; } /** * Sets whether JDO descriptors should be generated during code generation. * * @param newCreateJdoDescriptors True if JDO descriptors should be generated during code generation. */ public final void setCreateJdoDescriptors( final boolean newCreateJdoDescriptors ) { this.createJdoDescriptors = newCreateJdoDescriptors; } /** * Indicates whether mapping files should be created during code generation. * * @return True if mapping files should be created during code generation */ private boolean generateMapping() { return this.generateMappings; } /** * Sets whether mapping files should be created during code generation. * * @param generateMappings True if mapping files should be created during code generation. */ public final void setGenerateMappings( final boolean generateMappings ) { this.generateMappings = generateMappings; } /** * Returns the method to use for Java class generation. * * @return The method to use for Java class generation. */ public String getClassGenerationMode() { return this.classGenerationMode; } /** * Sets the method to use for Java class generation; possible values are 'standard' (default) and 'velocity'. * * @param classPrinterMethod The class generation mode to use. */ public void setClassGenerationMode( final String classPrinterMethod ) { this.classGenerationMode = classPrinterMethod; } }