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