View Javadoc

1   package org.apache.tomcat.maven.plugin.tomcat7.run;
2   /*
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  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,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   */
20  
21  import org.apache.commons.compress.archivers.ArchiveException;
22  import org.apache.commons.compress.archivers.ArchiveOutputStream;
23  import org.apache.commons.compress.archivers.ArchiveStreamFactory;
24  import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
25  import org.apache.commons.io.FileUtils;
26  import org.apache.commons.io.IOUtils;
27  import org.apache.commons.lang.StringUtils;
28  import org.apache.maven.artifact.Artifact;
29  import org.apache.maven.artifact.factory.ArtifactFactory;
30  import org.apache.maven.artifact.repository.ArtifactRepository;
31  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
32  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
33  import org.apache.maven.artifact.resolver.ArtifactResolver;
34  import org.apache.maven.model.Dependency;
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.Parameter;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.project.MavenProjectHelper;
41  import org.apache.tomcat.maven.plugin.tomcat7.AbstractTomcat7Mojo;
42  import org.apache.tomcat.maven.runner.Tomcat7Runner;
43  import org.apache.tomcat.maven.runner.Tomcat7RunnerCli;
44  import org.codehaus.plexus.archiver.jar.Manifest;
45  import org.codehaus.plexus.archiver.jar.ManifestException;
46  import org.codehaus.plexus.util.DirectoryScanner;
47  
48  import java.io.File;
49  import java.io.FileInputStream;
50  import java.io.FileNotFoundException;
51  import java.io.FileOutputStream;
52  import java.io.IOException;
53  import java.io.InputStream;
54  import java.io.OutputStream;
55  import java.io.PrintWriter;
56  import java.util.ArrayList;
57  import java.util.Enumeration;
58  import java.util.Iterator;
59  import java.util.List;
60  import java.util.Properties;
61  import java.util.jar.JarEntry;
62  import java.util.jar.JarFile;
63  
64  /**
65   * @author Olivier Lamy
66   * @since 2.0
67   */
68  public abstract class AbstractExecWarMojo
69      extends AbstractTomcat7Mojo
70  {
71  
72      @Parameter (defaultValue = "${project.artifact}", required = true, readonly = true)
73      private Artifact projectArtifact;
74  
75      /**
76       * The maven project.
77       */
78      @Parameter (defaultValue = "${project}", required = true, readonly = true)
79      protected MavenProject project;
80  
81      @Parameter (defaultValue = "${plugin.artifacts}", required = true)
82      private List<Artifact> pluginArtifacts;
83  
84      @Parameter (defaultValue = "${project.build.directory}")
85      private File buildDirectory;
86  
87      /**
88       * Path under {@link #buildDirectory} where this mojo may do temporary work.
89       */
90      @Parameter (defaultValue = "${project.build.directory}/tomcat7-maven-plugin-exec")
91      private File pluginWorkDirectory;
92  
93      @Parameter (property = "maven.tomcat.exec.war.tomcatConf", defaultValue = "src/main/tomcatconf")
94      private File tomcatConfigurationFilesDirectory;
95  
96      @Parameter (defaultValue = "src/main/tomcatconf/server.xml", property = "maven.tomcat.exec.war.serverXml")
97      private File serverXml;
98  
99      /**
100      * Name of the generated exec JAR.
101      */
102     @Parameter (property = "tomcat.jar.finalName",
103                 defaultValue = "${project.artifactId}-${project.version}-war-exec.jar", required = true)
104     private String finalName;
105 
106     /**
107      * The webapp context path to use for the web application being run.
108      * The name to store webapp in exec jar. Do not use /
109      */
110     @Parameter (property = "maven.tomcat.path", defaultValue = "${project.artifactId}", required = true)
111     protected String path;
112 
113     @Parameter
114     protected List<WarRunDependency> warRunDependencies;
115 
116     @Component
117     protected ArtifactResolver artifactResolver;
118 
119     /**
120      * Maven Artifact Factory component.
121      */
122     @Component
123     private ArtifactFactory artifactFactory;
124 
125     /**
126      * Location of the local repository.
127      */
128     @Parameter (defaultValue = "${localRepository}", required = true, readonly = true)
129     private ArtifactRepository local;
130 
131     /**
132      * List of Remote Repositories used by the resolver
133      */
134     @Parameter (defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true)
135     protected List<ArtifactRepository> remoteRepos;
136 
137     @Component
138     private MavenProjectHelper projectHelper;
139 
140     /**
141      * Attach or not the generated artifact to the build (use true if you want to install or deploy it)
142      */
143     @Parameter (property = "maven.tomcat.exec.war.attachArtifact", defaultValue = "true", required = true)
144     private boolean attachArtifact;
145 
146 
147     /**
148      * the classifier to use for the attached/generated artifact
149      */
150     @Parameter (property = "maven.tomcat.exec.war.attachArtifactClassifier", defaultValue = "exec-war",
151                 required = true)
152     private String attachArtifactClassifier;
153 
154 
155     /**
156      * the type to use for the attached/generated artifact
157      */
158     @Parameter (property = "maven.tomcat.exec.war.attachArtifactType", defaultValue = "jar", required = true)
159     private String attachArtifactClassifierType;
160 
161     /**
162      * to enable naming when starting tomcat
163      */
164     @Parameter (property = "maven.tomcat.exec.war.enableNaming", defaultValue = "false", required = true)
165     private boolean enableNaming;
166 
167     /**
168      * see http://tomcat.apache.org/tomcat-7.0-doc/config/valve.html
169      */
170     @Parameter (property = "maven.tomcat.exec.war.accessLogValveFormat", defaultValue = "%h %l %u %t %r %s %b %I %D",
171                 required = true)
172     private String accessLogValveFormat;
173 
174     /**
175      * list of extra dependencies to add in the standalone tomcat jar: your jdbc driver, mail.jar etc..
176      * <b>Those dependencies will be in root classloader.</b>
177      */
178     @Parameter
179     private List<ExtraDependency> extraDependencies;
180 
181     /**
182      * list of extra resources to add in the standalone tomcat jar: your logger configuration etc
183      */
184     @Parameter
185     private List<ExtraResource> extraResources;
186 
187     /**
188      * Main class to use for starting the standalone jar.
189      */
190     @Parameter (property = "maven.tomcat.exec.war.mainClass",
191                 defaultValue = "org.apache.tomcat.maven.runner.Tomcat7RunnerCli", required = true)
192     private String mainClass;
193 
194     /**
195      * which connector protocol to use HTTP/1.1 or org.apache.coyote.http11.Http11NioProtocol
196      */
197     @Parameter (property = "maven.tomcat.exec.war.connectorHttpProtocol", defaultValue = "HTTP/1.1", required = true)
198     private String connectorHttpProtocol;
199 
200     public void execute()
201         throws MojoExecutionException, MojoFailureException
202     {
203 
204         //project.addAttachedArtifact(  );
205         File warExecFile = new File( buildDirectory, finalName );
206         if ( warExecFile.exists() )
207         {
208             warExecFile.delete();
209         }
210 
211         File execWarJar = new File( buildDirectory, finalName );
212 
213         FileOutputStream execWarJarOutputStream = null;
214         ArchiveOutputStream os = null;
215         File tmpPropertiesFile = null;
216         File tmpManifestFile = null;
217         FileOutputStream tmpPropertiesFileOutputStream = null;
218         PrintWriter tmpManifestWriter = null;
219 
220         try
221         {
222 
223             tmpPropertiesFile = new File( buildDirectory, "war-exec.properties" );
224             if ( tmpPropertiesFile.exists() )
225             {
226                 tmpPropertiesFile.delete();
227             }
228             tmpPropertiesFile.getParentFile().mkdirs();
229 
230             tmpManifestFile = new File( buildDirectory, "war-exec.manifest" );
231             if ( tmpManifestFile.exists() )
232             {
233                 tmpManifestFile.delete();
234             }
235             tmpPropertiesFileOutputStream = new FileOutputStream( tmpPropertiesFile );
236             execWarJar.getParentFile().mkdirs();
237             execWarJar.createNewFile();
238             execWarJarOutputStream = new FileOutputStream( execWarJar );
239 
240             tmpManifestWriter = new PrintWriter( tmpManifestFile );
241 
242             // store :
243             //* wars in the root: foo.war
244             //* tomcat jars
245             //* file tomcat.standalone.properties with possible values :
246             //   * useServerXml=true/false to use directly the one provided
247             //   * enableNaming=true/false
248             //   * wars=foo.war|contextpath;bar.war  ( |contextpath is optionnal if empty use the war name )
249             //   * accessLogValveFormat=
250             //   * connectorhttpProtocol: HTTP/1.1 or org.apache.coyote.http11.Http11NioProtocol
251             //* optionnal: conf/ with usual tomcat configuration files
252             //* MANIFEST with Main-Class
253 
254             Properties properties = new Properties();
255 
256             properties.put( Tomcat7Runner.ARCHIVE_GENERATION_TIMESTAMP_KEY,
257                             Long.toString( System.currentTimeMillis() ) );
258             properties.put( Tomcat7Runner.ENABLE_NAMING_KEY, Boolean.toString( enableNaming ) );
259             properties.put( Tomcat7Runner.ACCESS_LOG_VALVE_FORMAT_KEY, accessLogValveFormat );
260             properties.put( Tomcat7Runner.HTTP_PROTOCOL_KEY, connectorHttpProtocol );
261 
262             os = new ArchiveStreamFactory().createArchiveOutputStream( ArchiveStreamFactory.JAR,
263                                                                        execWarJarOutputStream );
264 
265             if ( "war".equals( project.getPackaging() ) )
266             {
267 
268                 os.putArchiveEntry( new JarArchiveEntry( StringUtils.removeStart( path, "/" ) + ".war" ) );
269                 IOUtils.copy( new FileInputStream( projectArtifact.getFile() ), os );
270                 os.closeArchiveEntry();
271 
272                 properties.put( Tomcat7Runner.WARS_KEY, StringUtils.removeStart( path, "/" ) + ".war|" + path );
273             }
274             else if ( warRunDependencies != null && !warRunDependencies.isEmpty() )
275             {
276                 for ( WarRunDependency warRunDependency : warRunDependencies )
277                 {
278                     if ( warRunDependency.dependency != null )
279                     {
280                         Dependency dependency = warRunDependency.dependency;
281                         Artifact artifact = artifactFactory.createArtifactWithClassifier( dependency.getGroupId(),
282                                                                                           dependency.getArtifactId(),
283                                                                                           dependency.getVersion(),
284                                                                                           dependency.getType(),
285                                                                                           dependency.getClassifier() );
286 
287                         artifactResolver.resolve( artifact, this.remoteRepos, this.local );
288 
289                         File warFileToBundle = new File( resolvePluginWorkDir(), artifact.getFile().getName() );
290                         FileUtils.copyFile( artifact.getFile(), warFileToBundle );
291 
292                         if ( warRunDependency.contextXml != null )
293                         {
294                             warFileToBundle = addContextXmlToWar( warRunDependency.contextXml, warFileToBundle );
295                         }
296                         final String warFileName = artifact.getFile().getName();
297                         os.putArchiveEntry( new JarArchiveEntry( warFileName ) );
298                         IOUtils.copy( new FileInputStream( warFileToBundle ), os );
299                         os.closeArchiveEntry();
300                         String propertyWarValue = properties.getProperty( Tomcat7Runner.WARS_KEY );
301                         String contextPath =
302                             StringUtils.isEmpty( warRunDependency.contextPath ) ? "/" : warRunDependency.contextPath;
303                         if ( propertyWarValue != null )
304                         {
305                             properties.put( Tomcat7Runner.WARS_KEY,
306                                             propertyWarValue + ";" + warFileName + "|" + contextPath );
307                         }
308                         else
309                         {
310                             properties.put( Tomcat7Runner.WARS_KEY, warFileName + "|" + contextPath );
311                         }
312                     }
313                 }
314             }
315 
316             if ( serverXml != null && serverXml.exists() )
317             {
318                 os.putArchiveEntry( new JarArchiveEntry( "conf/server.xml" ) );
319                 IOUtils.copy( new FileInputStream( serverXml ), os );
320                 os.closeArchiveEntry();
321                 properties.put( Tomcat7Runner.USE_SERVER_XML_KEY, Boolean.TRUE.toString() );
322             }
323             else
324             {
325                 properties.put( Tomcat7Runner.USE_SERVER_XML_KEY, Boolean.FALSE.toString() );
326             }
327 
328             os.putArchiveEntry( new JarArchiveEntry( "conf/web.xml" ) );
329             IOUtils.copy( getClass().getResourceAsStream( "/conf/web.xml" ), os );
330             os.closeArchiveEntry();
331 
332             properties.store( tmpPropertiesFileOutputStream, "created by Apache Tomcat Maven plugin" );
333 
334             tmpPropertiesFileOutputStream.flush();
335             tmpPropertiesFileOutputStream.close();
336 
337             os.putArchiveEntry( new JarArchiveEntry( Tomcat7RunnerCli.STAND_ALONE_PROPERTIES_FILENAME ) );
338             IOUtils.copy( new FileInputStream( tmpPropertiesFile ), os );
339             os.closeArchiveEntry();
340 
341             // add tomcat classes
342             for ( Artifact pluginArtifact : pluginArtifacts )
343             {
344                 if ( StringUtils.equals( "org.apache.tomcat", pluginArtifact.getGroupId() ) || StringUtils.equals(
345                     "org.apache.tomcat.embed", pluginArtifact.getGroupId() ) || StringUtils.equals(
346                     "org.eclipse.jdt.core.compiler", pluginArtifact.getGroupId() ) || StringUtils.equals( "commons-cli",
347                                                                                                           pluginArtifact.getArtifactId() )
348                     || StringUtils.equals( "tomcat7-war-runner", pluginArtifact.getArtifactId() ) )
349                 {
350                     JarFile jarFile = new JarFile( pluginArtifact.getFile() );
351                     Enumeration<JarEntry> jarEntries = jarFile.entries();
352                     while ( jarEntries.hasMoreElements() )
353                     {
354                         JarEntry jarEntry = jarEntries.nextElement();
355                         InputStream jarEntryIs = jarFile.getInputStream( jarEntry );
356 
357                         os.putArchiveEntry( new JarArchiveEntry( jarEntry.getName() ) );
358                         IOUtils.copy( jarEntryIs, os );
359                         os.closeArchiveEntry();
360                     }
361                 }
362             }
363 
364             // add extra dependencies
365             if ( extraDependencies != null && !extraDependencies.isEmpty() )
366             {
367                 for ( Dependency dependency : extraDependencies )
368                 {
369                     // String groupId, String artifactId, String version, String scope, String type
370                     Artifact artifact =
371                         artifactFactory.createArtifact( dependency.getGroupId(), dependency.getArtifactId(),
372                                                         dependency.getVersion(), dependency.getScope(),
373                                                         dependency.getType() );
374 
375                     artifactResolver.resolve( artifact, this.remoteRepos, this.local );
376                     JarFile jarFile = new JarFile( artifact.getFile() );
377                     Enumeration<JarEntry> jarEntries = jarFile.entries();
378                     while ( jarEntries.hasMoreElements() )
379                     {
380                         JarEntry jarEntry = jarEntries.nextElement();
381                         InputStream jarEntryIs = jarFile.getInputStream( jarEntry );
382 
383                         os.putArchiveEntry( new JarArchiveEntry( jarEntry.getName() ) );
384                         IOUtils.copy( jarEntryIs, os );
385                         os.closeArchiveEntry();
386                     }
387                 }
388             }
389 
390             Manifest manifest = new Manifest();
391 
392             Manifest.Attribute mainClassAtt = new Manifest.Attribute();
393             mainClassAtt.setName( "Main-Class" );
394             mainClassAtt.setValue( mainClass );
395             manifest.addConfiguredAttribute( mainClassAtt );
396 
397             manifest.write( tmpManifestWriter );
398             tmpManifestWriter.flush();
399             tmpManifestWriter.close();
400 
401             os.putArchiveEntry( new JarArchiveEntry( "META-INF/MANIFEST.MF" ) );
402             IOUtils.copy( new FileInputStream( tmpManifestFile ), os );
403             os.closeArchiveEntry();
404 
405             if ( attachArtifact )
406             {
407                 //MavenProject project, String artifactType, String artifactClassifier, File artifactFile
408                 projectHelper.attachArtifact( project, attachArtifactClassifierType, attachArtifactClassifier,
409                                               execWarJar );
410             }
411 
412             if ( extraResources != null )
413             {
414                 for ( ExtraResource extraResource : extraResources )
415                 {
416 
417                     DirectoryScanner directoryScanner = new DirectoryScanner();
418                     directoryScanner.setBasedir( extraResource.getDirectory() );
419                     directoryScanner.addDefaultExcludes();
420                     directoryScanner.setExcludes( toStringArray( extraResource.getExcludes() ) );
421                     directoryScanner.setIncludes( toStringArray( extraResource.getIncludes() ) );
422                     directoryScanner.scan();
423                     for ( String includeFile : directoryScanner.getIncludedFiles() )
424                     {
425                         getLog().debug( "include file:" + includeFile );
426                         os.putArchiveEntry( new JarArchiveEntry( includeFile ) );
427                         IOUtils.copy( new FileInputStream( new File( extraResource.getDirectory(), includeFile ) ),
428                                       os );
429                         os.closeArchiveEntry();
430                     }
431                 }
432             }
433 
434             if ( tomcatConfigurationFilesDirectory != null && tomcatConfigurationFilesDirectory.exists() )
435             {
436                 // Because its the tomcat default dir for configs
437                 String aConfigOutputDir = "conf/";
438                 copyDirectoryContentIntoArchive( tomcatConfigurationFilesDirectory, aConfigOutputDir, os );
439             }
440 
441         }
442         catch ( ManifestException e )
443         {
444             throw new MojoExecutionException( e.getMessage(), e );
445         }
446         catch ( IOException e )
447         {
448             throw new MojoExecutionException( e.getMessage(), e );
449         }
450         catch ( ArchiveException e )
451         {
452             throw new MojoExecutionException( e.getMessage(), e );
453         }
454         catch ( ArtifactNotFoundException e )
455         {
456             throw new MojoExecutionException( e.getMessage(), e );
457         }
458         catch ( ArtifactResolutionException e )
459         {
460             throw new MojoExecutionException( e.getMessage(), e );
461         }
462         finally
463         {
464             IOUtils.closeQuietly( os );
465             IOUtils.closeQuietly( tmpManifestWriter );
466             IOUtils.closeQuietly( execWarJarOutputStream );
467             IOUtils.closeQuietly( tmpPropertiesFileOutputStream );
468         }
469     }
470 
471     private void copyDirectoryContentIntoArchive( File pSourceFolder, String pDestinationPath,
472                                                   ArchiveOutputStream pArchiveOutputSteam )
473         throws FileNotFoundException, IOException
474     {
475 
476         // Scan the directory
477         DirectoryScanner directoryScanner = new DirectoryScanner();
478         directoryScanner.setBasedir( pSourceFolder );
479         directoryScanner.addDefaultExcludes();
480         directoryScanner.scan();
481 
482         // Each File
483         for ( String aIncludeFileName : directoryScanner.getIncludedFiles() )
484         {
485             getLog().debug( "include configuration file : " + pDestinationPath + aIncludeFileName );
486             File aInputFile = new File( pSourceFolder, aIncludeFileName );
487 
488             FileInputStream aSourceFileInputStream = new FileInputStream( aInputFile );
489 
490             pArchiveOutputSteam.putArchiveEntry( new JarArchiveEntry( pDestinationPath + aIncludeFileName ) );
491             IOUtils.copy( aSourceFileInputStream, pArchiveOutputSteam );
492             pArchiveOutputSteam.closeArchiveEntry();
493 
494         }
495 
496     }
497 
498     /**
499      * Resolves the plugin work dir as a sub directory of {@link #buildDirectory}, creating it if it does not exist.
500      *
501      * @return File representing the resolved plugin work dir
502      * @throws MojoExecutionException if the plugin work dir cannot be created
503      */
504     protected File resolvePluginWorkDir()
505         throws MojoExecutionException
506     {
507         if ( !pluginWorkDirectory.exists() && !pluginWorkDirectory.mkdirs() )
508         {
509             throw new MojoExecutionException(
510                 "Could not create plugin work directory at " + pluginWorkDirectory.getAbsolutePath() );
511         }
512 
513         return pluginWorkDirectory;
514 
515     }
516 
517     private String[] toStringArray( List list )
518     {
519         if ( list == null || list.isEmpty() )
520         {
521             return new String[0];
522         }
523         List<String> res = new ArrayList<String>( list.size() );
524 
525         for ( Iterator ite = list.iterator(); ite.hasNext(); )
526         {
527             res.add( (String) ite.next() );
528         }
529         return res.toArray( new String[res.size()] );
530     }
531 
532 
533     /**
534      * return file can be deleted
535      */
536     private File addContextXmlToWar( File contextXmlFile, File warFile )
537         throws IOException, ArchiveException
538     {
539         ArchiveOutputStream os = null;
540         OutputStream warOutputStream = null;
541         File tmpWar = File.createTempFile( "tomcat", "war-exec" );
542         tmpWar.deleteOnExit();
543 
544         try
545         {
546             warOutputStream = new FileOutputStream( tmpWar );
547             os = new ArchiveStreamFactory().createArchiveOutputStream( ArchiveStreamFactory.JAR, warOutputStream );
548             os.putArchiveEntry( new JarArchiveEntry( "META-INF/context.xml" ) );
549             IOUtils.copy( new FileInputStream( contextXmlFile ), os );
550             os.closeArchiveEntry();
551 
552             JarFile jarFile = new JarFile( warFile );
553             Enumeration<JarEntry> jarEntries = jarFile.entries();
554             while ( jarEntries.hasMoreElements() )
555             {
556                 JarEntry jarEntry = jarEntries.nextElement();
557                 os.putArchiveEntry( new JarArchiveEntry( jarEntry.getName() ) );
558                 IOUtils.copy( jarFile.getInputStream( jarEntry ), os );
559                 os.closeArchiveEntry();
560             }
561             os.flush();
562         }
563         finally
564         {
565             IOUtils.closeQuietly( os );
566             IOUtils.closeQuietly( warOutputStream );
567         }
568         return tmpWar;
569     }
570 }