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