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