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