All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.jaxb2.AbstractXjcMojo Maven / Gradle / Ivy

Go to download

Mojo's JAXB-2 Maven plugin is used to create an object graph from XSDs based on the JAXB 2.1 implementation and to generate XSDs from JAXB annotated Java classes.

There is a newer version: 2.4
Show newest version
package org.codehaus.mojo.jaxb2;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.StringTokenizer;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.DirectoryScanner;
import org.xml.sax.SAXParseException;

import com.sun.tools.xjc.Driver;
import com.sun.tools.xjc.XJCListener;


/**
 * Abstract class for for parsing xsd and binding resources to produce a corresponding object model
 * based on the JAXB Xjc parsing engine
 */
public abstract class AbstractXjcMojo
    extends AbstractMojo
{

    /**
     * The default maven project object.
     *
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    /**
     * The optional directory where generated resources can be placed, generated by addons/plugins.
     *
     * @parameter
     */
    protected File generatedResourcesDirectory;

    /**
     * The package in which the source files will be generated.
     *
     * @parameter
     */
    protected String packageName;

    /**
     * Catalog file to resolve external entity references support TR9401,
     * XCatalog, and OASIS XML Catalog format.
     *
     * @parameter
     */
    protected File catalog;

    /**
     * Set HTTP/HTTPS proxy. Format is [user[:password]@]proxyHost[:proxyPort]
     *
     * @parameter
     */
    protected String httpproxy;

    /**
     * List of files to use for bindings, comma delimited. If none, then all xjb
     * files are used in the bindingDirectory.
     *
     * @parameter
     */
    protected String bindingFiles;

    /**
     * List of files to use for schemas, comma delimited. If none, then all xsd
     * files are used in the schemaDirectory. This parameter also accepts Ant style file-patterns.
     * Note: you can only use either the 'schemaFiles' or the 'schemaListFileName'
     * option (you may not use both at once!).
     *
     * @parameter
     */
    protected String schemaFiles;

    /**
     * A filename containing the list of files to use for schemas, comma delimited.
     * If none, then all xsd files are used in the schemaDirectory.
     * Note: you can only use either the 'schemaFiles' or the 'schemaListFileName'
     * option (you may not use both at once!).
     *
     * @parameter
     */
    protected String schemaListFileName;

    /**
     * Treat input schemas as XML DTD (experimental, unsupported).
     *
     * @parameter default-value="false"
     */
    protected boolean dtd;

    /**
     * Suppress generation of package level annotations (package-info.java).
     *
     * @parameter default-value="false"
     */
    protected boolean npa;

    /**
     * Do not perform strict validation of the input schema(s).
     *
     * @parameter default-value="false"
     */
    protected boolean nv;

    /**
     * Treat input schemas as RELAX NG (experimental, unsupported).
     *
     * @parameter default-value="false"
     */
    protected boolean relaxng;

    /**
     * Treat input as RELAX NG compact syntax (experimental,unsupported).
     *
     * @parameter default-value="false"
     */
    protected boolean relaxngCompact;

    /**
     * Suppress compiler output.
     *
     * @parameter default-value="false"
     */
    protected boolean quiet;

    /**
     * Generated files will be in read-only mode.
     *
     * @parameter default-value="false"
     */
    protected boolean readOnly;

    /**
     * Be extra verbose.
     *
     * @parameter expression="${xjc.verbose}" default-value="false"
     */
    protected boolean verbose;

    /**
     * Treat input as WSDL and compile schemas inside it (experimental,unsupported).
     *
     * @parameter default-value="false"
     */
    protected boolean wsdl;

    /**
     * Treat input as W3C XML Schema (default).
     *
     * @parameter default-value="true"
     */
    protected boolean xmlschema;

    /**
     * Allow to use the JAXB Vendor Extensions.
     *
     * @parameter default-value="false"
     */
    protected boolean extension;

    /**
     * Allow generation of explicit annotations that are needed for JAXB2 to work on RetroTranslator.
     *
     * @parameter default-value="false"
     */
    protected boolean explicitAnnotation;

    /**
     * Space separated string of extra arguments, for instance -Xfluent-api -episode somefile; These
     * will be passed on to XJC as "-Xfluent-api" "-episode" "somefile" options.
     *
     * @parameter expression="${xjc.arguments}"
     */
    protected String arguments;

    /**
     * The output path to include in your jar/war/etc if you wish to include your schemas in your artifact.
     *
     * @parameter
     */
    protected String includeSchemasOutputPath;

    /**
     * Clears the output directory on each run. Defaults to 'true' but if false, will not clear the directory.
     *
     * @parameter default-value="true"
     */
    protected boolean clearOutputDir;

    /**
     * Avoid generating code that relies on any current JAXB 2.x features.
     * This will allow the generated code to run with earlier JAXB 2.x runtime.
     *
     * @parameter
     * @since 1.3
     */
    protected String target;

    /**
     * Fails the mojo if no schemas are found.
     *
     * @parameter default-value="true"
     * @since 1.3
     */
    protected boolean failOnNoSchemas;

    /**
     * Enable correct generation of Boolean getters/setters to enable Bean Introspection apis. 
     *
     * @parameter default-value="false"
     * @since 1.4
     */
    private boolean enableIntrospection;

    /**
     * The char encoding for the generated Java source files.
     *
     * @parameter default-value="${project.build.sourceEncoding}"
     */
    private String encoding;

    public AbstractXjcMojo()
    {
        super();
    }

    public void execute()
        throws MojoExecutionException
    {

        try
        {
            if ( isOutputStale() )
            {
                getLog().info( "Generating source..." );

                prepareDirectory( getOutputDirectory() );

                if ( generatedResourcesDirectory != null )
                {
                    prepareDirectory( generatedResourcesDirectory );
                }

                // Need to build a URLClassloader since Maven removed it form
                // the chain
                ClassLoader parent = this.getClass().getClassLoader();
                List classpathFiles = getClasspathElements( project );
                List urls = new ArrayList( classpathFiles.size() + 1 );
                StringBuilder classPath = new StringBuilder();
                for ( String classpathFile : classpathFiles )
                {
                    getLog().debug( classpathFile );
                    urls.add( new File( classpathFile ).toURI().toURL() );
                    classPath.append( classpathFile );
                    classPath.append( File.pathSeparatorChar );
                }

                urls.add( new File( project.getBuild().getOutputDirectory() ).toURI().toURL() );
                URLClassLoader cl = new URLClassLoader( urls.toArray( new URL[0] ), parent );

                // Set the new classloader
                Thread.currentThread().setContextClassLoader( cl );

                try
                {

                    ArrayList args = getXJCArgs( classPath.toString() );
                    MojoXjcListener xjcListener = new MojoXjcListener();

                    // Run XJC
                    if ( 0 != Driver.run( args.toArray( new String[args.size()] ), xjcListener ) )
                    {
                        String msg = "Could not process schema";
                        if ( null != schemaFiles )
                        {
                            URL xsds[] = getXSDFiles();
                            msg += xsds.length > 1 ? "s:" : ":";
                            for ( int i = 0; i < xsds.length; i++ )
                            {
                                msg += "\n  " + xsds[i].getFile();
                            }
                        }
                        else
                        {
                            msg += " files in directory " + getSchemaDirectory();
                        }
                        throw new MojoExecutionException( msg );
                    }

                    // Workaround until upgrading to a JAXB impl that supports configuring
                    // the output char encoding (see http://java.net/jira/browse/JAXB-499)
                    changeEncoding( xjcListener.files );

                    touchStaleFile();
                }
                finally
                {
                    // Set back the old classloader
                    Thread.currentThread().setContextClassLoader( parent );
                }

            }
            else
            {
                getLog().info( "No changes detected in schema or binding files, skipping source generation." );
            }

            addCompileSourceRoot( project );

            if ( generatedResourcesDirectory != null )
            {
                Resource resource = new Resource();
                resource.setDirectory( generatedResourcesDirectory.getAbsolutePath() );
                addResource( project, resource );
            }

            if ( includeSchemasOutputPath != null )
            {

                FileUtils.forceMkdir( new File( project.getBuild().getOutputDirectory(), includeSchemasOutputPath ) );

                /**
                 * Resource resource = new Resource();
                 * resource.setDirectory( outputDirectory.getAbsolutePath() );
                 * project.getResources().add( resource );
                 **/
                copyXSDs();
            }
        }
        catch ( NoSchemasException e )
        {
            if ( failOnNoSchemas )
            {
                throw new MojoExecutionException( "no schemas has been found" );
            }
            else
            {
                getLog().warn( "skip xjc execution, no schemas has been found" );
            }
        }
        catch ( MojoExecutionException e )
        {
            throw e;
        }
        catch ( Exception e )
        {
            throw new MojoExecutionException( e.getMessage(), e );
        }

    }

    protected abstract void addCompileSourceRoot( MavenProject project );

    protected abstract void addResource( MavenProject project, Resource resource );

    protected void copyXSDs()
        throws MojoExecutionException
    {
        URL srcFiles[] = getXSDFiles();

        File baseDir = new File( project.getBuild().getOutputDirectory(), includeSchemasOutputPath );
        for ( int j = 0; j < srcFiles.length; j++ )
        {
            URL from = srcFiles[j];
            // the '/' is the URL-separator
            File to = new File( baseDir, FileUtils.removePath( from.getPath(), '/' ) );
            try
            {
                FileUtils.copyURLToFile( from, to );
            }
            catch ( IOException e )
            {
                throw new MojoExecutionException( "Error copying file", e );
            }
        }
    }

    private void prepareDirectory( File dir )
        throws MojoExecutionException
    {
        // If the directory exists, whack it to start fresh
        if ( clearOutputDir && dir.exists() )
        {
            try
            {
                FileUtils.deleteDirectory( dir );
            }
            catch ( IOException e )
            {
                throw new MojoExecutionException( "Error cleaning directory " + dir.getAbsolutePath(), e );
            }
        }

        if ( !dir.exists() )
        {
            if ( !dir.mkdirs() )
            {
                throw new MojoExecutionException( "Could not create directory " + dir.getAbsolutePath() );
            }
        }
    }

    /**
     * @param classPath
     * @return null if no schemas found
     * @throws MojoExecutionException
     */
    private ArrayList getXJCArgs( String classPath )
        throws MojoExecutionException, NoSchemasException
    {
        ArrayList args = new ArrayList();
        if ( npa )
        {
            args.add( "-npa" );
        }
        if ( nv )
        {
            args.add( "-nv" );
        }
        if ( dtd )
        {
            args.add( "-dtd" );
        }
        if ( verbose )
        {
            args.add( "-verbose" );
        }
        if ( quiet )
        {
            args.add( "-quiet" );
        }
        if ( readOnly )
        {
            args.add( "-readOnly" );
        }
        if ( relaxng )
        {
            args.add( "-relaxng" );
        }
        if ( relaxngCompact )
        {
            args.add( "-relaxng-compact" );
        }
        if ( wsdl )
        {
            args.add( "-wsdl" );
        }
        if ( xmlschema )
        {
            args.add( "-xmlschema" );
        }
        if ( explicitAnnotation )
        {
            args.add( "-XexplicitAnnotation" );
        }

        if ( httpproxy != null )
        {
            args.add( "-httpproxy" );
            args.add( httpproxy );
        }

        if ( packageName != null )
        {
            args.add( "-p" );
            args.add( packageName );
        }

        if ( catalog != null )
        {
            args.add( "-catalog" );
            args.add( catalog.getAbsolutePath() );
        }

        if ( extension )
        {
            args.add( "-extension" );
        }

        if ( target != null )
        {
            args.add( "-target" );
            args.add( target );
        }
        
        if ( enableIntrospection )
        {
            args.add( "-enableIntrospection" );
        }
        if ( arguments != null && arguments.trim().length() > 0 )
        {
            try
            {
                String[] argList = CommandLineUtils.translateCommandline( arguments );

                for ( int argIndex = 0; argIndex < argList.length; argIndex++ )
                {
                    args.add( argList[argIndex] );
                }
            }
            catch ( Exception e )
            {
                throw new MojoExecutionException( "failed to split property arguments" );
            }
        }

        args.add( "-d" );
        args.add( getOutputDirectory().getAbsolutePath() );
        args.add( "-classpath" );
        args.add( classPath );

        // Bindings
        File bindings[] = getBindingFiles();
        for ( int i = 0; i < bindings.length; i++ )
        {
            args.add( "-b" );
            args.add( bindings[i].getAbsolutePath() );
        }

        List schemas = new ArrayList();

        // XSDs
        if ( schemaFiles != null || schemaListFileName != null )
        {
            URL xsds[] = getXSDFiles();
            for ( int i = 0; i < xsds.length; i++ )
            {
                schemas.add( xsds[i].toString() );
            }
        }
        else
        {
            if ( getSchemaDirectory().exists() && getSchemaDirectory().isDirectory() )
            {
                File[] schemaFiles = getSchemaDirectory().listFiles( new XSDFile( getLog() ) );
                if ( schemaFiles != null && schemaFiles.length > 0 )
                {
                    schemas.add( getSchemaDirectory().getAbsolutePath() );
                }
            }
        }

        if ( schemas.isEmpty() )
        {
            throw new NoSchemasException();
        }

        args.addAll( schemas );

        getLog().debug( "JAXB XJC args: " + args );

        return args;
    }


    /**
     * getSchemasFromFileListing gets all the entries
     * in the given schemaListFileName and adds them to the list
     * of files to send to xjc
     *
     * @throws MojoExecutionException if an error occurs
     */
    protected void getSchemasFromFileListing( List files )
        throws MojoExecutionException
    {

        // check that the given file exists
        File schemaListFile = new File( schemaListFileName );

        // create a scanner over the input file
        Scanner scanner = null;
        try
        {
            scanner = new Scanner( schemaListFile ).useDelimiter( "," );
        }
        catch ( FileNotFoundException e )
        {
            throw new MojoExecutionException(
                "schemaListFileName: " + schemaListFileName + " could not be found - error:" + e.getMessage(), e );
        }

        // scan the file and add to the list for processing
        String nextToken = null;
        File nextFile = null;
        while ( scanner.hasNext() )
        {
            nextToken = scanner.next();
            URL url;
            try
            {
                url = new URL( nextToken );
            }
            catch ( MalformedURLException e )
            {
                getLog().debug( nextToken + " doesn't look like a URL..." );
                nextFile = new File( getSchemaDirectory(), nextToken.trim() );
                try
                {
                    url = nextFile.toURI().toURL();
                }
                catch ( MalformedURLException e2 )
                {
                    throw new MojoExecutionException( "Unable to convert file to a URL.", e2 );
                }
            }
            files.add( url );

        }
    }

    /**
     * Returns a file array of xjb files to translate to object models.
     *
     * @return An array of schema files to be parsed by the schema compiler.
     */
    public final File[] getBindingFiles()
    {

        List bindings = new ArrayList();
        if ( bindingFiles != null )
        {
            for ( StringTokenizer st = new StringTokenizer( bindingFiles, "," ); st.hasMoreTokens(); )
            {
                String schemaName = st.nextToken();
                bindings.add( new File( getBindingDirectory(), schemaName ) );
            }
        }
        else
        {
            getLog().debug( "The binding Directory is " + getBindingDirectory() );
            File[] files = getBindingDirectory().listFiles( new XJBFile() );
            if ( files != null )
            {
                for ( int i = 0; i < files.length; i++ )
                {
                    bindings.add( files[i] );
                }
            }
        }

        return bindings.toArray( new File[]{ } );
    }

    /**
     * Returns a file array of xsd files to translate to object models.
     *
     * @return An array of schema files to be parsed by the schema compiler.
     */
    public final URL[] getXSDFiles()
        throws MojoExecutionException
    {

        // illegal option check
        if ( schemaFiles != null && schemaListFileName != null )
        {

            // make sure user didn't specify both schema input options
            throw new MojoExecutionException( "schemaFiles and schemaListFileName options were provided, "
                                                  + "these options may not be used together - schemaFiles: "
                                                  + schemaFiles + "; schemaListFileName: " + schemaListFileName );

        }

        List xsdFiles = new ArrayList();
        if ( schemaFiles != null )
        {
            DirectoryScanner scanner = new DirectoryScanner();
            scanner.setBasedir( getSchemaDirectory() );
            scanner.setIncludes( schemaFiles.split( "," ) );
            scanner.scan();
            for ( String schemaName : scanner.getIncludedFiles() )
            {
                URL url = null;
                try
                {
                    url = new URL( schemaName.trim() );
                }
                catch ( MalformedURLException e )
                {
                    try
                    {
                        url = new File( getSchemaDirectory(), schemaName ).toURI().toURL();
                    }
                    catch ( MalformedURLException e2 )
                    {
                        throw new MojoExecutionException( "Unable to convert file to a URL.", e2 );
                    }
                }
                xsdFiles.add( url );
            }
        }
        else if ( schemaListFileName != null )
        {

            // add all the contents from the schemaListFileName file on disk
            getSchemasFromFileListing( xsdFiles );

        }
        else
        {
            getLog().debug( "The schema Directory is " + getSchemaDirectory() );
            File[] files = getSchemaDirectory().listFiles( new XSDFile( getLog() ) );
            if ( files != null )
            {
                for ( int i = 0; i < files.length; i++ )
                {
                    try
                    {
                        xsdFiles.add( files[i].toURI().toURL() );
                    }
                    catch ( MalformedURLException e )
                    {
                        throw new MojoExecutionException( "Unable to convert file to a URL.", e );
                    }
                }
            }
        }

        return xsdFiles.toArray( new URL[xsdFiles.size()] );
    }

    /**
     * Returns true of any one of the files in the XSD/XJB array are more new than
     * the staleFlag file.
     *
     * @return True if xsd files have been modified since the last build.
     */
    private boolean isOutputStale()
        throws MojoExecutionException
    {
        URL[] sourceXsds = getXSDFiles();
        File[] sourceXjbs = getBindingFiles();
        boolean stale = !getStaleFile().exists();
        if ( !stale )
        {
            getLog().debug( "Stale flag file exists, comparing to xsds and xjbs." );
            long staleMod = getStaleFile().lastModified();

            for ( int i = 0; i < sourceXsds.length; i++ )
            {
                URLConnection connection;
                try
                {
                    connection = sourceXsds[i].openConnection();
                    connection.connect();
                }
                catch ( IOException e )
                {
                    stale = true;
                    break;
                }

                try
                {
                    if ( connection.getLastModified() > staleMod )
                    {
                        getLog().debug( sourceXsds[i].toString() + " is newer than the stale flag file." );
                        stale = true;
                        break;
                    }
                }
                finally
                {
                    if ( connection instanceof HttpURLConnection )
                    {
                        ( (HttpURLConnection) connection ).disconnect();
                    }
                }
            }

            for ( int i = 0; i < sourceXjbs.length; i++ )
            {
                if ( sourceXjbs[i].lastModified() > staleMod )
                {
                    getLog().debug( sourceXjbs[i].getName() + " is newer than the stale flag file." );
                    stale = true;
                    break;
                }
            }
        }
        return stale;
    }

    private void touchStaleFile()
        throws IOException
    {

        if ( !getStaleFile().exists() )
        {
            getStaleFile().getParentFile().mkdirs();
            getStaleFile().createNewFile();
            getLog().debug( "Stale flag file created." );
        }
        else
        {
            getStaleFile().setLastModified( System.currentTimeMillis() );
        }
    }

    private void changeEncoding( List files )
        throws IOException
    {
        String to = encoding;
        String from = System.getProperty( "file.encoding" );
        boolean enableEncode = to != null && to.trim().length() > 0 && !to.equals( from );

        if ( enableEncode )
        {
            getLog().debug( "Changing encoding of generated source files from " + from
                            + " to " + to );
            for ( Iterator < String > it = files.iterator(); it.hasNext(); )
            {
                File file = new File( getOutputDirectory(), it.next() );

                if ( file.exists() )
                {
                    String data = FileUtils.fileRead( file );
                    FileUtils.fileWrite( file.getAbsolutePath(), to, data );
                }
                else
                {
                    getLog().warn( "Expected file " + file.getAbsolutePath()
                                    + " not found when changing encoding" );
                }
            }
        }
    }

    protected abstract File getStaleFile();

    protected abstract File getOutputDirectory();

    protected abstract File getSchemaDirectory();

    protected abstract File getBindingDirectory();

    protected abstract List getClasspathElements( MavenProject project )
        throws DependencyResolutionRequiredException;

    /**
     * A class used to look up .xjb documents from a given directory.
     */
    final class XJBFile
        implements FileFilter
    {

        /**
         * Returns true if the file ends with an xjb extension.
         *
         * @param file The file being reviewed by the filter.
         * @return true if an xjb file.
         */
        public boolean accept( final File file )
        {
            return file.isFile() && file.getName().endsWith( ".xjb" );
        }

    }

    /**
     * A class used to look up .xsd documents from a given directory.
     */
    final class XSDFile
        implements FileFilter
    {

        private Log log;

        public XSDFile( Log log )
        {
            this.log = log;
        }

        /**
         * Returns true if the file ends with an xsd extension.
         *
         * @param file The file being reviewed by the filter.
         * @return true if an xsd file.
         */
        public boolean accept( final java.io.File file )
        {
            boolean accept = file.isFile() && file.getName().endsWith( ".xsd" );
            if ( log.isDebugEnabled() )
            {
                log.debug( "accept " + accept + " for file " + file.getPath() );
            }
            return accept;
        }

    }

    /**
     * Class to tap into Maven's logging facility
     */
    class MojoXjcListener
        extends XJCListener
    {

        List files = new ArrayList();

        private String location( SAXParseException e )
        {
            return StringUtils.defaultString( e.getPublicId(), e.getSystemId() ) + "[" + e.getLineNumber() + ","
                + e.getColumnNumber() + "]";
        }

        public void error( SAXParseException arg0 )
        {
            getLog().error( location( arg0 ), arg0 );
        }

        public void fatalError( SAXParseException arg0 )
        {
            getLog().error( location( arg0 ), arg0 );
        }

        public void warning( SAXParseException arg0 )
        {
            getLog().warn( location( arg0 ), arg0 );
        }

        public void info( SAXParseException arg0 )
        {
            getLog().warn( location( arg0 ), arg0 );
        }

        public void message( String arg0 )
        {
            getLog().info( arg0 );
        }

        public void generatedFile( String arg0 )
        {
            getLog().info( arg0 );
            files.add( arg0 );
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy