View Javadoc
1   package org.apache.maven.plugins.ejb;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.Collections;
25  import java.util.List;
26  
27  import org.apache.commons.io.IOUtils;
28  import org.apache.commons.io.input.XmlStreamReader;
29  import org.apache.maven.archiver.MavenArchiveConfiguration;
30  import org.apache.maven.archiver.MavenArchiver;
31  import org.apache.maven.artifact.DependencyResolutionRequiredException;
32  import org.apache.maven.execution.MavenSession;
33  import org.apache.maven.plugin.AbstractMojo;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugins.annotations.Component;
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.project.MavenProjectHelper;
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.apache.maven.shared.utils.io.FileUtils.FilterWrapper;
46  import org.codehaus.plexus.archiver.Archiver;
47  import org.codehaus.plexus.archiver.ArchiverException;
48  import org.codehaus.plexus.archiver.jar.JarArchiver;
49  import org.codehaus.plexus.archiver.jar.ManifestException;
50  import org.codehaus.plexus.util.FileUtils;
51  
52  import com.google.inject.internal.util.Lists;
53  
54  /**
55   * Build an EJB (and optional client) from the current project.
56   *
57   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
58   * @version $Id$
59   */
60  // CHECKSTYLE_OFF: LineLength
61  @Mojo( name = "ejb", requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true, defaultPhase = LifecyclePhase.PACKAGE )
62  // CHECKSTYLE_ON: LineLength
63  public class EjbMojo
64      extends AbstractMojo
65  {
66      private static final List<String> DEFAULT_INCLUDES_LIST =
67          Collections.unmodifiableList( Lists.newArrayList( "**/**" ) );
68  
69      //@formatter:off
70      private static final List<String> DEFAULT_CLIENT_EXCLUDES_LIST =
71          Collections.unmodifiableList( 
72              Lists.newArrayList( 
73                "**/*Bean.class", 
74                "**/*CMP.class", 
75                "**/*Session.class",
76                "**/package.html" 
77              )
78          );
79      //@formatter:on
80  
81      /**
82       * Default value for {@link #clientClassifier}
83       */
84      public static final String DEFAULT_CLIENT_CLASSIFIER = "client";
85  
86      /**
87       * Default value for {@link #ejbJar}.
88       */
89      public static final String DEFAULT_EJBJAR = "META-INF/ejb-jar.xml";
90  
91      /**
92       * The directory location for the generated EJB.
93       */
94      @Parameter( defaultValue = "${project.build.directory}", required = true, readonly = true )
95      private File outputDirectory;
96  
97      /**
98       * Directory that contains the resources which are packaged into the created archive {@code target/classes}.
99       */
100     @Parameter( defaultValue = "${project.build.outputDirectory}", required = true )
101     private File sourceDirectory;
102 
103     /**
104      * The name of the EJB file to generate.
105      */
106     @Parameter( defaultValue = "${project.build.finalName}", readonly = true )
107     private String jarName;
108 
109     /**
110      * Classifier to add to the artifact generated. If given, the artifact will be an attachment instead.
111      */
112     @Parameter
113     private String classifier;
114 
115     /**
116      * Classifier which is used for the client artifact.
117      * 
118      * @since 3.0.0
119      */
120     @Parameter( defaultValue = DEFAULT_CLIENT_CLASSIFIER )
121     private String clientClassifier;
122 
123     /**
124      * You can define the location of <code>ejb-jar.xml</code> file.
125      */
126     @Parameter( defaultValue = DEFAULT_EJBJAR )
127     private String ejbJar;
128 
129     /**
130      * Whether the EJB client jar should be generated or not.
131      */
132     @Parameter( defaultValue = "false" )
133     private boolean generateClient;
134 
135     /**
136      * The files and directories to exclude from the client jar. Usage:
137      * <p/>
138      * 
139      * <pre>
140      * &lt;clientExcludes&gt;
141      * &nbsp;&nbsp;&lt;clientExclude&gt;**&#47;*Ejb.class&lt;&#47;clientExclude&gt;
142      * &nbsp;&nbsp;&lt;clientExclude&gt;**&#47;*Bean.class&lt;&#47;clientExclude&gt;
143      * &lt;&#47;clientExcludes&gt;
144      * </pre>
145      * 
146      * <br/>
147      * Attribute is used only if client jar is generated. <br/>
148      * Default exclusions: **&#47;*Bean.class, **&#47;*CMP.class, **&#47;*Session.class, **&#47;package.html
149      */
150     @Parameter
151     private List<String> clientExcludes;
152 
153     /**
154      * The files and directories to include in the client jar. Usage:
155      * <p/>
156      * 
157      * <pre>
158      * &lt;clientIncludes&gt;
159      * &nbsp;&nbsp;&lt;clientInclude&gt;**&#47;*&lt;&#47;clientInclude&gt;
160      * &lt;&#47;clientIncludes&gt;
161      * </pre>
162      * 
163      * <br/>
164      * Attribute is used only if client jar is generated. <br/>
165      * Default value: **&#47;**
166      */
167     @Parameter
168     private List<String> clientIncludes;
169 
170     /**
171      * The files and directories to exclude from the main EJB jar. Usage:
172      * <p/>
173      * 
174      * <pre>
175      * &lt;excludes&gt;
176      *   &lt;exclude&gt;**&#47;*Ejb.class&lt;&#47;exclude&gt;
177      *   &lt;exclude&gt;**&#47;*Bean.class&lt;&#47;exclude&gt;
178      * &lt;&#47;excludes&gt;
179      * </pre>
180      * 
181      * <br/>
182      * Default exclusions: META-INF&#47;ejb-jar.xml, **&#47;package.html
183      */
184     @Parameter
185     private List<String> excludes;
186 
187     /**
188      * The Maven project.
189      */
190     @Parameter( defaultValue = "${project}", readonly = true, required = true )
191     private MavenProject project;
192 
193     /**
194      * The Jar archiver.
195      */
196     @Component( role = Archiver.class, hint = "jar" )
197     private JarArchiver jarArchiver;
198 
199     /**
200      * What EJB version should the EJB Plugin generate? Valid values are "2.x" or "3.x" (where x is a digit). When
201      * ejbVersion is "3.x", the <code>ejb-jar.xml</code> file is optional.
202      * <p/>
203      * Usage:
204      * 
205      * <pre>
206      * &lt;ejbVersion&gt;3.0&lt;&#47;ejbVersion&gt;
207      * </pre>
208      * 
209      * @since 2.1
210      */
211     @Parameter( defaultValue = "3.1" )
212     private String ejbVersion;
213 
214     /**
215      * The client Jar archiver.
216      */
217     @Component( role = Archiver.class, hint = "jar" )
218     private JarArchiver clientJarArchiver;
219 
220     /**
221      * The Maven project's helper.
222      */
223     @Component
224     private MavenProjectHelper projectHelper;
225 
226     /**
227      * The archive configuration to use. See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven
228      * Archiver Reference</a>.
229      */
230     @Parameter
231     private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
232 
233     /**
234      * To escape interpolated value with windows path. c:\foo\bar will be replaced with c:\\foo\\bar.
235      *
236      * @since 2.3
237      */
238     @Parameter( defaultValue = "false" )
239     private boolean escapeBackslashesInFilePath;
240 
241     /**
242      * An expression preceded with this String won't be interpolated. \${foo} will be replaced with ${foo}.
243      *
244      * @since 2.3
245      */
246     @Parameter
247     protected String escapeString;
248 
249     /**
250      * To filter the deployment descriptor.
251      *
252      * @since 2.3
253      */
254     @Parameter( defaultValue = "false" )
255     private boolean filterDeploymentDescriptor;
256 
257     /**
258      * Filters (properties files) to include during the interpolation of the deployment descriptor.
259      *
260      * @since 2.3
261      */
262     @Parameter
263     private List<String> filters;
264 
265     /**
266      * @since 2.3
267      */
268     @Component( role = MavenFileFilter.class, hint = "default" )
269     private MavenFileFilter mavenFileFilter;
270 
271     /**
272      * @since 2.3
273      */
274     @Parameter( defaultValue = "${session}", readonly = true, required = true )
275     private MavenSession session;
276 
277     private static final String EJB_TYPE = "ejb";
278 
279     private static final String EJB_CLIENT_TYPE = "ejb-client";
280 
281     /**
282      * Generates an EJB jar and optionally an ejb-client jar.
283      */
284     public void execute()
285         throws MojoExecutionException
286     {
287 
288         if ( !sourceDirectory.exists() )
289         {
290             getLog().warn( "The created EJB jar will be empty cause the " + sourceDirectory.getPath()
291                 + " did not exist." );
292             sourceDirectory.mkdirs();
293         }
294 
295         File jarFile = generateEjb();
296 
297         if ( hasClassifier() )
298         {
299             if ( !isClassifierValid() )
300             {
301                 String message = "The given classifier '" + getClassifier() + "' is not valid.";
302                 getLog().error( message );
303                 throw new MojoExecutionException( message );
304             }
305 
306             // TODO: We should check the attached artifacts to be sure we don't attach
307             // the same file twice...
308             projectHelper.attachArtifact( project, EJB_TYPE, getClassifier(), jarFile );
309         }
310         else
311         {
312             if ( projectHasAlreadySetAnArtifact() )
313             {
314                 throw new MojoExecutionException( "You have to use a classifier "
315                     + "to attach supplemental artifacts to the project instead of replacing them." );
316             }
317 
318             project.getArtifact().setFile( jarFile );
319         }
320 
321         if ( generateClient )
322         {
323             File clientJarFile = generateEjbClient();
324             if ( hasClientClassifier() )
325             {
326                 if ( !isClientClassifierValid() )
327                 {
328                     String message = "The given client classifier '" + getClientClassifier() + "' is not valid.";
329                     getLog().error( message );
330                     throw new MojoExecutionException( message );
331                 }
332 
333                 projectHelper.attachArtifact( project, EJB_CLIENT_TYPE, getClientClassifier(), clientJarFile );
334             }
335             else
336             {
337                 // FIXME: This does not make sense, cause a classifier for the client should always exist otherwise
338                 // Failure!
339                 projectHelper.attachArtifact( project, "ejb-client", getClientClassifier(), clientJarFile );
340             }
341 
342         }
343     }
344 
345     private boolean projectHasAlreadySetAnArtifact()
346     {
347         if ( getProject().getArtifact().getFile() != null )
348         {
349             return getProject().getArtifact().getFile().isFile();
350         }
351         else
352         {
353             return false;
354         }
355     }
356 
357     private File generateEjb()
358         throws MojoExecutionException
359     {
360         File jarFile = EjbHelper.getJarFile( outputDirectory, jarName, getClassifier() );
361 
362         getLog().info( "Building EJB " + jarName + " with EJB version " + ejbVersion );
363 
364         MavenArchiver archiver = new MavenArchiver();
365 
366         archiver.setArchiver( jarArchiver );
367 
368         archiver.setOutputFile( jarFile );
369 
370         File deploymentDescriptor = new File( sourceDirectory, ejbJar );
371 
372         checkEJBVersionCompliance( deploymentDescriptor );
373 
374         try
375         {
376             List<String> defaultExcludes = Lists.newArrayList( ejbJar, "**/package.html" );
377             List<String> defaultIncludes = DEFAULT_INCLUDES_LIST;
378 
379             IncludesExcludes ie =
380                 new IncludesExcludes( Collections.<String>emptyList(), excludes, defaultIncludes, defaultExcludes );
381 
382             archiver.getArchiver().addDirectory( sourceDirectory, ie.resultingIncludes(), ie.resultingExcludes() );
383 
384             // FIXME: We should be able to filter more than just the deployment descriptor?
385             if ( deploymentDescriptor.exists() )
386             {
387                 // EJB-34 Filter ejb-jar.xml
388                 if ( filterDeploymentDescriptor )
389                 {
390                     filterDeploymentDescriptor( deploymentDescriptor );
391                 }
392                 archiver.getArchiver().addFile( deploymentDescriptor, ejbJar );
393             }
394 
395             // create archive
396             archiver.createArchive( session, project, archive );
397         }
398         catch ( ArchiverException e )
399         {
400             throw new MojoExecutionException( "There was a problem creating the EJB archive: " + e.getMessage(), e );
401         }
402         catch ( ManifestException e )
403         {
404             throw new MojoExecutionException( "There was a problem creating the EJB archive: " + e.getMessage(), e );
405         }
406         catch ( IOException e )
407         {
408             throw new MojoExecutionException( "There was a problem creating the EJB archive: " + e.getMessage(), e );
409         }
410         catch ( DependencyResolutionRequiredException e )
411         {
412             throw new MojoExecutionException( "There was a problem creating the EJB archive: " + e.getMessage(), e );
413         }
414         catch ( MavenFilteringException e )
415         {
416             throw new MojoExecutionException( "There was a problem filtering the deployment descriptor: "
417                 + e.getMessage(), e );
418         }
419 
420         return jarFile;
421 
422     }
423 
424     private File generateEjbClient()
425         throws MojoExecutionException
426     {
427         File clientJarFile = EjbHelper.getJarFile( outputDirectory, jarName, getClientClassifier() );
428 
429         getLog().info( "Building EJB client " + clientJarFile.getPath() );
430 
431         MavenArchiver clientArchiver = new MavenArchiver();
432 
433         clientArchiver.setArchiver( clientJarArchiver );
434 
435         clientArchiver.setOutputFile( clientJarFile );
436 
437         try
438         {
439             List<String> defaultExcludes = DEFAULT_CLIENT_EXCLUDES_LIST;
440             List<String> defaultIncludes = DEFAULT_INCLUDES_LIST;
441 
442             IncludesExcludes ie =
443                 new IncludesExcludes( clientIncludes, clientExcludes, defaultIncludes, defaultExcludes );
444 
445             clientArchiver.getArchiver().addDirectory( sourceDirectory, ie.resultingIncludes(),
446                                                        ie.resultingExcludes() );
447 
448             clientArchiver.createArchive( session, project, archive );
449 
450         }
451         catch ( ArchiverException e )
452         {
453             throw new MojoExecutionException( "There was a problem creating the EJB client archive: " + e.getMessage(),
454                                               e );
455         }
456         catch ( ManifestException e )
457         {
458             throw new MojoExecutionException( "There was a problem creating the EJB client archive: " + e.getMessage(),
459                                               e );
460         }
461         catch ( IOException e )
462         {
463             throw new MojoExecutionException( "There was a problem creating the EJB client archive: " + e.getMessage(),
464                                               e );
465         }
466         catch ( DependencyResolutionRequiredException e )
467         {
468             throw new MojoExecutionException( "There was a problem creating the EJB client archive: " + e.getMessage(),
469                                               e );
470         }
471 
472         return clientJarFile;
473     }
474 
475     private void checkEJBVersionCompliance( File deploymentDescriptor )
476         throws MojoExecutionException
477     {
478         if ( !ejbVersion.matches( "\\A[2-3]\\.[0-9]\\z" ) )
479         {
480             throw new MojoExecutionException( "ejbVersion is not valid: " + ejbVersion
481                 + ". Must be 2.x or 3.x (where x is a digit)" );
482         }
483 
484         if ( ejbVersion.matches( "\\A2\\.[0-9]\\z" ) && !deploymentDescriptor.exists() )
485         {
486             throw new MojoExecutionException( "Error assembling EJB: " + ejbJar + " is required for ejbVersion 2.x" );
487         }
488     }
489 
490     private void filterDeploymentDescriptor( File deploymentDescriptor )
491         throws MavenFilteringException, IOException
492     {
493         getLog().debug( "Filtering deployment descriptor." );
494         MavenResourcesExecution mavenResourcesExecution = new MavenResourcesExecution();
495         mavenResourcesExecution.setEscapeString( escapeString );
496         List<FilterWrapper> filterWrappers =
497             mavenFileFilter.getDefaultFilterWrappers( project, filters, escapeBackslashesInFilePath, this.session,
498                                                       mavenResourcesExecution );
499 
500         // Create a temporary file that we can copy-and-filter
501         File unfilteredDeploymentDescriptor = new File( sourceDirectory, ejbJar + ".unfiltered" );
502         FileUtils.copyFile( deploymentDescriptor, unfilteredDeploymentDescriptor );
503         mavenFileFilter.copyFile( unfilteredDeploymentDescriptor, deploymentDescriptor, true, filterWrappers,
504                                   getEncoding( unfilteredDeploymentDescriptor ) );
505         // Remove the temporary file
506         FileUtils.forceDelete( unfilteredDeploymentDescriptor );
507     }
508 
509     /**
510      * @return true in case where the classifier is not {@code null} and contains something else than white spaces.
511      */
512     private boolean hasClassifier()
513     {
514         return EjbHelper.hasClassifier( getClassifier() );
515     }
516 
517     /**
518      * @return true in case where the clientClassifier is not {@code null} and contains something else than white
519      *         spaces.
520      */
521     private boolean hasClientClassifier()
522     {
523         return EjbHelper.hasClassifier( getClientClassifier() );
524     }
525 
526     private boolean isClassifierValid()
527     {
528         return EjbHelper.isClassifierValid( getClassifier() );
529     }
530 
531     private boolean isClientClassifierValid()
532     {
533         return EjbHelper.isClassifierValid( getClientClassifier() );
534     }
535 
536     /**
537      * Get the encoding from an XML-file.
538      *
539      * @param xmlFile the XML-file
540      * @return The encoding of the XML-file, or UTF-8 if it's not specified in the file
541      * @throws IOException if an error occurred while reading the file
542      */
543     private String getEncoding( File xmlFile )
544         throws IOException
545     {
546         XmlStreamReader xmlReader = null;
547         try
548         {
549             xmlReader = new XmlStreamReader( xmlFile );
550             final String encoding = xmlReader.getEncoding();
551             xmlReader.close();
552             xmlReader = null;
553             return encoding;
554         }
555         finally
556         {
557             IOUtils.closeQuietly( xmlReader );
558         }
559     }
560 
561     public String getClassifier()
562     {
563         return classifier;
564     }
565 
566     public String getClientClassifier()
567     {
568         return clientClassifier;
569     }
570 
571     public MavenProject getProject()
572     {
573         return project;
574     }
575 
576 }