Package org.codehaus.enunciate.modules.csharp

Source Code of org.codehaus.enunciate.modules.csharp.CSharpDeploymentModule

/*
* Copyright 2006-2008 Web Cohesion
*
* 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.enunciate.modules.csharp;

import freemarker.template.*;
import net.sf.jelly.apt.decorations.JavaDoc;
import net.sf.jelly.apt.freemarker.FreemarkerJavaDoc;
import org.apache.commons.digester.RuleSet;
import org.codehaus.enunciate.EnunciateException;
import org.codehaus.enunciate.apt.EnunciateFreemarkerModel;
import org.codehaus.enunciate.config.SchemaInfo;
import org.codehaus.enunciate.config.WsdlInfo;
import org.codehaus.enunciate.contract.jaxb.TypeDefinition;
import org.codehaus.enunciate.contract.jaxws.EndpointInterface;
import org.codehaus.enunciate.contract.jaxws.WebFault;
import org.codehaus.enunciate.contract.jaxws.WebMethod;
import org.codehaus.enunciate.contract.validation.Validator;
import org.codehaus.enunciate.main.*;
import org.codehaus.enunciate.modules.FacetAware;
import org.codehaus.enunciate.modules.FreemarkerDeploymentModule;
import org.codehaus.enunciate.modules.csharp.config.CSharpRuleSet;
import org.codehaus.enunciate.modules.csharp.config.PackageNamespaceConversion;
import org.codehaus.enunciate.template.freemarker.AccessorOverridesAnotherMethod;
import org.codehaus.enunciate.template.freemarker.ClientPackageForMethod;
import org.codehaus.enunciate.template.freemarker.FindRootElementMethod;
import org.codehaus.enunciate.template.freemarker.SimpleNameWithParamsMethod;
import org.codehaus.enunciate.util.TypeDeclarationComparator;

import java.io.*;
import java.net.URL;
import java.util.*;

/**
* <h1>C# Module</h1>
*
* <p>The C# module generates C# client code for accessing the SOAP endpoints and makes an attempt at compiling the code in a .NET assembly. If the the compile
* attempt is to be successful, then you must have a C# compiler available on your system path, or specify a "compileExecutable" attribute in the Enunciate
* configuration file. If the compile attempt fails, only the C# source code will be made available as a client artifact.</p>
*
* <p>The order of the C# deployment module is 0, as it doesn't depend on any artifacts exported by any other module.</p>
*
* <ul>
* <li><a href="#config">configuration</a></li>
* </ul>
*
* <h1><a name="config">Configuration</a></h1>
*
* <p>The C# module is configured with the "csharp" element under the "modules" element of the enunciate configuration file. It supports the following
* attributes:</p>
*
* <ul>
* <li>The "label" attribute is the label for the C# API.  This is the name by which the files will be identified, producing [label].cs and [label].dll.
* By default the label is the same as the Enunciate project label. For a more custom configuration of the generated file names, use the
* "bundleFileName", "DLLFileName", "docXmlFileName", and "sourceFileName" attributes.</li>
* <li>The "compileExecutable" attribute is the executable for invoking the C# compiler. If not supplied, an attempt will be made to find a C# compiler on the
* system path.  If the attempt fails, the C# code will not be compiled (but the source code will still be made available as a download artifact).</li>
* <li>The "compileCommand" is a <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/Formatter.html#syntax">Java format string</a> that represents the
* full command that is used to invoke the C# compiler. The string will be formatted with the following arguments: compile executable, assembly path,
* doc xml path, source doc path. The default value is "%s /target:library /out:%s /r:System.Web.Services /doc:%s %s"</li>
* <li>The "singleFilePerClass" allows you to specify that Enunciate should generate a file for each C# class. By default (<tt>false</tt>), Enunciate
* puts all C# classes into a single file.</li>
* </ul>
*
* <h3>The "package-conversions" element</h3>
*
* <p>The "package-conversions" subelement of the "csharp" element is used to map packages from
* the original API packages to C# namespaces.  This element supports an arbitrary number of
* "convert" child elements that are used to specify the conversions.  These "convert" elements support
* the following attributes:</p>
*
* <ul>
* <li>The "from" attribute specifies the package that is to be converted.  This package will match
* all classes in the package as well as any subpackages of the package.  This means that if "org.enunciate"
* were specified, it would match "org.enunciate", "org.enunciate.api", and "org.enunciate.api.impl".</li>
* <li>The "to" attribute specifies what the package is to be converted to.  Only the part of the package
* that matches the "from" attribute will be converted.</li>
* </ul>
*
* <h3>The "facets" element</h3>
*
* <p>The "facets" element is applicable to the C# module to configure which facets are to be included/excluded from the C# artifacts. For
* more information, see <a href="http://docs.codehaus.org/display/ENUNCIATE/Enunciate+API+Facets">API Facets</a></p>
*
* @author Ryan Heaton
* @docFileName module_csharp.html
*/
public class CSharpDeploymentModule extends FreemarkerDeploymentModule implements FacetAware {

