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 }