View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2015 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.devops.conga.tooling.maven.plugin;
21  
22  import static io.wcm.devops.conga.generator.GeneratorOptions.CLASSPATH_ENVIRONMENTS_DIR;
23  import static io.wcm.devops.conga.generator.GeneratorOptions.CLASSPATH_ROLES_DIR;
24  import static io.wcm.devops.conga.generator.GeneratorOptions.CLASSPATH_TEMPLATES_DIR;
25  import static io.wcm.devops.conga.tooling.maven.plugin.BuildConstants.CLASSIFIER_DEFINITION;
26  import static io.wcm.devops.conga.tooling.maven.plugin.BuildConstants.FILE_EXTENSION_DEFINITION;
27  import static io.wcm.devops.conga.tooling.maven.plugin.BuildConstants.PACKAGING_CONFIGURATION;
28  import static io.wcm.devops.conga.tooling.maven.plugin.BuildConstants.PACKAGING_DEFINITION;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.nio.file.Files;
34  import java.util.Arrays;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.SortedSet;
38  
39  import javax.inject.Inject;
40  import javax.inject.Named;
41  
42  import org.apache.commons.io.FileUtils;
43  import org.apache.commons.io.IOUtils;
44  import org.apache.commons.lang3.StringUtils;
45  import org.apache.maven.archiver.MavenArchiveConfiguration;
46  import org.apache.maven.archiver.MavenArchiver;
47  import org.apache.maven.artifact.DependencyResolutionRequiredException;
48  import org.apache.maven.execution.MavenSession;
49  import org.apache.maven.plugin.AbstractMojo;
50  import org.apache.maven.plugin.MojoExecutionException;
51  import org.apache.maven.plugin.MojoFailureException;
52  import org.apache.maven.plugins.annotations.Parameter;
53  import org.apache.maven.project.MavenProject;
54  import org.apache.maven.project.MavenProjectHelper;
55  import org.codehaus.plexus.archiver.ArchiverException;
56  import org.codehaus.plexus.archiver.jar.JarArchiver;
57  import org.codehaus.plexus.archiver.jar.ManifestException;
58  
59  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
60  import io.wcm.devops.conga.generator.export.ModelExport;
61  import io.wcm.devops.conga.resource.Resource;
62  import io.wcm.devops.conga.resource.ResourceCollection;
63  import io.wcm.devops.conga.resource.ResourceLoader;
64  import io.wcm.devops.conga.tooling.maven.plugin.util.PluginConfigUtil;
65  
66  /**
67   * Common features for all Mojos.
68   */
69  abstract class AbstractCongaMojo extends AbstractMojo {
70  
71    /**
72     * Source path with templates.
73     */
74    @Parameter(defaultValue = "${basedir}/src/main/templates")
75    private File templateDir;
76  
77    /**
78     * Source path with role definitions.
79     */
80    @Parameter(defaultValue = "${basedir}/src/main/roles")
81    private File roleDir;
82  
83    /**
84     * Target path for the generated configuration files.
85     */
86    @Parameter(defaultValue = "${project.build.directory}/configuration")
87    private File target;
88  
89    /**
90     * Source path with environment definitions.
91     */
92    @Parameter(defaultValue = "${basedir}/src/main/environments")
93    private File environmentDir;
94  
95    /**
96     * Target path for the prepared definition files.
97     */
98    @Parameter(defaultValue = "${project.build.directory}/definitions")
99    private String definitionTarget;
100 
101   /**
102    * List for plugins for exporting model data for nodes.
103    * You can specify multiple plugins separated by ",".
104    * To disable export of model data set to "none".
105    */
106   @Parameter(defaultValue = "yaml")
107   private String modelExportNode;
108 
109   /**
110    * Configuration for value providers.
111    *
112    * <p>
113    * This uses the same syntax as OSGi manifest headers - example:
114    * </p>
115    *
116    * <pre>
117    * valueProviderPluginName1;param1=value1;param2=value2,
118    * valueProviderPluginName2;param3=value3
119    * </pre>
120    *
121    * <p>
122    * If you want to define multiple value providers of the same type, you can use an arbitrary value provider name, and
123    * specify the plugin name with the optional <code>_plugin_</code> parameter - example:
124    * </p>
125    *
126    * <pre>
127    * valueProvider1;_plugin_=valueProviderPluginName1,param1=value1;param2=value2,
128    * valueProvider2;_plugin_=valueProviderPluginName1,param3=value3
129    * </pre>
130    */
131   @Parameter
132   private String valueProvider;
133 
134   /**
135    * Plugin-specific configuration. This holds configuration for CONGA plugins that are not part of the built-in set of
136    * CONGA plugins (e.g. configuration for the CONGA AEM Plugin).
137    *
138    * <p>
139    * This uses the same syntax as OSGi manifest headers - example:
140    * </p>
141    *
142    * <pre>
143    * pluginName1;param1=value1;param2=value2,
144    * pluginName2;param3=value3
145    * </pre>
146    */
147   @Parameter
148   private String pluginConfig;
149 
150   /**
151    * Allows to define custom artifact type to extension mappings for resolving dependencies from artifact coordinates
152    * where it is not fully clear if the an extension is really the extension or a artifact type identifier.
153    * Defaults to <code>bundle</code>-&gt;<code>jar</code>, <code>content-package</code>-&gt;<code>zip</code>.
154    */
155   @Parameter
156   private Map<String,String> artifactTypeMappings;
157 
158   @Parameter(property = "project", required = true, readonly = true)
159   private MavenProject project;
160 
161   @Inject
162   @Named("jar")
163   private JarArchiver jarArchiver;
164 
165   /**
166    * The archive configuration to use.
167    * See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven Archiver Reference</a>.
168    */
169   @Parameter
170   @SuppressWarnings("PMD.ImmutableField")
171   private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
172 
173   @Parameter(property = "session", readonly = true, required = true)
174   private MavenSession mavenSession;
175 
176   @Inject
177   protected MavenProjectHelper projectHelper;
178 
179   private static final Map<String, String> DEFAULT_ARTIFACT_TYPE_MAPPINGS = Map.of(
180       "bundle", "jar",
181       "content-package", "zip");
182 
183   protected File getTemplateDir() {
184     return templateDir;
185   }
186 
187   protected File getRoleDir() {
188     return roleDir;
189   }
190 
191   protected File getEnvironmentDir() {
192     return environmentDir;
193   }
194 
195   protected File getTargetDir() {
196     return target;
197   }
198 
199   protected MavenProject getProject() {
200     return project;
201   }
202 
203   protected ModelExport getModelExport() {
204     ModelExport modelExport = new ModelExport();
205 
206     String[] nodeExportPlugins = StringUtils.split(this.modelExportNode, ",");
207     if (nodeExportPlugins != null) {
208       modelExport.setNode(Arrays.asList(nodeExportPlugins));
209     }
210 
211     return modelExport;
212   }
213 
214   protected Map<String, Map<String, Object>> getValueProviderConfig() {
215     return PluginConfigUtil.getConfigMap(this.valueProvider);
216   }
217 
218   protected Map<String, Map<String, Object>> getPluginConfig() {
219     return PluginConfigUtil.getConfigMap(this.pluginConfig);
220   }
221 
222   protected Map<String, String> getArtifactTypeMappings() {
223     Map<String, String> mappings = this.artifactTypeMappings;
224     if (mappings == null) {
225       mappings = DEFAULT_ARTIFACT_TYPE_MAPPINGS;
226     }
227     return mappings;
228   }
229 
230   /**
231    * Builds a JAR file with all CONGA definitions and resources. This is the main output artefact.
232    * @throws MojoExecutionException MOJO execution exception
233    * @throws MojoFailureException MOJO failure exception
234    */
235   protected void buildDefinitionsJarFile() throws MojoExecutionException, MojoFailureException {
236 
237     // copy definitions to classes dir
238     File definitionDir = copyDefinitions();
239 
240     // build JAR artifact
241     File outputFile = buildJarFile(definitionDir);
242 
243     // set or attach JAR artifact
244     if (StringUtils.equalsAny(project.getPackaging(), PACKAGING_DEFINITION, PACKAGING_CONFIGURATION)) {
245       project.getArtifact().setFile(outputFile);
246     }
247     else {
248       projectHelper.attachArtifact(project, outputFile, CLASSIFIER_DEFINITION);
249     }
250   }
251 
252   /**
253    * Build JAR file with definitions.
254    * @param contentDirectory Content directory for JAR file
255    * @return JAR file
256    */
257   private File buildJarFile(File contentDirectory) throws MojoExecutionException {
258     File jarFile = new File(project.getBuild().getDirectory(), buildJarFileName());
259 
260     MavenArchiver archiver = new MavenArchiver();
261     archiver.setArchiver(jarArchiver);
262     archiver.setOutputFile(jarFile);
263     archive.setForced(true);
264 
265     // include definitions
266     archiver.getArchiver().addDirectory(contentDirectory);
267 
268     // include resources
269     for (org.apache.maven.model.Resource resource : project.getResources()) {
270       File resourceDir = new File(resource.getDirectory());
271       if (resourceDir.exists()) {
272         archiver.getArchiver().addDirectory(resourceDir,
273             toArray(resource.getIncludes()), toArray(resource.getExcludes()));
274       }
275     }
276 
277     try {
278       archiver.createArchive(mavenSession, project, archive);
279     }
280     catch (ArchiverException | ManifestException | IOException | DependencyResolutionRequiredException ex) {
281       throw new MojoExecutionException("Unable to build file " + jarFile.getPath() + ": " + ex.getMessage(), ex);
282     }
283 
284     return jarFile;
285   }
286 
287   @SuppressWarnings("java:S1168") // null array is allowed
288   private String[] toArray(List<String> values) {
289     if (values == null || values.isEmpty()) {
290       return null;
291     }
292     return values.toArray(new String[0]);
293   }
294 
295   private String buildJarFileName() {
296     StringBuilder sb = new StringBuilder();
297     sb.append(project.getBuild().getFinalName());
298     if (!StringUtils.equalsAny(project.getPackaging(), PACKAGING_DEFINITION, PACKAGING_CONFIGURATION)) {
299       sb.append("-").append(CLASSIFIER_DEFINITION);
300     }
301     sb.append(".").append(FILE_EXTENSION_DEFINITION);
302     return sb.toString();
303   }
304 
305   /**
306    * Copy definitions and template files to classes folder to include them in JAR artifact.
307    */
308   @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
309   private File copyDefinitions() throws MojoExecutionException {
310     File outputDir = new File(definitionTarget);
311     if (!outputDir.exists()) {
312       outputDir.mkdirs();
313     }
314 
315     ResourceLoader resourceLoader = new ResourceLoader();
316     ResourceCollection roleDirCol = resourceLoader.getResourceCollection(ResourceLoader.FILE_PREFIX + getRoleDir());
317     ResourceCollection templateDirCol = resourceLoader.getResourceCollection(ResourceLoader.FILE_PREFIX + getTemplateDir());
318     ResourceCollection environmentDirCol = resourceLoader.getResourceCollection(ResourceLoader.FILE_PREFIX + getEnvironmentDir());
319 
320     // copy definitions
321     try {
322       copyDefinitions(roleDirCol, outputDir, outputDir, CLASSPATH_ROLES_DIR);
323       copyDefinitions(templateDirCol, outputDir, outputDir, CLASSPATH_TEMPLATES_DIR);
324       copyDefinitions(environmentDirCol, outputDir, outputDir, CLASSPATH_ENVIRONMENTS_DIR);
325     }
326     catch (IOException ex) {
327       throw new MojoExecutionException("Unable to copy definitions:" + ex.getMessage(), ex);
328     }
329 
330     return outputDir;
331   }
332 
333   @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
334   private void copyDefinitions(ResourceCollection sourceDir, File rootOutputDir, File parentTargetDir, String dirName) throws IOException {
335     if (!sourceDir.exists()) {
336       return;
337     }
338     SortedSet<Resource> files = sourceDir.getResources();
339     SortedSet<ResourceCollection> dirs = sourceDir.getResourceCollections();
340     if (files.isEmpty() && dirs.isEmpty()) {
341       return;
342     }
343 
344     File targetDir = new File(parentTargetDir, dirName);
345     if (!targetDir.exists()) {
346       targetDir.mkdirs();
347     }
348 
349     for (Resource file : files) {
350       File targetFile = new File(targetDir, file.getName());
351 
352       getLog().info("Include " + getPathForLog(rootOutputDir, targetFile));
353 
354       if (targetFile.exists()) {
355         Files.delete(targetFile.toPath());
356       }
357       try (InputStream is = file.getInputStream()) {
358         byte[] data = IOUtils.toByteArray(is);
359         FileUtils.writeByteArrayToFile(targetFile, data);
360       }
361     }
362 
363     for (ResourceCollection dir : dirs) {
364       copyDefinitions(dir, rootOutputDir, targetDir, dir.getName());
365     }
366   }
367 
368   @SuppressWarnings("java:S1075") // not a filesystem path
369   private String getPathForLog(File rootOutputDir, File file) throws IOException {
370     String path = unifySlashes(file.getCanonicalPath());
371     String rootPath = unifySlashes(rootOutputDir.getCanonicalPath()) + "/";
372     return StringUtils.substringAfter(path, rootPath);
373   }
374 
375   private String unifySlashes(String path) {
376     return StringUtils.replace(path, "\\", "/");
377   }
378 
379 }