  private boolean require = false;
  private boolean disableCompile = true;
  private String label = null;
  private String compileExecutable = null;
  private String compileCommand = "%s /target:library /out:%s /r:System.Web.Services /doc:%s %s";
  private final Map<String, String> packageToNamespaceConversions = new HashMap<String, String>();
  private String bundleFileName = null;
  private String DLLFileName = null;
  private String docXmlFileName = null;
  private String sourceFileName = null;
  private boolean singleFilePerClass = false;
  private Set<String> facetIncludes = new TreeSet<String>();
  private Set<String> facetExcludes = new TreeSet<String>();

  public CSharpDeploymentModule() {
  }

  /**
   * @return "csharp"
   */
  @Override
  public String getName() {
    return "csharp";
  }

  @Override
  public void init(Enunciate enunciate) throws EnunciateException {
    super.init(enunciate);

    if (!super.isDisabled()) { //if we're explicitly disabled, we can ignore this...
      if (isDisableCompile()) {
        info("C# compilation is disabled, but the source code will still be generated.");
        setCompileExecutable(null);
      }
      else {
        String compileExectuable = getCompileExecutable();
        if (compileExectuable == null) {
          String osName = System.getProperty("os.name");
          if (osName != null && osName.toUpperCase().contains("WINDOWS")) {
            //try the "csc" command on Windows environments.
            debug("Attempting to execute command \"csc /help\" for the current environment (%s).", osName);
            try {
              Process process = new ProcessBuilder("csc", "/help").redirectErrorStream(true).start();
              InputStream in = process.getInputStream();
              byte[] buffer = new byte[1024];
              int len = in.read(buffer);
              while (len >- 0) {
                len = in.read(buffer);
              }

              int exitCode = process.waitFor();
              if (exitCode != 0) {
                debug("Command \"csc /help\" failed with exit code " + exitCode + ".");
              }
              else {
                compileExectuable = "csc";
                debug("C# compile executable to be used: csc");
              }
            }
            catch (Throwable e) {
              debug("Command \"csc /help\" failed (" + e.getMessage() + ").");
            }
          }

          if (compileExectuable == null) {
            //try the "gmcs" command (Mono)
            debug("Attempting to execute command \"gmcs /help\" for the current environment (%s).", osName);
            try {
              Process process = new ProcessBuilder("gmcs", "/help").redirectErrorStream(true).start();
              InputStream in = process.getInputStream();
              byte[] buffer = new byte[1024];
              int len = in.read(buffer);
              while (len >- 0) {
                len = in.read(buffer);
              }

              int exitCode = process.waitFor();
              if (exitCode != 0) {
                debug("Command \"gmcs /help\" failed with exit code " + exitCode + ".");
              }
              else {
                compileExectuable = "gmcs";
                debug("C# compile executable to be used: %s", compileExectuable);
              }
            }
            catch (Throwable e) {
              debug("Command \"gmcs /help\" failed (" + e.getMessage() + ").");
            }
          }

          if (compileExectuable == null && isRequire()) {
            throw new EnunciateException("C# client code generation is required, but there was no valid compile executable found. " +
              "Please supply one in the configuration file, or set it up on your system path.");
          }

          setCompileExecutable(compileExectuable);
        }
      }
    }
  }

