Package com.betfair.cougar.codegen

Source Code of com.betfair.cougar.codegen.IdlToDSMojo

/*
* Copyright 2013, The Sporting Exchange Limited
*
* 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 com.betfair.cougar.codegen;

import com.betfair.cougar.codegen.except.PluginException;
import com.betfair.cougar.codegen.resolver.DefaultSchemaCatalogSource;
import com.betfair.cougar.codegen.resolver.InterceptingResolver;
import com.betfair.cougar.codegen.resolver.SchemaCatalogSource;
import com.betfair.cougar.codegen.resource.ResourceLoader;
import com.betfair.cougar.transformations.CougarTransformations;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.w3c.dom.Document;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.List;


/**
* A plugin which is responsible for generating Cougar-based services. This encompasses a number
* of code- and file-generation steps, as well as validation. The intention is for this mojo to
* do everything needed, keeping the plugins/plugin section of a Cougar service's pom as simple as
* possible.
* <p>
* <h2>NOTE: idd dependencies</h2>
* IDDs can be read from the file system or as resources. The IDD is expected to be named after the
* service (see {@link #services} param), suffixed with {@code .xml} for the service and
* {@code -Extensions.xml} for the extensions definition. If you're using an IDD file then it should
*  be in {@code /src/main/resources}. Switch between the two modes using the {@link #iddAsResource}
*  flag.
* <p>
* A gotcha exists when accessing IDDs as resources.
* Since the IDDs are not required at run-time, it would make sense to include the relevant IDD
* project (jar) as a plugin dependency (ie. in {@code project/plugins/plugin/dependencies} as opposed to a
* project dependency of {@code project/dependencies}). You can do this <em>unless</em> your
* service is built as part of a larger project tree, in which multiple services are built. Maven
* resolves dependencies for the plugin once, so you can't have projectA relying on projectA-idd
* and project B relying on projectB-idd respectively - you end up with both projects relying on
* (say) projectA-idd. To work around this, you have to include the IDD as part of the project
* dependencies.
* <p>
* TODO If there's an easy way to fix this, we should do so (maven-savvy volunteers welcome)
*
* @goal process
*
* @phase generate-sources
* @requiresDependencyResolution
*/
public class IdlToDSMojo extends AbstractMojo {

    private static final String RESOURCES_DIR = "src/main/resources";


  // =============================================================================================
  //  Mojo params
  // =============================================================================================

  //  this contains only those params which are mandatory or which need to be initialised by
  //  maven
  //
  //  it's not that hard to turn other class members into params, but if users don't need them
  //  and don't know about them, then the simpler things remain.
    //
    //  all access to these MUST be via the associated getters to enable subclasses to work

    /**
     * We may need the runtime classpath to access the idds if we're doing resource-based loading.
     * @parameter default-value="${project.runtimeClasspathElements}"
     * @readonly
     */
    private List<String> runtimeClassPath;

    protected List<String> getRuntimeClassPath() {
        return runtimeClassPath;
    }

  /**
     * name of service.
     * @parameter
     * @required
     */
    private Service[] services;

    protected Service[] getServices() {
        return services;
    }

    /**
     * the base directory of the project
     * @parameter default-value="${basedir}"
     */
    private String baseDir ;

    protected String getBaseDir() {
        return baseDir;
    }

    /**
     * Either {@code mvn -o} or in settings.xml
     *
     *  @parameter expression="${settings.offline}"
     */
    private boolean offline;

    protected boolean isOffline() {
        return offline;
    }

    /**
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;

    protected MavenProject getProject() {
        return project;
    }

    /**
     * If set to true, generate a client version of generated code.
     * @parameter
     */

    private boolean client;

    protected boolean isClient() {
        return client;
    }

    /**
     * If set to true, generate a server version of generated code.
     * @parameter
     */

    private boolean server;

    protected boolean isServer() {
        return server;
    }

    /**
     * Read IDDs and files from the file system (as opposed to as resources). Use as transition
     * for existing services, and also as a way to make writing/testing new services a bit simpler.
     *
     * @parameter
     */
    private boolean iddAsResource = false;

