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 }