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

org.codehaus.mojo.jaxb2.helpers.SchemagenHelper 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.x implementation and to generate XSDs from JAXB annotated Java classes.

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

/*
 * 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.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.mojo.jaxb2.TransformSchema;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * Utility class holding algorithms used by the AbstractSchemagenMojo and decendents.
 * 
 * @author Lennart Jörelid
 */
public final class SchemagenHelper
{
    // Constants
    private static final String MISCONFIG = "Misconfiguration detected: ";

    private static final TransformerFactory FACTORY;

    static
    {
        FACTORY = TransformerFactory.newInstance();
        FACTORY.setAttribute( "indent-number", 2 );
    }

    /**
     * Acquires a map relating generated schema filename to its SimpleNamespaceResolver.
     * 
     * @param outputDirectory The output directory of the generated schema files.
     * @return a map relating generated schema filename to an initialized SimpleNamespaceResolver.
     * @throws MojoExecutionException if two generated schema files used the same namespace URI.
     */
    public static Map getFileNameToResolverMap( final File outputDirectory )
        throws MojoExecutionException
    {
        final Map toReturn = new TreeMap();

        // Each generated schema file should be written to the output directory.
        // Each generated schema file should have a unique targetNamespace.
        File[] generatedSchemaFiles = outputDirectory.listFiles( new FileFilter()
        {
            public boolean accept( File pathname )
            {
                return pathname.getName().startsWith( "schema" ) && pathname.getName().endsWith( ".xsd" );
            }
        } );

        for ( File current : generatedSchemaFiles )
        {
            toReturn.put( current.getName(), new SimpleNamespaceResolver( current ) );
        }

        return toReturn;
    }

    /**
     * Validates that the list of Schemas provided within the configuration all contain unique values. Should a
     * MojoExecutionException be thrown, it contains informative text about the exact nature of the configuration
     * problem - we should simplify for all plugin users.
     * 
     * @param configuredTransformSchemas The List of configuration schemas provided to this mojo.
     * @throws MojoExecutionException if any two configuredSchemas instances contain duplicate values for any of the
     *             properties uri, prefix or file. Also throws a MojoExecutionException if the uri of any Schema is null
     *             or empty, or if none of the 'file' and 'prefix' properties are given within any of the
     *             configuredSchema instances.
     */
    public static void validateSchemasInPluginConfiguration( final List configuredTransformSchemas )
        throws MojoExecutionException
    {
        final List uris = new ArrayList();
        final List prefixes = new ArrayList();
        final List fileNames = new ArrayList();

        for ( int i = 0; i < configuredTransformSchemas.size(); i++ )
        {
            final TransformSchema current = configuredTransformSchemas.get( i );
            final String currentURI = current.getUri();
            final String currentPrefix = current.getToPrefix();
            final String currentFile = current.getToFile();

            // We cannot work with a null or empty uri
            if ( StringUtils.isEmpty( currentURI ) )
            {
                throw new MojoExecutionException( MISCONFIG + "Null or empty property 'uri' found in "
                    + "plugin configuration for schema element at index [" + i + "]: " + current );
            }

            // No point in having *only* a namespace.
            if ( StringUtils.isEmpty( currentPrefix ) && StringUtils.isEmpty( currentFile ) )
            {
                throw new MojoExecutionException( MISCONFIG + "Null or empty properties 'prefix' "
                    + "and 'file' found within plugin configuration for schema " + "element at index [" + i + "]: "
                    + current );
            }

            // Validate that all given uris are unique.
            if ( uris.contains( currentURI ) )
            {
                throw new MojoExecutionException( getDuplicationErrorMessage( "uri", currentURI,
                                                                              uris.indexOf( currentURI ), i ) );
            }
            uris.add( currentURI );

            // Validate that all given prefixes are unique.
            if ( prefixes.contains( currentPrefix ) && !( currentPrefix == null ) )
            {
                throw new MojoExecutionException( getDuplicationErrorMessage( "prefix", currentPrefix,
                                                                              prefixes.indexOf( currentPrefix ), i ) );
            }
            prefixes.add( currentPrefix );

            // Validate that all given files are unique.
            if ( fileNames.contains( currentFile ) )
            {
                throw new MojoExecutionException( getDuplicationErrorMessage( "file", currentFile,
                                                                              fileNames.indexOf( currentFile ), i ) );
            }
            fileNames.add( currentFile );
        }
    }

