Java tutorial
/* * 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.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. * @param value The value. */ public void addFacetExclude(String name, String value) { 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; } }