View Javadoc
1   /*
2    * @(#)JarDiffHandler.java	1.9 05/11/30
3    * 
4    * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions are met:
8    *
9    * -Redistribution of source code must retain the above copyright notice, this
10   *  list of conditions and the following disclaimer.
11   *
12   * -Redistribution in binary form must reproduce the above copyright notice,
13   *  this list of conditions and the following disclaimer in the documentation
14   *  and/or other materials provided with the distribution.
15   *
16   * Neither the name of Sun Microsystems, Inc. or the names of contributors may
17   * be used to endorse or promote products derived from this software without
18   * specific prior written permission.
19   *
20   * This software is provided "AS IS," without a warranty of any kind. ALL
21   * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
22   * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
23   * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
24   * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
25   * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
26   * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
27   * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
28   * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
29   * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
30   * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31   *
32   * You acknowledge that this software is not designed, licensed or intended
33   * for use in the design, construction, operation or maintenance of any
34   * nuclear facility.
35   */
36  
37  package jnlp.sample.servlet;
38  
39  import jnlp.sample.jardiff.JarDiff;
40  import jnlp.sample.util.VersionString;
41  
42  import javax.servlet.ServletContext;
43  import java.io.BufferedInputStream;
44  import java.io.BufferedOutputStream;
45  import java.io.File;
46  import java.io.FileOutputStream;
47  import java.io.IOException;
48  import java.io.OutputStream;
49  import java.net.URL;
50  import java.util.HashMap;
51  import java.util.StringTokenizer;
52  
53  /*
54  * A class that generates and caches information about JarDiff files
55  *
56  */
57  public class JarDiffHandler
58  {
59      // Default size of download buffer
60      private static final int BUF_SIZE = 32 * 1024;
61  
62      // Default JARDiff mime type
63      private static final String JARDIFF_MIMETYPE = "application/x-java-archive-diff";
64  
65      /**
66       * List of all generated JARDiffs
67       */
68      private HashMap _jarDiffEntries = null;
69  
70      /**
71       * Reference to ServletContext and logger object
72       */
73      private static Logger _log = null;
74  
75      private ServletContext _servletContext = null;
76  
77      private String _jarDiffMimeType = null;
78  
79      /* Contains information about a particular JARDiff entry */
80      private static class JarDiffKey
81          implements Comparable
82      {
83          private String _name;        // Name of file
84  
85          private String _fromVersionId;    // From version
86  
87          private String _toVersionId;    // To version
88  
89          private boolean _minimal;       // True if this is a minimal jardiff
90  
91          /**
92           * Constructor used to generate a query object
93           */
94          public JarDiffKey( String name, String fromVersionId, String toVersionId, boolean minimal )
95          {
96              _name = name;
97              _fromVersionId = fromVersionId;
98              _toVersionId = toVersionId;
99              _minimal = minimal;
100         }
101 
102         // Query methods
103         public String getName()
104         {
105             return _name;
106         }
107 
108         public String getFromVersionId()
109         {
110             return _fromVersionId;
111         }
112 
113         public String getToVersionId()
114         {
115             return _toVersionId;
116         }
117 
118         public boolean isMinimal()
119         {
120             return _minimal;
121         }
122 
123         // Collection framework interface methods
124 
125         public int compareTo( Object o )
126         {
127             // All non JarDiff entries are less
128             if ( !( o instanceof JarDiffKey ) )
129             {
130                 return -1;
131             }
132             JarDiffKey other = (JarDiffKey) o;
133 
134             int n = _name.compareTo( other.getName() );
135             if ( n != 0 )
136             {
137                 return n;
138             }
139 
140             n = _fromVersionId.compareTo( other.getFromVersionId() );
141             if ( n != 0 )
142             {
143                 return n;
144             }
145 
146             if ( _minimal != other.isMinimal() )
147             {
148                 return -1;
149             }
150 
151             return _toVersionId.compareTo( other.getToVersionId() );
152         }
153 
154         public boolean equals( Object o )
155         {
156             return compareTo( o ) == 0;
157         }
158 
159         public int hashCode()
160         {
161             return _name.hashCode() + _fromVersionId.hashCode() + _toVersionId.hashCode();
162         }
163     }
164 
165     static private class JarDiffEntry
166     {
167         private File _jardiffFile;    // Location of JARDiff file
168 
169         public JarDiffEntry( File jarDiffFile )
170         {
171             _jardiffFile = jarDiffFile;
172         }
173 
174         public File getJarDiffFile()
175         {
176             return _jardiffFile;
177         }
178     }
179 
180     /**
181      * Initialize JarDiff handler
182      */
183     public JarDiffHandler( ServletContext servletContext, Logger log )
184     {
185         _jarDiffEntries = new HashMap();
186         _servletContext = servletContext;
187         _log = log;
188 
189         _jarDiffMimeType = _servletContext.getMimeType( "xyz.jardiff" );
190         if ( _jarDiffMimeType == null )
191         {
192             _jarDiffMimeType = JARDIFF_MIMETYPE;
193         }
194     }
195 
196     /**
197      * Returns a JarDiff for the given request
198      */
199     public synchronized DownloadResponse getJarDiffEntry( ResourceCatalog catalog, DownloadRequest dreq,
200                                                           JnlpResource res )
201     {
202         if ( dreq.getCurrentVersionId() == null )
203         {
204             return null;
205         }
206 
207         // check whether the request is from javaws 1.0/1.0.1
208         // do not generate minimal jardiff if it is from 1.0/1.0.1
209         boolean doJarDiffWorkAround = isJavawsVersion( dreq, "1.0*" );
210 
211         // First do a lookup to find a match
212         JarDiffKey key =
213             new JarDiffKey( res.getName(), dreq.getCurrentVersionId(), res.getReturnVersionId(), !doJarDiffWorkAround );
214 
215         JarDiffEntry entry = (JarDiffEntry) _jarDiffEntries.get( key );
216         // If entry is not found, then the querty has not been made.
217         if ( entry == null )
218         {
219             if ( _log.isInformationalLevel() )
220             {
221                 _log.addInformational( "servlet.log.info.jardiff.gen", res.getName(), dreq.getCurrentVersionId(),
222                                        res.getReturnVersionId() );
223             }
224             File f = generateJarDiff( catalog, dreq, res, doJarDiffWorkAround );
225             if ( f == null )
226             {
227                 _log.addWarning( "servlet.log.warning.jardiff.failed", res.getName(), dreq.getCurrentVersionId(),
228                                  res.getReturnVersionId() );
229             }
230             // Store entry in table
231             entry = new JarDiffEntry( f );
232             _jarDiffEntries.put( key, entry );
233         }
234 
235         // Check for no JarDiff to return
236         if ( entry.getJarDiffFile() == null )
237         {
238             return null;
239         }
240         else
241         {
242             return DownloadResponse.getFileDownloadResponse( entry.getJarDiffFile(), _jarDiffMimeType,
243                                                              entry.getJarDiffFile().lastModified(),
244                                                              res.getReturnVersionId() );
245         }
246     }
247 
248 
249     public static boolean isJavawsVersion( DownloadRequest dreq, String version )
250     {
251         String javawsAgent = "javaws";
252         String jwsVer = dreq.getHttpRequest().getHeader( "User-Agent" );
253 
254         // check the request is coming from javaws
255         if ( !jwsVer.startsWith( "javaws-" ) )
256         {
257             // this is the new style User-Agent string
258             // User-Agent: JNLP/1.0.1 javaws/1.4.2 (b28) J2SE/1.4.2
259             StringTokenizer st = new StringTokenizer( jwsVer );
260             while ( st.hasMoreTokens() )
261             {
262                 String verString = st.nextToken();
263                 int index = verString.indexOf( javawsAgent );
264                 if ( index != -1 )
265                 {
266                     verString = verString.substring( index + javawsAgent.length() + 1 );
267                     return VersionString.contains( version, verString );
268                 }
269             }
270             return false;
271         }
272 
273         // extract the version id from the download request
274         int startIndex = jwsVer.indexOf( "-" );
275 
276         if ( startIndex == -1 )
277         {
278             return false;
279         }
280 
281         int endIndex = jwsVer.indexOf( "/" );
282 
283         if ( endIndex == -1 || endIndex < startIndex )
284         {
285             return false;
286         }
287 
288         String verId = jwsVer.substring( startIndex + 1, endIndex );
289 
290         // check whether the versionString contains the versionId
291         return VersionString.contains( version, verId );
292 
293     }
294 
295     /**
296      * Download resource to the given file
297      */
298     private boolean download( URL target, File file )
299     {
300 
301         _log.addDebug( "JarDiffHandler:  Doing download" );
302 
303         boolean ret = true;
304         boolean delete = false;
305         // use bufferedstream for better performance
306         BufferedInputStream in = null;
307         BufferedOutputStream out = null;
308         try
309         {
310             in = new BufferedInputStream( target.openStream() );
311             out = new BufferedOutputStream( new FileOutputStream( file ) );
312             int read = 0;
313             int totalRead = 0;
314             byte[] buf = new byte[BUF_SIZE];
315             while ( ( read = in.read( buf ) ) != -1 )
316             {
317                 out.write( buf, 0, read );
318                 totalRead += read;
319             }
320 
321             _log.addDebug( "total read: " + totalRead );
322             _log.addDebug( "Wrote URL " + target.toString() + " to file " + file );
323 
324         }
325         catch ( IOException ioe )
326         {
327 
328             _log.addDebug( "Got exception while downloading resource: " + ioe );
329 
330             ret = false;
331 
332             if ( file != null )
333             {
334                 delete = true;
335             }
336 
337         }
338         finally
339         {
340 
341             try
342             {
343                 in.close();
344                 in = null;
345             }
346             catch ( IOException ioe )
347             {
348                 _log.addDebug( "Got exception while downloading resource: " + ioe );
349             }
350 
351             try
352             {
353                 out.close();
354                 out = null;
355             }
356             catch ( IOException ioe )
357             {
358                 _log.addDebug( "Got exception while downloading resource: " + ioe );
359             }
360 
361             if ( delete )
362             {
363                 file.delete();
364             }
365 
366         }
367         return ret;
368     }
369 
370     // fix for 4720897
371     // if the jar file resides in a war file, download it to a temp dir
372     // so it can be used to generate jardiff
373     private String getRealPath( String path )
374         throws IOException
375     {
376 
377         URL fileURL = _servletContext.getResource( path );
378 
379         File tempDir = (File) _servletContext.getAttribute( "javax.servlet.context.tempdir" );
380 
381         // download file into temp dir
382         if ( fileURL != null )
383         {
384             File newFile = File.createTempFile( "temp", ".jar", tempDir );
385             if ( download( fileURL, newFile ) )
386             {
387                 String filePath = newFile.getPath();
388                 return filePath;
389             }
390         }
391         return null;
392     }
393 
394 
395     private File generateJarDiff( ResourceCatalog catalog, DownloadRequest dreq, JnlpResource res,
396                                   boolean doJarDiffWorkAround )
397     {
398         boolean del_old = false;
399         boolean del_new = false;
400 
401         // Lookup up file for request version
402         DownloadRequest fromDreq = dreq.getFromDownloadRequest();
403         try
404         {
405             JnlpResource fromRes = catalog.lookupResource( fromDreq );
406 
407             /* Get file locations */
408             String newFilePath = _servletContext.getRealPath( res.getPath() );
409             String oldFilePath = _servletContext.getRealPath( fromRes.getPath() );
410 
411             // fix for 4720897
412             if ( newFilePath == null )
413             {
414                 newFilePath = getRealPath( res.getPath() );
415                 if ( newFilePath != null )
416                 {
417                     del_new = true;
418                 }
419             }
420 
421             if ( oldFilePath == null )
422             {
423                 oldFilePath = getRealPath( fromRes.getPath() );
424                 if ( oldFilePath != null )
425                 {
426                     del_old = true;
427                 }
428             }
429 
430             if ( newFilePath == null || oldFilePath == null )
431             {
432                 return null;
433             }
434 
435             // Create temp. file to store JarDiff file in
436             File tempDir = (File) _servletContext.getAttribute( "javax.servlet.context.tempdir" );
437 
438             // fix for 4653036: JarDiffHandler() should use javax.servlet.context.tempdir to store the jardiff
439             File outputFile = File.createTempFile( "jnlp", ".jardiff", tempDir );
440 
441             _log.addDebug(
442                 "Generating Jardiff between " + oldFilePath + " and " + newFilePath + " Store in " + outputFile );
443 
444             // Generate JarDiff
445             OutputStream os = new FileOutputStream( outputFile );
446 
447             JarDiff.createPatch( oldFilePath, newFilePath, os, !doJarDiffWorkAround );
448             os.close();
449 
450             try
451             {
452 
453                 // Check that Jardiff is smaller, or return null
454                 if ( outputFile.length() >= ( new File( newFilePath ).length() ) )
455                 {
456                     _log.addDebug( "JarDiff discarded - since it is bigger" );
457                     return null;
458                 }
459 
460                 // Check that Jardiff is smaller than the packed version of
461                 // the new file, if the file exists at all
462                 File newFilePacked = new File( newFilePath + ".pack.gz" );
463                 if ( newFilePacked.exists() )
464                 {
465                     _log.addDebug( "generated jardiff size: " + outputFile.length() );
466                     _log.addDebug( "packed requesting file size: " + newFilePacked.length() );
467                     if ( outputFile.length() >= newFilePacked.length() )
468                     {
469                         _log.addDebug( "JarDiff discarded - packed version of requesting file is smaller" );
470                         return null;
471                     }
472                 }
473 
474                 _log.addDebug( "JarDiff generation succeeded" );
475                 return outputFile;
476 
477             }
478             finally
479             {
480                 // delete the temporarily downloaded file
481                 if ( del_new )
482                 {
483                     new File( newFilePath ).delete();
484                 }
485 
486                 if ( del_old )
487                 {
488                     new File( oldFilePath ).delete();
489                 }
490             }
491         }
492         catch ( IOException ioe )
493         {
494             _log.addDebug( "Failed to genereate jardiff", ioe );
495             return null;
496         }
497         catch ( ErrorResponseException ere )
498         {
499             _log.addDebug( "Failed to genereate jardiff", ere );
500             return null;
501         }
502     }
503 }
504