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.ear;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.MojoFailureException;
29  import org.apache.maven.plugins.annotations.LifecyclePhase;
30  import org.apache.maven.plugins.annotations.Mojo;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.plugins.annotations.ResolutionScope;
33  import org.apache.maven.plugins.ear.util.JavaEEVersion;
34  import org.codehaus.plexus.configuration.PlexusConfiguration;
35  import org.codehaus.plexus.interpolation.InterpolationException;
36  import org.codehaus.plexus.interpolation.Interpolator;
37  import org.codehaus.plexus.interpolation.MapBasedValueSource;
38  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
39  import org.codehaus.plexus.interpolation.ValueSource;
40  import org.codehaus.plexus.util.FileUtils;
41  
42  /**
43   * Generates the EAR deployment descriptor file(s).
44   *
45   * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
46   */
47  @Mojo(
48          name = "generate-application-xml",
49          defaultPhase = LifecyclePhase.GENERATE_RESOURCES,
50          threadSafe = true,
51          requiresDependencyResolution = ResolutionScope.TEST)
52  public class GenerateApplicationXmlMojo extends AbstractEarMojo {
53  
54      /**
55       * The DEFAULT library folder.
56       */
57      public static final String DEFAULT = "DEFAULT";
58  
59      /**
60       * The empty folder.
61       */
62      public static final String EMPTY = "EMPTY";
63  
64      /**
65       * The NONE not existent folder.
66       */
67      public static final String NONE = "NONE";
68  
69      /**
70       * Whether the application.xml should be generated or not.
71       */
72      @Parameter(defaultValue = "true")
73      private Boolean generateApplicationXml = Boolean.TRUE;
74  
75      /**
76       * Whether a module ID should be generated if none is specified.
77       */
78      @Parameter(defaultValue = "false")
79      private Boolean generateModuleId = Boolean.FALSE;
80  
81      /**
82       * Application name of the application to be used when the application.xml file is auto-generated. Since JavaEE6.
83       */
84      @Parameter
85      private String applicationName;
86  
87      /**
88       * Display name of the application to be used when the application.xml file is auto-generated.
89       */
90      @Parameter(defaultValue = "${project.artifactId}")
91      private String displayName;
92  
93      /**
94       * Description of the application to be used when the application.xml file is auto-generated.
95       */
96      @Parameter(defaultValue = "${project.description}")
97      private String description;
98  
99      /**
100      * Defines how the {@code library-directory} element should be written in the application.xml file.
101      * <p/>
102      * Three special values can be set:
103      * <ul>
104      * <li><code>DEFAULT</code> (default) generates a {@code library-directory} element with the value of the
105      * {@code defaultLibBundleDir} parameter</li>
106      * <li><code>EMPTY</code> generates an empty {@code library-directory} element. Per spec, this disables the
107      * scanning of jar files in the {@code lib} directory of the ear file</li>
108      * <li><code>NONE</code> does not write the library-directory element at all. A corner case that can be used in
109      * Oracle Weblogic to delegate the classloading to the container</li>
110      * </ul>
111      * <p/>
112      * Since JavaEE5.
113      */
114     @Parameter(defaultValue = DEFAULT)
115     private String libraryDirectoryMode;
116 
117     /**
118      * Defines the value of the initialize in order element to be used when the application.xml file is auto-generated.
119      * When set to true, modules must be initialized in the order they're listed in this deployment descriptor, with the
120      * exception of application client modules, which can be initialized in any order. If initialize-in-order is not set
121      * or set to false, the order of initialization is unspecified and may be product-dependent. Since JavaEE6.
122      */
123     @Parameter
124     private Boolean initializeInOrder;
125 
126     /**
127      * Defines the application id used when generating the deployment descriptor.
128      *
129      * @since 2.9
130      */
131     @Parameter
132     private String applicationId;
133 
134     /**
135      * The security-roles to be added to the auto-generated application.xml file.
136      */
137     @Parameter
138     private PlexusConfiguration security;
139 
140     /**
141      * The env-entries to be added to the auto-generated application.xml file. Since JavaEE6.
142      */
143     @Parameter(alias = "env-entries")
144     private PlexusConfiguration envEntries;
145 
146     /**
147      * The {@code ejb-ref} entries.
148      */
149     @Parameter(alias = "ejb-refs")
150     private PlexusConfiguration ejbRefs;
151 
152     /**
153      * The {@code resource-ref} entries.
154      */
155     @Parameter
156     private PlexusConfiguration resourceRefs;
157 
158     /**
159      * {@inheritDoc}
160      */
161     public void execute() throws MojoExecutionException, MojoFailureException {
162         // Initializes ear modules
163         super.execute();
164 
165         // Handle application.xml
166         if (!generateApplicationXml) {
167             getLog().debug("Generation of application.xml is disabled");
168         } else {
169             final JavaEEVersion javaEEVersion = JavaEEVersion.getJavaEEVersion(version);
170 
171             // Generate deployment descriptor and copy it to the build directory
172             getLog().info("Generating application.xml");
173             try {
174                 generateStandardDeploymentDescriptor(javaEEVersion);
175             } catch (EarPluginException e) {
176                 throw new MojoExecutionException("Failed to generate application.xml", e);
177             }
178 
179             try {
180                 FileUtils.copyFileToDirectory(
181                         new File(generatedDescriptorLocation, "application.xml"),
182                         new File(getWorkDirectory(), "META-INF"));
183             } catch (IOException e) {
184                 throw new MojoExecutionException("Unable to copy application.xml to final destination", e);
185             }
186         }
187 
188         // Handle jboss-app.xml
189         if (getJbossConfiguration() == null) {
190             getLog().debug("Generation of jboss-app.xml is disabled");
191         } else {
192             // Generate deployment descriptor and copy it to the build directory
193             getLog().info("Generating jboss-app.xml");
194             try {
195                 generateJbossDeploymentDescriptor();
196             } catch (EarPluginException e) {
197                 throw new MojoExecutionException("Failed to generate jboss-app.xml", e);
198             }
199 
200             try {
201                 FileUtils.copyFileToDirectory(
202                         new File(generatedDescriptorLocation, "jboss-app.xml"),
203                         new File(getWorkDirectory(), "META-INF"));
204             } catch (IOException e) {
205                 throw new MojoExecutionException("Unable to copy jboss-app.xml to final destination", e);
206             }
207         }
208     }
209 
210     /**
211      * Generates the deployment descriptor.
212      *
213      * @param javaEEVersion {@link JavaEEVersion}
214      * @throws EarPluginException if the configuration is invalid
215      */
216     protected void generateStandardDeploymentDescriptor(JavaEEVersion javaEEVersion) throws EarPluginException {
217         File outputDir = new File(generatedDescriptorLocation);
218         if (!outputDir.exists()) {
219             if (!outputDir.mkdirs()) {
220                 throw new EarPluginException("Error creating " + outputDir);
221             }
222         }
223 
224         File descriptor = new File(outputDir, "application.xml");
225 
226         final ApplicationXmlWriter writer = new ApplicationXmlWriter(javaEEVersion, encoding, generateModuleId);
227         final ApplicationXmlWriterContext context = new ApplicationXmlWriterContext(
228                         descriptor,
229                         getModules(),
230                         buildSecurityRoles(),
231                         buildEnvEntries(),
232                         buildEjbEntries(),
233                         buildResourceRefs(),
234                         displayName,
235                         description,
236                         getActualLibraryDirectory(),
237                         applicationName,
238                         initializeInOrder)
239                 .setApplicationId(applicationId);
240         writer.write(context);
241     }
242 
243     /**
244      * Generates the JBoss deployment descriptor.
245      *
246      * @throws EarPluginException if the configuration is invalid
247      */
248     protected void generateJbossDeploymentDescriptor() throws EarPluginException {
249         File outputDir = new File(generatedDescriptorLocation);
250         if (!outputDir.exists()) {
251             if (!outputDir.mkdirs()) {
252                 throw new EarPluginException("Error creating " + outputDir);
253             }
254         }
255 
256         File descriptor = new File(outputDir, "jboss-app.xml");
257 
258         JbossAppXmlWriter writer = new JbossAppXmlWriter(encoding);
259         writer.write(descriptor, getJbossConfiguration(), getModules());
260     }
261 
262     /**
263      * Builds the security roles based on the configuration.
264      *
265      * @return a list of SecurityRole object(s)
266      * @throws EarPluginException if the configuration is invalid
267      */
268     private List<SecurityRole> buildSecurityRoles() throws EarPluginException {
269         final List<SecurityRole> result = new ArrayList<>();
270         if (security == null) {
271             return result;
272         }
273         final PlexusConfiguration[] securityRoles = security.getChildren(SecurityRole.SECURITY_ROLE);
274 
275         for (PlexusConfiguration securityRole : securityRoles) {
276             final String id = securityRole.getAttribute(SecurityRole.ID_ATTRIBUTE);
277             final String childRoleName =
278                     securityRole.getChild(SecurityRole.ROLE_NAME).getValue();
279             final String childRoleNameId =
280                     securityRole.getChild(SecurityRole.ROLE_NAME).getAttribute(SecurityRole.ID_ATTRIBUTE);
281             final String childDescription =
282                     securityRole.getChild(SecurityRole.DESCRIPTION).getValue();
283             final String childDescriptionId =
284                     securityRole.getChild(SecurityRole.DESCRIPTION).getAttribute(SecurityRole.ID_ATTRIBUTE);
285 
286             if (childRoleName == null) {
287                 throw new EarPluginException("Invalid security-role configuration, role-name could not be null.");
288             } else {
289                 result.add(new SecurityRole(childRoleName, childRoleNameId, id, childDescription, childDescriptionId));
290             }
291         }
292         return result;
293     }
294 
295     /**
296      * This help method was needed otherwise the interpolate method of interpolator will make an empty string of a
297      * {@code null} element which results in supplemental elements for env-entry.
298      *
299      * @param interpolator The interpolator
300      * @param element The element
301      * @return The interpolated elements.
302      * @throws InterpolationException in case of an error.
303      */
304     private String interpolate(Interpolator interpolator, String element) throws InterpolationException {
305         if (element == null) {
306             return element;
307         } else {
308             return interpolator.interpolate(element);
309         }
310     }
311 
312     /**
313      * Builds the env-entries based on the configuration.
314      *
315      * @return a list of EnvEntry object(s)
316      * @throws EarPluginException if the configuration is invalid
317      */
318     private List<EnvEntry> buildEnvEntries() throws EarPluginException {
319         final List<EnvEntry> result = new ArrayList<>();
320         if (envEntries == null) {
321             return result;
322         }
323         try {
324             StringSearchInterpolator ssi = new StringSearchInterpolator();
325             ValueSource vs = new MapBasedValueSource(project.getProperties());
326             ssi.addValueSource(vs);
327 
328             final PlexusConfiguration[] allEnvEntries = envEntries.getChildren(EnvEntry.ENV_ENTRY);
329 
330             getLog().debug("buildEnvEntries: allEnvEntries size:" + allEnvEntries.length);
331             for (PlexusConfiguration envEntry : allEnvEntries) {
332                 final String childDescription =
333                         interpolate(ssi, envEntry.getChild(EnvEntry.DESCRIPTION).getValue());
334                 final String childEnvEntryName = interpolate(
335                         ssi, envEntry.getChild(EnvEntry.ENV_ENTRY_NAME).getValue());
336                 final String childEnvEntryType = interpolate(
337                         ssi, envEntry.getChild(EnvEntry.ENV_ENTRY_TYPE).getValue());
338                 final String childEnvEntryValue = interpolate(
339                         ssi, envEntry.getChild(EnvEntry.ENV_ENTRY_VALUE).getValue());
340                 final String childEnvLookupNameValue = interpolate(
341                         ssi, envEntry.getChild(EnvEntry.ENV_LOOKUP_NAME).getValue());
342 
343                 try {
344                     result.add(new EnvEntry(
345                             childDescription,
346                             childEnvEntryName,
347                             childEnvEntryType,
348                             childEnvEntryValue,
349                             childEnvLookupNameValue));
350                 } catch (IllegalArgumentException e) {
351                     throw new EarPluginException("Invalid env-entry [" + envEntry + "]", e);
352                 }
353             }
354             return result;
355         } catch (InterpolationException e) {
356             throw new EarPluginException("Interpolation exception:", e);
357         }
358     }
359 
360     /**
361      * Builds the ejb-ref based on the configuration.
362      *
363      * @return a list of EjbRef object(s)
364      * @throws EarPluginException if the configuration is invalid
365      */
366     private List<EjbRef> buildEjbEntries() throws EarPluginException {
367         final List<EjbRef> result = new ArrayList<>();
368         if (ejbRefs == null) {
369             return result;
370         }
371         try {
372             StringSearchInterpolator ssi = new StringSearchInterpolator();
373             ValueSource vs = new MapBasedValueSource(project.getProperties());
374             ssi.addValueSource(vs);
375 
376             final PlexusConfiguration[] allEjbEntries = ejbRefs.getChildren(EjbRef.EJB_REF);
377 
378             for (PlexusConfiguration ejbEntry : allEjbEntries) {
379                 final String childDescription =
380                         interpolate(ssi, ejbEntry.getChild(EnvEntry.DESCRIPTION).getValue());
381                 final String childEjbEntryName =
382                         interpolate(ssi, ejbEntry.getChild(EjbRef.EJB_NAME).getValue());
383                 final String childEjbEntryType =
384                         interpolate(ssi, ejbEntry.getChild(EjbRef.EJB_TYPE).getValue());
385                 final String childEjbLookupNameValue = interpolate(
386                         ssi, ejbEntry.getChild(EjbRef.EJB_LOOKUP_NAME).getValue());
387 
388                 try {
389                     result.add(new EjbRef(
390                             childDescription, childEjbEntryName, childEjbEntryType, childEjbLookupNameValue));
391                 } catch (IllegalArgumentException e) {
392                     throw new EarPluginException("Invalid ejb-ref [" + ejbEntry + "]", e);
393                 }
394             }
395             return result;
396         } catch (InterpolationException e) {
397             throw new EarPluginException("Interpolation exception:", e);
398         }
399     }
400 
401     /**
402      * Builds the <code>resource-ref</code> based on the configuration.
403      *
404      * @return a list of ResourceRef object(s)
405      * @throws EarPluginException if the configuration is invalid
406      */
407     private List<ResourceRef> buildResourceRefs() throws EarPluginException {
408         final List<ResourceRef> result = new ArrayList<>();
409         if (resourceRefs == null) {
410             return result;
411         }
412         try {
413             getLog().debug("Resources found");
414             StringSearchInterpolator ssi = new StringSearchInterpolator();
415             ValueSource vs = new MapBasedValueSource(project.getProperties());
416             ssi.addValueSource(vs);
417 
418             // TODO: Check if this is a good idea hard code that here? Better idea?
419             final PlexusConfiguration[] allResourceRefEntries = resourceRefs.getChildren("resourceRef");
420 
421             getLog().debug("allResourceRefEntries length: " + allResourceRefEntries.length);
422             for (PlexusConfiguration resEntry : allResourceRefEntries) {
423                 getLog().debug("Resources resEntry:" + resEntry.getName());
424 
425                 final String childResRefName = interpolate(
426                         ssi, resEntry.getChild(ResourceRef.RESOURCE_REF_NAME).getValue());
427                 final String childResType = interpolate(
428                         ssi, resEntry.getChild(ResourceRef.RESOURCE_TYPE).getValue());
429                 final String childResRefAuth = interpolate(
430                         ssi, resEntry.getChild(ResourceRef.RESOURCE_AUTH).getValue());
431                 final String childResRefLookupName = interpolate(
432                         ssi, resEntry.getChild(ResourceRef.LOOKUP_NAME).getValue());
433 
434                 try {
435                     result.add(new ResourceRef(childResRefName, childResType, childResRefAuth, childResRefLookupName));
436                 } catch (IllegalArgumentException e) {
437                     throw new EarPluginException("Invalid resource-ref [" + resEntry + "]", e);
438                 }
439             }
440             return result;
441         } catch (InterpolationException e) {
442             throw new EarPluginException("Interpolation exception:", e);
443         }
444     }
445 
446     /**
447      * Returns the value to use for the {@code library-directory} element, based on the library directory mode.
448      */
449     private String getActualLibraryDirectory() throws EarPluginException {
450         final String mode = libraryDirectoryMode == null ? DEFAULT : libraryDirectoryMode.toUpperCase();
451 
452         if (DEFAULT.equals(mode)) {
453             return defaultLibBundleDir;
454         } else if (EMPTY.equals(mode)) {
455             return "";
456         } else if (NONE.equals(mode)) {
457             return null;
458         } else {
459             throw new EarPluginException("Unsupported library directory mode [" + libraryDirectoryMode
460                     + "] Supported modes " + (Arrays.asList(DEFAULT, EMPTY, NONE)));
461         }
462     }
463 }