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.tooling.maven.plugin.BuildConstants.CLASSIFIER_CONFIGURATION;
23  import static io.wcm.devops.conga.tooling.maven.plugin.BuildConstants.FILE_EXTENSION_CONFIGURATION;
24  import static io.wcm.devops.conga.tooling.maven.plugin.BuildConstants.PACKAGING_CONFIGURATION;
25  
26  import java.io.File;
27  import java.io.IOException;
28  import java.nio.file.Files;
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.Set;
32  import java.util.stream.Collectors;
33  
34  import org.apache.commons.lang3.StringUtils;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.plugin.MojoFailureException;
37  import org.apache.maven.plugins.annotations.Component;
38  import org.apache.maven.plugins.annotations.LifecyclePhase;
39  import org.apache.maven.plugins.annotations.Mojo;
40  import org.apache.maven.plugins.annotations.Parameter;
41  import org.apache.maven.project.MavenProject;
42  import org.codehaus.plexus.archiver.Archiver;
43  import org.codehaus.plexus.archiver.ArchiverException;
44  import org.codehaus.plexus.archiver.zip.ZipArchiver;
45  
46  import io.wcm.devops.conga.generator.util.FileUtil;
47  
48  /**
49   * Packages the generated configurations in a ZIP file.
50   */
51  @Mojo(name = "package", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true)
52  public class PackageMojo extends AbstractCongaMojo {
53  
54    /**
55     * Selected environments to generate.
56     */
57    @Parameter(property = "conga.environments")
58    private String[] environments;
59  
60    /**
61     * If set to true (default) a separate ZIP artifact is generated per environment.
62     * Otherwise a single ZIP containing all environments in sub directories is created.
63     */
64    @Parameter(defaultValue = "true")
65    private boolean artifactPerEnvironment;
66  
67    @Component(role = Archiver.class, hint = "zip")
68    private ZipArchiver zipArchiver;
69  
70    @Override
71    @SuppressWarnings("PMD.UseStringBufferForStringAppends")
72    public void execute() throws MojoExecutionException, MojoFailureException {
73  
74      // build attachments with all generated configurations
75      buildGeneratedConfigurationAttachments();
76  
77    }
78  
79  
80    @SuppressWarnings({
81        "PMD.UseStringBufferForStringAppends",
82        "java:S3776" // ignore complexity
83    })
84    private void buildGeneratedConfigurationAttachments() throws MojoExecutionException, MojoFailureException {
85      Set<String> selectedEnvironments;
86      if (environments != null && environments.length > 0) {
87        selectedEnvironments = Set.copyOf(Arrays.asList(environments));
88      }
89      else {
90        selectedEnvironments = null;
91      }
92  
93      // collect configuration environment directories
94      File configRootDir = getTargetDir();
95      List<File> environmentDirs = Arrays.stream(configRootDir.listFiles())
96          .filter(File::isDirectory)
97          .filter(dir -> selectedEnvironments == null || selectedEnvironments.contains(dir.getName()))
98          .collect(Collectors.toList());
99  
100     MavenProject project = getProject();
101     if (artifactPerEnvironment) {
102       // generate an ZIP artifact with generated configurations for each environment
103       for (File environmentDir : environmentDirs) {
104 
105         // classifier is environment name
106         // if current project is not a config project, prefix the classifier
107         String classifier = environmentDir.getName();
108         if (!StringUtils.equals(project.getPackaging(), PACKAGING_CONFIGURATION)) {
109           classifier = CLASSIFIER_CONFIGURATION + "-" + classifier;
110         }
111         validateClassifier(classifier);
112 
113         // build ZIP artifact
114         File outputFile = buildZipFile(environmentDir, classifier);
115 
116         // attach ZIP artifact
117         projectHelper.attachArtifact(project, outputFile, classifier);
118 
119       }
120 
121       // additionally build a JAR file with all CONGA definitions and resources as main artifact
122       buildDefinitionsJarFile();
123 
124     }
125     else {
126       // generate an ZIP artifact containing all environments
127       String classifier = null;
128       if (!StringUtils.equals(project.getPackaging(), PACKAGING_CONFIGURATION)) {
129         classifier = CLASSIFIER_CONFIGURATION;
130       }
131       validateClassifier(classifier);
132 
133       File outputFile = buildZipFile(configRootDir, classifier);
134       // set or attach ZIP artifact
135       if (StringUtils.equals(project.getPackaging(), PACKAGING_CONFIGURATION)) {
136         project.getArtifact().setFile(outputFile);
137       }
138       else {
139         projectHelper.attachArtifact(project, outputFile, CLASSIFIER_CONFIGURATION);
140       }
141     }
142 
143   }
144 
145   /**
146    * Build JAR file with definitions.
147    * @param contentDirectory Content directory for JAR file
148    * @return JAR file
149    */
150   private File buildZipFile(File contentDirectory, String classifier) throws MojoExecutionException {
151     File zipFile = new File(getProject().getBuild().getDirectory(), buildZipFileName(classifier));
152 
153     String basePath = toZipDirectoryPath(contentDirectory);
154     addZipDirectory(basePath, contentDirectory);
155     zipArchiver.setDestFile(zipFile);
156     try {
157       zipArchiver.createArchive();
158     }
159     catch (ArchiverException | IOException ex) {
160       throw new MojoExecutionException("Unable to build file " + zipFile.getPath() + ": " + ex.getMessage(), ex);
161     }
162 
163     return zipFile;
164   }
165 
166   /**
167    * Recursive through all directory and add file to zipArchiver.
168    * This is used instead of zipArchiver.addDirectory to make sure for symlinks the target of the symlink
169    * are included rather than the symlink information itself.
170    * @param basePath Base path
171    * @param directory Directory to include
172    */
173   @SuppressWarnings("java:S3776") // ignore complexity
174   private void addZipDirectory(String basePath, File directory) throws MojoExecutionException {
175     String directoryPath = toZipDirectoryPath(directory);
176     if (StringUtils.startsWith(directoryPath, basePath)) {
177       String relativeDirectoryPath = StringUtils.substring(directoryPath, basePath.length());
178       File[] files = directory.listFiles();
179       if (files != null) {
180         for (File file : files) {
181           if (file.isDirectory()) {
182             addZipDirectory(basePath, file);
183           }
184           else if (Files.isSymbolicLink(file.toPath())) {
185             // include file symlink is pointing at
186             try {
187               zipArchiver.addFile(file.toPath().toRealPath().toFile(), relativeDirectoryPath + file.getName());
188             }
189             catch (IOException ex) {
190               throw new MojoExecutionException("Unable to include symlinked file " + FileUtil.getCanonicalPath(file), ex);
191             }
192           }
193           else {
194             zipArchiver.addFile(file, relativeDirectoryPath + file.getName());
195           }
196         }
197       }
198     }
199   }
200 
201   private String toZipDirectoryPath(File directory) {
202     String canoncialPath = FileUtil.getCanonicalPath(directory);
203     return StringUtils.replace(canoncialPath, "\\", "/") + "/";
204   }
205 
206   private String buildZipFileName(String classifier) {
207     StringBuilder sb = new StringBuilder();
208     sb.append(getProject().getBuild().getFinalName());
209     if (StringUtils.isNotBlank(classifier)) {
210       sb.append("-").append(classifier);
211     }
212     sb.append(".").append(FILE_EXTENSION_CONFIGURATION);
213     return sb.toString();
214   }
215 
216   private void validateClassifier(String classifier) throws MojoExecutionException {
217     // classifier should not contain dots to make sure separation from extension/packaging types is not affected
218     if (StringUtils.contains(classifier, ".")) {
219       throw new MojoExecutionException("Classifier must not contain dots: " + classifier);
220     }
221   }
222 
223 }