    protected boolean isIddAsResource() {
        return iddAsResource;
    }

    // =============================================================================================
  //  POJO stuff
  // =============================================================================================

    /**
     * Location of generated sources (relative to baseDir)
     *
     * This could be a property, but as noted above, not making it one until we need to
     */
    private String generatedSourceDir = "/target/generated-sources/java";

    /**
     * Location (resource) of the wsdl style sheet to be used
     */
    private String wsdlXslResource = "wsdl-xsl/wsdl.xsl";

    /**
     * Location (resource) of the xsd style sheet to be used
     */
    private String xsdXslResource = "xsd-xsl/xsd.xsl";

     /**
     * File location of the on-disk iddstripper.csl, relative to base directory
     */
    private String iddStripperXslResource = "bsidl/iddstripper.xsl";

    /**
     * File location of the on-disk wsdl.xsl, relative to base directory
     */
    private String iddStripperXslFile = "target/wrk/iddstripper.xsl";

    /**
     * File location of the on-disk wsdl.xsl, relative to base directory
     */
    private String wsdlXslFile = "target/wrk/wsdl.xsl";

    /**
     * File location of the on-disk wsdl.xsl, relative to base directory
     */
    private String xsdXslFile = "target/wrk/xsd.xsl";

    /**
     * Location for storing our catalog file and schemas for validation.
     */
    private String schemaDir = "target/wrk/schemas";

    /**
     * Actual file pointing to wsdl.xsl. Initialised by {@link #prepWsdlXsl()}.
     */
    private File wsdlXsl;

    /**
     * Actual file pointing to xsd.xsl. Initialised by {@link #prepXsdXsl()}.
     */
    private File xsdXsl;

    /**
     * Actual file pointing to iddstripper.xsl. Initialised by {@link #prepIddStripperXsl()}.
     */
    private File iddStripperXsl;

    /**
     * Actual file pointing to the catalog.xml used for validation.
     * Initialised by {@link #unwrapSchemas()}.
     */
    private File catalogFile = null;

  private InterceptingResolver resolver;

  private ResourceLoader resourceLoader;

  private XPathExpression namespaceExpr;


    public void execute()
        throws MojoExecutionException
    {
        getLog().info("Starting Cougar code generation");
        if (isOffline()) {
            getLog().warn("Maven in offline mode, plugin is NOT validating IDDs against schemas");
        }
        else {
          getLog().debug("Unbundling schemas for validation");
          catalogFile = unwrapSchemas();
        }
        initResourceLoader();
        initResolver()// needs the resource loader

        // load wsdl.xsl (as resource) and write (as file) to a working directory
        prepWsdlXsl();

        // load xsd.xsl (as resource) and write (as file) to a working directory
        prepXsdXsl();

        // load iddstripper.xsl (as resource) and write (as file) to a working directory
        prepIddStripperXsl();


        try {
            getLog().debug("Starting IDL to Java");

            for (Service service : getServices() ) {
                processService(service);
            }

            // this replaces the functionality of build-helper-maven-plugin
            addSource();
        }
        catch (Exception e) {
          getLog().error(e);
            throw new MojoExecutionException("Failed processing IDL: " + e, e);
        }

        getLog().info("Completed Cougar code generation");
    }

    private void prepIddStripperXsl() throws MojoExecutionException {
        try {
            iddStripperXsl = new File(getBaseDir(), iddStripperXslFile);
            initOutputDir(iddStripperXsl.getParentFile());
            writeIDDStylesheet(iddStripperXsl);
        } catch (Exception e) {
            throw new MojoExecutionException("Failed to write local copy of IDD stripper stylesheet: " + e, e);
        }

    }

    /**
     * Read a wsdl.xsl (stylesheet) from a resource, and write it to a working directory.
     *
     * @return the on-disk wsdl.xsl file
     */
    private void prepWsdlXsl() throws MojoExecutionException {
        try {
            wsdlXsl = new File(getBaseDir(), wsdlXslFile);
            initOutputDir(wsdlXsl.getParentFile());
            writeWsdlStylesheet(wsdlXsl);
        } catch (Exception e) {
            throw new MojoExecutionException("Failed to write local copy of WSDL stylesheet: " + e, e);
        }
    }

