View Javadoc

1   package org.apache.tomcat.maven.runner;
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.catalina.Context;
22  import org.apache.catalina.connector.Connector;
23  import org.apache.catalina.startup.Catalina;
24  import org.apache.catalina.startup.Tomcat;
25  import org.apache.catalina.valves.AccessLogValve;
26  import org.apache.tomcat.util.http.fileupload.FileUtils;
27  import org.apache.tomcat.util.http.fileupload.IOUtils;
28  
29  import java.io.BufferedOutputStream;
30  import java.io.File;
31  import java.io.FileNotFoundException;
32  import java.io.FileOutputStream;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.lang.reflect.InvocationTargetException;
36  import java.lang.reflect.Method;
37  import java.net.URL;
38  import java.util.HashMap;
39  import java.util.Map;
40  import java.util.Properties;
41  import java.util.StringTokenizer;
42  
43  /**
44   * FIXME add junit for that but when https://issues.apache.org/bugzilla/show_bug.cgi?id=52028 fixed
45   * Main class used to run the standalone wars in a Apache Tomcat instance.
46   *
47   * @author Olivier Lamy
48   * @since 2.0
49   */
50  public class Tomcat7Runner
51  {
52      // true/false to use the server.xml located in the jar /conf/server.xml
53      public static final String USE_SERVER_XML_KEY = "useServerXml";
54  
55      // contains war name wars=foo.war,bar.war
56      public static final String WARS_KEY = "wars";
57  
58      public static final String ENABLE_NAMING_KEY = "enableNaming";
59  
60      public static final String ACCESS_LOG_VALVE_FORMAT_KEY = "accessLogValveFormat";
61  
62      /**
63       * key of the property which contains http protocol : HTTP/1.1 or org.apache.coyote.http11.Http11NioProtocol
64       */
65      public static final String HTTP_PROTOCOL_KEY = "connectorhttpProtocol";
66  
67  
68      public int httpPort;
69  
70      public int httpsPort;
71  
72      public int ajpPort;
73  
74      public String serverXmlPath;
75  
76      public Properties runtimeProperties;
77  
78      public boolean resetExtract;
79  
80      public boolean debug = false;
81  
82      public boolean clientAuth = false;
83  
84      public String keyAlias = null;
85  
86      public String httpProtocol;
87  
88      public String extractDirectory = ".extract";
89  
90      public File extractDirectoryFile;
91  
92      public String loggerName;
93  
94      Catalina container;
95  
96      Tomcat tomcat;
97  
98      String uriEncoding = "ISO-8859-1";
99  
100     /**
101      * key = context of the webapp, value = war path on file system
102      */
103     Map<String, String> webappWarPerContext = new HashMap<String, String>();
104 
105     public Tomcat7Runner()
106     {
107         // no op
108     }
109 
110     public void run()
111         throws Exception
112     {
113 
114         PasswordUtil.deobfuscateSystemProps();
115 
116         if ( loggerName != null && loggerName.length() > 0 )
117         {
118             installLogger( loggerName );
119         }
120 
121         this.extractDirectoryFile = new File( this.extractDirectory );
122 
123         debugMessage( "use extractDirectory:" + extractDirectoryFile.getPath() );
124 
125         // do we have to extract content
126         if ( !extractDirectoryFile.exists() || resetExtract )
127         {
128             extract();
129         }
130         else
131         {
132             String wars = runtimeProperties.getProperty( WARS_KEY );
133             populateWebAppWarPerContext( wars );
134         }
135 
136         // create tomcat various paths
137         new File( extractDirectory, "conf" ).mkdirs();
138         new File( extractDirectory, "logs" ).mkdirs();
139         new File( extractDirectory, "webapps" ).mkdirs();
140         new File( extractDirectory, "work" ).mkdirs();
141         File tmpDir = new File( extractDirectory, "temp" );
142         tmpDir.mkdirs();
143 
144         System.setProperty( "java.io.tmpdir", tmpDir.getAbsolutePath() );
145 
146         System.setProperty( "catalina.base", extractDirectoryFile.getAbsolutePath() );
147         System.setProperty( "catalina.home", extractDirectoryFile.getAbsolutePath() );
148 
149         // start with a server.xml
150         if ( serverXmlPath != null || useServerXml() )
151         {
152             container = new Catalina();
153             container.setUseNaming( this.enableNaming() );
154             if ( serverXmlPath != null && new File( serverXmlPath ).exists() )
155             {
156                 container.setConfig( serverXmlPath );
157             }
158             else
159             {
160                 container.setConfig( new File( extractDirectory, "conf/server.xml" ).getAbsolutePath() );
161             }
162             container.start();
163         }
164         else
165         {
166             tomcat = new Tomcat();
167 
168             if ( this.enableNaming() )
169             {
170                 System.setProperty( "catalina.useNaming", "true" );
171                 tomcat.enableNaming();
172             }
173 
174             tomcat.getHost().setAppBase( new File( extractDirectory, "webapps" ).getAbsolutePath() );
175 
176             String connectorHttpProtocol = runtimeProperties.getProperty( HTTP_PROTOCOL_KEY );
177 
178             if ( httpProtocol != null && httpProtocol.trim().length() > 0 )
179             {
180                 connectorHttpProtocol = httpProtocol;
181             }
182 
183             debugMessage( "use connectorHttpProtocol:" + connectorHttpProtocol );
184 
185             if ( httpPort > 0 )
186             {
187                 Connector connector = new Connector( connectorHttpProtocol );
188                 connector.setPort( httpPort );
189 
190                 if ( httpsPort > 0 )
191                 {
192                     connector.setRedirectPort( httpsPort );
193                 }
194                 connector.setURIEncoding( uriEncoding );
195 
196                 tomcat.getService().addConnector( connector );
197 
198                 tomcat.setConnector( connector );
199             }
200 
201             // add a default acces log valve
202             AccessLogValve alv = new AccessLogValve();
203             alv.setDirectory( new File( extractDirectory, "logs" ).getAbsolutePath() );
204             alv.setPattern( runtimeProperties.getProperty( Tomcat7Runner.ACCESS_LOG_VALVE_FORMAT_KEY ) );
205             tomcat.getHost().getPipeline().addValve( alv );
206 
207             // create https connector
208             if ( httpsPort > 0 )
209             {
210                 Connector httpsConnector = new Connector( connectorHttpProtocol );
211                 httpsConnector.setPort( httpsPort );
212                 httpsConnector.setSecure( true );
213                 httpsConnector.setProperty( "SSLEnabled", "true" );
214                 httpsConnector.setProperty( "sslProtocol", "TLS" );
215                 httpsConnector.setURIEncoding( uriEncoding );
216 
217                 String keystoreFile = System.getProperty( "javax.net.ssl.keyStore" );
218                 String keystorePass = System.getProperty( "javax.net.ssl.keyStorePassword" );
219                 String keystoreType = System.getProperty( "javax.net.ssl.keyStoreType", "jks" );
220 
221                 if ( keystoreFile != null )
222                 {
223                     httpsConnector.setAttribute( "keystoreFile", keystoreFile );
224                 }
225                 if ( keystorePass != null )
226                 {
227                     httpsConnector.setAttribute( "keystorePass", keystorePass );
228                 }
229                 httpsConnector.setAttribute( "keystoreType", keystoreType );
230 
231                 String truststoreFile = System.getProperty( "javax.net.ssl.trustStore" );
232                 String truststorePass = System.getProperty( "javax.net.ssl.trustStorePassword" );
233                 String truststoreType = System.getProperty( "javax.net.ssl.trustStoreType", "jks" );
234                 if ( truststoreFile != null )
235                 {
236                     httpsConnector.setAttribute( "truststoreFile", truststoreFile );
237                 }
238                 if ( truststorePass != null )
239                 {
240                     httpsConnector.setAttribute( "truststorePass", truststorePass );
241                 }
242                 httpsConnector.setAttribute( "truststoreType", truststoreType );
243 
244                 httpsConnector.setAttribute( "clientAuth", clientAuth );
245                 httpsConnector.setAttribute( "keyAlias", keyAlias );
246 
247                 tomcat.getService().addConnector( httpsConnector );
248 
249                 if ( httpPort <= 0 )
250                 {
251                     tomcat.setConnector( httpsConnector );
252                 }
253             }
254 
255             // create ajp connector
256             if ( ajpPort > 0 )
257             {
258                 Connector ajpConnector = new Connector( "org.apache.coyote.ajp.AjpProtocol" );
259                 ajpConnector.setPort( ajpPort );
260                 ajpConnector.setURIEncoding( uriEncoding );
261                 tomcat.getService().addConnector( ajpConnector );
262             }
263 
264             // add webapps
265             for ( Map.Entry<String, String> entry : this.webappWarPerContext.entrySet() )
266             {
267                 String baseDir = null;
268                 if ( entry.getKey().equals( "/" ) )
269                 {
270                     baseDir = new File( extractDirectory, "webapps/ROOT.war" ).getAbsolutePath();
271                 }
272                 else
273                 {
274                     baseDir = new File( extractDirectory, "webapps/" + entry.getValue() ).getAbsolutePath();
275                 }
276                 Context context = tomcat.addWebapp( entry.getKey(), baseDir );
277                 URL contextFileUrl = getContextXml( baseDir );
278                 if ( contextFileUrl != null )
279                 {
280                     context.setConfigFile( contextFileUrl );
281                 }
282             }
283 
284             tomcat.start();
285         }
286 
287         waitIndefinitely();
288 
289     }
290 
291     private URL getContextXml( String warPath )
292         throws IOException
293     {
294         InputStream inputStream = null;
295         try
296         {
297             String urlStr = "jar:file:" + warPath + "!/META-INF/context.xml";
298             debugMessage( "search context.xml in url:'" + urlStr + "'" );
299             URL url = new URL( urlStr );
300             inputStream = url.openConnection().getInputStream();
301             if ( inputStream != null )
302             {
303                 return url;
304             }
305         }
306         catch ( FileNotFoundException e )
307         {
308             return null;
309         }
310         finally
311         {
312             IOUtils.closeQuietly( inputStream );
313         }
314         return null;
315     }
316 
317     private void waitIndefinitely()
318     {
319         Object lock = new Object();
320 
321         synchronized ( lock )
322         {
323             try
324             {
325                 lock.wait();
326             }
327             catch ( InterruptedException exception )
328             {
329                 throw new Error( "InterruptedException on wait Indefinitely lock:" + exception.getMessage(),
330                                  exception );
331             }
332         }
333     }
334 
335     public void stop()
336         throws Exception
337     {
338         if ( container != null )
339         {
340             container.stop();
341         }
342         if ( tomcat != null )
343         {
344             tomcat.stop();
345         }
346     }
347 
348     protected void extract()
349         throws Exception
350     {
351 
352         if ( extractDirectoryFile.exists() )
353         {
354             FileUtils.deleteDirectory( extractDirectoryFile );
355         }
356 
357         if ( !this.extractDirectoryFile.exists() )
358         {
359             boolean created = this.extractDirectoryFile.mkdirs();
360             if ( !created )
361             {
362                 throw new Exception( "FATAL: impossible to create directory:" + this.extractDirectoryFile.getPath() );
363             }
364         }
365 
366         // ensure webapp dir is here
367         boolean created = new File( extractDirectory, "webapps" ).mkdirs();
368         if ( !created )
369         {
370             throw new Exception(
371                 "FATAL: impossible to create directory:" + this.extractDirectoryFile.getPath() + "/webapps" );
372 
373         }
374 
375         String wars = runtimeProperties.getProperty( WARS_KEY );
376         populateWebAppWarPerContext( wars );
377 
378         for ( Map.Entry<String, String> entry : webappWarPerContext.entrySet() )
379         {
380             debugMessage( "webappWarPerContext entry key/value: " + entry.getKey() + "/" + entry.getValue() );
381             InputStream inputStream = null;
382             try
383             {
384                 inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( entry.getValue() );
385                 if ( !useServerXml() )
386                 {
387                     if ( entry.getKey().equals( "/" ) )
388                     {
389                         File expandFile = new File( extractDirectory, "webapps/ROOT.war" );
390                         debugMessage( "expand to file:" + expandFile.getPath() );
391                         expand( inputStream, expandFile );
392                     }
393                     else
394                     {
395                         File expandFile = new File( extractDirectory, "webapps/" + entry.getValue() );
396                         debugMessage( "expand to file:" + expandFile.getPath() );
397                         expand( inputStream, expandFile );
398                     }
399                 }
400                 else
401                 {
402                     File expandFile = new File( extractDirectory, "webapps/" + entry.getValue() );
403                     debugMessage( "expand to file:" + expandFile.getPath() );
404                     expand( inputStream, new File( extractDirectory, "webapps/" + entry.getValue() ) );
405                 }
406             }
407             finally
408             {
409                 if ( inputStream != null )
410                 {
411                     inputStream.close();
412                 }
413             }
414         }
415 
416         // expand tomcat configuration files if there
417         expandConfigurationFile( "catalina.properties", extractDirectoryFile );
418         expandConfigurationFile( "logging.properties", extractDirectoryFile );
419         expandConfigurationFile( "tomcat-users.xml", extractDirectoryFile );
420         expandConfigurationFile( "catalina.policy", extractDirectoryFile );
421         expandConfigurationFile( "context.xml", extractDirectoryFile );
422         expandConfigurationFile( "server.xml", extractDirectoryFile );
423         expandConfigurationFile( "web.xml", extractDirectoryFile );
424 
425     }
426 
427     private static void expandConfigurationFile( String fileName, File extractDirectory )
428         throws Exception
429     {
430         InputStream inputStream = null;
431         try
432         {
433             inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( "conf/" + fileName );
434             if ( inputStream != null )
435             {
436                 File confDirectory = new File( extractDirectory, "conf" );
437                 if ( !confDirectory.exists() )
438                 {
439                     confDirectory.mkdirs();
440                 }
441                 expand( inputStream, new File( confDirectory, fileName ) );
442             }
443         }
444         finally
445         {
446             if ( inputStream != null )
447             {
448                 inputStream.close();
449             }
450         }
451 
452     }
453 
454     /**
455      * @param warsValue we can value in format: wars=foo.war|contextpath;bar.war  ( |contextpath is optionnal if empty use the war name)
456      *                  so here we return war file name and populate webappWarPerContext
457      */
458     private void populateWebAppWarPerContext( String warsValue )
459     {
460         StringTokenizer st = new StringTokenizer( warsValue, ";" );
461         while ( st.hasMoreTokens() )
462         {
463             String warValue = st.nextToken();
464             debugMessage( "populateWebAppWarPerContext warValue:" + warValue );
465             String warFileName = "";
466             String contextValue = "";
467             int separatorIndex = warValue.indexOf( "|" );
468             if ( separatorIndex >= 0 )
469             {
470                 warFileName = warValue.substring( 0, separatorIndex );
471                 contextValue = warValue.substring( separatorIndex + 1, warValue.length() );
472 
473             }
474             else
475             {
476                 warFileName = contextValue;
477             }
478             debugMessage( "populateWebAppWarPerContext contextValue/warFileName:" + contextValue + "/" + warFileName );
479             this.webappWarPerContext.put( contextValue, warFileName );
480         }
481     }
482 
483 
484     /**
485      * Expand the specified input stream into the specified file.
486      *
487      * @param input InputStream to be copied
488      * @param file  The file to be created
489      * @throws java.io.IOException if an input/output error occurs
490      */
491     private static void expand( InputStream input, File file )
492         throws IOException
493     {
494         BufferedOutputStream output = null;
495         try
496         {
497             output = new BufferedOutputStream( new FileOutputStream( file ) );
498             byte buffer[] = new byte[2048];
499             while ( true )
500             {
501                 int n = input.read( buffer );
502                 if ( n <= 0 )
503                 {
504                     break;
505                 }
506                 output.write( buffer, 0, n );
507             }
508         }
509         finally
510         {
511             if ( output != null )
512             {
513                 try
514                 {
515                     output.close();
516                 }
517                 catch ( IOException e )
518                 {
519                     // Ignore
520                 }
521             }
522         }
523     }
524 
525     public boolean useServerXml()
526     {
527         return Boolean.parseBoolean( runtimeProperties.getProperty( USE_SERVER_XML_KEY, Boolean.FALSE.toString() ) );
528     }
529 
530 
531     public void debugMessage( String message )
532     {
533         if ( debug )
534         {
535             System.out.println( message );
536         }
537     }
538 
539 
540     public boolean enableNaming()
541     {
542         return Boolean.parseBoolean( runtimeProperties.getProperty( ENABLE_NAMING_KEY, Boolean.FALSE.toString() ) );
543     }
544 
545     private void installLogger( String loggerName )
546         throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException,
547         InvocationTargetException
548     {
549         if ( "slf4j".equals( loggerName ) )
550         {
551 
552             try
553             {
554                 // Check class is available
555                 final Class<?> clazz = Class.forName( "org.slf4j.bridge.SLF4JBridgeHandler" );
556 
557                 // Remove all JUL handlers
558                 java.util.logging.LogManager.getLogManager().reset();
559 
560                 // Install slf4j bridge handler
561                 final Method method = clazz.getMethod( "install", null );
562                 method.invoke( null );
563             }
564             catch ( ClassNotFoundException e )
565             {
566                 System.out.println( "WARNING: issue configuring slf4j jul bridge, skip it" );
567             }
568         }
569         else
570         {
571             System.out.println( "WARNING: loggerName " + loggerName + " not supported, skip it" );
572         }
573     }
574 }