  @Override
  public void initModel(EnunciateFreemarkerModel model) {
    super.initModel(model);

    if (!isDisabled()) {
      TreeSet<WebFault> allFaults = new TreeSet<WebFault>(new TypeDeclarationComparator());
      for (WsdlInfo wsdlInfo : model.getNamespacesToWSDLs().values()) {
        for (EndpointInterface ei : wsdlInfo.getEndpointInterfaces()) {
          String pckg = ei.getPackage().getQualifiedName();
          if (!this.packageToNamespaceConversions.containsKey(pckg)) {
            this.packageToNamespaceConversions.put(pckg, packageToNamespace(pckg));
          }
          for (WebMethod webMethod : ei.getWebMethods()) {
            for (WebFault webFault : webMethod.getWebFaults()) {
              allFaults.add(webFault);
            }
          }
        }
      }

      for (WebFault webFault : allFaults) {
        String pckg = webFault.getPackage().getQualifiedName();
        if (!this.packageToNamespaceConversions.containsKey(pckg)) {
          this.packageToNamespaceConversions.put(pckg, packageToNamespace(pckg));
        }
      }

      for (SchemaInfo schemaInfo : model.getNamespacesToSchemas().values()) {
        for (TypeDefinition typeDefinition : schemaInfo.getTypeDefinitions()) {
          String pckg = typeDefinition.getPackage().getQualifiedName();
          if (!this.packageToNamespaceConversions.containsKey(pckg)) {
            this.packageToNamespaceConversions.put(pckg, packageToNamespace(pckg));
          }
        }
      }
    }
  }

  protected String packageToNamespace(String pckg) {
    if (pckg == null) {
      return null;
    }
    else {
      StringBuilder ns = new StringBuilder();
      for (StringTokenizer toks = new StringTokenizer(pckg, "."); toks.hasMoreTokens();) {
        String tok = toks.nextToken();
        ns.append(Character.toString(tok.charAt(0)).toUpperCase());
        if (tok.length() > 1) {
          ns.append(tok.substring(1));
        }
        if (toks.hasMoreTokens()) {
          ns.append('.');
        }
      }
      return ns.toString();
    }
  }

  @Override
  public void doFreemarkerGenerate() throws IOException, TemplateException {
    File genDir = getGenerateDir();
    if (!enunciate.isUpToDateWithSources(genDir)) {
      EnunciateFreemarkerModel model = getModel();
      ClientPackageForMethod namespaceFor = new ClientPackageForMethod(this.packageToNamespaceConversions);
      namespaceFor.setUseClientNameConversions(true);
      model.put("namespaceFor", namespaceFor);
      model.put("findRootElement", new FindRootElementMethod());
      model.put("requestDocumentQName", new RequestDocumentQNameMethod());
      model.put("responseDocumentQName", new ResponseDocumentQNameMethod());
      ClientClassnameForMethod classnameFor = new ClientClassnameForMethod(this.packageToNamespaceConversions);
      classnameFor.setUseClientNameConversions(true);
      model.put("classnameFor", classnameFor);
      model.put("listsAsArraysClassnameFor", new ListsAsArraysClientClassnameForMethod(this.packageToNamespaceConversions));
      model.put("simpleNameFor", new SimpleNameWithParamsMethod(classnameFor));
      model.put("csFileName", getSourceFileName());
      model.put("accessorOverridesAnother", new AccessorOverridesAnotherMethod());

      debug("Generating the C# client classes...");
      URL apiTemplate = isSingleFilePerClass() ? getTemplateURL("api-multiple-files.fmt") : getTemplateURL("api.fmt");
      processTemplate(apiTemplate, model);
    }
    else {
      info("Skipping C# code generation because everything appears up-to-date.");
    }
  }

