View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.acr;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.nio.file.Path;
27  import java.util.List;
28  
29  import org.apache.commons.io.input.XmlStreamReader;
30  import org.apache.maven.archiver.MavenArchiveConfiguration;
31  import org.apache.maven.archiver.MavenArchiver;
32  import org.apache.maven.artifact.DependencyResolutionRequiredException;
33  import org.apache.maven.execution.MavenSession;
34  import org.apache.maven.plugin.AbstractMojo;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.plugins.annotations.LifecyclePhase;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.annotations.ResolutionScope;
40  import org.apache.maven.project.MavenProject;
41  import org.apache.maven.shared.filtering.FilterWrapper;
42  import org.apache.maven.shared.filtering.MavenFileFilter;
43  import org.apache.maven.shared.filtering.MavenFilteringException;
44  import org.apache.maven.shared.filtering.MavenResourcesExecution;
45  import org.codehaus.plexus.archiver.ArchiverException;
46  import org.codehaus.plexus.archiver.jar.JarArchiver;
47  import org.codehaus.plexus.archiver.jar.ManifestException;
48  import org.codehaus.plexus.util.FileUtils;
49  
50  /**
51   * Build a JavaEE Application Client jar file from the current project.
52   *
53   * @author <a href="pablo@anahata-it.com">Pablo Rodriguez</a>
54   * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
55   */
56  @Mojo(
57          name = "acr",
58          requiresDependencyResolution = ResolutionScope.RUNTIME,
59          threadSafe = true,
60          defaultPhase = LifecyclePhase.PACKAGE)
61  public class AcrMojo extends AbstractMojo {
62  
63      private static final String APP_CLIENT_XML = "META-INF/application-client.xml";
64  
65      // TODO: will null work instead?
66      private static final String[] DEFAULT_INCLUDES = {"**/**"};
67  
68      private static final String[] DEFAULT_EXCLUDES = {APP_CLIENT_XML};
69  
70      /**
71       * The directory for the generated jar.
72       */
73      @Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true)
74      private File basedir;
75  
76      /**
77       * Directory that resources are copied to during the build.<br/>
78       * Starting with <b>3.0.0</b> the property has been renamed from <code>outputDirectory</code> to
79       * <code>maven.acr.outputDirectory</code>.
80       */
81      @Parameter(property = "maven.acr.outputDirectory", defaultValue = "${project.build.outputDirectory}")
82      private File outputDirectory;
83  
84      /**
85       * The name of the Application client JAR file to generate.
86       */
87      @Parameter(defaultValue = "${project.build.finalName}")
88      private String jarName;
89  
90      /**
91       * The files and directories to exclude from the main Application Client jar. Usage:
92       * <p/>
93       *
94       * <pre>
95       * &lt;excludes&gt;
96       *   &lt;exclude&gt;**&#47;*DevOnly.class&lt;&#47;exclude&gt;
97       * &lt;&#47;excludes&gt;
98       * </pre>
99       *
100      * <br/>
101      * Default exclusions: META-INF&#47;application-client.xml,
102      */
103     @Parameter
104     private List<String> excludes;
105 
106     /**
107      * The Maven project.
108      */
109     @Parameter(defaultValue = "${project}", readonly = true, required = true)
110     private MavenProject project;
111 
112     /**
113      * The archive configuration to use. See <a href="https://maven.apache.org/shared/maven-archiver/index.html">Maven
114      * Archiver Reference</a>.
115      */
116     @Parameter
117     private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
118 
119     /**
120      * To escape interpolated value with windows path. c:\foo\bar will be replaced with c:\\foo\\bar.<br/>
121      * Starting with <b>3.0.0</b> the property has been renamed from <code>acr.escapeBackslashesInFilePath</code> to
122      * <code>maven.acr.escapeBackslashesInFilePath</code>.
123      */
124     @Parameter(property = "maven.acr.escapeBackslashesInFilePath", defaultValue = "false")
125     private boolean escapeBackslashesInFilePath;
126 
127     /**
128      * An expression preceded with this String won't be interpolated. \${foo} will be replaced with ${foo}.<br/>
129      * Starting with <b>3.0.0</b> the property has been renamed from <code>acr.escapeString</code> to
130      * <code>maven.acr.escapeString</code>.
131      */
132     @Parameter(property = "maven.acr.escapeString")
133     private String escapeString;
134 
135     /**
136      * To filter the deployment descriptor. Starting with <b>3.0.0</b> the property has been renamed from
137      * <code>acr.filterDeploymentDescriptor</code> to <code>maven.acr.filterDeploymentDescriptor</code>.
138      */
139     @Parameter(property = "maven.acr.filterDeploymentDescriptor", defaultValue = "false")
140     private boolean filterDeploymentDescriptor;
141 
142     /**
143      * Filters (properties files) to include during the interpolation of the deployment descriptor.
144      */
145     @Parameter
146     private List<String> filters;
147 
148     @Parameter(defaultValue = "${session}", readonly = true, required = true)
149     private MavenSession session;
150 
151     /**
152      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
153      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
154      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
155      *
156      * @since 3.2.0
157      */
158     @Parameter(defaultValue = "${project.build.outputTimestamp}")
159     private String outputTimestamp;
160 
161     /**
162      * The Jar archiver.
163      */
164     private final JarArchiver jarArchiver;
165 
166     private final MavenFileFilter mavenFileFilter;
167 
168     @Inject
169     public AcrMojo(JarArchiver jarArchiver, @Named("default") MavenFileFilter mavenFileFilter) {
170         this.jarArchiver = jarArchiver;
171         this.mavenFileFilter = mavenFileFilter;
172     }
173 
174     /** {@inheritDoc} */
175     public void execute() throws MojoExecutionException {
176         if (getLog().isInfoEnabled()) {
177             getLog().info("Building JavaEE Application client: " + jarName);
178         }
179 
180         File jarFile = getAppClientJarFile(basedir, jarName);
181 
182         MavenArchiver archiver = new MavenArchiver();
183 
184         archiver.setArchiver(jarArchiver);
185 
186         archiver.setCreatedBy("Maven ACR Plugin", "org.apache.maven.plugins", "maven-acr-plugin");
187 
188         archiver.setOutputFile(jarFile);
189 
190         // configure for Reproducible Builds based on outputTimestamp value
191         archiver.configureReproducibleBuild(outputTimestamp);
192 
193         try {
194             String[] mainJarExcludes = DEFAULT_EXCLUDES;
195 
196             if (excludes != null && !excludes.isEmpty()) {
197                 excludes.add(APP_CLIENT_XML);
198                 mainJarExcludes = excludes.toArray(new String[0]);
199             }
200 
201             if (outputDirectory.exists()) {
202                 archiver.getArchiver().addDirectory(outputDirectory, DEFAULT_INCLUDES, mainJarExcludes);
203             } else {
204                 // CHECKSTYLE_OFF: LineLength
205                 getLog().info(
206                                 "JAR will only contain the META-INF/application-client.xml as no content was marked for inclusion");
207                 // CHECKSTYLE_ON: LineLength
208             }
209 
210             File deploymentDescriptor = new File(outputDirectory, APP_CLIENT_XML);
211 
212             if (deploymentDescriptor.exists()) {
213                 if (filterDeploymentDescriptor) {
214                     getLog().debug("Filtering deployment descriptor.");
215                     MavenResourcesExecution mavenResourcesExecution = new MavenResourcesExecution();
216                     mavenResourcesExecution.setEscapeString(escapeString);
217                     List<FilterWrapper> filterWrappers = mavenFileFilter.getDefaultFilterWrappers(
218                             project, filters, escapeBackslashesInFilePath, this.session, mavenResourcesExecution);
219 
220                     // Create a temporary file that we can copy-and-filter
221                     File unfilteredDeploymentDescriptor = new File(outputDirectory, APP_CLIENT_XML + ".unfiltered");
222                     FileUtils.copyFile(deploymentDescriptor, unfilteredDeploymentDescriptor);
223                     mavenFileFilter.copyFile(
224                             unfilteredDeploymentDescriptor,
225                             deploymentDescriptor,
226                             true,
227                             filterWrappers,
228                             getEncoding(unfilteredDeploymentDescriptor.toPath()));
229                     // Remove the temporary file
230                     FileUtils.forceDelete(unfilteredDeploymentDescriptor);
231                 }
232                 archiver.getArchiver().addFile(deploymentDescriptor, APP_CLIENT_XML);
233             }
234 
235             // create archive
236             archiver.createArchive(session, project, archive);
237             // CHECKSTYLE_OFF: LineLength
238         } catch (ArchiverException e) {
239             throw new MojoExecutionException(
240                     "There was a problem creating the JavaEE Application Client  archive: " + e.getMessage(), e);
241         } catch (ManifestException e) {
242             throw new MojoExecutionException(
243                     "There was a problem reading / creating the manifest for the JavaEE Application Client  archive: "
244                             + e.getMessage(),
245                     e);
246         } catch (IOException e) {
247             throw new MojoExecutionException(
248                     "There was a I/O problem creating the JavaEE Application Client archive: " + e.getMessage(), e);
249         } catch (DependencyResolutionRequiredException e) {
250             throw new MojoExecutionException(
251                     "There was a problem resolving dependencies while creating the JavaEE Application Client archive: "
252                             + e.getMessage(),
253                     e);
254         } catch (MavenFilteringException e) {
255             throw new MojoExecutionException(
256                     "There was a problem filtering the deployment descriptor: " + e.getMessage(), e);
257         }
258 
259         project.getArtifact().setFile(jarFile);
260 
261         // CHECKSTYLE_ON: LineLength
262     }
263 
264     /**
265      * Returns the App-client Jar file to generate.
266      *
267      * @param basedir the output directory
268      * @param finalName the name of the ear file
269      * @return the Application client JAR file to generate
270      */
271     private static File getAppClientJarFile(File basedir, String finalName) {
272         return new File(basedir, finalName + ".jar");
273     }
274 
275     /**
276      * Get the encoding from an XML-file.
277      *
278      * @param xmlFile the XML-file
279      * @return The encoding of the XML-file, or UTF-8 if it's not specified in the file
280      * @throws IOException if an error occurred while reading the file
281      */
282     private String getEncoding(Path xmlFile) throws IOException {
283         try (XmlStreamReader xmlReader =
284                 XmlStreamReader.builder().setPath(xmlFile).get()) {
285             return xmlReader.getEncoding();
286         }
287     }
288 }