001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 * $Id$ 020 */ 021 022package org.nuxeo.osgi; 023 024import java.io.File; 025import java.io.FileNotFoundException; 026import java.io.IOException; 027import java.io.InputStream; 028import java.net.MalformedURLException; 029import java.net.URL; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.Collections; 033import java.util.Enumeration; 034import java.util.List; 035import java.util.jar.Attributes; 036import java.util.jar.JarEntry; 037import java.util.jar.JarFile; 038import java.util.jar.Manifest; 039import java.util.zip.ZipEntry; 040 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.common.utils.FileUtils; 044import org.nuxeo.common.utils.StringUtils; 045import org.nuxeo.osgi.util.EntryFilter; 046import org.osgi.framework.Constants; 047 048/** 049 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 050 */ 051public class JarBundleFile implements BundleFile { 052 053 private static final Log log = LogFactory.getLog(JarBundleFile.class); 054 055 protected JarFile jarFile; 056 057 protected String urlBase; 058 059 public JarBundleFile(File file) throws IOException { 060 this(new JarFile(file)); 061 } 062 063 public JarBundleFile(JarFile jarFile) { 064 this.jarFile = jarFile; 065 try { 066 urlBase = "jar:" + new File(jarFile.getName()).toURI().toURL() + "!/"; 067 } catch (MalformedURLException e) { 068 log.error("Failed to convert bundle location to an URL: " + jarFile.getName() 069 + ". Bundle getEntry will not work.", e); 070 } 071 } 072 073 @Override 074 public Enumeration<URL> findEntries(String name, String pattern, boolean recurse) { 075 if (name.startsWith("/")) { 076 name = name.substring(1); 077 } 078 String prefix; 079 if (name.length() == 0) { 080 name = null; 081 prefix = ""; 082 } else if (!name.endsWith("/")) { 083 prefix = name + "/"; 084 } else { 085 prefix = name; 086 } 087 int len = prefix.length(); 088 EntryFilter filter = EntryFilter.newFilter(pattern); 089 Enumeration<JarEntry> entries = jarFile.entries(); 090 ArrayList<URL> result = new ArrayList<URL>(); 091 try { 092 while (entries.hasMoreElements()) { 093 JarEntry entry = entries.nextElement(); 094 if (entry.isDirectory()) { 095 continue; 096 } 097 String ename = entry.getName(); 098 if (name != null && !ename.startsWith(prefix)) { 099 continue; 100 } 101 int i = ename.lastIndexOf('/'); 102 if (!recurse) { 103 if (i > -1) { 104 if (ename.indexOf('/', len) > -1) { 105 continue; 106 } 107 } 108 } 109 String n = i > -1 ? ename.substring(i + 1) : ename; 110 if (filter.match(n)) { 111 result.add(getEntryUrl(ename)); 112 } 113 } 114 } catch (MalformedURLException e) { 115 throw new RuntimeException(e); 116 } 117 return Collections.enumeration(result); 118 } 119 120 @Override 121 public URL getEntry(String name) { 122 ZipEntry entry = jarFile.getEntry(name); 123 if (entry == null) { 124 return null; 125 } 126 if (name.startsWith("/")) { 127 name = name.substring(1); 128 } 129 try { 130 return new URL(urlBase + name); 131 } catch (MalformedURLException e) { 132 return null; 133 } 134 } 135 136 @Override 137 public Enumeration<String> getEntryPaths(String path) { 138 throw new UnsupportedOperationException("The operation BundleFile.geEntryPaths() was not yet implemented"); 139 } 140 141 @Override 142 public File getFile() { 143 return new File(jarFile.getName()); 144 } 145 146 @Override 147 public String getFileName() { 148 String path = jarFile.getName(); 149 int punix = path.lastIndexOf('/'); 150 int pwin = path.lastIndexOf('\\'); 151 int p = punix > pwin ? punix : pwin; 152 if (p == -1) { 153 return path; 154 } 155 if (p == 0) { 156 return ""; 157 } 158 return path.substring(p + 1); 159 } 160 161 @Override 162 public String getLocation() { 163 return jarFile.getName(); 164 } 165 166 @Override 167 public Manifest getManifest() { 168 try { 169 return jarFile.getManifest(); 170 } catch (IOException e) { 171 return null; 172 } 173 } 174 175 @Override 176 public Collection<BundleFile> getNestedBundles(File tmpDir) throws IOException { 177 Attributes attrs = jarFile.getManifest().getMainAttributes(); 178 String cp = attrs.getValue(Constants.BUNDLE_CLASSPATH); 179 if (cp == null) { 180 cp = attrs.getValue("Class-Path"); 181 } 182 if (cp == null) { 183 return null; 184 } 185 String[] paths = StringUtils.split(cp, ',', true); 186 URL base = new URL("jar:" + new File(jarFile.getName()).toURI().toURL().toExternalForm() + "!/"); 187 String fileName = getFileName(); 188 List<BundleFile> nested = new ArrayList<BundleFile>(); 189 for (String path : paths) { 190 if (path.equals(".")) { 191 continue; // TODO 192 } 193 String location = base + path; 194 String name = path.replace('/', '_'); 195 File dest = new File(tmpDir, fileName + '-' + name); 196 try { 197 extractNestedJar(jarFile, path, dest); 198 nested.add(new NestedJarBundleFile(location, dest)); 199 } catch (FileNotFoundException e) { 200 log.error("A nested jar is referenced in manifest but not found: " + location); 201 } catch (IOException e) { 202 log.error(e); 203 } 204 } 205 return nested; 206 } 207 208 public static void extractNestedJar(JarFile file, String path, File dest) throws IOException { 209 InputStream in = null; 210 ZipEntry entry = file.getEntry(path); 211 try { 212 in = file.getInputStream(entry); 213 FileUtils.copyToFile(in, dest); 214 } finally { 215 if (in != null) { 216 in.close(); 217 } 218 } 219 } 220 221 public static void extractNestedJar(JarFile file, ZipEntry entry, File dest) throws IOException { 222 InputStream in = null; 223 try { 224 in = file.getInputStream(entry); 225 FileUtils.copyToFile(in, dest); 226 } finally { 227 if (in != null) { 228 in.close(); 229 } 230 } 231 } 232 233 @Override 234 public Collection<BundleFile> findNestedBundles(File tmpDir) throws IOException { 235 URL base = new URL("jar:" + new File(jarFile.getName()).toURI().toURL().toExternalForm() + "!/"); 236 String fileName = getFileName(); 237 Enumeration<JarEntry> entries = jarFile.entries(); 238 List<BundleFile> nested = new ArrayList<BundleFile>(); 239 while (entries.hasMoreElements()) { 240 JarEntry entry = entries.nextElement(); 241 String path = entry.getName(); 242 if (entry.getName().endsWith(".jar")) { 243 String location = base + path; 244 String name = path.replace('/', '_'); 245 File dest = new File(tmpDir, fileName + '-' + name); 246 extractNestedJar(jarFile, entry, dest); 247 nested.add(new NestedJarBundleFile(location, dest)); 248 } 249 } 250 return nested; 251 } 252 253 @Override 254 public String getSymbolicName() { 255 try { 256 String value = jarFile.getManifest().getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); 257 return value != null ? BundleManifestReader.removePropertiesFromHeaderValue(value) : null; 258 } catch (IOException e) { 259 return null; 260 } 261 } 262 263 @Override 264 public URL getURL() { 265 try { 266 return new File(jarFile.getName()).toURI().toURL(); 267 } catch (MalformedURLException e) { 268 return null; 269 } 270 } 271 272 public URL getJarURL() { 273 try { 274 String url = new File(jarFile.getName()).toURI().toURL().toExternalForm(); 275 return new URL("jar:" + url + "!/"); 276 } catch (MalformedURLException e) { 277 return null; 278 } 279 } 280 281 @Override 282 public boolean isDirectory() { 283 return false; 284 } 285 286 @Override 287 public boolean isJar() { 288 return true; 289 } 290 291 @Override 292 public String toString() { 293 return getLocation(); 294 } 295 296 protected final URL getEntryUrl(String name) throws MalformedURLException { 297 return new URL(urlBase + name); 298 } 299 300 @Override 301 public void close(OSGiAdapter osgi) throws IOException { 302 if (jarFile == null) { 303 return; 304 } 305 try { 306 osgi.getURLJarFileCloser().close(jarFile); 307 } finally { 308 jarFile = null; 309 } 310 } 311 312}