    /**
     * Read a wsdl.xsl (stylesheet) from a resource, and write it to a working directory.
     *
     * @return the on-disk wsdl.xsl file
     */
    private void prepXsdXsl() throws MojoExecutionException {
        try {
            xsdXsl = new File(getBaseDir(), xsdXslFile);
            initOutputDir(xsdXsl.getParentFile());
            writeXsdStylesheet(xsdXsl);
        } catch (Exception e) {
            throw new MojoExecutionException("Failed to write local copy of XSD stylesheet: " + e, e);
        }
    }

    /**
     * Various steps needing to be done for each IDD
     */
    private void processService(Service service) throws Exception {

        getLog().info("  Service: " + service.getServiceName());

        Document iddDoc = parseIddFile(service.getServiceName());

        // 1. validate
        if (!isOffline()) {
            getLog().debug("Validating XML..");
            new XmlValidator(resolver).validate(iddDoc);
        }

        // 2. generate outputs
        generateJavaCode(service, iddDoc);
    }

    private void generateExposedIDD(Document iddDoc, String serviceName, String version) throws Exception {

        File iddFile = new File(getBaseDir(), "target/generated-resources/idd/" + serviceName+"_"+version.replace("_",".") + "_Exposed.idd");
        getLog().debug("Writing to idd file " + iddFile);
        initOutputDir(iddFile.getParentFile());

        ExposedIDDGenerator.transform(iddDoc, iddStripperXsl, iddFile);


    }

    /**
     * Find, open and parse the IDD implied by the specified service name. Reads either an explicit
     * file or else a resource based on {@link #iddAsResource} flag.
     * <p>
     * The implied name is simply the service name + ".xml".
     */
    private Document parseIddFile(String serviceName) {

    String iddFileName = serviceName + ".xml";

      if (isIddAsResource()) {
        InputStream is = resourceLoader.getResourceAsStream(iddFileName);
        if (is == null) {
          throw new RuntimeException("Cannot open IDD resource named '" + iddFileName + "'");
        }
        return XmlUtil.parse(is, resolver);
      }
      else {
        File iddFile = new File( new File(getBaseDir(), RESOURCES_DIR), iddFileName);
        if (!iddFile.exists()) {
          throw new RuntimeException("Cannot open IDD file named '" + iddFileName + "'");
        }
        return XmlUtil.parse(iddFile, resolver);
      }
  }

        /**
     * Find, open and parse the IDD implied by the specified service name. Reads either an explicit
     * file or else a resource based on {@link #iddAsResource} flag.
     * <p>
     * The implied name is simply the service name + ".xml".
     */
    private Document parseIddFromString(String iddContent) {

      return XmlUtil.parse(new ByteArrayInputStream(iddContent.getBytes()), resolver);

  }

  /**
     * Find, open and parse the extensions xml file or null if it doesn't exist. Reads from
     * file or resource based on {@link #iddAsResource} flag.
     * <p>
     * Name of extensions file should be ServiceName-Extensions.xml.
     */
    private Document parseExtensionFile(String serviceName) {

    String extensionFileName = serviceName + "-Extensions.xml";

      if (isIddAsResource()) {
        InputStream is = resourceLoader.getResourceAsStream(extensionFileName);
        if (is != null) {
          return XmlUtil.parse(is, resolver);
        }
        else {
          return null;
        }
      }
      else {
        File extensionsFile = new File( new File(getBaseDir(), RESOURCES_DIR), extensionFileName);
        if (extensionsFile.exists()) {
            return XmlUtil.parse(extensionsFile, resolver);
        }
        else {
          return null;
        }
      }
    }

