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