  @Override
  protected void doCompile() throws EnunciateException, IOException {
    File compileDir = getCompileDir();
    Enunciate enunciate = getEnunciate();
    String compileExecutable = getCompileExecutable();
    if (getCompileExecutable() != null) {
      if (!enunciate.isUpToDateWithSources(compileDir)) {
        compileDir.mkdirs();

        String compileCommand = getCompileCommand();
        if (compileCommand == null) {
          throw new IllegalStateException("Somehow the \"compile\" step was invoked on the C# module without a valid compile command.");
        }

        compileCommand = compileCommand.replace(' ', '\0'); //replace all spaces with the null character, so the command can be tokenized later.
        File dll = new File(compileDir, getDLLFileName());
        File docXml = new File(compileDir, getDocXmlFileName());
        File sourceFile = new File(getGenerateDir(), getSourceFileName());
        compileCommand = String.format(compileCommand, compileExecutable,
                                       dll.getAbsolutePath(),
                                       docXml.getAbsolutePath(),
                                       sourceFile.getAbsolutePath());
        StringTokenizer tokenizer = new StringTokenizer(compileCommand, "\0"); //tokenize on the null character to preserve the spaces in the command.
        List<String> command = new ArrayList<String>();
        while (tokenizer.hasMoreElements()) {
          command.add((String) tokenizer.nextElement());
        }

        Process process = new ProcessBuilder(command).redirectErrorStream(true).directory(compileDir).start();
        BufferedReader procReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line = procReader.readLine();
        while (line != null) {
          info(line);
          line = procReader.readLine();
        }
        int procCode;
        try {
          procCode = process.waitFor();
        }
        catch (InterruptedException e1) {
          throw new EnunciateException("Unexpected inturruption of the C# compile process.");
        }

        if (procCode != 0) {
          throw new EnunciateException("C# compile failed.");
        }

        enunciate.addArtifact(new FileArtifact(getName(), "csharp.assembly", dll));
        if (docXml.exists()) {
          enunciate.addArtifact(new FileArtifact(getName(), "csharp.docs.xml", docXml));
        }
      }
      else {
        info("Skipping C# compile because everything appears up-to-date.");
      }
    }
    else {
      debug("Skipping C# compile because a compile executale was neither found nor provided.  The C# bundle will only include the sources.");
    }

  }

  @Override
  protected void doBuild() throws EnunciateException, IOException {
    Enunciate enunciate = getEnunciate();
    File buildDir = getBuildDir();
    if (!enunciate.isUpToDateWithSources(buildDir)) {
      File compileDir = getCompileDir();
      compileDir.mkdirs(); //might not exist if we couldn't actually compile.
      //we want to zip up the source file, too, so we'll just copy it to the compile dir.
      enunciate.copyDir(getGenerateDir(), compileDir);

      buildDir.mkdirs();
      File bundle = new File(buildDir, getBundleFileName());
      enunciate.zip(bundle, compileDir);

      ClientLibraryArtifact artifactBundle = new ClientLibraryArtifact(getName(), "csharp.client.library", ".NET Client Library");
      artifactBundle.setPlatform(".NET 2.0");

      StringBuilder builder = new StringBuilder("C# source code");
      boolean docsExist = new File(compileDir, getDocXmlFileName()).exists();
      boolean dllExists = new File(compileDir, getDLLFileName()).exists();
      if (docsExist && dllExists) {
        builder.append(", the assembly, and the XML docs");
      }
      else if (dllExists) {
        builder.append("and the assembly");
      }

      //read in the description from file:
      String description = readResource("library_description.fmt", builder.toString());
      artifactBundle.setDescription(description);
      NamedFileArtifact binariesJar = new NamedFileArtifact(getName(), "dotnet.client.bundle", bundle);
      binariesJar.setArtifactType(ArtifactType.binaries);
      binariesJar.setDescription(String.format("The %s for the .NET client library.", builder.toString()));
      binariesJar.setPublic(false);
      artifactBundle.addArtifact(binariesJar);
      enunciate.addArtifact(artifactBundle);
    }
  }

