/** * Copyright © 2006-2016 Web Cohesion (info@webcohesion.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.webcohesion.enunciate.mojo; /* * Copyright 2001-2005 The Apache Software Foundation. * * 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. */ import com.webcohesion.enunciate.Enunciate; import com.webcohesion.enunciate.EnunciateConfiguration; import com.webcohesion.enunciate.EnunciateException; import com.webcohesion.enunciate.EnunciateLogger; import com.webcohesion.enunciate.module.EnunciateModule; import com.webcohesion.enunciate.module.ProjectExtensionModule; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Contributor; import org.apache.maven.model.License; import org.apache.maven.model.Plugin; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.*; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.shared.filtering.MavenFileFilter; import org.apache.maven.shared.filtering.MavenFilteringException; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.xml.sax.SAXException; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; /** * Goal which initializes an Enunciate build process. */ @SuppressWarnings ( "unchecked" ) @Mojo ( name = "config", defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME ) public class ConfigMojo extends AbstractMojo { public static final String ENUNCIATE_PROPERTY = "com.webcohesion.enunciate.mojo.ConfigMojo#ENUNCIATE_PROPERTY"; /** * List of all supported packaging types */ private static final List<String> SUPPORTED_TYPES = Arrays.asList("jar", "bundle", "eclipse-plugin", "ejb", "ejb-client"); @Component protected MavenProjectHelper projectHelper; @Component protected MavenFileFilter configFilter; @Component protected ArtifactFactory artifactFactory; @Component protected ArtifactResolver artifactResolver; @Parameter ( defaultValue = "${project}", required = true, readonly = true ) protected MavenProject project; @Parameter ( defaultValue = "${plugin.artifacts}", required = true, readonly = true ) protected Collection<org.apache.maven.artifact.Artifact> pluginDependencies; @Parameter ( defaultValue = "${session}", required = true, readonly = true ) protected MavenSession session; @Parameter ( defaultValue = "${localRepository}", required = true, readonly = true ) protected ArtifactRepository localRepository; @Parameter ( defaultValue = "${project.build.directory}", required = true ) protected File exportsDir = null; /** * The enunciate artifacts. */ @Parameter protected Artifact[] artifacts; /** * The enunciate configuration file. */ @Parameter protected File configFile = null; /** * The build directory for Enunciate. */ @Parameter ( defaultValue = "${project.build.directory}/enunciate", property = "enunciate.build.directory" ) protected File buildDir = null; /** * The place where classes are compiled for the current project. Enunciate needs to know this to include the classes on the classpath. */ @Parameter ( defaultValue = "${project.build.outputDirectory}" ) protected File outputDir = null; /** * The Enunciate exports. */ @Parameter protected Map<String, String> exports = new HashMap<String, String>(); /** * The include patterns. */ @Parameter protected String[] includes; /** * The exclude patterns. */ @Parameter protected String[] excludes; /** * The modules to include as project extensions. */ @Parameter ( name = "project-extensions" ) protected String[] projectExtensions; /** * List of compiler arguments. */ @Parameter protected String[] compilerArgs; /** * Compiler -source version parameter */ @Parameter ( property = "maven.compiler.source" ) private String source = null; /** * Compiler -target version parameter */ @Parameter ( property = "maven.compiler.target" ) private String target = null; /** * The -encoding argument for the Java compiler */ @Parameter ( property = "encoding", defaultValue = "${project.build.sourceEncoding}" ) private String encoding = null; /** * A flag used to disable enunciate. This is primarily intended for usage from the command line to occasionally adjust the build. */ @Parameter ( defaultValue = "false", property = "enunciate.skip" ) protected boolean skipEnunciate; /** * A flag used to disable the sourcepath. This may be the quickest and dirtiest way to bypass the infamous "Java compiler crashed" errors. */ @Parameter ( defaultValue = "false", property = "enunciate.disable.sourcepath" ) protected boolean disableSourcepath = false; /** * The list of dependencies on which Enunciate should attempt to lookup their sources for inclusion in the source path. * By default, dependencies with the same groupId as the current project will be included. */ @Parameter ( name = "sourcepath-includes" ) protected DependencySourceSpec[] sourcepathIncludes; /** * The list of dependencies on which Enunciate should NOT attempt to lookup their sources for inclusion in the source path. * By default, dependencies that do _not_ have the same groupId as the current project will be excluded. */ @Parameter ( name = "sourcepath-excludes" ) protected DependencySourceSpec[] sourcepathExcludes; /** * The list of source directories on which to invoke Enunciate. By default, Enunciate will use the project compiler source directories. */ @Parameter ( name = "sources" ) protected String[] sources; public void execute() throws MojoExecutionException { if (skipEnunciate) { getLog().info("[ENUNCIATE] Skipping enunciate per configuration."); return; } Enunciate enunciate = new Enunciate(); //set up the logger. enunciate.setLogger(new MavenEnunciateLogger()); //set the build dir. enunciate.setBuildDir(this.buildDir); //load the config. EnunciateConfiguration config = enunciate.getConfiguration(); File configFile = this.configFile; if (configFile == null) { configFile = new File(project.getBasedir(), "enunciate.xml"); } if (configFile.exists()) { getLog().info("[ENUNCIATE] Using enunciate configuration at " + configFile.getAbsolutePath()); try { loadConfig(enunciate, configFile); config.setBase(configFile.getParentFile()); } catch (Exception e) { throw new MojoExecutionException("Problem with enunciate config file " + configFile, e); } } //set the default configured label. config.setDefaultSlug(project.getArtifactId()); if (project.getName() != null && !"".equals(project.getName().trim())) { StringBuilder description = new StringBuilder("<h1>").append(project.getName()).append("</h1>"); config.setDefaultTitle(project.getName()); if (project.getDescription() != null && !"".equals(project.getDescription().trim())) { description.append("<p>").append(project.getDescription()).append("</p>"); } config.setDefaultDescription(description.toString()); } if (project.getVersion() != null && !"".equals(project.getVersion().trim())) { config.setDefaultVersion(project.getVersion()); } List contributors = project.getContributors(); if (contributors != null && !contributors.isEmpty()) { List<EnunciateConfiguration.Contact> contacts = new ArrayList<EnunciateConfiguration.Contact>(contributors.size()); for (Object c : contributors) { Contributor contributor = (Contributor) c; contacts.add(new EnunciateConfiguration.Contact(contributor.getName(), contributor.getUrl(), contributor.getEmail())); } config.setDefaultContacts(contacts); } List licenses = project.getLicenses(); if (licenses != null && !licenses.isEmpty()) { License license = (License) licenses.get(0); config.setDefaultApiLicense(new EnunciateConfiguration.License(license.getName(), license.getUrl(), null, null)); } //set the class paths. setClasspathAndSourcepath(enunciate); //load any modules on the classpath. List<URL> pluginClasspath = buildPluginClasspath(); ServiceLoader<EnunciateModule> moduleLoader = ServiceLoader.load(EnunciateModule.class, new URLClassLoader(pluginClasspath.toArray(new URL[pluginClasspath.size()]), Thread.currentThread().getContextClassLoader())); for (EnunciateModule module : moduleLoader) { enunciate.addModule(module); } //set the compiler arguments. List<String> compilerArgs = new ArrayList<String>(); String sourceVersion = findSourceVersion(); if (sourceVersion != null) { compilerArgs.add("-source"); compilerArgs.add(sourceVersion); } String targetVersion = findTargetVersion(); if (targetVersion != null) { compilerArgs.add("-target"); compilerArgs.add(targetVersion); } String sourceEncoding = this.encoding; if (sourceEncoding != null) { compilerArgs.add("-encoding"); compilerArgs.add(sourceEncoding); } if (this.compilerArgs != null) { compilerArgs.addAll(Arrays.asList(this.compilerArgs)); } enunciate.getCompilerArgs().addAll(compilerArgs); //includes. if (this.includes != null) { for (String include : this.includes) { enunciate.addInclude(include); } } //excludes. if (this.excludes != null) { for (String exclude : this.excludes) { enunciate.addExclude(exclude); } } //exports. if (this.exports != null) { for (String exportId : this.exports.keySet()) { String filename = this.exports.get(exportId); if (filename == null || "".equals(filename)) { throw new MojoExecutionException("Invalid (empty or null) filename for export " + exportId + "."); } File exportFile = new File(filename); if (!exportFile.isAbsolute()) { exportFile = new File(this.exportsDir, filename); } enunciate.addExport(exportId, exportFile); } } Set<String> enunciateAddedSourceDirs = new TreeSet<String>(); List<EnunciateModule> modules = enunciate.getModules(); if (modules != null) { Set<String> projectExtensions = new TreeSet<String>(this.projectExtensions == null ? Collections.<String>emptyList() : Arrays.asList(this.projectExtensions)); for (EnunciateModule module : modules) { //configure the project with the module project extensions. if (projectExtensions.contains(module.getName()) && module instanceof ProjectExtensionModule) { ProjectExtensionModule extensions = (ProjectExtensionModule) module; for (File projectSource : extensions.getProjectSources()) { String sourceDir = projectSource.getAbsolutePath(); enunciateAddedSourceDirs.add(sourceDir); if (!project.getCompileSourceRoots().contains(sourceDir)) { getLog().debug("[ENUNCIATE] Adding '" + sourceDir + "' to the compile source roots."); project.addCompileSourceRoot(sourceDir); } } for (File testSource : extensions.getProjectTestSources()) { project.addTestCompileSourceRoot(testSource.getAbsolutePath()); } for (File resourceDir : extensions.getProjectResourceDirectories()) { Resource restResource = new Resource(); restResource.setDirectory(resourceDir.getAbsolutePath()); project.addResource(restResource); } for (File resourceDir : extensions.getProjectTestResourceDirectories()) { Resource resource = new Resource(); resource.setDirectory(resourceDir.getAbsolutePath()); project.addTestResource(resource); } } applyAdditionalConfiguration(module); } } //add any new source directories to the project. Set<File> sourceDirs = new HashSet<File>(); Collection<String> sourcePaths = this.sources == null || this.sources.length == 0 ? (Collection<String>) project.getCompileSourceRoots() : Arrays.asList(this.sources); for (String sourcePath : sourcePaths) { File sourceDir = new File(sourcePath); if (!enunciateAddedSourceDirs.contains(sourceDir.getAbsolutePath())) { sourceDirs.add(sourceDir); } else { getLog().info("[ENUNCIATE] " + sourceDir + " appears to be added to the source roots by Enunciate. Excluding from original source roots...."); } } for (File sourceDir : sourceDirs) { enunciate.addSourceDir(sourceDir); } postProcessConfig(enunciate); try { enunciate.run(); } catch (Exception e) { Throwable t = unwrap(e); if (t instanceof EnunciateException) { throw new MojoExecutionException(t.getMessage(), t); } throw new MojoExecutionException("Error invoking Enunciate.", e); } if (this.artifacts != null) { for (Artifact projectArtifact : artifacts) { if (projectArtifact.getEnunciateArtifactId() == null) { getLog().warn("[ENUNCIATE] No enunciate export id specified. Skipping project artifact..."); continue; } com.webcohesion.enunciate.artifacts.Artifact artifact = null; for (com.webcohesion.enunciate.artifacts.Artifact enunciateArtifact : enunciate.getArtifacts()) { if (projectArtifact.getEnunciateArtifactId().equals(enunciateArtifact.getId()) || enunciateArtifact.getAliases().contains(projectArtifact.getEnunciateArtifactId())) { artifact = enunciateArtifact; break; } } if (artifact != null) { try { File tempExportFile = enunciate.createTempFile(project.getArtifactId() + "-" + projectArtifact.getClassifier(), projectArtifact.getArtifactType()); artifact.exportTo(tempExportFile, enunciate); projectHelper.attachArtifact(project, projectArtifact.getArtifactType(), projectArtifact.getClassifier(), tempExportFile); } catch (IOException e) { throw new MojoExecutionException("Error exporting Enunciate artifact.", e); } } else { getLog().warn("[ENUNCIATE] Enunciate artifact '" + projectArtifact.getEnunciateArtifactId() + "' not found in the project..."); } } } postProcess(enunciate); getPluginContext().put(ConfigMojo.ENUNCIATE_PROPERTY, enunciate); } private Throwable unwrap(Throwable e) { while (e != null && !(e instanceof EnunciateException)) { e = e.getCause(); } return e; } protected void postProcess(Enunciate enunciate) { } protected void applyAdditionalConfiguration(EnunciateModule module) { } protected String findSourceVersion() { String source = this.source; if (source == null) { List plugins = this.project.getBuildPlugins(); for (Object plugin : plugins) { if (plugin instanceof Plugin && "org.apache.maven.plugins".equals(((Plugin) plugin).getGroupId()) && "maven-compiler-plugin".equals(((Plugin) plugin).getArtifactId()) && ((Plugin) plugin).getConfiguration() instanceof Xpp3Dom) { Xpp3Dom configuration = (Xpp3Dom) ((Plugin) plugin).getConfiguration(); Xpp3Dom sourceConfig = configuration.getChild("source"); if (sourceConfig != null) { source = sourceConfig.getValue(); } } } } return source; } protected String findTargetVersion() { String target = this.target; if (target == null) { List plugins = this.project.getBuildPlugins(); for (Object plugin : plugins) { if (plugin instanceof Plugin && "org.apache.maven.plugins".equals(((Plugin) plugin).getGroupId()) && "maven-compiler-plugin".equals(((Plugin) plugin).getArtifactId()) && ((Plugin) plugin).getConfiguration() instanceof Xpp3Dom) { Xpp3Dom configuration = (Xpp3Dom) ((Plugin) plugin).getConfiguration(); Xpp3Dom targetConfig = configuration.getChild("target"); if (targetConfig != null) { target = targetConfig.getValue(); } } } } return target; } protected List<URL> buildPluginClasspath() throws MojoExecutionException { List<URL> classpath = new ArrayList<URL>(); for (org.apache.maven.artifact.Artifact next : this.pluginDependencies) { try { classpath.add(next.getFile().toURI().toURL()); } catch (MalformedURLException e) { throw new MojoExecutionException("Unable to add artifact " + next + " to the classpath.", e); } } return classpath; } protected void setClasspathAndSourcepath(Enunciate enunciate) throws MojoExecutionException { List<File> classpath = new ArrayList<File>(); if (this.outputDir != null && this.outputDir.exists() && this.outputDir.isDirectory()) { if (getLog().isDebugEnabled()) { getLog().debug("[ENUNCIATE] Adding " + this.outputDir + " to the enunciate classpath."); } classpath.add(this.outputDir); } Set<org.apache.maven.artifact.Artifact> dependencies = new LinkedHashSet<org.apache.maven.artifact.Artifact>(); dependencies.addAll(((Set<org.apache.maven.artifact.Artifact>) this.project.getArtifacts())); Iterator<org.apache.maven.artifact.Artifact> it = dependencies.iterator(); while (it.hasNext()) { org.apache.maven.artifact.Artifact artifact = it.next(); String artifactScope = artifact.getScope(); String type = artifact.getType() == null ? "jar" : artifact.getType(); if (!SUPPORTED_TYPES.contains(type)) { //remove the non-jars from the classpath. if (getLog().isDebugEnabled()) { Iterator<String> typesIt = SUPPORTED_TYPES.iterator(); String types = ""; while (typesIt.hasNext()) { types += "'" + typesIt.next() + (typesIt.hasNext() ? "', " : "'"); } getLog().debug("[ENUNCIATE] Artifact " + artifact + " will be removed from the enunciate classpath because it's of type '" + type + "' and not of type " + types + "."); } it.remove(); } else if (org.apache.maven.artifact.Artifact.SCOPE_TEST.equals(artifactScope)) { //remove just the test-scope artifacts from the classpath. if (getLog().isDebugEnabled()) { getLog().debug("[ENUNCIATE] Artifact " + artifact + " will be removed from the enunciate classpath because it's of scope '" + artifactScope + "'."); } it.remove(); } else { classpath.add(artifact.getFile()); } } enunciate.setClasspath(classpath); if (!this.disableSourcepath) { List<org.apache.maven.artifact.Artifact> sourcepathDependencies = new ArrayList<org.apache.maven.artifact.Artifact>(); for (org.apache.maven.artifact.Artifact projectDependency : dependencies) { if (projectDependency.getGroupId().equals(this.project.getGroupId())) { if (getLog().isDebugEnabled()) { getLog().debug("[ENUNCIATE] Attempt will be made to lookup the sources for " + projectDependency + " because it has the same groupId as the current project."); } sourcepathDependencies.add(projectDependency); } else if (this.sourcepathIncludes != null) { for (DependencySourceSpec include : this.sourcepathIncludes) { if (include.specifies(projectDependency)) { if (getLog().isDebugEnabled()) { getLog().debug("[ENUNCIATE] Attempt will be made to lookup the sources for " + projectDependency + " because it was explicitly included in the plugin configuration."); } sourcepathDependencies.add(projectDependency); break; } } } } //now go through the excludes. if (this.sourcepathExcludes != null && sourcepathExcludes.length > 0) { Iterator<org.apache.maven.artifact.Artifact> sourcepathIt = sourcepathDependencies.iterator(); while (sourcepathIt.hasNext()) { org.apache.maven.artifact.Artifact sourcepathDependency = sourcepathIt.next(); for (DependencySourceSpec exclude : this.sourcepathExcludes) { if (exclude.specifies(sourcepathDependency)) { if (getLog().isDebugEnabled()) { getLog().debug("[ENUNCIATE] Attempt will NOT be made to lookup the sources for " + sourcepathDependency + " because it was explicitly excluded in the plugin configuration."); } sourcepathIt.remove(); } } } } //now attempt the source path lookup for the needed dependencies List<File> sourcepath = new ArrayList<File>(); for (org.apache.maven.artifact.Artifact sourcepathDependency : sourcepathDependencies) { try { org.apache.maven.artifact.Artifact sourceArtifact = this.artifactFactory.createArtifactWithClassifier(sourcepathDependency.getGroupId(), sourcepathDependency.getArtifactId(), sourcepathDependency.getVersion(), sourcepathDependency.getType(), "sources"); this.artifactResolver.resolve(sourceArtifact, this.project.getRemoteArtifactRepositories(), this.localRepository); if (getLog().isDebugEnabled()) { getLog().debug("[ENUNCIATE] Source artifact found at " + sourceArtifact + "."); } sourcepath.add(sourceArtifact.getFile()); } catch (Exception e) { if (getLog().isDebugEnabled()) { getLog().debug("[ENUNCIATE] Attempt to find source artifact for " + sourcepathDependency + " failed."); } } } enunciate.setSourcepath(sourcepath); } else { getLog().warn("[ENUNCIATE] Source path has been disabled. This may result is some missing documentation elements because the source code won't be available."); } } protected void postProcessConfig(Enunciate enunciate) { //no-op in this implementation. } /** * Load the config, do filtering as needed. * * @param config The config to load into. * @param configFile The config file. */ protected void loadConfig(Enunciate config, File configFile) throws IOException, SAXException, MavenFilteringException { if (this.configFilter == null) { getLog().debug("[ENUNCIATE] No maven file filter was provided, so no filtering of the config file will be done."); config.loadConfiguration(configFile); } else { this.buildDir.mkdirs(); File filteredConfig = File.createTempFile("enunciateConfig", ".xml", this.buildDir); getLog().debug("[ENUNCIATE] Filtering " + configFile + " to " + filteredConfig + "..."); this.configFilter.copyFile(configFile, filteredConfig, true, this.project, new ArrayList(), true, "utf-8", this.session); config.loadConfiguration(filteredConfig); //load the filtered configuration... config.getConfiguration().setConfigFile(configFile); //but make sure the original is still the config file location. } } protected class MavenEnunciateLogger implements EnunciateLogger { @Override public void debug(String message, Object... formatArgs) { if (getLog().isDebugEnabled()) { getLog().debug("[ENUNCIATE] " + String.format(message, formatArgs)); } } @Override public void info(String message, Object... formatArgs) { if (getLog().isInfoEnabled()) { getLog().info("[ENUNCIATE] " + String.format(message, formatArgs)); } } @Override public void warn(String message, Object... formatArgs) { if (getLog().isWarnEnabled()) { getLog().warn("[ENUNCIATE] " + String.format(message, formatArgs)); } } @Override public void error(String message, Object... formatArgs) { if (getLog().isErrorEnabled()) { getLog().error("[ENUNCIATE] " + String.format(message, formatArgs)); } } } }