/*
* 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.docs;
import com.sun.mirror.declaration.PackageDeclaration;
import freemarker.ext.dom.NodeModel;
import freemarker.template.ObjectWrapper;
import freemarker.template.TemplateException;
import net.sf.jelly.apt.Context;
import net.sf.jelly.apt.decorations.declaration.DecoratedPackageDeclaration;
import net.sf.jelly.apt.util.JavaDocTagHandler;
import net.sf.jelly.apt.util.JavaDocTagHandlerFactory;
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.main.Artifact;
import org.codehaus.enunciate.main.Enunciate;
import org.codehaus.enunciate.main.FileArtifact;
import org.codehaus.enunciate.main.NamedArtifact;
import org.codehaus.enunciate.main.webapp.BaseWebAppFragment;
import org.codehaus.enunciate.main.webapp.WebAppComponent;
import org.codehaus.enunciate.modules.DocumentationAwareModule;
import org.codehaus.enunciate.modules.FacetAware;
import org.codehaus.enunciate.modules.FreemarkerDeploymentModule;
import org.codehaus.enunciate.modules.docs.config.DocsRuleSet;
import org.codehaus.enunciate.modules.docs.config.DownloadConfig;
import org.codehaus.enunciate.template.freemarker.IsDefinedGloballyMethod;
import org.codehaus.enunciate.template.freemarker.UniqueContentTypesMethod;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import org.codehaus.enunciate.main.BaseArtifact;
/**
* <h1>Documentation Module</h1>
*
* <p>The documentation deployment module is responsible for generating the documentation
* for the API. This includes both the HTML pages and any other static content, e.g. client libraries.</p>
*
* <p>The order of the documentation module is 100, essentially putting it after the generation
* of any static documents (e.g. WSDL, schemas) or static downloads (e.g. client libraries), but
* before the assembly of the war (see the spring-app module, order 200).</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>
*
* <p>The only significant steps in the documentation module are the "generate" step and the "build"
* step.</p>
*
* <h3>generate</h3>
*
* <p>During the <b>generate</b> step, the documentation deployment module generates an XML file that conforms to
* <a href="https://github.com/stoicflame/enunciate/blob/master/docs/src/main/resources/org/codehaus/enunciate/modules/docs/docs.xsd">this schema</a>
* containing all the documentation for the entire API in a structured form.</p>
*
* <h3>build</h3>
*
* <p>The build step is where all the documentation files are generated as needed and assembled into
* a single directory. The first step is to copy the static files (the "base") to the build directory.
* The documentation base can be specified in the <a href="#config">configuration</a>, or you can
* use the default base.</p>
*
* <p>Then, the documentation module generates another XML file conforming to
* <a href="https://github.com/stoicflame/enunciate/blob/master/docs/src/main/resources/org/codehaus/enunciate/modules/docs/downloads.xsd">this schema</a>
* that contains the information for the files available for download in a structured form. By default, all named artifacts
* are included as a download (see the architecture guide for details). Other downloads can also
* be specified in the <a href="#config">configuration</a>.</p>
*
* <p>Finally, the XML generated in the <tt>generate</tt> phase and the XML generated for the download files
* is processed together to create the static documentation elements. By default, a
* <a href="http://freemarker.sourceforge.net/docs/xgui.html">Freemarker Processing Template is applied</a> to
* process the XML. You can supply your own Freemarker processing template in the configuration, or you can supply a custom
* an <a href="http://www.w3.org/TR/xslt">XML Stylesheet Transformation</a> to generate your site documentation.
* <a href="https://github.com/stoicflame/enunciate/blob/master/docs/src/main/resources/org/codehaus/enunciate/modules/docs/docs.fmt">Click here</a> to see
* the default Freemarker XML Processing Template used by Enunciate, which might be helpful as a starter for you to create your own custom one.
* <a href="https://github.com/stoicflame/enunciate/blob/v1.23/docs/src/main/resources/org/codehaus/enunciate/modules/docs/doc-files/docs.xslt">Click here</a>
* for an example XSLT file.</p>
*
* <h1><a name="config">Configuration</a></h1>
*
* <p>The configuration for the documentation deployment module is specified by the "docs" element
* under the "modules" element in the Enunciate configuration file.</p>
*
* <h3>attributes</h3>
*
* <ul>
* <li>The "docsDir" attribute is the subdirectory to which the documentation will be put. The default is the root.</li>
* <li>The "<b>splashPackage</b>" attribute specifies the package that contains the documentation
* to use as the introduction to the API documentation. By default, no text is used for the
* introduction.</li>
* <li>The "<b>copyright</b>" attribute specifies the text for the copyright holder on the web
* page. By default, there is no copyright information displayed on the webpage.</li>
* <li>The "<b>title</b>" specifies the title of the generated HTML pages. By default, the title is "Web API".</li>
* <li>The "<b>includeDefaultDownloads</b>" is a boolean attribute specifying whether the default downloads should
* be included. The default is "true".</li>
* <li>The "<b>includeExampleXml</b>" is a boolean attribute specifying whether example XML should
* be included. The default is "true".</li>
* <li>The "<b>includeExampleJson</b>" is a boolean attribute specifying whether example JSON should
* be included. The default is "true" if <tt>jackson-xc</tt> is on the classpath and "false" otherwise.</li>
* <li>The "<b>css</b>" attribute is used to specify the file to be used as the cascading stylesheet for the HTML.
* If one isn't supplied, a default will be provided.</p>
* <li>The "<b>indexPageName</b>" attribute is used to specify the name of the generated index page. Default: "index.html"</li>
* <li>The "<b>xslt</b>" attribute specifies the file that is the XML Stylesheet Transform that will be applied to the
* documentation XML to generate the HTML docs. If no XSLT is specified, the freemarker XML processing template will be used
* to process the XML.</p>
* <li>The "<b>xsltURL</b>" attribute specifies the URL to the XML Stylesheet Transform that will be applied to the
* documentation XML to generate the HTML docs. If no XSLT is specified, the freemarker XML processing template will be used
* to process the XML.</p>
* <li>The "<b>freemarkerXMLProcessingTemplate</b>" attribute specifies the file that is the freemarker XML processing template
* to use to generate the site. See <a href="http://freemarker.sourceforge.net/docs/xgui.html">the Freemarker XML Processing Guide</a>.
* If none is supplied, a default one will be used.</li>
* <li>The "<b>freemarkerXMLProcessingTemplateURL</b>" attribute specifies the URL to the freemarker XML processing template
* to use to generate the site. See <a href="http://freemarker.sourceforge.net/docs/xgui.html">the Freemarker XML Processing Guide</a>.
* If none is supplied, a default one will be used.</li>
* <li>The "<b>freemarkerDocsXMLTemplate</b>" attribute specifies the file that is the freemarker template
* to use to generate the docs.xml. If none is supplied, a default one will be used.</li>
* <li>The "<b>freemarkerDocsXMLTemplateURL</b>" attribute specifies the URL to the freemarker template
* to use to generate the docs.xml. If none is supplied, a default one will be used.</li>
* <li>The "<b>base</b>" attribute specifies a gzipped file or a directory to use as the documentation base. If none is supplied,
* a default base will be provided.
* <li>The "javadocTagHandling" attribute is used to specify the handling of JavaDoc tags. It must be either "OFF" or the FQN
* of an instance of <tt>net.sf.jelly.apt.util.JavaDocTagHandler</tt></li>
* <li>The "<b>applyWsdlFilter</b>" attribute specifies whether to apply a filter for the WSDL files that will attempt to resolve
* the soap paths dynamically. Default: "true".</li>
* <li>The "<b>applyWadlFilter</b>" attribute specifies whether to apply a filter for the WADL files that will attempt to resolve
* the rest paths dynamically. Default: "true".</li>
* <li>The "<b>groupRestResources</b>" attribute specifies the name of a facet by which REST resources are grouped together. Default: "org.codehaus.enunciate.contract.jaxrs.Resource".</li>
* </ul>
*
* <h3>The "download" element</h3>
*
* <p>There can be any number of "download" elements specified. This element is used to indicate another file or Enunciate artifact
* that is to be included in the "downloads" page. The download element supports the following attributes:</p>
*
* <ul>
* <li>The "name" attribute specifies a name for the download.</li>
* <li>The "artifact" attribute specifies the id of an Enunciate artifact that is to be included as a download.</li>
* <li>The "file" attribute specifies a file on the filesystem that is to be included as a download. This attribute is
* ignored if the "artifact" attribute is set.</li>
* <li>The "description" attribute includes a description of the download. This attribute is ignored if
* the "artifact" attribute is set.</li>
* </ul>
*
* <h3>The "additional-css" element</h3>
*
* <p>You can apply additinoal css files to the generated documentation. Use the "file" attribute to specify the additional css file to apply.</p>
*
* <h3>The "facets" element</h3>
*
* <p>The "facets" element is applicable to the documentation module to configure which facets are to be included/excluded from the documentation artifacts. For
* more information, see <a href="http://docs.codehaus.org/display/ENUNCIATE/Enunciate+API+Facets">API Facets</a></p>
*
* <h1><a name="artifacts">Artifacts</a></h1>
*
* <h3>docs</h3>
*
* <p>The documentation deployment module exports only one artifact: the build directory for the documentation.
* The artifact id is "docs", and it is exported during the "build" step.</p>
*
* @author Ryan Heaton
* @docFileName module_docs.html
*/
public class DocumentationDeploymentModule extends FreemarkerDeploymentModule implements DocumentationAwareModule, EnunciateClasspathListener, FacetAware {
private String splashPackage;
private String copyright;
private String title;
private boolean includeDefaultDownloads = true;
private boolean includeExampleXml = true;
private boolean includeExampleJson = true;
private boolean includeDeprecatedFieldsInExample = true;
private boolean forceExampleJson = false;
private String xslt;
private URL xsltURL;
private String freemarkerXMLProcessingTemplate;
private URL freemarkerXMLProcessingTemplateURL;
private String freemarkerDocsXMLTemplate;
private URL freemarkerDocsXMLTemplateURL;
private String css;
private final List<String> additionalCss = new ArrayList<String>();
private String base;
private final ArrayList<DownloadConfig> downloads = new ArrayList<DownloadConfig>();
private String docsDir = null;
private String javadocTagHandling;
private boolean applyWsdlFilter = true;
private boolean applyWadlFilter = true;
private boolean jacksonXcAvailable = false;
private String indexPageName = "index.html";
private boolean disableRestMountpoint = false;
private String defaultNamespace = null;
private String groupRestResources = "org.codehaus.enunciate.contract.jaxrs.Resource";
private Set<String> facetIncludes = new TreeSet<String>();
private Set<String> facetExcludes = new TreeSet<String>(Arrays.asList("org.codehaus.enunciate.doc.ExcludeFromDocumentation"));
/**
* @return "docs"
*/
@Override
public String getName() {
return "docs";
}
/**
* @return 100
*/
@Override
public int getOrder() {
return 100;
}
public void onClassesFound(Set<String> classes) {
jacksonXcAvailable |= classes.contains("org.codehaus.jackson.xc.JaxbAnnotationIntrospector");
}
/**
* The package that contains the splash page documentation for the API.
*
* @return The package that contains the splash page documentation for the API.
*/
public String getSplashPackage() {
return splashPackage;
}
/**
* The package that contains the splash page documentation for the API.
*
* @param splashPackage The package that contains the splash page documentation for the API.
*/
public void setSplashPackage(String splashPackage) {
this.splashPackage = splashPackage;
}
/**
* The copyright (posted on the website).
*
* @return The copyright (posted on the website).
*/
public String getCopyright() {
return copyright;
}
/**
* The copyright (posted on the website).
*
* @param copyRight The copyright (posted on the website).
*/
public void setCopyright(String copyRight) {
this.copyright = copyRight;
}
/**
* The title of the documentation.
*
* @return The title of the documentation.
*/
public String getTitle() {
return title;
}
/**
* The title of the documentation.
*
* @param title The title of the documentation.
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Set the title for this project iff it hasn't already been set.
*
* @param title The title.
*/
public void setTitleConditionally(String title) {
if (this.title == null) {
this.title = title;
}
}
/**
* JavaDoc tag handing.
*
* @return The javadoc tag handling.
*/
public String getJavadocTagHandling() {
return javadocTagHandling;
}
/**
* The javadoc tag handling.
*
* @param javadocTagHandling The javadoc tag handling.
*/
public void setJavadocTagHandling(String javadocTagHandling) {
this.javadocTagHandling = javadocTagHandling;
}
/**
* Adds a download to the documentation.
*
* @param download The download to add.
*/
public void addDownload(DownloadConfig download) {
this.downloads.add(download);
}
/**
* The configured list of downloads to add to the documentation.
*
* @return The configured list of downloads to add to the documentation.
*/
public Collection<DownloadConfig> getDownloads() {
return downloads;
}
/**
* Adds an additional css file to the generated documentation.
*
* @param additionalCss The additional css.
*/
public void addAdditionalCss(String additionalCss) {
this.additionalCss.add(additionalCss);
}
/**
* The additional css files.
*
* @return The additional css files.
*/
public List<String> getAdditionalCss() {
return additionalCss;
}
/**
* Whether to include the default downloads (named artifacts) in the downloads section.
*
* @return Whether to include the default downloads (named artifacts) in the downloads section.
*/
public boolean isIncludeDefaultDownloads() {
return includeDefaultDownloads;
}
/**
* Whether to include the default downloads (named artifacts) in the downloads section.
*
* @param includeDefaultDownloads Whether to include the default downloads (named artifacts) in the downloads section.
*/
public void setIncludeDefaultDownloads(boolean includeDefaultDownloads) {
this.includeDefaultDownloads = includeDefaultDownloads;
}
/**
* Whether to include example XML in the documentation.
*
* @return Whether to include example XML in the documentation.
*/
public boolean isIncludeExampleXml() {
return includeExampleXml;
}
/**
* Whether to include example XML in the documentation.
*
* @param includeExampleXml Whether to include example XML in the documentation.
*/
public void setIncludeExampleXml(boolean includeExampleXml) {
this.includeExampleXml = includeExampleXml;
}
/**
* Whether to include example JSON in the documentation.
*
* @return Whether to include example JSON in the documentation.
*/
public boolean isIncludeExampleJson() {
return includeExampleJson;
}
/**
* Whether to include example JSON in the documentation.
*
* @param includeExampleJson Whether to include example JSON in the documentation.
*/
public void setIncludeExampleJson(boolean includeExampleJson) {
this.includeExampleJson = includeExampleJson;
}
/**
* Whether to include deprecated fields in example JSON and example XML.
*
* @return Whether to include deprecated fields in example JSON and example XML.
*/
public boolean isIncludeDeprecatedFieldsInExample() {
return includeDeprecatedFieldsInExample;
}
/**
* Whether to include deprecated fields in example JSON and example XML.
*
* @param includeDeprecatedFieldsInExample Whether to include deprecated fields in example JSON and example XML.
*/
public void setIncludeDeprecatedFieldsInExample(boolean includeDeprecatedFieldsInExample) {
this.includeDeprecatedFieldsInExample = includeDeprecatedFieldsInExample;
}
/**
* Whether to force example JSON. Default is to only include the example JSON only if Jackson is available on the classpath.
*
* @return Whether to force example JSON.
*/
public boolean isForceExampleJson() {
return forceExampleJson;
}
/**
* Whether to force example JSON.
*
* @param forceExampleJson Whether to force example JSON.
*/
public void setForceExampleJson(boolean forceExampleJson) {
this.forceExampleJson = forceExampleJson;
}
/**
* The stylesheet to use to generate the documentation.
*
* @param xslt The stylesheet to use to generate the documentation.
*/
public void setXslt(String xslt) throws MalformedURLException {
this.xslt = xslt;
}
/**
* Set the url to the xslt to use to generate the documentation.
*
* @param xslt The xslt.
*/
public void setXsltURL(URL xslt) {
this.xsltURL = xslt;
}
/**
* The Freemarker XML processing template file that will be used to transforms the docs.xml to the site documentation. For more information,
* see http://freemarker.sourceforge.net/docs/xgui.html.
*
* @param freemarkerProcessingTemplate The Freemarker XML processing template file that will be used to transforms the docs.xml to the site documentation.
*/
public void setFreemarkerXMLProcessingTemplate(String freemarkerProcessingTemplate) throws MalformedURLException {
this.freemarkerXMLProcessingTemplate = freemarkerProcessingTemplate;
}
/**
* The url to the freemarker XML processing template that will be used to transforms the docs.xml to the site documentation. For more
* information, see http://freemarker.sourceforge.net/docs/xgui.html
*
* @return The url to the freemarker XML processing template.
*/
public URL getFreemarkerXMLProcessingTemplateURL() {
return freemarkerXMLProcessingTemplateURL;
}
/**
* The url to the freemarker XML processing template that will be used to transforms the docs.xml to the site documentation.
*
* @param freemarkerXMLProcessingTemplateURL The url to the freemarker XML processing template.
*/
public void setFreemarkerXMLProcessingTemplateURL(URL freemarkerXMLProcessingTemplateURL) {
this.freemarkerXMLProcessingTemplateURL = freemarkerXMLProcessingTemplateURL;
}
public String getFreemarkerDocsXMLTemplate() {
return freemarkerDocsXMLTemplate;
}
public void setFreemarkerDocsXMLTemplate(String freemarkerXmlTemplate) throws MalformedURLException {
this.freemarkerDocsXMLTemplate = freemarkerXmlTemplate;
}
public URL getFreemarkerDocsXMLTemplateURL() {
return freemarkerDocsXMLTemplateURL;
}
public void setFreemarkerDocsXMLTemplateURL(URL freemarkerDOCSXmlTemplateURL) {
this.freemarkerDocsXMLTemplateURL = freemarkerDOCSXmlTemplateURL;
}
/**
* The cascading stylesheet to use instead of the default. This is ignored if the 'base' is also set.
*
* @return The cascading stylesheet to use.
*/
public String getCss() {
return css;
}
/**
* The cascading stylesheet to use instead of the default. This is ignored if the 'base' is also set.
*
* @param css The cascading stylesheet to use instead of the default.
*/
public void setCss(String css) {
this.css = css;
}
/**
* The documentation "base". The documentation base is the initial contents of the directory
* where the documentation will be output. Can be a zip file or a directory.
*
* @return The documentation "base".
*/
public String getBase() {
return base;
}
/**
* The documentation "base".
*
* @param base The documentation "base".
*/
public void setBase(String base) {
this.base = base;
}
/**
* The URL to the Freemarker template for processing the base documentation xml file.
*
* @return The URL to the Freemarker template for processing the base documentation xml file.
*/
protected URL getDocsTemplateURL() throws MalformedURLException {
URL freemarkerProcessingTemplateURL = this.freemarkerDocsXMLTemplateURL;
if (freemarkerProcessingTemplateURL == null) {
if (this.freemarkerDocsXMLTemplate != null) {
freemarkerProcessingTemplateURL = getEnunciate().resolvePath(this.freemarkerDocsXMLTemplate).toURI().toURL();
}
else {
freemarkerProcessingTemplateURL = DocumentationDeploymentModule.class.getResource("docs.xml.fmt");
}
}
return freemarkerProcessingTemplateURL;
}
/**
* The URL to the Freemarker template for processing the downloads xml file.
*
* @return The URL to the Freemarker template for processing the downloads xml file.
*/
protected URL getDownloadsTemplateURL() {
return DocumentationDeploymentModule.class.getResource("downloads.xml.fmt");
}
/**
* The subdirectory in the web application where the documentation will be put.
*
* @return The subdirectory in the web application where the documentation will be put.
*/
public String getDocsDir() {
return docsDir;
}
/**
* The subdirectory in the web application where the documentation will be put.
*
* @param docsDir The subdirectory in the web application where the documentation will be put.
*/
public void setDocsDir(String docsDir) {
this.docsDir = docsDir;
}
/**
* Whether to apply a filter for the WSDL files that will attempt to resolve the soap paths dynamically.
*
* @return Whether to apply a filter for the WSDL files that will attempt to resolve the soap paths dynamically.
*/
public boolean isApplyWsdlFilter() {
return applyWsdlFilter;
}
/**
* Whether to apply a filter for the WSDL files that will attempt to resolve the soap paths dynamically.
*
* @param applyWsdlFilter Whether to apply a filter for the WSDL files that will attempt to resolve the soap paths dynamically.
*/
public void setApplyWsdlFilter(boolean applyWsdlFilter) {
this.applyWsdlFilter = applyWsdlFilter;
}
/**
* Whether to apply a filter for the WADL files that will attempt to resolve the paths dynamically.
*
* @return Whether to apply a filter for the WADL files that will attempt to resolve the paths dynamically.
*/
public boolean isApplyWadlFilter() {
return applyWadlFilter;
}
/**
* Whether to apply a filter for the WADL files that will attempt to resolve the paths dynamically.
*
* @param applyWadlFilter Whether to apply a filter for the WADL files that will attempt to resolve the paths dynamically.
*/
public void setApplyWadlFilter(boolean applyWadlFilter) {
this.applyWadlFilter = applyWadlFilter;
}
/**
* The name of the index page.
*
* @return The name of the index page.
*/
public String getIndexPageName() {
return indexPageName;
}
/**
* The name of the index page.
*
* @param indexPageName The name of the index page.
*/
public void setIndexPageName(String indexPageName) {
this.indexPageName = indexPageName;
}
/**
* Whether to disable the REST mountpoint documentation.
*
* @return Whether to disable the REST mountpoint documentation.
*/
public boolean isDisableRestMountpoint() {
return disableRestMountpoint;
}
/**
* Whether to disable the REST mountpoint documentation.
*
* @param disableRestMountpoint Whether to disable the REST mountpoint documentation.
*/
public void setDisableRestMountpoint(boolean disableRestMountpoint) {
this.disableRestMountpoint = disableRestMountpoint;
}
/**
* The default namespace for the purposes of generating documentation.
*
* @return The default namespace for the purposes of generating documentation.
*/
public String getDefaultNamespace() {
return defaultNamespace;
}
/**
* The default namespace for the purposes of generating documentation.
*
* @param defaultNamespace The default namespace for the purposes of generating documentation.
*/
public void setDefaultNamespace(String defaultNamespace) {
this.defaultNamespace = defaultNamespace;
}
/**
* How to group the REST resources together.
*
* @return How to group the REST resources together.
*/
public String getGroupRestResources() {
return groupRestResources;
}
/**
* How to group the REST resources together.
*
* @param groupRestResources How to group the REST resources together.
*/
public void setGroupRestResources(String groupRestResources) {
if ("false".equalsIgnoreCase(groupRestResources) || "byPath".equalsIgnoreCase(groupRestResources)) {
groupRestResources = null;
}
else if ("byDocumentationGroup".equalsIgnoreCase(groupRestResources)) {
groupRestResources = "org.codehaus.enunciate.doc.DocumentationGroup";
}
this.groupRestResources = groupRestResources;
}
/**
* The directory into which the documentation is put.
*
* @return The directory into which the documentation is put.
*/
public File getDocsBuildDir() {
File docsDir = getBuildDir();
if (getDocsDir() != null) {
docsDir = new File(docsDir, getDocsDir());
}
return docsDir;
}
@Override
public void init(Enunciate enunciate) throws EnunciateException {
super.init(enunciate);
//some application components might want to reference their documentation, so we'll put a reference to the configured docs dir.
enunciate.setProperty("docs.webapp.dir", getDocsDir());
if (getJavadocTagHandling() == null) {
JavaDocTagHandlerFactory.setTagHandler(new DocumentationJavaDocTagHandler());
}
else if (!"OFF".equalsIgnoreCase(getJavadocTagHandling())) {
try {
JavaDocTagHandlerFactory.setTagHandler((JavaDocTagHandler) Class.forName(getJavadocTagHandling()).newInstance());
}
catch (Throwable e) {
throw new EnunciateException(e);
}
}
}
@Override
public void initModel(EnunciateFreemarkerModel model) {
super.initModel(model);
if (!getModelInternal().getNamespacesToWSDLs().isEmpty()) {
String docsDir = getDocsDir() == null ? "" : getDocsDir();
if (!docsDir.startsWith("/")) {
docsDir = "/" + docsDir;
}
while (docsDir.endsWith("/")) {
docsDir = docsDir.substring(0, docsDir.length() - 1);
}
for (WsdlInfo wsdlInfo : getModelInternal().getNamespacesToWSDLs().values()) {
Object filename = wsdlInfo.getProperty("filename");
if (filename != null) {
wsdlInfo.setProperty("redirectLocation", docsDir + "/" + filename);
}
}
}
}
/**
* The generate logic builds the XML documentation structure for the enunciated API.
*/
public void doFreemarkerGenerate() throws EnunciateException, IOException, TemplateException {
if (!getEnunciate().isUpToDateWithSources(getGenerateDir())) {
EnunciateFreemarkerModel model = getModel();
if (this.splashPackage != null) {
PackageDeclaration packageDeclaration = Context.getCurrentEnvironment().getPackage(this.splashPackage);
if (packageDeclaration != null) {
debug("Including documentation for package %s as the splash documentation.", this.splashPackage);
model.setVariable("apiDoc", new DecoratedPackageDeclaration(packageDeclaration).getJavaDoc());
}
else {
warn("Splash package %s not found. No splash documentation included.", this.splashPackage);
}
}
if (this.copyright != null) {
debug("Documentation copyright: %s", this.copyright);
model.setVariable("copyright", this.copyright);
}
String title = this.title;
if (title == null) {
title = "Web API";
}
debug("Documentation title: %s", title);
model.setVariable("title", title);
model.setVariable("uniqueContentTypes", new UniqueContentTypesMethod());
model.setVariable("schemaForNamespace", new SchemaForNamespaceMethod(model.getNamespacesToSchemas()));
model.setVariable(JsonSchemaForType.NAME, new JsonSchemaForType(model));
model.setVariable(JsonTypeNameForQualifiedName.NAME, new JsonTypeNameForQualifiedName(model));
model.put("isDefinedGlobally", new IsDefinedGloballyMethod());
model.put("includeExampleXml", isIncludeExampleXml());
model.put("generateExampleXml", new GenerateExampleXmlMethod(getDefaultNamespace(), model, isIncludeDeprecatedFieldsInExample()));
model.put("includeExampleJson", (forceExampleJson || (jacksonXcAvailable && isIncludeExampleJson())));
model.put("generateExampleJson", new GenerateExampleJsonMethod(model, isIncludeDeprecatedFieldsInExample()));
processTemplate(getDocsTemplateURL(), model);
}
else {
info("Skipping documentation source generation as everything appears up-to-date...");
}
}
@Override
protected void doBuild() throws EnunciateException, IOException {
if (!getEnunciate().isUpToDateWithSources(getBuildDir())) {
buildBase();
generateDownloadsXML();
doXmlTransform();
}
else {
info("Skipping build of documentation as everything appears up-to-date...");
}
//export the generated documentation as an artifact.
getEnunciate().addArtifact(new FileArtifact(getName(), "docs", getDocsBuildDir()));
//add the webapp fragment...
BaseWebAppFragment webAppFragment = new BaseWebAppFragment(getName());
webAppFragment.setBaseDir(getBuildDir());
TreeMap<String, String> mimeMappings = new TreeMap<String, String>();
mimeMappings.put("wsdl", "text/xml");
mimeMappings.put("xsd", "text/xml");
webAppFragment.setMimeMappings(mimeMappings);
ArrayList<WebAppComponent> filters = new ArrayList<WebAppComponent>();
if (isApplyWsdlFilter() && !getModelInternal().getNamespacesToWSDLs().isEmpty()) {
WebAppComponent wsdlFilter = new WebAppComponent();
wsdlFilter.setName("wsdl-filter");
wsdlFilter.setClassname("org.codehaus.enunciate.webapp.IDLFilter");
HashMap<String, String> initParams = new HashMap<String, String>();
initParams.put("assumed-base-address", getModel().getBaseDeploymentAddress());
initParams.put("match-prefix", ":address location=\"");
wsdlFilter.setInitParams(initParams);
TreeSet<String> wsdls = new TreeSet<String>();
for (WsdlInfo wsdlInfo : getModelInternal().getNamespacesToWSDLs().values()) {
String wsdlLocation = (String) wsdlInfo.getProperty("redirectLocation");
if (wsdlLocation != null) {
wsdls.add(wsdlLocation);
}
}
wsdlFilter.setUrlMappings(wsdls);
filters.add(wsdlFilter);
}
if (isApplyWadlFilter() && getModelInternal().getWadlFile() != null) {
WebAppComponent wadlFilter = new WebAppComponent();
wadlFilter.setName("wadl-filter");
wadlFilter.setClassname("org.codehaus.enunciate.webapp.IDLFilter");
HashMap<String, String> initParams = new HashMap<String, String>();
initParams.put("assumed-base-address", getModel().getBaseDeploymentAddress());
initParams.put("match-prefix", ":resources base=\"");
wadlFilter.setInitParams(initParams);
TreeSet<String> wadls = new TreeSet<String>();
String docsDir = getDocsDir() == null ? "" : getDocsDir();
if (!docsDir.startsWith("/")) {
docsDir = "/" + docsDir;
}
while (docsDir.endsWith("/")) {
docsDir = docsDir.substring(0, docsDir.length() - 1);
}
wadls.add(docsDir + "/" + getModelInternal().getWadlFile().getName());
wadlFilter.setUrlMappings(wadls);
filters.add(wadlFilter);
}
webAppFragment.setFilters(filters);
getEnunciate().addWebAppFragment(webAppFragment);
}
/**
* Generates the downloads xml indicating the available downloads.
*/
protected void generateDownloadsXML() throws IOException, EnunciateException {
EnunciateFreemarkerModel model = getModel();
model.put("defaultDate", new Date());
try {
processTemplate(getDownloadsTemplateURL(), model);
}
catch (TemplateException e) {
//there's something wrong with the template.
throw new EnunciateException(e);
}
}
/**
* Builds the base output directory.
*/
protected void buildBase() throws IOException {
Enunciate enunciate = getEnunciate();
File buildDir = getDocsBuildDir();
buildDir.mkdirs();
if (this.base == null) {
InputStream discoveredBase = DocumentationDeploymentModule.class.getResourceAsStream("/META-INF/enunciate/docs-base.zip");
if (discoveredBase == null) {
debug("Default base to be used for documentation base.");
enunciate.extractBase(loadDefaultBase(), buildDir);
URL discoveredCss = DocumentationDeploymentModule.class.getResource("/META-INF/enunciate/css/style.css");
if (discoveredCss != null) {
enunciate.copyResource(discoveredCss, new File(new File(buildDir, "css"), "style.css"));
}
else if (this.css != null) {
enunciate.copyFile(enunciate.resolvePath(this.css), new File(new File(buildDir, "css"), "style.css"));
}
}
else {
debug("Discovered documentation base at /META-INF/enunciate/docs-base.zip");
enunciate.extractBase(discoveredBase, buildDir);
}
}
else {
File baseFile = enunciate.resolvePath(this.base);
if (baseFile.isDirectory()) {
debug("Directory %s to be used as the documentation base.", baseFile);
enunciate.copyDir(baseFile, buildDir);
}
else {
debug("Zip file %s to be extracted as the documentation base.", baseFile);
enunciate.extractBase(new FileInputStream(baseFile), buildDir);
}
}
for (SchemaInfo schemaInfo : getModel().getNamespacesToSchemas().values()) {
if (schemaInfo.getProperty("file") != null) {
File from = (File) schemaInfo.getProperty("file");
String filename = schemaInfo.getProperty("filename") != null ? (String) schemaInfo.getProperty("filename") : from.getName();
File to = new File(getDocsBuildDir(), filename);
enunciate.copyFile(from, to);
}
}
for (WsdlInfo wsdlInfo : getModel().getNamespacesToWSDLs().values()) {
if (wsdlInfo.getProperty("file") != null) {
File from = (File) wsdlInfo.getProperty("file");
String filename = wsdlInfo.getProperty("filename") != null ? (String) wsdlInfo.getProperty("filename") : from.getName();
File to = new File(getDocsBuildDir(), filename);
enunciate.copyFile(from, to);
}
}
File wadlFile = getModelInternal().getWadlFile();
if (wadlFile != null) {
enunciate.copyFile(wadlFile, new File(getDocsBuildDir(), wadlFile.getName()));
}
HashSet<String> explicitArtifacts = new HashSet<String>();
TreeSet<Artifact> downloads = new TreeSet<Artifact>();
for (DownloadConfig download : this.downloads) {
if (download.getArtifact() != null) {
explicitArtifacts.add(download.getArtifact());
}
else if (download.getFile() != null) {
File downloadFile = enunciate.resolvePath(download.getFile());
debug("File %s to be added as an extra download.", downloadFile.getAbsolutePath());
DownloadBundle downloadArtifact = new DownloadBundle(getName(), downloadFile.getName(), downloadFile);
if (download.getName() != null) {
downloadArtifact.setName(download.getName());
}
if (download.getDescription() != null) {
downloadArtifact.setDescription(download.getDescription());
}
if(download.getShowLink().equals("false")){
debug("Exporting %s to directory %s.", downloadArtifact.getId(), buildDir);
downloadArtifact.exportTo(buildDir, enunciate);
} else {
downloads.add(downloadArtifact);
}
}
}
for (Artifact artifact : enunciate.getArtifacts()) {
if (((artifact instanceof NamedArtifact) && (includeDefaultDownloads)) || (explicitArtifacts.contains(artifact.getId()))) {
if (artifact.isPublic()) {
downloads.add(artifact);
}
debug("Artifact %s to be added as an extra download.", artifact.getId());
explicitArtifacts.remove(artifact.getId());
}
}
if (explicitArtifacts.size() > 0) {
for (String artifactId : explicitArtifacts) {
warn("WARNING: Unknown artifact '%s'. Will not be available for download.", artifactId);
}
}
for (Artifact download : downloads) {
debug("Exporting %s to directory %s.", download.getId(), buildDir);
download.exportTo(buildDir, enunciate);
}
Set<String> additionalCssFiles = new HashSet<String>();
for (String additionalCss : getAdditionalCss()) {
File additionalCssFile = enunciate.resolvePath(additionalCss);
debug("File %s to be added as an additional css file.", additionalCss);
enunciate.copyFile(additionalCssFile, new File(buildDir, additionalCssFile.getName()));
additionalCssFiles.add(additionalCssFile.getName());
}
EnunciateFreemarkerModel model = getModel();
model.put("downloads", downloads);
model.put("additionalCssFiles", additionalCssFiles);
}
/**
* Do the XSLT tranformation to generate the documentation.
*/
protected void doXmlTransform() throws IOException, EnunciateException {
debug("Executing XML transformation.");
URL xsltURL = this.xsltURL;
if (xsltURL == null) {
if (this.xslt != null) {
xsltURL = getEnunciate().resolvePath(this.xslt).toURI().toURL();
}
else {
xsltURL = DocumentationDeploymentModule.class.getResource("/META-INF/enunciate/docs.xslt");
}
}
URL freemarkerXMLProcessingTemplateURL = this.freemarkerXMLProcessingTemplateURL;
if (freemarkerXMLProcessingTemplateURL == null) {
if (this.freemarkerXMLProcessingTemplate != null) {
freemarkerXMLProcessingTemplateURL = getEnunciate().resolvePath(this.freemarkerXMLProcessingTemplate).toURI().toURL();
}
else {
freemarkerXMLProcessingTemplateURL = DocumentationDeploymentModule.class.getResource("/META-INF/enunciate/docs.fmt");
}
}
if (xsltURL == null && freemarkerXMLProcessingTemplateURL == null) {
freemarkerXMLProcessingTemplateURL = DocumentationDeploymentModule.class.getResource("docs.fmt");
}
if (xsltURL != null) {
doXSLT(xsltURL);
}
else {
doFreemarkerXMLProcessing(freemarkerXMLProcessingTemplateURL);
}
}
protected void doFreemarkerXMLProcessing(URL freemarkerXMLProcessingTemplateURL) throws IOException, EnunciateException {
debug("Using freemarker XML processing template %s", freemarkerXMLProcessingTemplateURL);
EnunciateFreemarkerModel model = getModel();
File docsXml = new File(getGenerateDir(), "docs.xml");
model.put("docsxml", loadNodeModel(docsXml));
File downloadsXml = new File(getGenerateDir(), "downloads.xml");
if (downloadsXml.exists()) {
model.put("downloadsxml", loadNodeModel(downloadsXml));
}
File buildDir = getDocsBuildDir();
buildDir.mkdirs();
model.setFileOutputDirectory(buildDir);
model.put("apiRelativePath", getRelativePathToRootDir());
model.put("indexPageName", getIndexPageName());
model.put("disableRestMountpoint", isDisableRestMountpoint());
model.put("groupRestResources", getGroupRestResources());
model.put("additionalCss", getAdditionalCss());
try {
processTemplate(freemarkerXMLProcessingTemplateURL, model);
}
catch (TemplateException e) {
throw new EnunciateException(e);
}
}
private NodeModel loadNodeModel(File xml) throws EnunciateException {
Document doc;
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(false);
builderFactory.setValidating(false);
DocumentBuilder builder = builderFactory.newDocumentBuilder();
builder.setEntityResolver(new EntityResolver() {
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
//we don't want to validate or parse external dtds...
return new InputSource(new StringReader(""));
}
});
doc = builder.parse(new FileInputStream(xml));
}
catch (Exception e) {
throw new EnunciateException("Error parsing " + xml, e);
}
NodeModel.simplify(doc);
return NodeModel.wrap(doc.getDocumentElement());
}
protected void doXSLT(URL xsltURL) throws IOException, EnunciateException {
debug("Using stylesheet %s", xsltURL);
StreamSource source = new StreamSource(xsltURL.openStream());
try {
Transformer transformer = SAXTransformerFactory.newInstance().newTransformer(source);
transformer.setURIResolver(new URIResolver() {
public Source resolve(String href, String base) throws TransformerException {
return new StreamSource(new File(getGenerateDir(), href));
}
});
transformer.setParameter("downloads-exists", new File(getGenerateDir(), "downloads.xml").exists());
debug("Extra downloads exist: %b", transformer.getParameter("downloads-exists"));
File docsXml = new File(getGenerateDir(), "docs.xml");
File buildDir = getDocsBuildDir();
buildDir.mkdirs();
transformer.setParameter("output-dir", buildDir.getAbsolutePath() + File.separator);
transformer.setParameter("api-relative-path", getRelativePathToRootDir());
transformer.setParameter("index-page-name", getIndexPageName());
transformer.setParameter("disable-rest-mountpoint", isDisableRestMountpoint());
transformer.setParameter("group-rest-resources", getGroupRestResources());
File indexPage = new File(buildDir, getIndexPageName());
debug("Transforming %s to %s.", docsXml, indexPage);
transformer.transform(new StreamSource(docsXml), new StreamResult(indexPage));
}
catch (TransformerException e) {
throw new EnunciateException("Error during transformation of the documentation (stylesheet " + xsltURL +
", document " + new File(getGenerateDir(), "docs.xml") + ")", e);
}
}
/**
* Get the relative path to the root directory from the docs directory.
*
* @return the relative path to the root directory.
*/
protected String getRelativePathToRootDir() {
String relativePath = ".";
String docsDir = getDocsDir();
if (docsDir != null) {
StringBuilder builder = new StringBuilder();
StringTokenizer pathTokens = new StringTokenizer(docsDir.replace(File.separatorChar, '/'), "/");
if (pathTokens.hasMoreTokens()) {
while (pathTokens.hasMoreTokens()) {
builder.append("..");
pathTokens.nextToken();
if (pathTokens.hasMoreTokens()) {
builder.append('/');
}
}
}
else {
builder.append('.');
}
relativePath = builder.toString();
}
return relativePath;
}
/**
* 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);
}
}
/**
* Loads the default base for the documentation.
*
* @return The default base for the documentation.
*/
protected InputStream loadDefaultBase() {
return DocumentationDeploymentModule.class.getResourceAsStream("/docs.base.zip");
}
@Override
protected ObjectWrapper getObjectWrapper() {
return new DocumentationObjectWrapper();
}
@Override
public RuleSet getConfigurationRules() {
return new DocsRuleSet();
}
}