  /**
   * Reads a resource into string form.
   *
   * @param resource The resource to read.
   * @param contains The description of what the bundle contains.
   * @return The string form of the resource.
   */
  protected String readResource(String resource, String contains) throws IOException, EnunciateException {
    HashMap<String, Object> model = new HashMap<String, Object>();
    model.put("sample_service_method", getModelInternal().findExampleWebMethod());
    model.put("sample_resource", getModelInternal().findExampleResourceMethod());
    model.put("bundle_contains", contains);

    URL res = CSharpDeploymentModule.class.getResource(resource);
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    PrintStream out = new PrintStream(bytes);
    try {
      processTemplate(res, model, out);
      out.flush();
      bytes.flush();
      return bytes.toString("utf-8");
    }
    catch (TemplateException e) {
      throw new EnunciateException(e);
    }

  }

  /**
   * The name of the bundle file.
   *
   * @return The name of the bundle file.
   */
  protected String getBundleFileName() {
    if (this.bundleFileName != null) {
      return this.bundleFileName;
    }

    String label = getLabel();
    if (label == null) {
      label = getEnunciate().getConfig().getLabel();
    }
    return label + "-dotnet.zip";
  }

  /**
   * The name of the bundle file.
   *
   * @param bundleFileName The name of the bundle file.
   */
  public void setBundleFileName(String bundleFileName) {
    this.bundleFileName = bundleFileName;
  }

  /**
   * The name of the generated C# dll.
   *
   * @return The name of the generated C# file.
   */
  protected String getDLLFileName() {
    if (this.DLLFileName != null) {
      return this.DLLFileName;
    }

    String label = getLabel();
    if (label == null) {
      label = getEnunciate().getConfig().getLabel();
    }
    return label + ".dll";
  }

  /**
   * The name of the generated C# dll.
   *
   * @param DLLFileName The name of the generated C# dll.
   */
  public void setDLLFileName(String DLLFileName) {
    this.DLLFileName = DLLFileName;
  }

  /**
   * The name of the generated C# xml documentation.
   *
   * @return The name of the generated C# xml documentation.
   */
  protected String getDocXmlFileName() {
    if (this.docXmlFileName != null) {
      return this.docXmlFileName;
    }

    String label = getLabel();
    if (label == null) {
      label = getEnunciate().getConfig().getLabel();
    }
    return label + "-docs.xml";
  }

  /**
   * The name of the generated C# xml documentation.
   *
   * @param docXmlFileName The name of the generated C# xml documentation.
   */
  public void setDocXmlFileName(String docXmlFileName) {
    this.docXmlFileName = docXmlFileName;
  }

  /**
   * The name of the generated C# source file.
   *
   * @return The name of the generated C# source file.
   */
  protected String getSourceFileName() {
    if (this.sourceFileName != null) {
      return this.sourceFileName;
    }
   
    String label = getLabel();
    if (label == null) {
      label = getEnunciate().getConfig().getLabel();
    }
    return label + ".cs";
  }

  /**
   * The name of the generated C# source file.
   *
   * @param sourceFileName The name of the generated C# source file.
   */
  public void setSourceFileName(String sourceFileName) {
    this.sourceFileName = sourceFileName;
  }

  @Override
  protected ObjectWrapper getObjectWrapper() {
    return new DefaultObjectWrapper() {
      @Override
      public TemplateModel wrap(Object obj) throws TemplateModelException {
        if (obj instanceof JavaDoc) {
          return new FreemarkerJavaDoc((JavaDoc) obj);
        }

        return super.wrap(obj);
      }
    };
  }

  /**
   * Get a template URL for the template of the given name.
   *
   * @param template The specified template.
   * @return The URL to the specified template.
   */
  protected URL getTemplateURL(String template) {
    return CSharpDeploymentModule.class.getResource(template);
  }

  /**
   * Whether the generate dir is up-to-date.
   *
   * @param genDir The generate dir.
   * @return Whether the generate dir is up-to-date.
   */
  protected boolean isUpToDate(File genDir) {
    return enunciate.isUpToDateWithSources(genDir);
  }

  /**
   * Whether to require the C# client code.
   *
   * @return Whether to require the C# client code.
   */
  public boolean isRequire() {
    return require;
  }

  /**
   * Whether to require the C# client code.
   *
   * @param require Whether to require the C# client code.
   */
  public void setRequire(boolean require) {
    this.require = require;
  }

