Archive.java

00001 /*
00002  * Copyright (c) 2003-2006, KNOPFLERFISH project
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following
00007  * conditions are met:
00008  *
00009  * - Redistributions of source code must retain the above copyright
00010  *   notice, this list of conditions and the following disclaimer.
00011  *
00012  * - Redistributions in binary form must reproduce the above
00013  *   copyright notice, this list of conditions and the following
00014  *   disclaimer in the documentation and/or other materials
00015  *   provided with the distribution.
00016  *
00017  * - Neither the name of the KNOPFLERFISH project nor the names of its
00018  *   contributors may be used to endorse or promote products derived
00019  *   from this software without specific prior written permission.
00020  *
00021  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00027  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
00028  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00029  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
00030  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00031  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
00032  * OF THE POSSIBILITY OF SUCH DAMAGE.
00033  */
00034 
00035 package org.knopflerfish.framework.bundlestorage.file;
00036 
00037 import org.knopflerfish.framework.*;
00038 import org.osgi.framework.*;
00039 import java.io.*;
00040 import java.net.*;
00041 //import java.security.*;
00042 import java.util.Enumeration;
00043 import java.util.StringTokenizer;
00044 import java.util.Iterator;
00045 import java.util.Vector;
00046 import java.util.jar.*;
00047 import java.util.zip.*;
00048 import java.util.Hashtable;
00049 
00057 class Archive {
00058 
00062   final private static String ARCHIVE = "jar";
00063 
00067   final private static String SUBDIR = "sub";
00068 
00069   // Directory where optional bundle files are stored
00070   // these need not be unpacked locally
00071   final private static String OSGI_OPT_DIR = "OSGI-OPT/";
00072 
00077   final private static boolean unpack = new Boolean(System.getProperty("org.knopflerfish.framework.bundlestorage.file.unpack", "true")).booleanValue();
00078 
00083   final private static boolean alwaysUnpack = new Boolean(System.getProperty("org.knopflerfish.framework.bundlestorage.file.always_unpack", "false")).booleanValue();
00084 
00089   boolean bReference = new Boolean(System.getProperty("org.knopflerfish.framework.bundlestorage.file.reference", "false")).booleanValue();
00090 
00094   private FileTree file;
00095 
00099   private ZipFile jar;
00100 
00104   Manifest manifest /*= null*/;
00105 
00110   private ZipEntry subJar /*= null*/;
00111   
00122   Archive(File dir, int rev, InputStream is) throws IOException {
00123     this(dir, rev, is, null);
00124   }
00125 
00126   FileTree refFile = null;
00127   
00128   Archive(File dir, int rev, InputStream is, URL source) throws IOException {
00129         BufferedInputStream bis;
00130         //Dodge Skelmir-specific problem. Not a great solution, since problem not well understood at this time. Passes KF test suite
00131         if(System.getProperty("java.vendor").startsWith("Skelmir")){
00132                 bis = new BufferedInputStream(is, is.available());
00133         }
00134         else{
00135                 bis = new BufferedInputStream(is, 8192);
00136         }
00137         
00138     ZipInputStream zi = null;
00139         
00140     // Handle reference: URLs by overriding global flag
00141     if(source != null && "reference".equals(source.getProtocol())) {
00142       bReference = true;
00143     }
00144 
00145     if (alwaysUnpack) {
00146       zi = new ZipInputStream(bis);
00147     } else if (unpack) {
00148       //Dodge Skelmir-specific problem. Not a great solution, since problem not well understood at this time. Passes KF test suite
00149       if(System.getProperty("java.vendor").startsWith("Skelmir")){
00150             bis.mark(98304);
00151           }
00152           else{
00153         //Is 16000 enough?
00154                 bis.mark(16000);
00155       }   
00156       JarInputStream ji = new JarInputStream(bis);
00157       manifest = ji.getManifest();
00158       if (manifest != null) {
00159         if (checkManifest()) {
00160           zi = ji;
00161         } else {
00162           bis.reset();
00163         }
00164       } else {
00165         // Manifest probably not first in JAR, should we complain?
00166         // Now, try to use the jar anyway. Maybe the manifest is there.
00167         bis.reset();
00168       }
00169     }
00170     file = new FileTree(dir, ARCHIVE + rev);
00171   
00172   
00173     if (zi != null) {
00174       File f;
00175       file.mkdirs();
00176       if (manifest != null) {
00177         f = new File(file, "META-INF");
00178         f.mkdir();
00179         BufferedOutputStream o = new BufferedOutputStream(new FileOutputStream(new File(f, "MANIFEST.MF")));
00180         try {
00181           manifest.write(o);
00182         } finally {
00183           o.close();
00184         }
00185       }
00186       ZipEntry ze;
00187       while ((ze = zi.getNextEntry()) != null) {
00188         if (!ze.isDirectory()) {
00189           if (isSkipped(ze.getName())) {
00190             // Optional files are not copied to disk
00191           } else {
00192             StringTokenizer st = new StringTokenizer(ze.getName(), "/");
00193             f = new File(file, st.nextToken());
00194             while (st.hasMoreTokens()) {
00195               f.mkdir();
00196               f = new File(f, st.nextToken());
00197             }
00198             loadFile(f, zi, true);
00199           }
00200         }
00201       }
00202       jar = null;
00203     } else {
00204       // Only copy to storage when applicable, otherwise
00205       // use reference
00206       if (source != null && bReference && "file".equals(source.getProtocol())) {
00207         refFile = new FileTree(source.getFile());
00208         jar = new ZipFile(refFile);
00209       } else {
00210         loadFile(file, bis, true);
00211         jar = new ZipFile(file);
00212       }
00213     }
00214     if (manifest == null) {
00215       manifest = getManifest();
00216       checkManifest();
00217     }
00218   }
00219 
00220 
00225   boolean isSkipped(String pathName) {
00226     return pathName.startsWith(OSGI_OPT_DIR);
00227   }
00228 
00229 
00236   Archive(File dir, int rev, String location) throws IOException {
00237     String [] f = dir.list();
00238     file = null;
00239     if (rev != -1) {
00240       file = new FileTree(dir, ARCHIVE + rev);
00241     } else {
00242       rev = Integer.MAX_VALUE;
00243       for (int i = 0; i < f.length; i++) {
00244         if (f[i].startsWith(ARCHIVE)) {
00245           try {
00246             int c = Integer.parseInt(f[i].substring(ARCHIVE.length()));
00247             if (c < rev) {
00248               rev = c;
00249               file = new FileTree(dir, f[i]);
00250             }
00251           } catch (NumberFormatException ignore) { }
00252         }
00253       }
00254     }
00255     for (int i = 0; i < f.length; i++) {
00256       if (f[i].startsWith(ARCHIVE)) {
00257         try {
00258           int c = Integer.parseInt(f[i].substring(ARCHIVE.length()));
00259           if (c != rev) {
00260             (new FileTree(dir, f[i])).delete();
00261           }
00262         } catch (NumberFormatException ignore) { }
00263       }
00264       if (f[i].startsWith(SUBDIR)) {
00265         try {
00266           int c = Integer.parseInt(f[i].substring(SUBDIR.length()));
00267           if (c != rev) {
00268             (new FileTree(dir, f[i])).delete();
00269           }
00270         } catch (NumberFormatException ignore) { }
00271       }
00272     }
00273     if (file == null || !file.exists()) {
00274       if(bReference && (location != null)) {
00275         try {
00276           URL source = new URL(location);
00277           if("file".equals(source.getProtocol())) {
00278             refFile = file = new FileTree(source.getFile());
00279           }
00280         } catch (Exception e) {
00281           throw new IOException("Bad file URL stored in referenced jar in: " +
00282                                 dir.getAbsolutePath() + 
00283                                 ", location=" + location);
00284         }
00285       }
00286       if(file == null || !file.exists()) {
00287         throw new IOException("No saved jar file found in: " + dir.getAbsolutePath() + ", old location=" + location);
00288       }
00289     }
00290     
00291     if (file.isDirectory()) {
00292       jar = null;
00293     } else {
00294       jar = new ZipFile(file);
00295     }
00296     manifest = getManifest();
00297   }
00298 
00299 
00310   Archive(Archive a, String path) throws IOException {
00311     if (a.jar != null) {
00312       jar = a.jar;
00313       subJar = jar.getEntry(path);
00314       if (subJar == null) {
00315           throw new IOException("No such JAR component: " + path);
00316       }
00317       // If directory make sure that it ends with "/"
00318       if (subJar.isDirectory() && !path.endsWith("/")) {
00319         subJar = jar.getEntry(path + "/");
00320       }
00321       file = a.file;
00322     } else {
00323       file = findFile(a.file, path);
00324       if (file.isDirectory()) {
00325         jar = null;
00326       } else {
00327         jar = new ZipFile(file);
00328       }
00329     }
00330   }
00331 
00332 
00338   public String toString() {
00339     if (subJar != null) {
00340       return file.getAbsolutePath() + "(" + subJar.getName() + ")";
00341     } else {
00342       return file.getAbsolutePath();
00343     }
00344   }
00345 
00346 
00352   int getRevision() {
00353     try {
00354       return Integer.parseInt(file.getName().substring(ARCHIVE.length()));
00355     } catch (NumberFormatException ignore) {
00356       // assert?
00357       return -1;
00358     }
00359   }
00360 
00361 
00368   String getAttribute(String key) {
00369     Attributes a = manifest.getMainAttributes();
00370     if (a != null) {
00371       return a.getValue(key);
00372     }
00373     return null;
00374   }
00375   
00376   
00385   byte[] getClassBytes(String classFile) throws IOException {
00386     if(bClosed) {
00387         return null;
00388     }
00389     InputFlow cif = getInputFlow(classFile);
00390     if (cif != null) {
00391       byte[] bytes;
00392       if (cif.length >= 0) {
00393         bytes = new byte[(int)cif.length];
00394         DataInputStream dis = new DataInputStream(cif.is);
00395         dis.readFully(bytes);
00396       } else {
00397         bytes = new byte[0];
00398         byte[] tmp = new byte[8192];
00399         try {
00400           int len;
00401           while ((len = cif.is.read(tmp)) > 0) {
00402             byte[] oldbytes = bytes;
00403             bytes = new byte[oldbytes.length + len];
00404             System.arraycopy(oldbytes, 0, bytes, 0, oldbytes.length);
00405             System.arraycopy(tmp, 0, bytes, oldbytes.length, len);
00406           }
00407         } 
00408         catch (EOFException ignore) {
00409           // On Pjava we somtimes get a mysterious EOF excpetion,
00410           // but everything seems okey. (SUN Bug 4040920)
00411         }
00412       }
00413       cif.is.close();
00414       return bytes;
00415     } else {
00416       return null;
00417     }
00418   }
00419 
00420 
00427   InputFlow getInputFlow(String component) {
00428     if(bClosed) {
00429       return null;
00430     }
00431     if (component.startsWith("/")) {
00432       throw new RuntimeException("Assert! Path should never start with / here");
00433     }
00434     ZipEntry ze;
00435     try {
00436       if (jar != null) {
00437         if (subJar != null) {
00438           if (subJar.isDirectory()) {
00439             ze = jar.getEntry(subJar.getName() + component);
00440           } else {
00441             JarInputStream ji = new JarInputStream(jar.getInputStream(subJar));
00442             do {
00443               ze = ji.getNextJarEntry();
00444               if (ze == null) {
00445                 ji.close();
00446                 return null;
00447               }
00448             } while (!component.equals(ze.getName()));
00449             return new InputFlow((InputStream)ji, ze.getSize());
00450           }
00451         } else {
00452           ze = jar.getEntry(component);
00453         }
00454         return ze != null ? new InputFlow(jar.getInputStream(ze), ze.getSize()) : null;
00455       } else {
00456         File f = findFile(file, component);
00457         return f.exists() ? new InputFlow(new FileInputStream(f), f.length()) : null;
00458       }
00459     } catch (IOException ignore) {
00460       return null;
00461     }
00462   }
00463 
00464 
00465   //TODO not extensively tested
00466   Enumeration findResourcesPath(String path) {
00467     Vector answer = new Vector();
00468     if (jar != null) { 
00469       ZipEntry entry; 
00470       //"normalize" + erroneous path check: be generous
00471       path.replace('\\', '/');
00472       if (path.startsWith("/")){
00473         path =  path.substring(1);   
00474       }  
00475       if (!path.endsWith("/")/*in case bad argument*/){
00476         if (path.length() > 1){
00477           path += "/";
00478         }       
00479       } 
00480                 
00481       Enumeration entries = jar.entries();
00482       while (entries.hasMoreElements()){
00483         entry = (ZipEntry) entries.nextElement();
00484         String name = entry.getName();
00485         if (name.startsWith(path)){
00486           int idx = name.lastIndexOf('/');
00487           if (entry.isDirectory()){
00488             idx = name.substring(0, idx).lastIndexOf('/');
00489           }
00490           if (idx > 0){
00491             if (name.substring(0, idx + 1).equals(path)){
00492               answer.add(name);
00493             }
00494           } else if (path.equals("")){
00495             answer.add(name);
00496           }
00497         }       
00498       }
00499     } else {
00500       File f = findFile(file, path);
00501       if (!f.exists()) {
00502         return null;
00503       }
00504       if (!f.isDirectory()) {
00505         return null;
00506       }
00507       File[] files = f.listFiles();
00508       int length = files.length;
00509       for(int i = 0; i < length; i++){
00510         String filePath = files[i].getPath();
00511         filePath = filePath.substring(file.getPath().length() + 1);
00512         filePath.replace(File.separatorChar, '/');
00513         if(files[i].isDirectory()){
00514           filePath += "/";
00515         }
00516         answer.add(filePath);
00517       }
00518     }
00519     
00520     if(answer.size() == 0){
00521       return null;
00522     }
00523     return answer.elements();
00524   }
00525 
00526 
00535   Archive getSubArchive(String path) throws IOException {
00536     if(bClosed) {
00537       return null;
00538     }
00539     return new Archive(this, path);
00540   }
00541 
00542 
00549   String getNativeLibrary(String path) throws IOException {
00550     if(bClosed) {
00551       throw new IOException("Archive is closed");
00552     }
00553     if (path.startsWith("/")) {
00554       path = path.substring(1);
00555     }
00556     File lib;
00557     if (jar != null) {
00558       lib = getSubFile(this, path);
00559       if (!lib.exists()) {
00560         (new File(lib.getParent())).mkdirs();
00561         ZipEntry ze = jar.getEntry(path);
00562         if (ze != null) {
00563           InputStream is = jar.getInputStream(ze);
00564           try {
00565             loadFile(lib, is, false);
00566           } finally {
00567             is.close();
00568           }
00569         } else {
00570           throw new FileNotFoundException("No such sub-archive: " + path);
00571         }
00572       }
00573     } else {
00574       lib = findFile(file, path);
00575 //XXX - start L-3 modification
00576       if (!lib.exists() && (lib.getParent() != null)) {
00577         final String libname = lib.getName();
00578         File[] list = lib.getParentFile().listFiles(new FilenameFilter() {
00579           public boolean accept(File dir, String name) {
00580             int pos = name.lastIndexOf(libname);
00581             return ((pos > 1) && (name.charAt(pos - 1) == '_'));
00582           }
00583         });
00584         if (list.length > 0) {
00585           list[0].renameTo(lib);
00586         }
00587       }
00588 //XXX - end L-3 modification
00589     }
00590     return lib.getAbsolutePath();
00591   }
00592 
00593 
00597   void purge() {
00598     close();
00599     // Remove archive
00600     file.delete();
00601     // Remove any cached sub files
00602     getSubFileTree(this).delete();
00603   }
00604 
00605   boolean bClosed = false;
00606 
00611   void close() {
00612     bClosed = true; // Mark as closed to safely handle referenced files
00613     if (subJar == null && jar != null) {
00614       try {
00615         jar.close();
00616       } catch (IOException ignore) {}
00617     }
00618   }
00619   
00620   
00621 
00622   //
00623   // Private methods
00624   //
00625 
00634   private boolean checkManifest() {
00635     Attributes a = manifest.getMainAttributes();
00636     Util.parseEntries(Constants.EXPORT_PACKAGE, a.getValue(Constants.EXPORT_PACKAGE),
00637                       false, true, false);
00638     Util.parseEntries(Constants.IMPORT_PACKAGE, a.getValue(Constants.IMPORT_PACKAGE),
00639                       false, true, false);
00640     Iterator nc = Util.parseEntries(Constants.BUNDLE_NATIVECODE,
00641                                     a.getValue(Constants.BUNDLE_NATIVECODE),
00642                                     false, false, false);
00643     String bc = a.getValue(Constants.BUNDLE_CLASSPATH);
00644     return (bc != null && !bc.trim().equals(".")) || nc.hasNext();
00645   }
00646 
00647 
00657   private FileTree findFile(File root, String path) {
00658     return new FileTree(root, path.replace('/', File.separatorChar));
00659   }
00660 
00666   private Manifest getManifest() throws IOException {
00667     // TBD: Should recognize entry with lower case?
00668     InputFlow mif = getInputFlow("META-INF/MANIFEST.MF");
00669     if (mif != null) {
00670       return new Manifest(mif.is);
00671     } else {
00672       throw new IOException("Manifest is missing");
00673     }
00674   }
00675 
00676 
00683   private FileTree getSubFileTree(Archive archive) {
00684     return new FileTree(archive.file.getParent(),
00685                         SUBDIR + archive.file.getName().substring(ARCHIVE.length()));
00686   }
00687 
00688 
00696   private File getSubFile(Archive archive, String path) {
00697     return new File(getSubFileTree(archive), path.replace('/', '-'));
00698   }
00699 
00700 
00707   private void loadFile(File output, InputStream is, boolean verify) throws IOException {
00708     OutputStream os = null;
00709     // NYI! Verify
00710     try {
00711       os = new FileOutputStream(output);
00712       byte[] buf = new byte[8192];
00713       int n;
00714       try {
00715         while ((n = is.read(buf)) > 0) {
00716           os.write(buf, 0, n);
00717         }
00718       } catch (EOFException ignore) {
00719         // On Pjava we sometimes get a mysterious EOF exception,
00720         // but everything seems okey. (SUN Bug 4040920)
00721       }
00722     } catch (IOException e) {
00723       output.delete();
00724       throw e;
00725     } finally {
00726       if (os != null) {
00727         os.close();
00728       }
00729     }
00730   }
00731 
00735   String getPath() {
00736     return file.getAbsolutePath();    
00737   }
00738 
00742   class InputFlow {
00743     final InputStream is;
00744     final long length;
00745 
00746     InputFlow(InputStream is, long length) {
00747       this.is = is;
00748       this.length = length;
00749     }
00750   }
00751 
00752 }

Generated on Mon Jan 11 21:19:13 2010 for OpenMobileIS by  doxygen 1.5.4