  /**
     * The original concept of the IDLReader (other devs) has gone away a bit, so there could be
     * some refactoring around this.
     */
  private void generateJavaCode(Service service, Document iddDoc) throws Exception {

    IDLReader reader = new IDLReader();

    Document extensionDoc = parseExtensionFile(service.getServiceName());

    String packageName = derivePackageName(service, iddDoc);

        reader.init(iddDoc, extensionDoc, service.getServiceName(), packageName, getBaseDir(),
                generatedSourceDir, getLog(), service.getOutputDir(), isClient(), isServer());

        runMerge(reader);

        // also create the stripped down, combined version of the IDD doc
        getLog().debug("Generating combined IDD sans comments...");
        Document combinedIDDDoc = parseIddFromString(reader.serialize());
        // WARNING: this absolutely has to be run after a call to reader.runMerge (called by runMerge above) as otherwise the version will be null...
        generateExposedIDD(combinedIDDDoc, reader.getInterfaceName(), reader.getInterfaceMajorMinorVersion());

        // generate WSDL/XSD
        getLog().debug("Generating wsdl...");
        generateWsdl(iddDoc, reader.getInterfaceName(), reader.getInterfaceMajorMinorVersion());
        getLog().debug("Generating xsd...");
        generateXsd(iddDoc, reader.getInterfaceName(), reader.getInterfaceMajorMinorVersion());
  }

    /**
   * Package name comes from explicit plugin param (if set), else the namespace definition, else
   * skream and die.
   * <p>
   * Having the plugin override allows backwards compatibility as well as being useful for
   * fiddling and tweaking.
   */
  private String derivePackageName(Service service, Document iddDoc) {

    String packageName = service.getPackageName();
    if (packageName == null) {
      packageName = readNamespaceAttr(iddDoc);
      if (packageName == null) {
        throw new PluginException("Cannot find a package name "
                + "(not specified in plugin and no namespace in IDD");
      }
    }
    return packageName;
  }

  private void generateWsdl(Document iddDoc, String serviceName, String version) throws Exception {

      File wsdlFile = new File(getBaseDir(), "target/generated-resources/wsdl/" + serviceName +"_"+version.replace("_",".")+ ".wsdl");
      getLog().debug("Writing to wsdl file " + wsdlFile);
      initOutputDir(wsdlFile.getParentFile());

      new XmlGenerator().transform(iddDoc, wsdlXsl, wsdlFile);
    }

  private void generateXsd(Document iddDoc, String serviceName, String version) throws Exception {

      File xsdFile = new File(getBaseDir(), "target/generated-resources/xsd/" + serviceName +"_"+version.replace("_",".")+ ".xsd");
      getLog().debug("Writing to xsd file " + xsdFile);
      initOutputDir(xsdFile.getParentFile());

      new XmlGenerator().transform(iddDoc, xsdXsl, xsdFile);
    }

    private void writeWsdlStylesheet(File xslFile) throws Exception {

      if (wsdlXslResource == null) {
          throw new MojoExecutionException("wsdl resource not specified");
      }

      FileUtil.resourceToFile(wsdlXslResource, xslFile, getClass());

        getLog().debug("Wrote wsdl stylesheet from resource " + wsdlXslResource + " to " + xslFile);
    }

    private void writeXsdStylesheet(File xslFile) throws Exception {

      if (xsdXslResource == null) {
          throw new MojoExecutionException("xsd resource not specified");
      }

      FileUtil.resourceToFile(xsdXslResource, xslFile, getClass());

        getLog().debug("Wrote xsd stylesheet from resource " + xsdXslResource + " to " + xslFile);
    }
    private void writeIDDStylesheet(File xslFile) throws Exception {

      if (iddStripperXslResource == null) {
          throw new MojoExecutionException("wsdl resource not specified");
      }

      FileUtil.resourceToFile(iddStripperXslResource, xslFile, getClass());

        getLog().debug("Wrote IDD stylesheet from resource " + iddStripperXslResource + " to " + xslFile);
    }

    private File unwrapSchemas() {

      File dir = new File(getBaseDir(), schemaDir);
      dir.mkdirs();
      return getCatalogSource().getCatalog(dir, getLog());
  }

    protected Transformations getTransformations() {
        return new CougarTransformations();
    }

