/* * @(#)JarDiffHandler.java 1.9 05/11/30 * * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * -Redistribution of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ package jnlp.sample.servlet; import java.io.*; import java.util.*; import jnlp.sample.jardiff.*; import javax.servlet.*; import javax.servlet.http.*; import jnlp.sample.util.VersionString; import java.net.URL; /* * A class that generates and caches information about JarDiff files * */ public class JarDiffHandler { // Default size of download buffer private static final int BUF_SIZE = 32 * 1024; // Default JARDiff mime type private static final String JARDIFF_MIMETYPE = "application/x-java-archive-diff"; /** List of all generated JARDiffs */ private HashMap _jarDiffEntries = null; /** Reference to ServletContext and logger object */ private static Logger _log = null; private ServletContext _servletContext = null; private String _jarDiffMimeType = null; /* Contains information about a particular JARDiff entry */ private static class JarDiffKey implements Comparable{ private String _name; // Name of file private String _fromVersionId; // From version private String _toVersionId; // To version private boolean _minimal; // True if this is a minimal jardiff /** Constructor used to generate a query object */ public JarDiffKey(String name, String fromVersionId, String toVersionId, boolean minimal) { _name = name; _fromVersionId = fromVersionId; _toVersionId = toVersionId; _minimal = minimal; } // Query methods public String getName() { return _name; } public String getFromVersionId() { return _fromVersionId; } public String getToVersionId() { return _toVersionId; } public boolean isMinimal() { return _minimal; } // Collection framework interface methods public int compareTo(Object o) { // All non JarDiff entries are less if (!(o instanceof JarDiffKey)) return -1; JarDiffKey other = (JarDiffKey)o; int n = _name.compareTo(other.getName()); if (n != 0) return n; n = _fromVersionId.compareTo(other.getFromVersionId()); if (n != 0) return n; if (_minimal != other.isMinimal()) return -1; return _toVersionId.compareTo(other.getToVersionId()); } public boolean equals(Object o) { return compareTo(o) == 0; } public int hashCode() { return _name.hashCode() + _fromVersionId.hashCode() + _toVersionId.hashCode(); } } static private class JarDiffEntry { private File _jardiffFile; // Location of JARDiff file public JarDiffEntry(File jarDiffFile) { _jardiffFile = jarDiffFile; } public File getJarDiffFile() { return _jardiffFile; } } /** Initialize JarDiff handler */ public JarDiffHandler(ServletContext servletContext, Logger log) { _jarDiffEntries = new HashMap(); _servletContext = servletContext; _log = log; _jarDiffMimeType = _servletContext.getMimeType("xyz.jardiff"); if (_jarDiffMimeType == null) _jarDiffMimeType = JARDIFF_MIMETYPE; } /** Returns a JarDiff for the given request */ public synchronized DownloadResponse getJarDiffEntry(ResourceCatalog catalog, DownloadRequest dreq, JnlpResource res) { if (dreq.getCurrentVersionId() == null) return null; // check whether the request is from javaws 1.0/1.0.1 // do not generate minimal jardiff if it is from 1.0/1.0.1 boolean doJarDiffWorkAround = isJavawsVersion(dreq, "1.0*"); // First do a lookup to find a match JarDiffKey key = new JarDiffKey(res.getName(), dreq.getCurrentVersionId(), res.getReturnVersionId(), !doJarDiffWorkAround); JarDiffEntry entry = (JarDiffEntry)_jarDiffEntries.get(key); // If entry is not found, then the querty has not been made. if (entry == null) { if (_log.isInformationalLevel()) { _log.addInformational("servlet.log.info.jardiff.gen", res.getName(), dreq.getCurrentVersionId(), res.getReturnVersionId()); } File f = generateJarDiff(catalog, dreq, res, doJarDiffWorkAround); if (f == null) { _log.addWarning("servlet.log.warning.jardiff.failed", res.getName(), dreq.getCurrentVersionId(), res.getReturnVersionId()); } // Store entry in table entry = new JarDiffEntry(f); _jarDiffEntries.put(key, entry); } // Check for no JarDiff to return if (entry.getJarDiffFile() == null) { return null; } else { return DownloadResponse.getFileDownloadResponse(entry.getJarDiffFile(), _jarDiffMimeType, entry.getJarDiffFile().lastModified(), res.getReturnVersionId()); } } public static boolean isJavawsVersion(DownloadRequest dreq, String version) { String javawsAgent = "javaws"; String jwsVer = dreq.getHttpRequest().getHeader("User-Agent"); // check the request is coming from javaws if (!jwsVer.startsWith("javaws-")) { // this is the new style User-Agent string // User-Agent: JNLP/1.0.1 javaws/1.4.2 (b28) J2SE/1.4.2 StringTokenizer st = new StringTokenizer(jwsVer); while (st.hasMoreTokens()) { String verString = st.nextToken(); int index = verString.indexOf(javawsAgent); if (index != -1) { verString = verString.substring(index + javawsAgent.length() + 1); return VersionString.contains(version, verString); } } return false; } // extract the version id from the download request int startIndex = jwsVer.indexOf("-"); if (startIndex == -1) { return false; } int endIndex = jwsVer.indexOf("/"); if (endIndex == -1 || endIndex < startIndex) { return false; } String verId = jwsVer.substring(startIndex + 1, endIndex); // check whether the versionString contains the versionId return VersionString.contains(version, verId); } /** Download resource to the given file */ private boolean download(URL target, File file) { _log.addDebug("JarDiffHandler: Doing download"); boolean ret = true; boolean delete = false; // use bufferedstream for better performance BufferedInputStream in = null; BufferedOutputStream out = null; try { in = new BufferedInputStream(target.openStream()); out = new BufferedOutputStream(new FileOutputStream(file)); int read = 0; int totalRead = 0; byte[] buf = new byte[BUF_SIZE]; while ((read = in.read(buf)) != -1) { out.write(buf, 0, read); totalRead += read; } _log.addDebug("total read: " + totalRead); _log.addDebug("Wrote URL " + target.toString() + " to file " + file); } catch(IOException ioe) { _log.addDebug("Got exception while downloading resource: " + ioe); ret = false; if (file != null) delete = true; } finally { try { in.close(); in = null; } catch (IOException ioe) { _log.addDebug("Got exception while downloading resource: " + ioe); } try { out.close(); out = null; } catch (IOException ioe) { _log.addDebug("Got exception while downloading resource: " + ioe); } if (delete) { file.delete(); } } return ret; } // fix for 4720897 // if the jar file resides in a war file, download it to a temp dir // so it can be used to generate jardiff private String getRealPath(String path) throws IOException{ URL fileURL = _servletContext.getResource(path); File tempDir = (File)_servletContext.getAttribute("javax.servlet.context.tempdir"); // download file into temp dir if (fileURL != null) { File newFile = File.createTempFile("temp", ".jar", tempDir); if (download(fileURL, newFile)) { String filePath = newFile.getPath(); return filePath; } } return null; } private File generateJarDiff(ResourceCatalog catalog, DownloadRequest dreq, JnlpResource res, boolean doJarDiffWorkAround) { boolean del_old = false; boolean del_new = false; // Lookup up file for request version DownloadRequest fromDreq = dreq.getFromDownloadRequest(); try { JnlpResource fromRes = catalog.lookupResource(fromDreq); /* Get file locations */ String newFilePath = _servletContext.getRealPath(res.getPath()); String oldFilePath = _servletContext.getRealPath(fromRes.getPath()); // fix for 4720897 if (newFilePath == null) { newFilePath = getRealPath(res.getPath()); if (newFilePath != null) del_new = true; } if (oldFilePath == null) { oldFilePath = getRealPath(fromRes.getPath()); if (oldFilePath != null) del_old = true; } if (newFilePath == null || oldFilePath == null) { return null; } // Create temp. file to store JarDiff file in File tempDir = (File)_servletContext.getAttribute("javax.servlet.context.tempdir"); // fix for 4653036: JarDiffHandler() should use javax.servlet.context.tempdir to store the jardiff File outputFile = File.createTempFile("jnlp", ".jardiff", tempDir); _log.addDebug("Generating Jardiff between " + oldFilePath + " and " + newFilePath + " Store in " + outputFile); // Generate JarDiff OutputStream os = new FileOutputStream(outputFile); JarDiff.createPatch(oldFilePath, newFilePath, os, !doJarDiffWorkAround); os.close(); try { // Check that Jardiff is smaller, or return null if (outputFile.length() >= (new File(newFilePath).length())) { _log.addDebug("JarDiff discarded - since it is bigger"); return null; } // Check that Jardiff is smaller than the packed version of // the new file, if the file exists at all File newFilePacked = new File(newFilePath + ".pack.gz"); if (newFilePacked.exists()) { _log.addDebug("generated jardiff size: " + outputFile.length()); _log.addDebug("packed requesting file size: " + newFilePacked.length()); if (outputFile.length() >= newFilePacked.length()) { _log.addDebug("JarDiff discarded - packed version of requesting file is smaller"); return null; } } _log.addDebug("JarDiff generation succeeded"); return outputFile; } finally { // delete the temporarily downloaded file if (del_new) { new File(newFilePath).delete(); } if (del_old) { new File(oldFilePath).delete(); } } } catch(IOException ioe) { _log.addDebug("Failed to genereate jardiff", ioe); return null; } catch(ErrorResponseException ere) { _log.addDebug("Failed to genereate jardiff", ere); return null; } } }