    /**
     * Replaces all namespaces within generated schema files, as instructed by the configured Schema instances.
     * 
     * @param resolverMap The map relating generated schema file name to SimpleNamespaceResolver instances.
     * @param configuredTransformSchemas The Schema instances read from the configuration of this plugin.
     * @param mavenLog The active Log.
     * @param schemaDirectory The directory where all generated schema files reside.
     * @throws MojoExecutionException If the namespace replacement could not be done.
     */
    public static void replaceNamespacePrefixes( final Map resolverMap,
                                                 final List configuredTransformSchemas,
                                                 final Log mavenLog, final File schemaDirectory )
        throws MojoExecutionException
    {
        if ( mavenLog.isDebugEnabled() )
        {
            mavenLog.debug( "Got resolverMap.keySet() [generated filenames]: " + resolverMap.keySet() );
        }

        for ( SimpleNamespaceResolver currentResolver : resolverMap.values() )
        {
            File generatedSchemaFile = new File( schemaDirectory, currentResolver.getSourceFilename() );
            Document generatedSchemaFileDocument = null;

            for ( TransformSchema currentTransformSchema : configuredTransformSchemas )
            {
                // Should we alter the namespace prefix as instructed by the current schema?
                final String newPrefix = currentTransformSchema.getToPrefix();
                final String currentUri = currentTransformSchema.getUri();

                if ( StringUtils.isNotEmpty( newPrefix ) )
                {
                    // Find the old/current prefix of the namespace for the current schema uri.
                    final String oldPrefix = currentResolver.getNamespaceURI2PrefixMap().get( currentUri );

                    if ( StringUtils.isNotEmpty( oldPrefix ) )
                    {
                        // Can we perform the prefix substitution?
                        validatePrefixSubstitutionIsPossible( oldPrefix, newPrefix, currentResolver );

                        if ( mavenLog.isDebugEnabled() )
                        {
                            mavenLog.debug( "Subtituting namespace prefix [" + oldPrefix + "] with [" + newPrefix
                                + "] in file [" + currentResolver.getSourceFilename() + "]." );
                        }

                        // Get the Document of the current schema file.
                        if ( generatedSchemaFileDocument == null )
                        {
                            generatedSchemaFileDocument = parseXmlToDocument( generatedSchemaFile );
                        }

                        // Replace all namespace prefixes within the provided document.
                        process( generatedSchemaFileDocument.getFirstChild(), true,
                                 new ChangeNamespacePrefixProcessor( oldPrefix, newPrefix ) );
                    }
                }
            }

            if ( generatedSchemaFileDocument != null )
            {
                // Overwrite the generatedSchemaFile with the content of the generatedSchemaFileDocument.
                mavenLog.debug( "Overwriting file [" + currentResolver.getSourceFilename() + "] with content ["
                    + getHumanReadableXml( generatedSchemaFileDocument ) + "]" );
                savePrettyPrintedDocument( generatedSchemaFileDocument, generatedSchemaFile );
            }
            else
            {
                mavenLog.debug( "No namespace prefix changes to generated schema file ["
                    + generatedSchemaFile.getName() + "]" );
            }
        }
    }

