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