    @SuppressWarnings("unchecked")
  private void runMerge(IDLReader reader) throws Exception {
        Transformations transformations = getTransformations();

        // First let's mangle the document if need be.
        if (transformations.getManglers() != null) {
          getLog().debug("mangling IDL using "+transformations.getManglers().size()+" manglers");
          for(DocumentMangler m : transformations.getManglers()) {
                getLog().debug(m.getName());
                reader.mangle(m);
            }
        }

        if (transformations.getPreValidations() != null) {
          getLog().debug("Pre validating IDL using "+transformations.getPreValidations().size()+" pre validations");
            for(Validator v : transformations.getPreValidations()) {
                getLog().debug(v.getName());
                reader.validate(v);
            }

        }

        for(Transformation t : transformations.getTransformations()) {
            getLog().debug(t.toString());
        }
        reader.runMerge(transformations.getTransformations());

        reader.writeResult();




  }

    /**
     * Set up and validate the creation of the specified output directory
     */
    private void initOutputDir(File outputDir) {

        if (!outputDir.exists()) {
            if (!outputDir.mkdirs()) {
                throw new IllegalArgumentException("Output Directory "+outputDir+" could not be created");
            }
        }
        if (!outputDir.isDirectory() || (!outputDir.canWrite())) {
            throw new IllegalArgumentException("Output Directory "+outputDir+" is not a directory or cannot be written to.");
        }
    }

    /**
     * Add the generated-sources directory to the classpath.
     * <p>
     * This one-liner is nicked from build-helper-maven-plugin v1.4, AddSourceMojo.java.
     */
    private void addSource() {

      // TODO this should be shared between here and IDLReader
      File generatedSources = new File(getBaseDir(), generatedSourceDir);

        this.getProject().addCompileSourceRoot( generatedSources.getAbsolutePath() );
        this.getLog().debug( "Source directory " + generatedSources + " added." );
  }

    private void initResolver() {

      // catalogs aren't needed if we're offline because we don't validate
      String[] catalogs = isOffline() ? new String[0] : new String[] { catalogFile.getAbsolutePath() };

      resolver = new InterceptingResolver(getLog(), (isIddAsResource() ? resourceLoader : null), catalogs);
    }

    private void initResourceLoader() throws MojoExecutionException {

      try {
          if (isIddAsResource()) {
            // we need this classLoader because it's the only way to get to the project dependencies
            resourceLoader = new ResourceLoader(getRuntimeClassPath());
          }
          else {
            resourceLoader = new ResourceLoader();
          }
      }
      catch (Exception e) {
        throw new MojoExecutionException("Error initialising classloader: " + e, e);
      }
    }

    private XPathExpression initNamespaceAttrExpression() {

    XPathFactory xfactory = XPathFactory.newInstance();
    XPath xpath = xfactory.newXPath();
    try {
      return xpath.compile("/interface/@namespace");
    } catch (XPathExpressionException e) {
      throw new PluginException("Error compiling namespace XPath expression: " + e, e);
    }
    }

    /**
     * Retrieve 'namespace' attr of interface definition or null if not found
     */
    private String readNamespaceAttr(Document doc) {

      // lazy loading is mostly pointless but it keeps things together
      if (namespaceExpr == null) {
        namespaceExpr = initNamespaceAttrExpression();
      }

    String s;
    try {
      s = namespaceExpr.evaluate(doc);
    } catch (XPathExpressionException e) {
      throw new PluginException("Error evaluating namespace XPath expression: " + e, e);
    }
    // xpath returns an empty string if not found, null is cleaner for callers
    return (s == null || s.length() == 0) ? null : s;
    }

    /**
     * For tests
     */
    void setBaseDir(String s) {
        this.baseDir = s;
    }

    /**
     * For tests
     */
    void setWsdlXslResource(String s) {
        this.wsdlXslResource = s;
    }

    /**
     * For tests
     */
    void setXsdXslResource(String xsdXslResource) {
        this.xsdXslResource = xsdXslResource;
    }

    /**
     * For tests
     */
    void setServices(Service[] services) {//NOSONAR
        this.services = services;
    }

    protected SchemaCatalogSource getCatalogSource() {
        return new DefaultSchemaCatalogSource();
    }
}

TOP

Related Classes of com.betfair.cougar.codegen.IdlToDSMojo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.