    /**
     * Updates all schemaLocation attributes within the generated schema files to match the 'file' properties within the
     * Schemas read from the plugin configuration. After that, the files are physically renamed.
     * 
     * @param resolverMap The map relating generated schema file name to SimpleNamespaceResolver instances.
     * @param configuredTransformSchemas The Schema instances read from the configuration of this plugin.
     * @param mavenLog The active Log.
     * @param schemaDirectory The directory where all generated schema files reside.
     */
    public static void renameGeneratedSchemaFiles( final Map resolverMap,
                                                   final List configuredTransformSchemas,
                                                   final Log mavenLog, final File schemaDirectory )
    {
        // Create the map relating namespace URI to desired filenames.
        Map namespaceUriToDesiredFilenameMap = new TreeMap();
        for ( TransformSchema current : configuredTransformSchemas )
        {
            if ( StringUtils.isNotEmpty( current.getToFile() ) )
            {
                namespaceUriToDesiredFilenameMap.put( current.getUri(), current.getToFile() );
            }
        }

        // Replace the schemaLocation values to correspond to the new filenames
        for ( SimpleNamespaceResolver currentResolver : resolverMap.values() )
        {
            File generatedSchemaFile = new File( schemaDirectory, currentResolver.getSourceFilename() );
            Document generatedSchemaFileDocument = parseXmlToDocument( generatedSchemaFile );

            // Replace all namespace prefixes within the provided document.
            process( generatedSchemaFileDocument.getFirstChild(), true,
                     new ChangeFilenameProcessor( namespaceUriToDesiredFilenameMap ) );

            // Overwrite the generatedSchemaFile with the content of the generatedSchemaFileDocument.
            if ( mavenLog.isDebugEnabled() )
            {
                mavenLog.debug( "Changed schemaLocation entries within [" + currentResolver.getSourceFilename() + "]. "
                    + "Result: [" + getHumanReadableXml( generatedSchemaFileDocument ) + "]" );
            }
            savePrettyPrintedDocument( generatedSchemaFileDocument, generatedSchemaFile );
        }

        // Now, rename the actual files.
        for ( SimpleNamespaceResolver currentResolver : resolverMap.values() )
        {
            final String localNamespaceURI = currentResolver.getLocalNamespaceURI();

            if ( StringUtils.isEmpty( localNamespaceURI ) )
            {
                mavenLog.warn( "SimpleNamespaceResolver contained no localNamespaceURI; aborting rename." );
                continue;
            }

            final String newFilename = namespaceUriToDesiredFilenameMap.get( localNamespaceURI );
            final File originalFile = new File( schemaDirectory, currentResolver.getSourceFilename() );

            if ( StringUtils.isNotEmpty( newFilename ) )
            {
                File renamedFile = FileUtils.resolveFile( schemaDirectory, newFilename );
                String renameResult = ( originalFile.renameTo( renamedFile ) ? "Success " : "Failure " );

                if ( mavenLog.isDebugEnabled() )
                {
                    String suffix = "renaming [" + originalFile.getAbsolutePath() + "] to [" + renamedFile + "]";
                    mavenLog.debug( renameResult + suffix );
                }
            }
        }
    }

    /**
     * Drives the supplied visitor to process the provided Node and all its children, should the recurseToChildren flag
     * be set to true. All attributes of the current node are processed before recursing to children (i.e.
     * breadth first recursion).
     * 
     * @param node The Node to process.
     * @param recurseToChildren if true, processes all children of the supplied node recursively.
     * @param visitor The NodeProcessor instance which should process the nodes.
     */
    public static void process( final Node node, final boolean recurseToChildren, final NodeProcessor visitor )
    {
        // Process the current Node, if the NodeProcessor accepts it.
        if ( visitor.accept( node ) )
        {
            visitor.process( node );
        }

        NamedNodeMap attributes = node.getAttributes();
        for ( int i = 0; i < attributes.getLength(); i++ )
        {
            Node attribute = attributes.item( i );

            // Process the current attribute, if the NodeProcessor accepts it.
            if ( visitor.accept( attribute ) )
            {
                visitor.process( attribute );
            }
        }

        if ( recurseToChildren )
        {
            NodeList children = node.getChildNodes();
            for ( int i = 0; i < children.getLength(); i++ )
            {
                Node child = children.item( i );

                // Recurse to Element children.
                if ( child.getNodeType() == Node.ELEMENT_NODE )
                {
                    process( child, true, visitor );
                }
            }
        }
    }

