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.amf; import flex.messaging.MessageBrokerServlet; import freemarker.ext.dom.NodeModel; 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.EnunciateClasspathListener; import org.codehaus.enunciate.apt.EnunciateFreemarkerModel; import org.codehaus.enunciate.config.SchemaInfo; import org.codehaus.enunciate.config.WsdlInfo; import org.codehaus.enunciate.contract.HasFacets; import org.codehaus.enunciate.contract.jaxb.TypeDefinition; import org.codehaus.enunciate.contract.jaxws.EndpointInterface; import org.codehaus.enunciate.contract.validation.Validator; import org.codehaus.enunciate.main.*; import org.codehaus.enunciate.main.webapp.BaseWebAppFragment; import org.codehaus.enunciate.main.webapp.WebAppComponent; import org.codehaus.enunciate.modules.FacetAware; import org.codehaus.enunciate.modules.FlexHomeAwareModule; import org.codehaus.enunciate.modules.FreemarkerDeploymentModule; import org.codehaus.enunciate.modules.ProjectExtensionModule; import org.codehaus.enunciate.modules.amf.config.AMFRuleSet; import org.codehaus.enunciate.modules.amf.config.FlexApp; import org.codehaus.enunciate.modules.amf.config.FlexCompilerConfig; import org.codehaus.enunciate.modules.amf.config.License; import org.codehaus.enunciate.template.freemarker.AccessorOverridesAnotherMethod; import org.codehaus.enunciate.template.freemarker.ClientPackageForMethod; import org.codehaus.enunciate.template.freemarker.ComponentTypeForMethod; import org.codehaus.enunciate.template.freemarker.SimpleNameWithParamsMethod; import org.codehaus.enunciate.util.FacetFilter; import org.w3c.dom.Document; import javax.xml.parsers.DocumentBuilderFactory; import java.io.*; import java.net.URL; import java.util.*; /** * <h1>AMF Module</h1> * * <p>The AMF deployment module generates the server-side and client-side libraries used to support an * <a href="http://en.wikipedia.org/wiki/Action_Message_Format">Action Message Format</a> API. The client-side * library is a set of <a href="http://en.wikipedia.org/wiki/Actionscript">ActionScript</a> classes that are * type-safe wrappers around the ActionScript remoting API that are designed to add clarity and to be easy * to consume for Flex development. Furthermore, the server-side support classes add an extra degree of security to * your Data Services by ensuring that only your public methods are made available for invocation via AMF. There is * also support for invoking the <a href="http://en.wikipedia.org/wiki/Adobe_Flex">Adobe Flex</a> compiler to compile * a set of <a href="http://en.wikipedia.org/wiki/Adobe_Flash">Flash</a> applications that can be added to your * Enunciate-generated web application.</p> * * <p>The AMF API leverages the <a href="http://labs.adobe.com/technologies/blazeds/">Blaze DS</a> package that was recently made * available as an open source product by Adobe. To use the AMF module, you will have to have the * <a href="http://www.adobe.com/products/flex/sdk/">Flex SDK</a> installed.</p> * * <p>This documentation is an overview of how to use Enunciate to build your Flex Data Services (AMF API) and associated Flash * application(s). The reader is redirected to the <a href="http://www.adobe.com/products/flex/">documentation for Flex</a> for * instructions on how to use Flex. There are also two sample applications you may find useful, petclinic and addressbook, that you * will find bundled with the Enunciate distribution.</p> * * <ul> * <li><a href="#steps">steps</a></li> * <li><a href="#config">configuration</a></li> * <li><a href="#artifacts">artifacts</a></li> * </ul> * * <h1><a name="steps">Steps</a></h1> * * <h3>generate</h3> * * <p>The "generate" step generates all source code for the AMF API. This includes server-side support classes and client-side * ActionScript classes that can be used to access the API via AMF.</p> * * <h3>compile</h3> * * <p>During the "compile" step, the AMF module compiles the code that was generated. The generated client-side ActionScript * classes are compiled into an SWC file that is made available as an Enunciate artifact. The SWC file can also be made * available as a download from the deployed web application (see the configuration). It is also during the "compile" step that * the Flex compiler is invoked on any Flex applications that are specified in the configuration.</p> * * <h1><a name="config">Configuration</a></h1> * * <p>The AMF module is configured by the "amf" element under the "modules" element of the enunciate configuration file. <b>The * AMF module is disabled by default because of the added constraints applied to the service endpoints and because of the additional * dependencies required by the module.</b> To enable AMF, be sure to specify <i>disabled="false"</i> on the "amf" element.</p> * * <p>The "amf" element supports the following attributes:</p> * * <ul> * <li>The "flexHome" attribute <b>must</b> be supplied. It is the path to the directory where the Flex SDK is installed.</li> * <li>The "label" attribute is used to determine the name of the client-side artifact files. The default is the Enunciate project label.</li> * <li>The "swcName" attribute specifies the name of the compiled SWC. By default, the name is determined by the Enunciate * project label (see the main configuration docs).</li> * <li>The "swcDownloadable" attribute specifies whether the generated SWC is to be made available as a download from the * generated web application. Default: "false".</li> * <li>The "asSourcesDownloadable" attribute specifies whether the generated ActionScript source files are downloadable from * generated web application. Default: "false".</li> * <li>The "mergeServicesConfigXML" attribute specifies the services-config.xml file that is to be merged into the Enunciate-generated * services-config.xml file. No file will be merged if none is specified.</li> * <li>The "enforceNoFieldAccessors" attribute specifies whether to enforce that a field accessor cannot be used for AMF mapping. * <i>Note: whether this option is enabled or disabled, there currently MUST be a getter and setter for each accessor. This option only * disables the compile-time validation check.</i></li> * </ul> * * <h3>The "war" element</h3> * * <p>The "war" element under the "amf" element is used to configure the webapp that will host the AMF endpoints and Flex applications. It supports * the following attributes:</p> * * <ul> * <li>The "amfSubcontext" attribute is the subcontext at which the amf endpoints will be mounted. Default: "/amf".</li> * <li>The "flexAppDir" attribute is the directory in the war to which the flex applications will be put. The default is the root of the war.</li> * </ul> * * <h3>The "compiler" element</h3> * * <p>The "compiler" element under the "amf" element is used to configure the compiler that will be used to compile the SWC * and the Flex applications. It supports the following attributes, associated directly to the Flex compiler options. For details, * see the documentation for the Flex compiler.</p> * * <ul> * <li><b>contextRoot</b> (default: the Enunciate project label)</li> * <li><b>flexConfig</b> (default: "$FLEX_SDK_HOME/frameworks/flex-config.xml")</li> * <li><b>locale</b> (default: unspecified)</li> * <li><b>optimize</b> (boolean, default: unspecified)</li> * <li><b>debug</b> (boolean, default: unspecified)</li> * <li><b>profile</b> (boolean, default: unspecified)</li> * <li><b>strict</b> (boolean, default: unspecified)</li> * <li><b>useNetwork</b> (boolean, default: unspecified)</li> * <li><b>incremental</b> (boolean, default: unspecified)</li> * <li><b>warnings</b> (boolean, default: unspecified)</li> * <li><b>showActionscriptWarnings</b> (boolean, default: unspecified)</li> * <li><b>showBindingWarnings</b> (boolean, default: unspecified)</li> * <li><b>showDeprecationWarnings</b> (boolean, default: unspecified)</li> * <li><b>flexCompileCommand</b> (default "flex2.tools.Compiler")</li> * <li><b>swcCompileCommand</b> (default "flex2.tools.Compc")</li> * </ul> * * <p>The "compiler" element also supports the following subelements:</p> * * <ul> * <li>"JVMArg" (additional JVM arguments, passed in order to the JVM used to invoke the compiler, supports a single attribute: "value")</li> * <li>"arg" (additional compiler arguments, passed in order to the compiler)</li> * <li>"license" (supports attributes "product" and "serialNumber")</li> * </ul> * * <h3>The "app" element</h3> * * <p>The AMF module supports the development of Flex apps that can be compiled and packaged with the generated Enunciate app. * The "app" element supports the folowing attributes:</p> * * <ul> * <li>The "name" attribute is the name of the Flex app. This attribute is required.</li> * <li>The "srcDir" attribute specifies the source directory for the application. This attribute is required.</li> * <li>The "mainMxmlFile" attribute specifies the main mxml file for the app. This attribute is required. The path to this file is resolved * relative to the enunciate.xml file (not to the "srcDir" attribute of the app).</li> * <li>The "outputPath" attribute specified the output directory for the application, relative to the "flexAppDir".</li> * </ul> * * <h3>The "facets" element</h3> * * <p>The "facets" element is applicable to the AMF module to configure which facets are to be included/excluded from the AMF artifacts. For * more information, see <a href="http://docs.codehaus.org/display/ENUNCIATE/Enunciate+API+Facets">API Facets</a></p> * * <h3>Example Configuration</h3> * * <p>As an example, consider the following configuration:</p> * * <code class="console"> * <enunciate> * <modules> * <amf disabled="false" swcName="mycompany-amf.swc" * flexHome="/home/myusername/tools/flex-sdk-2"> * <app srcDir="src/main/flexapp" name="main" mainMxmlFile="src/main/flexapp/com/mycompany/main.mxml"/> * <app srcDir="src/main/anotherapp" name="another" mainMxmlFile="src/main/anotherapp/com/mycompany/another.mxml"/> * <facets> * ... * </facets> * ... * </amf> * </modules> * </enunciate> * </code> * * <p>This configuration enables the AMF module and gives a specific name to the compiled SWC for the client-side ActionScript classes.</p> * * <p>There also two Flex applications defined. The first is located at "src/main/flexapp". The name of this app is "main". The MXML * file that defines this app sits at "src/main/flexapp/com/mycompany/main.mxml", relative to the enunciate configuration file. The * second application, rooted at "src/main/anotherapp", is named "another". The mxml file that defines this application sits at * "src/main/anotherapp/com/mycompany/another.mxml".</p> * * <p>After the "compile" step of the AMF module, assuming everything compiles correctly, there will be two Flash applications, "main.swf" and "another.swf", * that sit in the applications directory (see "artifacts" below).</p> * * <p>For a less contrived example, see the "petclinic" sample Enunciate project bundled with the Enunciate distribution.</p> * * <h1><a name="artifacts">Artifacts</a></h1> * * <ul> * <li>The "amf.client.src.dir" artifact is the directory where the client-side source code is generated.</li> * <li>The "amf.server.src.dir" artifact is the directory where the server-side source code is generated.</li> * <li>The "as3.client.swc" artifact is the packaged client-side ActionScript SWC.</li> * <li>The "flex.app.dir" artifact is the directory to which the Flex apps are compiled.</li> * </ul> * * @author Ryan Heaton * @docFileName module_amf.html */ public class AMFDeploymentModule extends FreemarkerDeploymentModule implements ProjectExtensionModule, FlexHomeAwareModule, EnunciateClasspathListener, FacetAware { private String amfSubcontext = "/amf/"; private String flexAppDir = null; private final List<FlexApp> flexApps = new ArrayList<FlexApp>(); private final AMFRuleSet configurationRules = new AMFRuleSet(); private String label = null; private String flexHome = System.getProperty("flex.home") == null ? System.getenv("FLEX_HOME") : System.getProperty("flex.home"); private FlexCompilerConfig compilerConfig = new FlexCompilerConfig(); private String swcName; private boolean swcDownloadable = false; private boolean asSourcesDownloadable = false; private boolean amfRtFound = false; private boolean springDIFound = false; private boolean enforceNoFieldAccessors = true; private String mergeServicesConfigXML; private Set<String> facetIncludes = new TreeSet<String>(); private Set<String> facetExcludes = new TreeSet<String>( Arrays.asList("org.codehaus.enunciate.modules.amf.AMFTransient")); /** * @return "amf" */ @Override public String getName() { return "amf"; } @Override public void init(Enunciate enunciate) throws EnunciateException { super.init(enunciate); if (!isDisabled()) { if (this.flexHome == null && (isSwcDownloadable() || !flexApps.isEmpty())) { throw new EnunciateException( "To compile a flex app you must specify the Flex SDK home directory, either in configuration, by setting the FLEX_HOME environment variable, or setting the 'flex.home' system property."); } for (FlexApp flexApp : flexApps) { if (flexApp.getName() == null) { throw new EnunciateException("A flex app must have a name."); } String srcPath = flexApp.getSrcDir(); if (srcPath == null) { throw new EnunciateException("A source directory for the flex app '" + flexApp.getName() + "' must be supplied with the 'srcDir' attribute."); } File srcDir = enunciate.resolvePath(srcPath); if (!srcDir.exists()) { throw new EnunciateException( "Source directory for the flex app '" + flexApp.getName() + "' doesn't exist."); } } } } @Override public void initModel(EnunciateFreemarkerModel model) { super.initModel(model); if (!isDisabled()) { if (!amfRtFound) { warn("WARNING! The AMF module is enabled, but the Enunciate AMF runtime libraries were found on the Enunciate classpath. " + "Application startup will fail unless the Enunciate AMF runtime libraries are on the classpath."); } } } public void onClassesFound(Set<String> classes) { amfRtFound |= classes.contains("org.codehaus.enunciate.modules.amf.AMFEndpointImpl"); springDIFound |= classes.contains("org.springframework.beans.factory.annotation.Autowired"); } @Override public void doFreemarkerGenerate() throws IOException, TemplateException, EnunciateException { File serverGenerateDir = getServerSideGenerateDir(); File clientGenerateDir = getClientSideGenerateDir(); File xmlGenerateDir = getXMLGenerateDir(); Enunciate enunciate = getEnunciate(); if (!enunciate.isUpToDateWithSources(serverGenerateDir) || !enunciate.isUpToDateWithSources(clientGenerateDir) || !enunciate.isUpToDateWithSources(xmlGenerateDir)) { //load the references to the templates.... URL amfEndpointTemplate = getTemplateURL("amf-endpoint.fmt"); URL amfTypeTemplate = getTemplateURL("amf-type.fmt"); URL amfTypeMapperTemplate = getTemplateURL("amf-type-mapper.fmt"); EnunciateFreemarkerModel model = getModel(); model.setFileOutputDirectory(serverGenerateDir); model.put("useSpringDI", this.springDIFound); TreeMap<String, String> packages = new TreeMap<String, String>(new Comparator<String>() { public int compare(String package1, String package2) { int comparison = package1.length() - package2.length(); if (comparison == 0) { return package1.compareTo(package2); } return comparison; } }); for (SchemaInfo schemaInfo : model.getNamespacesToSchemas().values()) { for (TypeDefinition typeDefinition : schemaInfo.getTypeDefinitions()) { if (!isFacetExcluded(typeDefinition)) { String packageName = typeDefinition.getPackage().getQualifiedName(); packages.put(packageName, packageName + ".amf"); } } } debug("Generating the AMF externalizable types and their associated mappers..."); AMFClassnameForMethod amfClassnameForMethod = new AMFClassnameForMethod(packages); model.put("simpleNameFor", new SimpleNameWithParamsMethod(amfClassnameForMethod)); model.put("classnameFor", amfClassnameForMethod); for (SchemaInfo schemaInfo : model.getNamespacesToSchemas().values()) { for (TypeDefinition typeDefinition : schemaInfo.getTypeDefinitions()) { if (!isFacetExcluded(typeDefinition)) { model.put("type", typeDefinition); processTemplate(amfTypeTemplate, model); processTemplate(amfTypeMapperTemplate, model); } } } debug("Generating the AMF endpoint beans..."); for (WsdlInfo wsdlInfo : model.getNamespacesToWSDLs().values()) { for (EndpointInterface ei : wsdlInfo.getEndpointInterfaces()) { if (!isFacetExcluded(ei)) { model.put("endpointInterface", ei); processTemplate(amfEndpointTemplate, model); } } } URL endpointTemplate = getTemplateURL("as3-endpoint.fmt"); URL typeTemplate = getTemplateURL("as3-type.fmt"); URL enumTypeTemplate = getTemplateURL("as3-enum-type.fmt"); //build up the list of as3Aliases... HashMap<String, String> as3Aliases = new HashMap<String, String>(); for (SchemaInfo schemaInfo : model.getNamespacesToSchemas().values()) { for (TypeDefinition typeDefinition : schemaInfo.getTypeDefinitions()) { if (!isFacetExcluded(typeDefinition)) { as3Aliases.put(amfClassnameForMethod.convert(typeDefinition), typeDefinition.getClientSimpleName()); } } } model.setFileOutputDirectory(clientGenerateDir); HashMap<String, String> conversions = new HashMap<String, String>(); //todo: accept client-side package mappings? ClientPackageForMethod as3PackageFor = new ClientPackageForMethod(conversions); as3PackageFor.setUseClientNameConversions(true); model.put("packageFor", as3PackageFor); AS3UnqualifiedClassnameForMethod as3ClassnameFor = new AS3UnqualifiedClassnameForMethod(conversions); as3ClassnameFor.setUseClientNameConversions(true); model.put("classnameFor", as3ClassnameFor); model.put("simpleNameFor", new SimpleNameWithParamsMethod(as3ClassnameFor)); ComponentTypeForMethod as3ComponentTypeFor = new ComponentTypeForMethod(conversions); as3ComponentTypeFor.setUseClientNameConversions(true); model.put("componentTypeFor", as3ComponentTypeFor); model.put("amfClassnameFor", amfClassnameForMethod); model.put("amfComponentTypeFor", new ComponentTypeForMethod(packages)); model.put("forEachAS3Import", new ForEachAS3ImportTransform(null, as3ClassnameFor)); model.put("accessorOverridesAnother", new AccessorOverridesAnotherMethod()); model.put("as3Aliases", as3Aliases); debug("Generating the ActionScript types..."); for (SchemaInfo schemaInfo : model.getNamespacesToSchemas().values()) { for (TypeDefinition typeDefinition : schemaInfo.getTypeDefinitions()) { if (!isFacetExcluded(typeDefinition)) { model.put("type", typeDefinition); URL template = typeDefinition.isEnum() ? enumTypeTemplate : typeTemplate; processTemplate(template, model); } } } for (WsdlInfo wsdlInfo : model.getNamespacesToWSDLs().values()) { for (EndpointInterface ei : wsdlInfo.getEndpointInterfaces()) { if (!isFacetExcluded(ei)) { model.put("endpointInterface", ei); processTemplate(endpointTemplate, model); } } } File servicesConfigXML = new File(xmlGenerateDir, "services-config.xml"); URL servicesConfigTemplate = getTemplateURL("services-config-xml.fmt"); model.setFileOutputDirectory(xmlGenerateDir); debug("Generating the configuration files."); processTemplate(servicesConfigTemplate, model); File mergeTarget = new File(xmlGenerateDir, "merged-services-config.xml"); if (this.mergeServicesConfigXML != null) { URL servicesConfigXmlToMerge = enunciate.resolvePath(this.mergeServicesConfigXML).toURL(); try { model.put("source1", loadMergeXmlModel(servicesConfigXmlToMerge.openStream())); model.put("source2", loadMergeXmlModel(new FileInputStream(servicesConfigXML))); processTemplate(getMergeServicesConfigXmlTemplateURL(), model); } catch (TemplateException e) { throw new EnunciateException("Error while merging services-config xml files.", e); } debug("Merged %s and %s into %s...", servicesConfigXmlToMerge, servicesConfigXML, mergeTarget); } else { enunciate.copyFile(servicesConfigXML, mergeTarget); } if (!mergeTarget.exists()) { throw new EnunciateException("Error: " + mergeTarget + " doesn't exist."); } } else { info("Skipping generation of AMF support as everything appears up-to-date..."); } this.enunciate.addArtifact(new FileArtifact(getName(), "amf.client.src.dir", clientGenerateDir)); this.enunciate.addArtifact(new FileArtifact(getName(), "amf.server.src.dir", serverGenerateDir)); this.enunciate.addAdditionalSourceRoot(serverGenerateDir); } /** * Loads the node model for merging xml. * * @param inputStream The input stream of the xml. * @return The node model. */ protected NodeModel loadMergeXmlModel(InputStream inputStream) throws EnunciateException { try { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); builderFactory.setNamespaceAware(false); //no namespace for the merging... Document doc = builderFactory.newDocumentBuilder().parse(inputStream); NodeModel.simplify(doc); return NodeModel.wrap(doc.getDocumentElement()); } catch (Exception e) { throw new EnunciateException("Error parsing web.xml file for merging", e); } } /** * Invokes the flex compiler on the apps specified in the configuration file. */ protected void doFlexCompile() throws EnunciateException, IOException { File swcFile = null; File asSources = null; Enunciate enunciate = getEnunciate(); if (isSwcDownloadable() || !flexApps.isEmpty()) { if (this.flexHome == null) { throw new EnunciateException( "To compile a flex app you must specify the Flex SDK home directory, either in configuration, by setting the FLEX_HOME environment variable, or setting the 'flex.home' system property."); } File flexHomeDir = new File(this.flexHome); if (!flexHomeDir.exists()) { throw new EnunciateException("Flex home not found ('" + flexHomeDir.getAbsolutePath() + "')."); } File javaBinDir = new File(System.getProperty("java.home"), "bin"); File javaExecutable = new File(javaBinDir, "java"); if (!javaExecutable.exists()) { //append the "exe" for windows users. javaExecutable = new File(javaBinDir, "java.exe"); } String javaCommand = javaExecutable.getAbsolutePath(); if (!javaExecutable.exists()) { warn("No java executable found in %s. We'll just hope the environment is set up to execute 'java'...", javaBinDir.getAbsolutePath()); javaCommand = "java"; } int compileCommandIndex; int outputFileIndex; int sourcePathIndex; int mainMxmlPathIndex; List<String> commandLine = new ArrayList<String>(); int argIndex = 0; commandLine.add(argIndex++, javaCommand); for (String jvmarg : this.compilerConfig.getJVMArgs()) { commandLine.add(argIndex++, jvmarg); } commandLine.add(argIndex++, "-cp"); File flexHomeLib = new File(flexHomeDir, "lib"); if (!flexHomeLib.exists()) { throw new EnunciateException("File not found: " + flexHomeLib); } else { StringBuilder builder = new StringBuilder(); Iterator<File> flexLibIt = Arrays.asList(flexHomeLib.listFiles()).iterator(); while (flexLibIt.hasNext()) { File flexJar = flexLibIt.next(); if (flexJar.getAbsolutePath().endsWith("jar")) { builder.append(flexJar.getAbsolutePath()); if (flexLibIt.hasNext()) { builder.append(File.pathSeparatorChar); } } else { debug("File %s will not be included on the classpath because it's not a jar.", flexJar); } } commandLine.add(argIndex++, builder.toString()); } compileCommandIndex = argIndex; commandLine.add(argIndex++, null); commandLine.add(argIndex++, "-output"); outputFileIndex = argIndex; commandLine.add(argIndex++, null); if (compilerConfig.getFlexConfig() == null) { compilerConfig.setFlexConfig(new File(new File(flexHome, "frameworks"), "flex-config.xml")); } if (compilerConfig.getFlexConfig().exists()) { commandLine.add(argIndex++, "-load-config"); commandLine.add(argIndex++, compilerConfig.getFlexConfig().getAbsolutePath()); } else { warn("Configured flex configuration file %s doesn't exist. Ignoring...", compilerConfig.getFlexConfig()); } if (compilerConfig.getContextRoot() == null) { if (getEnunciate().getConfig().getLabel() != null) { compilerConfig.setContextRoot("/" + getEnunciate().getConfig().getLabel()); } else { compilerConfig.setContextRoot("/enunciate"); } } commandLine.add(argIndex++, "-compiler.context-root"); commandLine.add(argIndex++, compilerConfig.getContextRoot()); if (compilerConfig.getLocale() != null) { commandLine.add(argIndex++, "-compiler.locale"); commandLine.add(argIndex++, compilerConfig.getLocale()); } if (compilerConfig.getLicenses().size() > 0) { commandLine.add(argIndex++, "-licenses.license"); for (License license : compilerConfig.getLicenses()) { commandLine.add(argIndex++, license.getProduct()); commandLine.add(argIndex++, license.getSerialNumber()); } } if (compilerConfig.getOptimize() != null && compilerConfig.getOptimize()) { commandLine.add(argIndex++, "-compiler.optimize"); } if (compilerConfig.getDebug() != null && compilerConfig.getDebug()) { commandLine.add(argIndex++, "-compiler.debug=true"); } if (compilerConfig.getStrict() != null && compilerConfig.getStrict()) { commandLine.add(argIndex++, "-compiler.strict"); } if (compilerConfig.getUseNetwork() != null && compilerConfig.getUseNetwork()) { commandLine.add(argIndex++, "-use-network"); } if (compilerConfig.getIncremental() != null && compilerConfig.getIncremental()) { commandLine.add(argIndex++, "-compiler.incremental"); } if (compilerConfig.getShowActionscriptWarnings() != null && compilerConfig.getShowActionscriptWarnings()) { commandLine.add(argIndex++, "-show-actionscript-warnings"); } if (compilerConfig.getShowBindingWarnings() != null && compilerConfig.getShowBindingWarnings()) { commandLine.add(argIndex++, "-show-binding-warnings"); } if (compilerConfig.getShowDeprecationWarnings() != null && compilerConfig.getShowDeprecationWarnings()) { commandLine.add(argIndex++, "-show-deprecation-warnings"); } for (String arg : this.compilerConfig.getArgs()) { commandLine.add(argIndex++, arg); } commandLine.add(argIndex++, "-compiler.services"); File xmlGenerateDir = getXMLGenerateDir(); commandLine.add(argIndex++, new File(xmlGenerateDir, "merged-services-config.xml").getAbsolutePath()); commandLine.add(argIndex, "-include-sources"); File clientSideGenerateDir = getClientSideGenerateDir(); commandLine.add(argIndex + 1, clientSideGenerateDir.getAbsolutePath()); String swcName = getSwcName(); if (swcName == null) { String label = "enunciate"; if (getLabel() != null) { label = getLabel(); } else if ((enunciate.getConfig() != null) && (enunciate.getConfig().getLabel() != null)) { label = enunciate.getConfig().getLabel(); } swcName = label + "-as3-client.swc"; } File swcCompileDir = getSwcCompileDir(); swcFile = new File(swcCompileDir, swcName); boolean swcUpToDate = swcFile.exists() && enunciate.isUpToDate(xmlGenerateDir, swcCompileDir) && enunciate.isUpToDate(clientSideGenerateDir, swcCompileDir); if (!swcUpToDate) { commandLine.set(compileCommandIndex, compilerConfig.getSwcCompileCommand()); commandLine.set(outputFileIndex, swcFile.getAbsolutePath()); debug("Compiling %s for the client-side ActionScript classes...", swcFile.getAbsolutePath()); if (enunciate.isDebug()) { StringBuilder command = new StringBuilder(); for (String commandPiece : commandLine) { command.append(' ').append(commandPiece); } debug("Executing SWC compile for client-side actionscript with the command: %s", command); } compileSwc(commandLine); } else { info("Skipping compilation of %s as everything appears up-to-date...", swcFile.getAbsolutePath()); } //swc is compiled while (commandLine.size() > argIndex) { //remove the compc-specific options... commandLine.remove(argIndex); } if (compilerConfig.getProfile() != null && compilerConfig.getProfile()) { commandLine.add(argIndex++, "-compiler.profile"); } if (compilerConfig.getWarnings() != null && compilerConfig.getWarnings()) { commandLine.add(argIndex++, "-warnings"); } commandLine.add(argIndex++, "-source-path"); commandLine.add(argIndex++, clientSideGenerateDir.getAbsolutePath()); commandLine.add(argIndex++, "-source-path"); sourcePathIndex = argIndex; commandLine.add(argIndex++, null); commandLine.add(argIndex++, "--"); mainMxmlPathIndex = argIndex; commandLine.add(argIndex++, null); commandLine.set(compileCommandIndex, compilerConfig.getFlexCompileCommand()); File outputDirectory = getSwfCompileDir(); debug("Creating output directory: " + outputDirectory); outputDirectory.mkdirs(); for (FlexApp flexApp : flexApps) { String mainMxmlPath = flexApp.getMainMxmlFile(); if (mainMxmlPath == null) { throw new EnunciateException("A main MXML file for the flex app '" + flexApp.getName() + "' must be supplied with the 'mainMxmlFile' attribute."); } File mainMxmlFile = enunciate.resolvePath(mainMxmlPath); if (!mainMxmlFile.exists()) { throw new EnunciateException( "Main MXML file for the flex app '" + flexApp.getName() + "' doesn't exist."); } File swfDir = outputDirectory; if (flexApp.getOutputPath() != null && !"".equals(flexApp.getOutputPath())) { swfDir = new File(outputDirectory, flexApp.getOutputPath()); swfDir.mkdirs(); } File swfFile = new File(swfDir, flexApp.getName() + ".swf"); File appSrcDir = enunciate.resolvePath(flexApp.getSrcDir()); String swfFilePath = swfFile.getAbsolutePath(); boolean swfUpToDate = swfFile.exists() && mainMxmlFile.lastModified() < swfFile.lastModified() && enunciate.isUpToDate(appSrcDir, swfFile); if (!swfUpToDate) { commandLine.set(outputFileIndex, swfFilePath); commandLine.set(mainMxmlPathIndex, mainMxmlFile.getAbsolutePath()); commandLine.set(sourcePathIndex, appSrcDir.getAbsolutePath()); debug("Compiling %s ...", swfFilePath); if (enunciate.isDebug()) { StringBuilder command = new StringBuilder(); for (String commandPiece : commandLine) { command.append(' ').append(commandPiece); } debug("Executing flex compile for module %s with the command: %s", flexApp.getName(), command); } ProcessBuilder processBuilder = new ProcessBuilder( commandLine.toArray(new String[commandLine.size()])); processBuilder.directory(getSwfCompileDir()); processBuilder.redirectErrorStream(true); Process process = processBuilder.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 Flex compile process."); } if (procCode != 0) { throw new EnunciateException("Flex compile failed for module " + flexApp.getName()); } } else { info("Skipping compilation of %s as everything appears up-to-date...", swfFilePath); } } } if (isAsSourcesDownloadable()) { String label = "enunciate"; if ((enunciate.getConfig() != null) && (enunciate.getConfig().getLabel() != null)) { label = enunciate.getConfig().getLabel(); } asSources = new File(new File(getCompileDir(), "src"), label + "-as3-sources.zip"); enunciate.zip(asSources, getClientSideGenerateDir()); } if (swcFile != null || asSources != null) { List<ArtifactDependency> clientDeps = new ArrayList<ArtifactDependency>(); BaseArtifactDependency as3Dependency = new BaseArtifactDependency(); as3Dependency.setId("flex-sdk"); as3Dependency.setArtifactType("zip"); as3Dependency.setDescription("The flex SDK."); as3Dependency.setURL("http://www.adobe.com/products/flex/"); as3Dependency.setVersion("2.0.1"); clientDeps.add(as3Dependency); ClientLibraryArtifact as3ClientArtifact = new ClientLibraryArtifact(getName(), "as3.client.library", "ActionScript 3 Client Library"); as3ClientArtifact.setPlatform("Adobe Flex"); //read in the description from file: as3ClientArtifact.setDescription(readResource("library_description.fmt")); as3ClientArtifact.setDependencies(clientDeps); if (swcFile != null) { NamedFileArtifact clientArtifact = new NamedFileArtifact(getName(), "as3.client.swc", swcFile); clientArtifact.setDescription("The compiled SWC."); clientArtifact.setPublic(false); clientArtifact.setArtifactType(ArtifactType.binaries); as3ClientArtifact.addArtifact(clientArtifact); enunciate.addArtifact(clientArtifact); } if (asSources != null) { NamedFileArtifact clientArtifact = new NamedFileArtifact(getName(), "as3.client.sources", asSources); clientArtifact.setDescription("The client-side ActionScript sources."); clientArtifact.setPublic(false); clientArtifact.setArtifactType(ArtifactType.sources); as3ClientArtifact.addArtifact(clientArtifact); enunciate.addArtifact(clientArtifact); } enunciate.addArtifact(as3ClientArtifact); } } /** * Compiles the SWC. * * @param commandLine The command line. */ protected void compileSwc(List<String> commandLine) throws IOException, EnunciateException { getSwcCompileDir().mkdirs(); Process process = new ProcessBuilder(commandLine).directory(getSwcCompileDir()).redirectErrorStream(true) .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 Flex compile process."); } if (procCode != 0) { throw new EnunciateException("SWC compile failed."); } } @Override protected void doCompile() throws EnunciateException, IOException { Enunciate enunciate = getEnunciate(); if (isSwcDownloadable() || this.flexApps.size() > 0 || isAsSourcesDownloadable()) { doFlexCompile(); if (this.flexApps.size() > 0) { enunciate.addArtifact(new FileArtifact(getName(), "flex.app.dir", getSwfCompileDir())); } } } @Override protected void doBuild() throws EnunciateException, IOException { //assemble the server-side webapp fragment BaseWebAppFragment webAppFragment = new BaseWebAppFragment(getName()); //base webapp dir... File webappDir = new File(getBuildDir(), "webapp"); webappDir.mkdirs(); File servicesConfigFile = new File(getXMLGenerateDir(), "merged-services-config.xml"); if (servicesConfigFile.exists()) { getEnunciate().copyFile(servicesConfigFile, new File(new File(new File(webappDir, "WEB-INF"), "flex"), "services-config.xml")); } else { throw new FileNotFoundException("File not found: " + servicesConfigFile.getAbsolutePath()); } File swfCompileDir = getSwfCompileDir(); if ((this.flexApps.size() > 0) && (swfCompileDir != null) && (swfCompileDir.exists())) { File flexAppDir = webappDir; if ((getFlexAppDir() != null) && (!"".equals(getFlexAppDir()))) { debug("Flex applications will be put into the %s subdirectory of the web application.", getFlexAppDir()); flexAppDir = new File(webappDir, getFlexAppDir()); } getEnunciate().copyDir(swfCompileDir, flexAppDir); } else { debug("No flex apps were found."); } webAppFragment.setBaseDir(webappDir); //servlets. WebAppComponent messageServlet = new WebAppComponent(); messageServlet.setClassname(MessageBrokerServlet.class.getName()); messageServlet.setName("AMFMessageServlet"); TreeSet<String> urlMappings = new TreeSet<String>(); for (WsdlInfo wsdlInfo : getModel().getNamespacesToWSDLs().values()) { for (EndpointInterface ei : wsdlInfo.getEndpointInterfaces()) { urlMappings.add(getAmfSubcontext() + ei.getServiceName()); } } messageServlet.setUrlMappings(urlMappings); TreeMap<String, String> initParams = new TreeMap<String, String>(); initParams.put("services.configuration.file", "/WEB-INF/flex/services-config.xml"); initParams.put("flex.write.path", "/WEB-INF/flex"); messageServlet.setInitParams(initParams); webAppFragment.setServlets(Arrays.asList(messageServlet)); getEnunciate().addWebAppFragment(webAppFragment); } /** * Reads a resource into string form. * * @param resource The resource to read. * @return The string form of the resource. */ protected String readResource(String resource) throws IOException, EnunciateException { HashMap<String, Object> model = new HashMap<String, Object>(); model.put("sample_service_method", getModelInternal().findExampleWebMethod()); model.put("sample_resource", getModelInternal().findExampleResourceMethod()); URL res = AMFDeploymentModule.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); } } /** * Whether the given type declaration is AMF-transient. * * @param declaration The type declaration. * @return Whether the given tyep declaration is AMF-transient. */ protected boolean isFacetExcluded(HasFacets declaration) { return !FacetFilter.accept(declaration); } /** * 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 AMFDeploymentModule.class.getResource(template); } /** * Get the generate directory for server-side AMF classes. * * @return The generate directory for server-side AMF classes. */ public File getServerSideGenerateDir() { return new File(getGenerateDir(), "server"); } /** * Get the generate directory for client-side AMF classes. * * @return The generate directory for client-side AMF classes. */ public File getClientSideGenerateDir() { return new File(getGenerateDir(), "client"); } /** * Get the generate directory for XML configuration. * * @return The generate directory for the XML configuration. */ public File getXMLGenerateDir() { return new File(getGenerateDir(), "xml"); } /** * The directory for the destination for the SWC. * * @return The directory for the destination for the SWC. */ public File getSwcCompileDir() { return new File(getCompileDir(), "swc"); } /** * The directory for the destination for the SWF. * * @return The directory for the destination for the SWF. */ public File getSwfCompileDir() { return new File(getCompileDir(), "swf"); } /** * AMF configuration rule set. * * @return AMF configuration rule set. */ @Override public RuleSet getConfigurationRules() { return this.configurationRules; } /** * AMF validator. * * @return AMF validator. */ @Override public Validator getValidator() { return new AMFValidator(this.enforceNoFieldAccessors); } @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); } }; } /** * The amf home directory * * @return The amf home directory */ public String getFlexHome() { return flexHome; } /** * Set the path to the AMF home directory. * * @param flexHome The amf home directory */ public void setFlexHome(String flexHome) { this.flexHome = flexHome; } /** * The amf apps to compile. * * @return The amf apps to compile. */ public List<FlexApp> getFlexApps() { return flexApps; } /** * Adds a flex app to be compiled. * * @param flexApp The flex app to be compiled. */ public void addFlexApp(FlexApp flexApp) { this.flexApps.add(flexApp); } /** * The compiler configuration. * * @return The compiler configuration. */ public FlexCompilerConfig getCompilerConfig() { return compilerConfig; } /** * The compiler configuration. * * @param compilerConfig The compiler configuration. */ public void setCompilerConfig(FlexCompilerConfig compilerConfig) { this.compilerConfig = compilerConfig; } /** * @return The URL to "web.xml.fmt" */ protected URL getMergeServicesConfigXmlTemplateURL() { return this.getClass().getResource("merge-services-config-xml.fmt"); } /** * The name of the swc file. * * @return The name of the swc file. */ public String getSwcName() { return swcName; } /** * The name of the swc file. * * @param swcName The name of the swc file. */ public void setSwcName(String swcName) { this.swcName = swcName; } /** * Whether the swc is downloadable. * * @return Whether the swc is downloadable. */ public boolean isSwcDownloadable() { return swcDownloadable; } /** * Whether the swc is downloadable. * * @param swcDownloadable Whether the swc is downloadable. */ public void setSwcDownloadable(boolean swcDownloadable) { this.swcDownloadable = swcDownloadable; } /** * Whether the generated ActionScript sources are downloadable. * * @return Whether the generated ActionScript sources are downloadable. */ public boolean isAsSourcesDownloadable() { return asSourcesDownloadable; } /** * Whether the generated ActionScript sources are downloadable. * * @param asSourcesDownloadable Whether the generated ActionScript sources are downloadable. */ public void setAsSourcesDownloadable(boolean asSourcesDownloadable) { this.asSourcesDownloadable = asSourcesDownloadable; } /** * The services-config.xml file that is to be merged into the Enunciate-generated * services-config.xml file. * * @return the services-config.xml file that is to be merged into the Enunciate-generated * services-config.xml file. */ public String getMergeServicesConfigXML() { return this.mergeServicesConfigXML; } /** * Specifies the services-config.xml file that is to be merged into the Enunciate-generated * services-config.xml file. * * @param mergeServicesConfigXML the services-config.xml file that is to be merged into the * Enunciate-generated services-config.xml file. */ public void setMergeServicesConfigXML(String mergeServicesConfigXML) { this.mergeServicesConfigXML = mergeServicesConfigXML; } /** * The amf subcontext. * * @return The amf subcontext. */ public String getAmfSubcontext() { return amfSubcontext; } /** * The amf subcontext. * * @param amfSubcontext The amf subcontext. */ public void setAmfSubcontext(String amfSubcontext) { if (amfSubcontext == null) { throw new IllegalArgumentException("The AMF context must not be null."); } if ("".equals(amfSubcontext)) { throw new IllegalArgumentException("The AMF context must not be the emtpy string."); } if (!amfSubcontext.startsWith("/")) { amfSubcontext = "/" + amfSubcontext; } if (!amfSubcontext.endsWith("/")) { amfSubcontext = amfSubcontext + "/"; } this.amfSubcontext = amfSubcontext; } /** * The flex app dir. * * @return The flex app dir. */ public String getFlexAppDir() { return flexAppDir; } /** * The flex app dir. * * @param flexAppDir The flex app dir. */ public void setFlexAppDir(String flexAppDir) { this.flexAppDir = flexAppDir; } /** * The label for the ActionScript API. * * @return The label for the ActionScript API. */ public String getLabel() { return label; } /** * The label for the ActionScript API. * * @param label The label for the ActionScript API. */ public void setLabel(String label) { this.label = label; } /** * 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); } } // Inherited. @Override public boolean isDisabled() { if (super.isDisabled()) { return true; } else if (getModelInternal() != null && getModelInternal().getNamespacesToWSDLs().isEmpty() && getModelInternal().getNamespacesToSchemas().isEmpty()) { debug("AMF module is disabled because there are no endpoint interfaces nor any schema objects."); return true; } return false; } public List<File> getProjectSources() { List<File> sources = new ArrayList<File>( Arrays.asList(getClientSideGenerateDir(), getServerSideGenerateDir())); for (FlexApp flexApp : getFlexApps()) { sources.add(getEnunciate().resolvePath(flexApp.getSrcDir())); } return sources; } public List<File> getProjectTestSources() { return Collections.emptyList(); } public List<File> getProjectResourceDirectories() { return Collections.emptyList(); } public List<File> getProjectTestResourceDirectories() { return Collections.emptyList(); } /** * Whether to enforce that field accessors can't be used. * * @return Whether to enforce that field accessors can't be used. */ public boolean isEnforceNoFieldAccessors() { return enforceNoFieldAccessors; } /** * Whether to enforce that field accessors can't be used. * * @param enforceNoFieldAccessors Whether to enforce that field accessors can't be used. */ public void setEnforceNoFieldAccessors(boolean enforceNoFieldAccessors) { this.enforceNoFieldAccessors = enforceNoFieldAccessors; } }