View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2020 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.plugins.aem.maven;
21  
22  import static io.wcm.devops.conga.generator.util.FileUtil.getCanonicalPath;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.SortedMap;
30  import java.util.TreeMap;
31  
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.MojoFailureException;
34  import org.apache.maven.plugins.annotations.Component;
35  import org.apache.maven.plugins.annotations.Mojo;
36  import org.apache.maven.plugins.annotations.Parameter;
37  import org.apache.maven.project.MavenProject;
38  import org.apache.maven.project.MavenProjectHelper;
39  
40  import io.wcm.devops.conga.plugins.aem.maven.allpackage.AllPackageBuilder;
41  import io.wcm.devops.conga.plugins.aem.maven.model.InstallableFile;
42  import io.wcm.devops.conga.plugins.aem.maven.model.ModelParser;
43  
44  /**
45   * Builds an "all" content package dedicated for deployment via Adobe Cloud Manager
46   * for the given environment and node(s).
47   * <p>
48   * By default, it builds one "all" package per environment and node without adding any Cloud Manager
49   * environment-specific run mode suffixes to the folders. By defining a parameter <code>cloudManager.target</code>
50   * (contains a list of string values) in the CONGA environment it is possible:
51   * </p>
52   * <ul>
53   * <li>If it contains <code>none</code> no "all" package is build.</li>
54   * <li>If set to one or multiple environment names (normally dev/stage/prod) one "all" package for each of
55   * them is defined, and the environment name is added as runmode suffix to all config and install folders.</li>
56   * </ul>
57   */
58  @Mojo(name = "cloudmanager-all-package", threadSafe = true)
59  public final class CloudManagerAllPackageMojo extends AbstractCloudManagerMojo {
60  
61    /**
62     * Package name for the "all" content package.
63     */
64    @Parameter(property = "conga.cloudManager.allPackage.name", defaultValue = "all")
65    private String name;
66  
67    /**
68     * Group name for the "all" content package.
69     */
70    @Parameter(property = "conga.cloudManager.allPackage.group", required = true)
71    private String group;
72  
73    /**
74     * Build one single content package for all environments and nodes.
75     */
76    @Parameter(property = "conga.cloudManager.allPackage.singlePackage", defaultValue = "false")
77    private boolean singlePackage;
78  
79    /**
80     * Attach "all" content package(s) as artifacts to maven build lifecycle.
81     * The given package name will be used as classifier.
82     */
83    @Parameter(property = "conga.cloudManager.allPackage.attachArtifact", defaultValue = "false")
84    private boolean attachArtifact;
85  
86    /**
87     * Automatically generate dependencies between content packages based on file order in CONGA configuration.
88     * <p>
89     * Possible options see
90     * <a href="apidocs/io/wcm/devops/conga/plugins/aem/maven/AutoDependenciesMode.html">AutoDependenciesMode</a>.
91     * </p>
92     */
93    @Parameter(property = "conga.cloudManager.allPackage.autoDependenciesMode")
94    private AutoDependenciesMode autoDependenciesMode;
95  
96    /**
97     * How to optimize author/publish run modes in resulting "all" package.
98     * <p>
99     * Possible options see
100    * <a href="apidocs/io/wcm/devops/conga/plugins/aem/maven/RunModeOptimization.html">RunModeOptimization</a>.
101    * </p>
102    */
103   @Parameter(property = "conga.cloudManager.allPackage.runModeOptimization", defaultValue = "OFF")
104   private RunModeOptimization runModeOptimization;
105 
106   /**
107    * How to validate package types to be included in "all" package.
108    * <p>
109    * Possible options see
110    * <a href="apidocs/io/wcm/devops/conga/plugins/aem/maven/PackageTypeValidation.html">PackageTypeValidation</a>.
111    * </p>
112    */
113   @Parameter(property = "conga.cloudManager.allPackage.packageTypeValidation", defaultValue = "STRICT")
114   private PackageTypeValidation packageTypeValidation;
115 
116   /**
117    * How to handle versions of packages and sub-packages inside "all" package.
118    * <p>
119    * Possible options see
120    * <a href="apidocs/io/wcm/devops/conga/plugins/aem/maven/PackageVersionMode.html">PackageVersionMode</a>.
121    * </p>
122    */
123   @Parameter(property = "conga.cloudManager.allPackage.packageVersionMode", defaultValue = "DEFAULT")
124   private PackageVersionMode packageVersionMode;
125 
126   /**
127    * Automatically generate dependencies between content packages based on file order in CONGA configuration.
128    * @deprecated Please use autoDependenciesMode instead.
129    */
130   @Deprecated(forRemoval = true)
131   @Parameter(property = "conga.cloudManager.allPackage.autoDependencies", defaultValue = "true")
132   private boolean autoDependencies;
133 
134   /**
135    * Use separate dependency chains for mutable and immutable packages.
136    * @deprecated Please use autoDependenciesMode instead.
137    */
138   @Deprecated(forRemoval = true)
139   @Parameter(property = "conga.cloudManager.allPackage.autoDependenciesSeparateMutable", defaultValue = "false")
140   private boolean autoDependenciesSeparateMutable;
141 
142   /**
143    * Specifies additional properties to be set in the properties.xml file.
144    */
145   @Parameter
146   private Map<String, String> properties;
147 
148   /**
149    * Set this to "true" to skip installing packages to CRX although configured in the POM.
150    */
151   @Parameter(property = "conga.cloudManager.allPackage.skip", defaultValue = "false")
152   private boolean skip;
153 
154   @Parameter(defaultValue = "${project.build.outputTimestamp}")
155   private String outputTimestamp;
156 
157   @Parameter(readonly = true, defaultValue = "${project}")
158   private MavenProject project;
159   @Component
160   private MavenProjectHelper projectHelper;
161 
162   private static final String CLOUDMANAGER_TARGET_NONE = "none";
163 
164   @Override
165   @SuppressWarnings("java:S5738") // use deprecated for backward-compatibility
166   public void execute() throws MojoExecutionException, MojoFailureException {
167     if (skip) {
168       return;
169     }
170 
171     if (this.autoDependenciesMode == null) {
172       if (this.autoDependencies) {
173         if (this.autoDependenciesSeparateMutable) {
174           this.autoDependenciesMode = AutoDependenciesMode.IMMUTABLE_MUTABLE_SEPARATE;
175         }
176         else {
177           this.autoDependenciesMode = AutoDependenciesMode.IMMUTABLE_MUTABLE_COMBINED;
178         }
179       }
180       else {
181         this.autoDependenciesMode = AutoDependenciesMode.OFF;
182       }
183     }
184 
185     if (singlePackage) {
186       buildSingleAllPackage();
187     }
188     else if (runModeOptimization == RunModeOptimization.ELIMINATE_DUPLICATES) {
189       buildAllPackagesPerEnvironment();
190     }
191     else {
192       buildAllPackagesPerEnvironmentAndNode();
193     }
194   }
195 
196   /**
197    * Build an "all" package for each environment and node.
198    */
199   private void buildAllPackagesPerEnvironmentAndNode() throws MojoExecutionException, MojoFailureException {
200     visitEnvironmentsNodes((environmentDir, nodeDir, cloudManagerTarget, files) -> {
201       String packageName = environmentDir.getName() + "." + nodeDir.getName() + "." + this.name;
202       AllPackageBuilder builder = createBuilder(packageName);
203       try {
204         builder.add(files, cloudManagerTarget);
205       }
206       catch (IllegalArgumentException ex) {
207         throw new MojoFailureException(ex.getMessage(), ex);
208       }
209       buildAllPackage(builder);
210     });
211   }
212 
213   /**
214    * Build an "all" package for each environment, including all nodes of that environment in a single file.
215    */
216   private void buildAllPackagesPerEnvironment() throws MojoExecutionException, MojoFailureException {
217     SortedMap<String, AllPackageBuilder> builderPerEnvironment = new TreeMap<>();
218     visitEnvironmentsNodes((environmentDir, nodeDir, cloudManagerTarget, files) -> {
219       String packageName = environmentDir.getName() + "." + this.name;
220       AllPackageBuilder builder = builderPerEnvironment.computeIfAbsent(packageName, this::createBuilder);
221       try {
222         builder.add(files, cloudManagerTarget);
223       }
224       catch (IllegalArgumentException ex) {
225         throw new MojoFailureException(ex.getMessage(), ex);
226       }
227     });
228     for (AllPackageBuilder builder : builderPerEnvironment.values()) {
229       buildAllPackage(builder);
230     }
231   }
232 
233   /**
234    * Build a single "all" package containing packages from all environments and nodes.
235    */
236   private void buildSingleAllPackage() throws MojoExecutionException, MojoFailureException {
237     String packageName = this.name;
238     AllPackageBuilder builder = createBuilder(packageName);
239     visitEnvironmentsNodes((environmentDir, nodeDir, cloudManagerTarget, files) -> {
240       try {
241         builder.add(files, cloudManagerTarget);
242       }
243       catch (IllegalArgumentException ex) {
244         throw new MojoFailureException(ex.getMessage(), ex);
245       }
246     });
247     buildAllPackage(builder);
248   }
249 
250   private AllPackageBuilder createBuilder(String packageName) {
251     String fileName;
252     if (attachArtifact) {
253       fileName = project.getArtifactId() + "." + packageName + "-" + project.getVersion() + ".zip";
254     }
255     else {
256       fileName = packageName + ".zip";
257     }
258     File targetFile = new File(getTargetDir(), fileName);
259     return new AllPackageBuilder(targetFile, this.group, packageName)
260         .version(project.getVersion())
261         .autoDependenciesMode(this.autoDependenciesMode)
262         .runModeOptimization(this.runModeOptimization)
263         .packageTypeValidation(this.packageTypeValidation)
264         .packageVersionMode(this.packageVersionMode)
265         .logger(getLog())
266         .buildOutputTimestamp(new BuildOutputTimestamp(outputTimestamp));
267   }
268 
269   private void buildAllPackage(AllPackageBuilder builder) throws MojoExecutionException {
270     try {
271       getLog().debug("Start generating " + getCanonicalPath(builder.getTargetFile()) + "...");
272       if (builder.build(properties)) {
273         getLog().info("Generated " + getCanonicalPath(builder.getTargetFile()));
274         if (attachArtifact) {
275           projectHelper.attachArtifact(this.project, "zip", builder.getPackageName(), builder.getTargetFile());
276         }
277       }
278       else {
279         getLog().debug("Skipped " + getCanonicalPath(builder.getTargetFile()) + " - no valid package.");
280       }
281     }
282     catch (IOException ex) {
283       throw new MojoExecutionException("Unable to generate " + getCanonicalPath(builder.getTargetFile()), ex);
284     }
285   }
286 
287   private void visitEnvironmentsNodes(EnvironmentNodeVisitor visitor) throws MojoExecutionException, MojoFailureException {
288     List<File> environmentDirs = getEnvironmentDir();
289     for (File environmentDir : environmentDirs) {
290       List<File> nodeDirs = getNodeDirs(environmentDir);
291       for (File nodeDir : nodeDirs) {
292         ModelParser modelParser = new ModelParser(nodeDir);
293         Set<String> cloudManagerTarget = modelParser.getCloudManagerTarget();
294 
295         boolean validNodeForAllPackage = false;
296         if (cloudManagerTarget.contains(CLOUDMANAGER_TARGET_NONE)) {
297           if (isEnvironmentConfiguredExplicitely(environmentDir.getName())) {
298             // cloud manager target is set to "none" - but environment is configured explicitly, so include it
299             validNodeForAllPackage = true;
300             cloudManagerTarget.remove(CLOUDMANAGER_TARGET_NONE);
301           }
302         }
303         else {
304           // cloud manager target is not set to "none" - include node
305           validNodeForAllPackage = true;
306         }
307 
308         if (validNodeForAllPackage) {
309           List<InstallableFile> files = modelParser.getInstallableFilesForNode();
310           visitor.visit(environmentDir, nodeDir, cloudManagerTarget, files);
311         }
312       }
313     }
314   }
315 
316   interface EnvironmentNodeVisitor {
317     void visit(File environmentDir, File nodeDir, Set<String> cloudManagerTarget,
318         List<InstallableFile> files) throws MojoExecutionException, MojoFailureException;
319   }
320 
321 }