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