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  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   * FIXME add junit for that but when https://issues.apache.org/bugzilla/show_bug.cgi?id=52028 fixed
55   * Main class used to run the standalone wars in a Apache Tomcat instance.
56   *
57   * @author Olivier Lamy
58   * @since 2.0
59   */
60  public class Tomcat7Runner
61  {
62      // true/false to use the server.xml located in the jar /conf/server.xml
63      public static final String USE_SERVER_XML_KEY = "useServerXml";
64  
65      // contains war name wars=foo.war,bar.war
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       * key of the property which contains http protocol : HTTP/1.1 or org.apache.coyote.http11.Http11NioProtocol
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      * key = context of the webapp, value = war path on file system
120      */
121     Map<String, String> webappWarPerContext = new HashMap<String, String>();
122 
123     public Tomcat7Runner()
124     {
125         // no op
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         // compare timestamp stored during previous run if exists
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         // do we have to extract content
197         {
198             if ( !extractDirectoryFile.exists() || resetExtract || archiveTimestampChanged )
199             {
200                 extract();
201                 //if archiveTimestampChanged or timestamp file not exists store the last timestamp from the archive
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         // create tomcat various paths
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         // start with a server.xml
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             // add a default acces log valve
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             // create https connector
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             // create ajp connector
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             // add webapps
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             // no op
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                 // If JULI is used, shut JULI down *after* the server shuts down
438                 // so log messages aren't lost
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         // ensure webapp dir is here
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         //Copy code source to webapps folder
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         // expand tomcat configuration files if there
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      * @param warsValue we can value in format: wars=foo.war|contextpath;bar.war  ( |contextpath is optionnal if empty use the war name)
634      *                  so here we return war file name and populate webappWarPerContext
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      * Expand the specified input stream into the specified file.
669      *
670      * @param input InputStream to be copied
671      * @param file  The file to be created
672      * @throws java.io.IOException if an input/output error occurs
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                     // Ignore
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                 // Check class is available
738 
739                 //final Class<?> clazz = Class.forName( "org.slf4j.bridge.SLF4JBridgeHandler" );
740                 final Class<?> clazz =
741                     Thread.currentThread().getContextClassLoader().loadClass( "org.slf4j.bridge.SLF4JBridgeHandler" );
742 
743                 // Remove all JUL handlers
744                 java.util.logging.LogManager.getLogManager().reset();
745 
746                 // Install slf4j bridge handler
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 }