    /**
     * Parses the provided InputStream to create a dom Document.
     * 
     * @param xmlStream An InputStream connected to an XML document.
     * @return A DOM Document created from the contents of the provided stream.
     */
    public static Document parseXmlStream( final Reader xmlStream )
    {
        // Build a DOM model of the provided xmlFileStream.
        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware( true );

        try
        {
            return factory.newDocumentBuilder().parse( new InputSource( xmlStream ) );
        }
        catch ( Exception e )
        {
            throw new IllegalArgumentException( "Could not acquire DOM Document", e );
        }
    }

    /**
     * Converts the provided DOM Node to a pretty-printed XML-formatted string.
     * 
     * @param node The Node whose children should be converted to a String.
     * @return a pretty-printed XML-formatted string.
     */
    protected static String getHumanReadableXml( final Node node )
    {
        StringWriter toReturn = new StringWriter();

        try
        {
            Transformer transformer = FACTORY.newTransformer();
            transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
            transformer.setOutputProperty( OutputKeys.STANDALONE, "yes" );
            transformer.transform( new DOMSource( node ), new StreamResult( toReturn ) );
        }
        catch ( TransformerException e )
        {
            throw new IllegalStateException( "Could not transform node [" + node.getNodeName() + "] to XML", e );
        }

        return toReturn.toString();
    }

    //
    // Private helpers
    //

    private static String getDuplicationErrorMessage( final String propertyName, final String propertyValue,
                                                      final int firstIndex, final int currentIndex )
    {
        return MISCONFIG + "Duplicate '" + propertyName + "' property with value [" + propertyValue
            + "] found in plugin configuration. Correct schema elements index (" + firstIndex + ") and ("
            + currentIndex + "), to ensure that all '" + propertyName + "' values are unique.";
    }

    /**
     * Validates that the transformation from oldPrefix to newPrefix is possible, in that
     * newPrefix is not already used by a schema file. This would corrupt the schema by assigning elements
     * from one namespace to another.
     * 
     * @param oldPrefix The old/current namespace prefix.
     * @param newPrefix The new/future namespace prefix.
     * @param currentResolver The currently active SimpleNamespaceResolver.
     * @throws MojoExecutionException if any schema file currently uses newPrefix.
     */
    private static void validatePrefixSubstitutionIsPossible( final String oldPrefix, final String newPrefix,
                                                              final SimpleNamespaceResolver currentResolver )
        throws MojoExecutionException
    {
        // Make certain the newPrefix does not exist already.
        if ( currentResolver.getNamespaceURI2PrefixMap().containsValue( newPrefix ) )
        {
            throw new MojoExecutionException( MISCONFIG + "Namespace prefix [" + newPrefix + "] is already in use."
                + " Cannot replace namespace prefix [" + oldPrefix + "] with [" + newPrefix + "] in file ["
                + currentResolver.getSourceFilename() + "]." );
        }
    }

    /**
     * Creates a Document from parsing the XML within the provided xmlFile.
     * 
     * @param xmlFile The XML file to be parsed.
     * @return The Document corresponding to the xmlFile.
     */
    private static Document parseXmlToDocument( final File xmlFile )
    {
        Document result = null;
        Reader reader = null;
        try
        {
            reader = new FileReader( xmlFile );
            result = parseXmlStream( reader );
        }
        catch ( FileNotFoundException e )
        {
            // This should never happen...
        }
        finally
        {
            IOUtil.close( reader );
        }

        return result;
    }

    private static void savePrettyPrintedDocument( final Document toSave, final File targetFile )
    {
        Writer out = null;
        try
        {
            out = new BufferedWriter( new FileWriter( targetFile ) );
            out.write( getHumanReadableXml( toSave.getFirstChild() ) );
        }
        catch ( IOException e )
        {
            throw new IllegalStateException( "Could not write to file [" + targetFile.getAbsolutePath() + "]", e );
        }
        finally
        {
            IOUtil.close( out );
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy