1 package org.apache.tomcat.maven.runner;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 import org.apache.catalina.Context;
22 import org.apache.catalina.Host;
23 import org.apache.catalina.connector.Connector;
24 import org.apache.catalina.core.StandardContext;
25 import org.apache.catalina.startup.Catalina;
26 import org.apache.catalina.startup.ContextConfig;
27 import org.apache.catalina.startup.Tomcat;
28 import org.apache.catalina.valves.AccessLogValve;
29 import org.apache.juli.ClassLoaderLogManager;
30 import org.apache.tomcat.util.ExceptionUtils;
31 import org.apache.tomcat.util.http.fileupload.FileUtils;
32 import org.apache.tomcat.util.http.fileupload.IOUtils;
33
34 import java.io.BufferedOutputStream;
35 import java.io.File;
36 import java.io.FileInputStream;
37 import java.io.FileNotFoundException;
38 import java.io.FileOutputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.lang.reflect.InvocationTargetException;
42 import java.lang.reflect.Method;
43 import java.net.URISyntaxException;
44 import java.net.URL;
45 import java.security.AccessController;
46 import java.security.PrivilegedAction;
47 import java.util.HashMap;
48 import java.util.Map;
49 import java.util.Properties;
50 import java.util.StringTokenizer;
51 import java.util.logging.LogManager;
52
53
54
55
56
57
58
59
60 public class Tomcat7Runner
61 {
62
63 public static final String USE_SERVER_XML_KEY = "useServerXml";
64
65
66 public static final String WARS_KEY = "wars";
67
68 public static final String ARCHIVE_GENERATION_TIMESTAMP_KEY = "generationTimestamp";
69
70 public static final String ENABLE_NAMING_KEY = "enableNaming";
71
72 public static final String ACCESS_LOG_VALVE_FORMAT_KEY = "accessLogValveFormat";
73
74 public static final String CODE_SOURCE_CONTEXT_PATH = "codeSourceContextPath";
75
76
77
78
79 public static final String HTTP_PROTOCOL_KEY = "connectorhttpProtocol";
80
81
82 public int httpPort;
83
84 public int httpsPort;
85
86 public int ajpPort;
87
88 public String serverXmlPath;
89
90 public Properties runtimeProperties;
91
92 public boolean resetExtract;
93
94 public boolean debug = false;
95
96 public String clientAuth = "false";
97
98 public String keyAlias = null;
99
100 public String httpProtocol;
101
102 public String extractDirectory = ".extract";
103
104 public File extractDirectoryFile;
105
106 public String codeSourceContextPath = null;
107
108 public File codeSourceWar = null;
109
110 public String loggerName;
111
112 Catalina container;
113
114 Tomcat tomcat;
115
116 String uriEncoding = "ISO-8859-1";
117
118
119
120
121 Map<String, String> webappWarPerContext = new HashMap<String, String>();
122
123 public Tomcat7Runner()
124 {
125
126 }
127
128 public void run()
129 throws Exception
130 {
131
132 PasswordUtil.deobfuscateSystemProps();
133
134 if ( loggerName != null && loggerName.length() > 0 )
135 {
136 installLogger( loggerName );
137 }
138
139 this.extractDirectoryFile = new File( this.extractDirectory );
140
141 debugMessage( "use extractDirectory:" + extractDirectoryFile.getPath() );
142
143 boolean archiveTimestampChanged = false;
144
145
146 File timestampFile = new File( extractDirectoryFile, ".tomcat_executable_archive.timestamp" );
147
148 Properties timestampProps = loadProperties( timestampFile );
149
150 if ( timestampFile.exists() )
151 {
152 String timestampValue = timestampProps.getProperty( Tomcat7Runner.ARCHIVE_GENERATION_TIMESTAMP_KEY );
153 if ( timestampValue != null )
154 {
155 long timestamp = Long.parseLong( timestampValue );
156 archiveTimestampChanged =
157 Long.parseLong( runtimeProperties.getProperty( Tomcat7Runner.ARCHIVE_GENERATION_TIMESTAMP_KEY ) )
158 > timestamp;
159
160 debugMessage( "read timestamp from file " + timestampValue + ", archiveTimestampChanged: "
161 + archiveTimestampChanged );
162 }
163
164 }
165
166 codeSourceContextPath = runtimeProperties.getProperty( CODE_SOURCE_CONTEXT_PATH );
167 if ( codeSourceContextPath != null && !codeSourceContextPath.isEmpty() )
168 {
169 codeSourceWar = AccessController.doPrivileged( new PrivilegedAction<File>()
170 {
171 public File run()
172 {
173 try
174 {
175 File src =
176 new File( Tomcat7Runner.class.getProtectionDomain().getCodeSource().getLocation().toURI() );
177 if ( src.getName().endsWith( ".war" ) )
178 {
179 return src;
180 }
181 else
182 {
183 debugMessage( "ERROR: Code source is not a war file, ignoring." );
184 }
185 }
186 catch ( URISyntaxException e )
187 {
188 debugMessage( "ERROR: Could not find code source. " + e.getMessage() );
189
190 }
191 return null;
192 }
193 } );
194 }
195
196
197 {
198 if ( !extractDirectoryFile.exists() || resetExtract || archiveTimestampChanged )
199 {
200 extract();
201
202 if ( archiveTimestampChanged || !timestampFile.exists() )
203 {
204 timestampProps.put( Tomcat7Runner.ARCHIVE_GENERATION_TIMESTAMP_KEY, runtimeProperties.getProperty(
205 Tomcat7Runner.ARCHIVE_GENERATION_TIMESTAMP_KEY ) );
206 saveProperties( timestampProps, timestampFile );
207 }
208 }
209 else
210 {
211 String wars = runtimeProperties.getProperty( WARS_KEY );
212 populateWebAppWarPerContext( wars );
213 }
214 }
215
216
217 new File( extractDirectory, "conf" ).mkdirs();
218 new File( extractDirectory, "logs" ).mkdirs();
219 new File( extractDirectory, "webapps" ).mkdirs();
220 new File( extractDirectory, "work" ).mkdirs();
221 File tmpDir = new File( extractDirectory, "temp" );
222 tmpDir.mkdirs();
223
224 System.setProperty( "java.io.tmpdir", tmpDir.getAbsolutePath() );
225
226 System.setProperty( "catalina.base", extractDirectoryFile.getAbsolutePath() );
227 System.setProperty( "catalina.home", extractDirectoryFile.getAbsolutePath() );
228
229
230 if ( serverXmlPath != null || useServerXml() )
231 {
232 container = new Catalina();
233 container.setUseNaming( this.enableNaming() );
234 if ( serverXmlPath != null && new File( serverXmlPath ).exists() )
235 {
236 container.setConfig( serverXmlPath );
237 }
238 else
239 {
240 container.setConfig( new File( extractDirectory, "conf/server.xml" ).getAbsolutePath() );
241 }
242 container.start();
243 }
244 else
245 {
246 tomcat = new Tomcat()
247 {
248 public Context addWebapp( Host host, String url, String name, String path )
249 {
250
251 Context ctx = new StandardContext();
252 ctx.setName( name );
253 ctx.setPath( url );
254 ctx.setDocBase( path );
255
256 ContextConfig ctxCfg = new ContextConfig();
257 ctx.addLifecycleListener( ctxCfg );
258
259 ctxCfg.setDefaultWebXml( new File( extractDirectory, "conf/web.xml" ).getAbsolutePath() );
260
261 if ( host == null )
262 {
263 getHost().addChild( ctx );
264 }
265 else
266 {
267 host.addChild( ctx );
268 }
269
270 return ctx;
271 }
272 };
273
274 if ( this.enableNaming() )
275 {
276 System.setProperty( "catalina.useNaming", "true" );
277 tomcat.enableNaming();
278 }
279
280 tomcat.getHost().setAppBase( new File( extractDirectory, "webapps" ).getAbsolutePath() );
281
282 String connectorHttpProtocol = runtimeProperties.getProperty( HTTP_PROTOCOL_KEY );
283
284 if ( httpProtocol != null && httpProtocol.trim().length() > 0 )
285 {
286 connectorHttpProtocol = httpProtocol;
287 }
288
289 debugMessage( "use connectorHttpProtocol:" + connectorHttpProtocol );
290
291 if ( httpPort > 0 )
292 {
293 Connector connector = new Connector( connectorHttpProtocol );
294 connector.setPort( httpPort );
295
296 if ( httpsPort > 0 )
297 {
298 connector.setRedirectPort( httpsPort );
299 }
300 connector.setURIEncoding( uriEncoding );
301
302 tomcat.getService().addConnector( connector );
303
304 tomcat.setConnector( connector );
305 }
306
307
308 AccessLogValve alv = new AccessLogValve();
309 alv.setDirectory( new File( extractDirectory, "logs" ).getAbsolutePath() );
310 alv.setPattern( runtimeProperties.getProperty( Tomcat7Runner.ACCESS_LOG_VALVE_FORMAT_KEY ) );
311 tomcat.getHost().getPipeline().addValve( alv );
312
313
314 if ( httpsPort > 0 )
315 {
316 Connector httpsConnector = new Connector( connectorHttpProtocol );
317 httpsConnector.setPort( httpsPort );
318 httpsConnector.setSecure( true );
319 httpsConnector.setProperty( "SSLEnabled", "true" );
320 httpsConnector.setProperty( "sslProtocol", "TLS" );
321 httpsConnector.setURIEncoding( uriEncoding );
322
323 String keystoreFile = System.getProperty( "javax.net.ssl.keyStore" );
324 String keystorePass = System.getProperty( "javax.net.ssl.keyStorePassword" );
325 String keystoreType = System.getProperty( "javax.net.ssl.keyStoreType", "jks" );
326
327 if ( keystoreFile != null )
328 {
329 httpsConnector.setAttribute( "keystoreFile", keystoreFile );
330 }
331 if ( keystorePass != null )
332 {
333 httpsConnector.setAttribute( "keystorePass", keystorePass );
334 }
335 httpsConnector.setAttribute( "keystoreType", keystoreType );
336
337 String truststoreFile = System.getProperty( "javax.net.ssl.trustStore" );
338 String truststorePass = System.getProperty( "javax.net.ssl.trustStorePassword" );
339 String truststoreType = System.getProperty( "javax.net.ssl.trustStoreType", "jks" );
340 if ( truststoreFile != null )
341 {
342 httpsConnector.setAttribute( "truststoreFile", truststoreFile );
343 }
344 if ( truststorePass != null )
345 {
346 httpsConnector.setAttribute( "truststorePass", truststorePass );
347 }
348 httpsConnector.setAttribute( "truststoreType", truststoreType );
349
350 httpsConnector.setAttribute( "clientAuth", clientAuth );
351 httpsConnector.setAttribute( "keyAlias", keyAlias );
352
353 tomcat.getService().addConnector( httpsConnector );
354
355 if ( httpPort <= 0 )
356 {
357 tomcat.setConnector( httpsConnector );
358 }
359 }
360
361
362 if ( ajpPort > 0 )
363 {
364 Connector ajpConnector = new Connector( "org.apache.coyote.ajp.AjpProtocol" );
365 ajpConnector.setPort( ajpPort );
366 ajpConnector.setURIEncoding( uriEncoding );
367 tomcat.getService().addConnector( ajpConnector );
368 }
369
370
371 for ( Map.Entry<String, String> entry : this.webappWarPerContext.entrySet() )
372 {
373 String baseDir = null;
374 Context context = null;
375 if ( entry.getKey().equals( "/" ) )
376 {
377 baseDir = new File( extractDirectory, "webapps/ROOT.war" ).getAbsolutePath();
378 context = tomcat.addWebapp( "", baseDir );
379 }
380 else
381 {
382 baseDir = new File( extractDirectory, "webapps/" + entry.getValue() ).getAbsolutePath();
383 context = tomcat.addWebapp( entry.getKey(), baseDir );
384 }
385
386 URL contextFileUrl = getContextXml( baseDir );
387 if ( contextFileUrl != null )
388 {
389 context.setConfigFile( contextFileUrl );
390 }
391 }
392
393 if ( codeSourceWar != null )
394 {
395 String baseDir = new File( extractDirectory, "webapps/" + codeSourceWar.getName() ).getAbsolutePath();
396 Context context = tomcat.addWebapp( codeSourceContextPath, baseDir );
397 URL contextFileUrl = getContextXml( baseDir );
398 if ( contextFileUrl != null )
399 {
400 context.setConfigFile( contextFileUrl );
401 }
402 }
403
404 tomcat.start();
405
406 Runtime.getRuntime().addShutdownHook( new TomcatShutdownHook() );
407
408 }
409
410 waitIndefinitely();
411
412 }
413
414 protected class TomcatShutdownHook
415 extends Thread
416 {
417
418 protected TomcatShutdownHook()
419 {
420
421 }
422
423 @Override
424 public void run()
425 {
426 try
427 {
428 Tomcat7Runner.this.stop();
429 }
430 catch ( Throwable ex )
431 {
432 ExceptionUtils.handleThrowable( ex );
433 System.out.println( "fail to properly shutdown Tomcat:" + ex.getMessage() );
434 }
435 finally
436 {
437
438
439 LogManager logManager = LogManager.getLogManager();
440 if ( logManager instanceof ClassLoaderLogManager )
441 {
442 ( (ClassLoaderLogManager) logManager ).shutdown();
443 }
444 }
445 }
446 }
447
448 private URL getContextXml( String warPath )
449 throws IOException
450 {
451 InputStream inputStream = null;
452 try
453 {
454 String urlStr = "jar:file:" + warPath + "!/META-INF/context.xml";
455 debugMessage( "search context.xml in url:'" + urlStr + "'" );
456 URL url = new URL( urlStr );
457 inputStream = url.openConnection().getInputStream();
458 if ( inputStream != null )
459 {
460 return url;
461 }
462 }
463 catch ( FileNotFoundException e )
464 {
465 return null;
466 }
467 finally
468 {
469 IOUtils.closeQuietly( inputStream );
470 }
471 return null;
472 }
473
474 private void waitIndefinitely()
475 {
476 Object lock = new Object();
477
478 synchronized ( lock )
479 {
480 try
481 {
482 lock.wait();
483 }
484 catch ( InterruptedException exception )
485 {
486 throw new Error( "InterruptedException on wait Indefinitely lock:" + exception.getMessage(),
487 exception );
488 }
489 }
490 }
491
492 public void stop()
493 throws Exception
494 {
495 if ( container != null )
496 {
497 container.stop();
498 }
499 if ( tomcat != null )
500 {
501 tomcat.stop();
502 }
503 }
504
505 protected void extract()
506 throws Exception
507 {
508
509 if ( extractDirectoryFile.exists() )
510 {
511 debugMessage( "delete extractDirectory:" + extractDirectoryFile.getAbsolutePath() );
512 FileUtils.deleteDirectory( extractDirectoryFile );
513 }
514
515 if ( !this.extractDirectoryFile.exists() )
516 {
517 boolean created = this.extractDirectoryFile.mkdirs();
518 if ( !created )
519 {
520 throw new Exception( "FATAL: impossible to create directory:" + this.extractDirectoryFile.getPath() );
521 }
522 }
523
524
525 boolean created = new File( extractDirectory, "webapps" ).mkdirs();
526 if ( !created )
527 {
528 throw new Exception(
529 "FATAL: impossible to create directory:" + this.extractDirectoryFile.getPath() + "/webapps" );
530
531 }
532
533 String wars = runtimeProperties.getProperty( WARS_KEY );
534 populateWebAppWarPerContext( wars );
535
536 for ( Map.Entry<String, String> entry : webappWarPerContext.entrySet() )
537 {
538 debugMessage( "webappWarPerContext entry key/value: " + entry.getKey() + "/" + entry.getValue() );
539 InputStream inputStream = null;
540 try
541 {
542 inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( entry.getValue() );
543 if ( !useServerXml() )
544 {
545 if ( entry.getKey().equals( "/" ) )
546 {
547 File expandFile = new File( extractDirectory, "webapps/ROOT.war" );
548 debugMessage( "expand to file:" + expandFile.getPath() );
549 expand( inputStream, expandFile );
550 }
551 else
552 {
553 File expandFile = new File( extractDirectory, "webapps/" + entry.getValue() );
554 debugMessage( "expand to file:" + expandFile.getPath() );
555 expand( inputStream, expandFile );
556 }
557 }
558 else
559 {
560 File expandFile = new File( extractDirectory, "webapps/" + entry.getValue() );
561 debugMessage( "expand to file:" + expandFile.getPath() );
562 expand( inputStream, new File( extractDirectory, "webapps/" + entry.getValue() ) );
563 }
564 }
565 finally
566 {
567 if ( inputStream != null )
568 {
569 inputStream.close();
570 }
571 }
572 }
573
574
575 if ( codeSourceWar != null )
576 {
577 FileInputStream inputStream = null;
578 try
579 {
580 File expandFile = new File( extractDirectory, "webapps/" + codeSourceContextPath + ".war" );
581 inputStream = new FileInputStream( codeSourceWar );
582 debugMessage( "move code source to file:" + expandFile.getPath() );
583 expand( inputStream, expandFile );
584 }
585 finally
586 {
587 if ( inputStream != null )
588 {
589 inputStream.close();
590 }
591 }
592 }
593
594
595 expandConfigurationFile( "catalina.properties", extractDirectoryFile );
596 expandConfigurationFile( "logging.properties", extractDirectoryFile );
597 expandConfigurationFile( "tomcat-users.xml", extractDirectoryFile );
598 expandConfigurationFile( "catalina.policy", extractDirectoryFile );
599 expandConfigurationFile( "context.xml", extractDirectoryFile );
600 expandConfigurationFile( "server.xml", extractDirectoryFile );
601 expandConfigurationFile( "web.xml", extractDirectoryFile );
602
603 }
604
605 private static void expandConfigurationFile( String fileName, File extractDirectory )
606 throws Exception
607 {
608 InputStream inputStream = null;
609 try
610 {
611 inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( "conf/" + fileName );
612 if ( inputStream != null )
613 {
614 File confDirectory = new File( extractDirectory, "conf" );
615 if ( !confDirectory.exists() )
616 {
617 confDirectory.mkdirs();
618 }
619 expand( inputStream, new File( confDirectory, fileName ) );
620 }
621 }
622 finally
623 {
624 if ( inputStream != null )
625 {
626 inputStream.close();
627 }
628 }
629
630 }
631
632
633
634
635
636 private void populateWebAppWarPerContext( String warsValue )
637 {
638 if ( warsValue == null )
639 {
640 return;
641 }
642
643 StringTokenizer st = new StringTokenizer( warsValue, ";" );
644 while ( st.hasMoreTokens() )
645 {
646 String warValue = st.nextToken();
647 debugMessage( "populateWebAppWarPerContext warValue:" + warValue );
648 String warFileName = "";
649 String contextValue = "";
650 int separatorIndex = warValue.indexOf( "|" );
651 if ( separatorIndex >= 0 )
652 {
653 warFileName = warValue.substring( 0, separatorIndex );
654 contextValue = warValue.substring( separatorIndex + 1, warValue.length() );
655
656 }
657 else
658 {
659 warFileName = contextValue;
660 }
661 debugMessage( "populateWebAppWarPerContext contextValue/warFileName:" + contextValue + "/" + warFileName );
662 this.webappWarPerContext.put( contextValue, warFileName );
663 }
664 }
665
666
667
668
669
670
671
672
673
674 private static void expand( InputStream input, File file )
675 throws IOException
676 {
677 BufferedOutputStream output = null;
678 try
679 {
680 output = new BufferedOutputStream( new FileOutputStream( file ) );
681 byte buffer[] = new byte[2048];
682 while ( true )
683 {
684 int n = input.read( buffer );
685 if ( n <= 0 )
686 {
687 break;
688 }
689 output.write( buffer, 0, n );
690 }
691 }
692 finally
693 {
694 if ( output != null )
695 {
696 try
697 {
698 output.close();
699 }
700 catch ( IOException e )
701 {
702
703 }
704 }
705 }
706 }
707
708 public boolean useServerXml()
709 {
710 return Boolean.parseBoolean( runtimeProperties.getProperty( USE_SERVER_XML_KEY, Boolean.FALSE.toString() ) );
711 }
712
713
714 public void debugMessage( String message )
715 {
716 if ( debug )
717 {
718 System.out.println( message );
719 }
720 }
721
722
723 public boolean enableNaming()
724 {
725 return Boolean.parseBoolean( runtimeProperties.getProperty( ENABLE_NAMING_KEY, Boolean.FALSE.toString() ) );
726 }
727
728 private void installLogger( String loggerName )
729 throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
730 InvocationTargetException
731 {
732 if ( "slf4j".equals( loggerName ) )
733 {
734
735 try
736 {
737
738
739
740 final Class<?> clazz =
741 Thread.currentThread().getContextClassLoader().loadClass( "org.slf4j.bridge.SLF4JBridgeHandler" );
742
743
744 java.util.logging.LogManager.getLogManager().reset();
745
746
747 final Method method = clazz.getMethod( "install", null );
748 method.invoke( null );
749 }
750 catch ( ClassNotFoundException e )
751 {
752 System.out.println( "WARNING: issue configuring slf4j jul bridge, skip it" );
753 }
754 }
755 else
756 {
757 System.out.println( "WARNING: loggerName " + loggerName + " not supported, skip it" );
758 }
759 }
760
761 private Properties loadProperties( File file )
762 throws FileNotFoundException, IOException
763 {
764 Properties properties = new Properties();
765 if ( file.exists() )
766 {
767
768 FileInputStream fileInputStream = new FileInputStream( file );
769 try
770 {
771 properties.load( fileInputStream );
772 }
773 finally
774 {
775 fileInputStream.close();
776 }
777
778 }
779 return properties;
780 }
781
782 private void saveProperties( Properties properties, File file )
783 throws FileNotFoundException, IOException
784 {
785 FileOutputStream fileOutputStream = new FileOutputStream( file );
786 try
787 {
788 properties.store( fileOutputStream, "Timestamp file for executable war/jar" );
789 }
790 finally
791 {
792 fileOutputStream.close();
793 }
794 }
795 }