  /**
   * The label for the C# API.
   *
   * @return The label for the C# API.
   */
  public String getLabel() {
    return label;
  }

  /**
   * The label for the C# API.
   *
   * @param label The label for the C# API.
   */
  public void setLabel(String label) {
    this.label = label;
  }

  /**
   * The path to the compile executable.
   *
   * @return The path to the compile executable.
   */
  public String getCompileExecutable() {
    return compileExecutable;
  }

  /**
   * The path to the compile executable.
   *
   * @param compileExecutable The path to the compile executable.
   */
  public void setCompileExecutable(String compileExecutable) {
    this.compileExecutable = compileExecutable;
  }

  /**
   * The C# compile command.
   *
   * @return The C# compile command.
   */
  public String getCompileCommand() {
    return compileCommand;
  }

  /**
   * The C# compile command.
   *
   * @param compileCommand The C# compile command.
   */
  public void setCompileCommand(String compileCommand) {
    this.compileCommand = compileCommand;
  }

  /**
   * The package-to-namespace conversions.
   *
   * @return The package-to-namespace conversions.
   */
  public Map<String, String> getPackageToNamespaceConversions() {
    return packageToNamespaceConversions;
  }

  /**
   * Whether to disable the compile step.
   *
   * @return Whether to disable the compile step.
   */
  public boolean isDisableCompile() {
    return disableCompile;
  }

  /**
   * Whether to disable the compile step.
   *
   * @param disableCompile Whether to disable the compile step.
   */
  public void setDisableCompile(boolean disableCompile) {
    this.disableCompile = disableCompile;
  }

  /**
   * Whether there should be a single file per class. Default: false (all classes are contained in a single file).
   *
   * @return Whether there should be a single file per class.
   */
  public boolean isSingleFilePerClass() {
    return singleFilePerClass;
  }

  /**
   * Whether there should be a single file per class.
   *
   * @param singleFilePerClass Whether there should be a single file per class.
   */
  public void setSingleFilePerClass(boolean singleFilePerClass) {
    this.singleFilePerClass = singleFilePerClass;
  }

  /**
   * The set of facets to include.
   *
   * @return The set of facets to include.
   */
  public Set<String> getFacetIncludes() {
    return facetIncludes;
  }

  /**
   * Add a facet include.
   *
   * @param name The name.
   */
  public void addFacetInclude(String name) {
    if (name != null) {
      this.facetIncludes.add(name);
    }
  }

  /**
   * The set of facets to exclude.
   *
   * @return The set of facets to exclude.
   */
  public Set<String> getFacetExcludes() {
    return facetExcludes;
  }

  /**
   * Add a facet exclude.
   *
   * @param name The name.
   */
  public void addFacetExclude(String name) {
    if (name != null) {
      this.facetExcludes.add(name);
    }
  }

  /**
   * Add a client package conversion.
   *
   * @param conversion The conversion to add.
   */
  public void addClientPackageConversion(PackageNamespaceConversion conversion) {
    String from = conversion.getFrom();
    String to = conversion.getTo();

    if (from == null) {
      throw new IllegalArgumentException("A 'from' attribute must be specified on a package-conversion element.");
    }

    if (to == null) {
      throw new IllegalArgumentException("A 'to' attribute must be specified on a package-conversion element.");
    }

    this.packageToNamespaceConversions.put(from, to);
  }

  @Override
  public RuleSet getConfigurationRules() {
    return new CSharpRuleSet();
  }

  @Override
  public Validator getValidator() {
    return new CSharpValidator();
  }

  // Inherited.
  @Override
  public boolean isDisabled() {
    if (super.isDisabled()) {
      return true;
    }
    else if (getModelInternal() != null && getModelInternal().getNamespacesToWSDLs().isEmpty() && getModelInternal().getNamespacesToSchemas().isEmpty()) {
      debug("C# module is disabled because there are no endpoint interfaces, nor any XML types.");
      return true;
    }

    return false;
  }
}
TOP

Related Classes of org.codehaus.enunciate.modules.csharp.CSharpDeploymentModule

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.