/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.gradle.plugins.maven.plugin.builder.tasks; import com.liferay.gradle.plugins.maven.plugin.builder.internal.util.GradleUtil; import com.liferay.gradle.plugins.maven.plugin.builder.internal.util.XMLUtil; import com.liferay.gradle.util.Validator; import com.thoughtworks.qdox.JavaDocBuilder; import com.thoughtworks.qdox.model.BeanProperty; import com.thoughtworks.qdox.model.DocletTag; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.JavaSource; import com.thoughtworks.qdox.model.Type; import java.io.File; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ResolvedConfiguration; import org.gradle.api.artifacts.ResolvedDependency; import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer; import org.gradle.api.file.CopySpec; import org.gradle.api.file.FileCollection; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.specs.Spec; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputDirectory; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.process.JavaExecSpec; import org.gradle.util.GUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * @author Andrea Di Giorgi */ public class BuildPluginDescriptorTask extends DefaultTask { public BuildPluginDescriptorTask() { _configurationScopeMappings.put( JavaPlugin.COMPILE_CONFIGURATION_NAME, Conf2ScopeMappingContainer.COMPILE); _configurationScopeMappings.put( "provided", Conf2ScopeMappingContainer.PROVIDED); _pomRepositories.put( "liferay-public", "http://repository.liferay.com/nexus/content/groups/public"); } @TaskAction public void buildPluginDescriptor() { Project project = getProject(); File pomFile = project.file(System.currentTimeMillis() + ".xml"); File preparedSourceDir = null; try { if (isUseSetterComments()) { preparedSourceDir = new File( getTemporaryDir(), "prepared-source"); _prepareSources(preparedSourceDir); } _buildPomFile(pomFile, preparedSourceDir); _buildPluginDescriptor(pomFile); _readdForcedExclusions(); } catch (Exception e) { throw new GradleException(e.getMessage(), e); } finally { if (preparedSourceDir != null) { project.delete(preparedSourceDir); } project.delete(pomFile); } } public void configurationScopeMapping( String configurationName, String scope) { _configurationScopeMappings.put(configurationName, scope); } @SuppressWarnings("unchecked") public BuildPluginDescriptorTask forcedExclusions( Iterable<String> forcedExclusions) { GUtil.addToCollection(_forcedExclusions, forcedExclusions); return this; } public BuildPluginDescriptorTask forcedExclusions( String... forcedExclusions) { return forcedExclusions(Arrays.asList(forcedExclusions)); } @InputDirectory public File getClassesDir() { return GradleUtil.toFile(getProject(), _classesDir); } public Map<String, String> getConfigurationScopeMappings() { return _configurationScopeMappings; } @Input public Set<String> getForcedExclusions() { return _forcedExclusions; } @Input public String getGoalPrefix() { return GradleUtil.toString(_goalPrefix); } @InputFiles public FileCollection getMavenEmbedderClasspath() { return _mavenEmbedderClasspath; } @Input public String getMavenEmbedderMainClassName() { return GradleUtil.toString(_mavenEmbedderMainClassName); } @Input public String getMavenPluginPluginVersion() { return GradleUtil.toString(_mavenPluginPluginVersion); } @InputFile @Optional public File getMavenSettingsFile() { return GradleUtil.toFile(getProject(), _mavenSettingsFile); } @OutputDirectory public File getOutputDir() { return GradleUtil.toFile(getProject(), _outputDir); } @Input public String getPomArtifactId() { return GradleUtil.toString(_pomArtifactId); } @Input public String getPomGroupId() { return GradleUtil.toString(_pomGroupId); } @Input public Map<String, Object> getPomRepositories() { return _pomRepositories; } @Input public String getPomVersion() { return GradleUtil.toString(_pomVersion); } @InputDirectory public File getSourceDir() { return GradleUtil.toFile(getProject(), _sourceDir); } @Input public boolean isUseSetterComments() { return _useSetterComments; } public BuildPluginDescriptorTask pomRepositories( Map<String, ?> pomRepositories) { _pomRepositories.putAll(pomRepositories); return this; } public BuildPluginDescriptorTask pomRepository(String id, Object url) { _pomRepositories.put(id, url); return this; } public void setClassesDir(Object classesDir) { _classesDir = classesDir; } public void setForcedExclusions(Iterable<String> forcedExclusions) { _forcedExclusions.clear(); forcedExclusions(forcedExclusions); } public void setForcedExclusions(String... forcedExclusions) { setForcedExclusions(Arrays.asList(forcedExclusions)); } public void setGoalPrefix(Object goalPrefix) { _goalPrefix = goalPrefix; } public void setMavenEmbedderClasspath( FileCollection mavenEmbedderClasspath) { _mavenEmbedderClasspath = mavenEmbedderClasspath; } public void setMavenEmbedderMainClassName( Object mavenEmbedderMainClassName) { _mavenEmbedderMainClassName = mavenEmbedderMainClassName; } public void setMavenPluginPluginVersion(Object mavenPluginPluginVersion) { _mavenPluginPluginVersion = mavenPluginPluginVersion; } public void setMavenSettingsFile(Object mavenSettingsFile) { _mavenSettingsFile = mavenSettingsFile; } public void setOutputDir(Object outputDir) { _outputDir = outputDir; } public void setPomArtifactId(Object pomArtifactId) { _pomArtifactId = pomArtifactId; } public void setPomGroupId(Object pomGroupId) { _pomGroupId = pomGroupId; } public void setPomRepositories(Map<String, ?> pomRepositories) { _pomRepositories.clear(); pomRepositories(pomRepositories); } public void setPomVersion(Object pomVersion) { _pomVersion = pomVersion; } public void setSourceDir(Object sourceDir) { _sourceDir = sourceDir; } public void setUseSetterComments(boolean useSetterComments) { _useSetterComments = useSetterComments; } private void _appendDependencyElements( Document document, Element dependenciesElement, String configurationName, String scope) { Project project = getProject(); ConfigurationContainer configurationContainer = project.getConfigurations(); Configuration configuration = configurationContainer.findByName( configurationName); if (configuration == null) { return; } Set<String> forcedExclusions = getForcedExclusions(); ResolvedConfiguration resolvedConfiguration = configuration.getResolvedConfiguration(); for (Dependency dependency : configuration.getDependencies()) { Element dependencyElement = document.createElement("dependency"); dependenciesElement.appendChild(dependencyElement); final String dependencyGroup = dependency.getGroup(); final String dependencyName = dependency.getName(); XMLUtil.appendElement( document, dependencyElement, "groupId", dependencyGroup); XMLUtil.appendElement( document, dependencyElement, "artifactId", dependencyName); String dependencyVersion = dependency.getVersion(); Set<ResolvedDependency> resolvedDependencies = resolvedConfiguration.getFirstLevelModuleDependencies( new Spec<Dependency>() { @Override public boolean isSatisfiedBy(Dependency dependency) { if (dependencyGroup.equals(dependency.getGroup()) && dependencyName.equals(dependency.getName())) { return true; } return false; } }); if (!resolvedDependencies.isEmpty()) { Iterator<ResolvedDependency> iterator = resolvedDependencies.iterator(); ResolvedDependency resolvedDependency = iterator.next(); dependencyVersion = resolvedDependency.getModuleVersion(); } else if (_logger.isWarnEnabled()) { _logger.warn( "Unable to find resolved module version for " + dependency); } XMLUtil.appendElement( document, dependencyElement, "version", dependencyVersion); XMLUtil.appendElement(document, dependencyElement, "scope", scope); if (!forcedExclusions.isEmpty()) { Element exclusionsElement = document.createElement( "exclusions"); dependencyElement.appendChild(exclusionsElement); for (String dependencyNotation : forcedExclusions) { _appendDependencyExclusionElement( document, exclusionsElement, dependencyNotation); } } } } private void _appendDependencyExclusionElement( Document document, Element exclusionsElement, String dependencyNotation) { String[] tokens = _parseDependencyNotation(dependencyNotation); String groupId = tokens[0]; String artifactId = tokens[1]; _appendDependencyExclusionElement( document, exclusionsElement, groupId, artifactId); } private void _appendDependencyExclusionElement( Document document, Element exclusionsElement, String groupId, String artifactId) { Element exclusionElement = document.createElement("exclusion"); exclusionsElement.appendChild(exclusionElement); XMLUtil.appendElement( document, exclusionElement, "artifactId", artifactId); XMLUtil.appendElement(document, exclusionElement, "groupId", groupId); } private void _appendRepositoryElement( Document document, Element repositoriesElement, String id, String url) { Element repositoryElement = document.createElement("repository"); repositoriesElement.appendChild(repositoryElement); XMLUtil.appendElement(document, repositoryElement, "id", id); XMLUtil.appendElement(document, repositoryElement, "url", url); } private void _buildPluginDescriptor(final File pomFile) throws Exception { final Project project = getProject(); project.javaexec( new Action<JavaExecSpec>() { @Override public void execute(JavaExecSpec javaExecSpec) { javaExecSpec.args("--batch-mode", "--errors"); javaExecSpec.args("--file"); javaExecSpec.args(project.relativePath(pomFile)); File mavenSettingsFile = getMavenSettingsFile(); if (mavenSettingsFile != null) { javaExecSpec.args("--settings"); javaExecSpec.args( project.relativePath(mavenSettingsFile)); } javaExecSpec.args("-Dencoding=UTF-8"); javaExecSpec.args("plugin:descriptor"); javaExecSpec.setClasspath(getMavenEmbedderClasspath()); javaExecSpec.setMain(getMavenEmbedderMainClassName()); javaExecSpec.systemProperty( "maven.multiModuleProjectDirectory", project.getProjectDir()); } }); File dir = new File(getClassesDir(), "META-INF/maven"); File outputDir = getOutputDir(); project.delete(outputDir); Files.move(dir.toPath(), outputDir.toPath()); } private void _buildPomFile(File pomFile, File sourceDir) throws Exception { Project project = getProject(); if (sourceDir == null) { sourceDir = getSourceDir(); } Document document = XMLUtil.createDocument(); Element projectElement = document.createElementNS( "http://maven.apache.org/POM/4.0.0", "project"); document.appendChild(projectElement); XMLUtil.appendElement( document, projectElement, "modelVersion", "4.0.0"); XMLUtil.appendElement( document, projectElement, "groupId", getPomGroupId()); XMLUtil.appendElement( document, projectElement, "artifactId", getPomArtifactId()); XMLUtil.appendElement( document, projectElement, "version", getPomVersion()); XMLUtil.appendElement( document, projectElement, "packaging", "maven-plugin"); // Build Element buildElement = document.createElement("build"); projectElement.appendChild(buildElement); XMLUtil.appendElement( document, buildElement, "outputDirectory", project.relativePath(getClassesDir())); XMLUtil.appendElement( document, buildElement, "sourceDirectory", project.relativePath(sourceDir)); Element pluginsElement = document.createElement("plugins"); buildElement.appendChild(pluginsElement); Element pluginElement = document.createElement("plugin"); pluginsElement.appendChild(pluginElement); XMLUtil.appendElement( document, pluginElement, "groupId", "org.apache.maven.plugins"); XMLUtil.appendElement( document, pluginElement, "artifactId", "maven-plugin-plugin"); XMLUtil.appendElement( document, pluginElement, "version", getMavenPluginPluginVersion()); String goalPrefix = getGoalPrefix(); if (Validator.isNotNull(goalPrefix)) { Element configurationElement = document.createElement( "configuration"); pluginElement.appendChild(configurationElement); XMLUtil.appendElement( document, configurationElement, "goalPrefix", goalPrefix); } // Dependencies Element dependenciesElement = document.createElement("dependencies"); projectElement.appendChild(dependenciesElement); Map<String, String> pomConfigurationScopeMappings = getConfigurationScopeMappings(); for (Map.Entry<String, String> entry : pomConfigurationScopeMappings.entrySet()) { String configurationName = entry.getKey(); String scope = entry.getValue(); _appendDependencyElements( document, dependenciesElement, configurationName, scope); } // Repositories Map<String, Object> pomRepositories = getPomRepositories(); if (!pomRepositories.isEmpty()) { Element repositoriesElement = document.createElement( "repositories"); projectElement.appendChild(repositoriesElement); for (Map.Entry<String, Object> entry : pomRepositories.entrySet()) { String id = entry.getKey(); String url = GradleUtil.toString(entry.getValue()); _appendRepositoryElement( document, repositoriesElement, id, url); } } XMLUtil.write(document, pomFile); } private String _getComments(JavaMethod javaMethod) { String code = javaMethod.getCodeBlock(); int start = code.indexOf("/**"); if (start < 0) { throw new GradleException("Unable to find comments start " + code); } int end = code.indexOf("*/", start); if (end < 0) { throw new GradleException("Unable to find comments end " + code); } return code.substring(start, end + 2); } private String _getTypeName(Type type) { String name = type.getFullyQualifiedName(); int pos = name.lastIndexOf('.'); if (pos != -1) { name = name.substring(pos + 1); } return name; } private String[] _parseDependencyNotation(String dependencyNotation) { String[] tokens = dependencyNotation.split(":"); if (tokens.length != 3) { throw new GradleException( "Unable to parse dependency notation " + dependencyNotation); } return tokens; } private void _prepareSource(JavaClass javaClass) throws Exception { StringBuilder sb = new StringBuilder(); for (BeanProperty beanProperty : javaClass.getBeanProperties()) { JavaMethod javaMethod = beanProperty.getMutator(); DocletTag parameterDocletTag = javaMethod.getTagByName("parameter"); if (parameterDocletTag == null) { continue; } sb.append(_getComments(javaMethod)); sb.append('\n'); sb.append("private "); Type type = beanProperty.getType(); sb.append(_getTypeName(type)); sb.append(' '); sb.append(beanProperty.getName()); sb.append(';'); sb.append('\n'); } if (sb.length() == 0) { return; } JavaSource javaSource = javaClass.getSource(); URL url = javaSource.getURL(); Path path = Paths.get(url.toURI()); String code = new String( Files.readAllBytes(path), StandardCharsets.UTF_8); int pos = code.lastIndexOf('}'); code = code.substring(0, pos) + sb.toString() + code.substring(pos); Files.write(path, code.getBytes(StandardCharsets.UTF_8)); } private void _prepareSources(final File preparedSourceDir) throws Exception { Project project = getProject(); project.copy( new Action<CopySpec>() { @Override public void execute(CopySpec copySpec) { copySpec.from(getSourceDir()); copySpec.include("**/*Mojo.java"); copySpec.into(preparedSourceDir); } }); JavaDocBuilder javaDocBuilder = new JavaDocBuilder(); javaDocBuilder.addSourceTree(preparedSourceDir); for (JavaClass javaClass : javaDocBuilder.getClasses()) { _prepareSource(javaClass); } } private void _readdForcedExclusions() throws Exception { Set<String> forcedExclusions = getForcedExclusions(); if (forcedExclusions.isEmpty()) { return; } File file = new File(getOutputDir(), "plugin.xml"); Path path = file.toPath(); String content = new String( Files.readAllBytes(path), StandardCharsets.UTF_8); int pos = content.lastIndexOf("</dependencies>"); if (pos == -1) { if (_logger.isWarnEnabled()) { _logger.warn("Unable to readd forced exclusions"); } return; } StringBuilder sb = new StringBuilder(); sb.append(content, 0, pos - 1); for (String dependencyNotation : forcedExclusions) { String[] tokens = _parseDependencyNotation(dependencyNotation); String groupId = tokens[0]; String artifactId = tokens[1]; String version = tokens[2]; sb.append("<dependency>"); sb.append("<groupId>"); sb.append(groupId); sb.append("</groupId>"); sb.append("<artifactId>"); sb.append(artifactId); sb.append("</artifactId>"); sb.append("<type>jar</type>"); sb.append("<version>"); sb.append(version); sb.append("</version>"); sb.append("</dependency>"); } sb.append(content, pos, content.length()); content = sb.toString(); Files.write(path, content.getBytes(StandardCharsets.UTF_8)); } private static final Logger _logger = Logging.getLogger( BuildPluginDescriptorTask.class); private Object _classesDir; private final Map<String, String> _configurationScopeMappings = new HashMap<>(); private final Set<String> _forcedExclusions = new HashSet<>(); private Object _goalPrefix; private FileCollection _mavenEmbedderClasspath; private Object _mavenEmbedderMainClassName = "org.apache.maven.cli.MavenCli"; private Object _mavenPluginPluginVersion = "3.4"; private Object _mavenSettingsFile; private Object _outputDir; private Object _pomArtifactId; private Object _pomGroupId; private final Map<String, Object> _pomRepositories = new LinkedHashMap<>(); private Object _pomVersion; private Object _sourceDir; private boolean _useSetterComments = true; }