001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.xbean.osgi.bundle.util; 021 022import java.io.FilterInputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.net.URL; 026import java.util.Enumeration; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Set; 030import java.util.zip.ZipEntry; 031import java.util.zip.ZipInputStream; 032 033import org.apache.xbean.osgi.bundle.util.BundleDescription.HeaderEntry; 034import org.osgi.framework.Bundle; 035import org.osgi.service.packageadmin.PackageAdmin; 036 037/** 038 * Finds all available resources to a bundle by scanning Bundle-ClassPath header 039 * of the given bundle and its fragments. 040 * DynamicImport-Package header is not considered during scanning. 041 * 042 * @version $Rev: 1452425 $ $Date: 2013-03-04 19:13:52 +0100 (Mon, 04 Mar 2013) $ 043 */ 044public class BundleResourceFinder { 045 046 public static final ResourceDiscoveryFilter FULL_DISCOVERY_FILTER = new DummyDiscoveryFilter(); 047 048 private final Bundle bundle; 049 private final PackageAdmin packageAdmin; 050 private final String prefix; 051 private final String suffix; 052 private final String osgiSuffix; 053 private final boolean extendedMatching; 054 private ResourceDiscoveryFilter discoveryFilter; 055 056 public BundleResourceFinder(PackageAdmin packageAdmin, Bundle bundle, String prefix, String suffix) { 057 this(packageAdmin, bundle, prefix, suffix, FULL_DISCOVERY_FILTER); 058 } 059 060 /** 061 * Set up a BundleResourceFinder 062 * The suffix may contain a path fragment, unlike the bundle.findEntries method. 063 * 064 * @param packageAdmin package admin for finding fragments 065 * @param bundle bundle to search 066 * @param prefix search only paths and zip files starting with this prefix 067 * @param suffix return only entries ending in this suffix. 068 * @param discoveryFilter filter for matching directories and zip files. 069 */ 070 public BundleResourceFinder(PackageAdmin packageAdmin, Bundle bundle, String prefix, String suffix, ResourceDiscoveryFilter discoveryFilter) { 071 this.packageAdmin = packageAdmin; 072 this.bundle = BundleUtils.unwrapBundle(bundle); 073 this.prefix = addSlash(prefix.trim()); 074 this.suffix = suffix.trim(); 075 int pos = this.suffix.lastIndexOf("/"); 076 if (pos > -1) { 077 osgiSuffix = this.suffix.substring(pos + 1, this.suffix.length()); 078 extendedMatching = true; 079 } else { 080 osgiSuffix = "*" + this.suffix; 081 extendedMatching = false; 082 } 083 this.discoveryFilter = discoveryFilter; 084 } 085 086 public void find(ResourceFinderCallback callback) throws Exception { 087 if (discoveryFilter.rangeDiscoveryRequired(DiscoveryRange.BUNDLE_CLASSPATH)) { 088 if (!scanBundleClassPath(callback, bundle)) { 089 return; 090 } 091 } 092 if (packageAdmin != null && discoveryFilter.rangeDiscoveryRequired(DiscoveryRange.FRAGMENT_BUNDLES)) { 093 Bundle[] fragments = packageAdmin.getFragments(bundle); 094 if (fragments != null) { 095 for (Bundle fragment : fragments) { 096 if (!scanBundleClassPath(callback, fragment)) { 097 return; 098 } 099 } 100 } 101 } 102 } 103 104 public Set<URL> find() { 105 Set<URL> resources = new LinkedHashSet<URL>(); 106 try { 107 find(new DefaultResourceFinderCallback(resources)); 108 } catch (Exception e) { 109 // this should not happen 110 throw new RuntimeException("Resource discovery failed", e); 111 } 112 return resources; 113 } 114 115 private boolean scanBundleClassPath(ResourceFinderCallback callback, Bundle bundle) throws Exception { 116 BundleDescription desc = new BundleDescription(bundle.getHeaders()); 117 List<HeaderEntry> paths = desc.getBundleClassPath(); 118 boolean continueScanning = true; 119 if (paths.isEmpty()) { 120 continueScanning = scanDirectory(callback, bundle, prefix); 121 } else { 122 for (HeaderEntry path : paths) { 123 String name = path.getName(); 124 if (name.equals(".") || name.equals("/")) { 125 // scan root 126 continueScanning = scanDirectory(callback, bundle, prefix); 127 } else if (name.endsWith(".jar") || name.endsWith(".zip")) { 128 // scan embedded jar/zip 129 continueScanning = scanZip(callback, bundle, name); 130 } else { 131 // assume it's a directory 132 continueScanning = scanDirectory(callback, bundle, prefix.startsWith("/") ? name + prefix : name + "/" + prefix); 133 } 134 if (!continueScanning) { 135 break; 136 } 137 } 138 } 139 return continueScanning; 140 } 141 142 private boolean scanDirectory(ResourceFinderCallback callback, Bundle bundle, String basePath) throws Exception { 143 if (!discoveryFilter.directoryDiscoveryRequired(basePath)) { 144 return true; 145 } 146 Enumeration e = bundle.findEntries(basePath, osgiSuffix, true); 147 if (e != null) { 148 while (e.hasMoreElements()) { 149 URL url = (URL) e.nextElement(); 150 if (!extendedMatching || suffixMatches(url.getPath())) { 151 if (!callback.foundInDirectory(bundle, basePath, url)) { 152 return false; 153 } 154 } 155 } 156 } 157 return true; 158 } 159 160 private boolean scanZip(ResourceFinderCallback callback, Bundle bundle, String zipName) throws Exception { 161 if (!discoveryFilter.zipFileDiscoveryRequired(zipName)) { 162 return true; 163 } 164 URL zipEntry = bundle.getEntry(zipName); 165 if (zipEntry == null) { 166 return true; 167 } 168 ZipInputStream in = null; 169 try { 170 in = new ZipInputStream(zipEntry.openStream()); 171 ZipEntry entry; 172 while ((entry = in.getNextEntry()) != null) { 173 String name = entry.getName(); 174 if (prefixMatches(name) && suffixMatches(name)) { 175 if (!callback.foundInJar(bundle, zipName, entry, new ZipEntryInputStream(in))) { 176 return false; 177 } 178 } 179 } 180 } catch (IOException e) { 181 e.printStackTrace(); 182 } finally { 183 if (in != null) { 184 try { in.close(); } catch (Exception e) {} 185 } 186 } 187 return true; 188 } 189 190 private static class ZipEntryInputStream extends FilterInputStream { 191 public ZipEntryInputStream(ZipInputStream in) { 192 super(in); 193 } 194 public void close() throws IOException { 195 // not really necessary 196 // ((ZipInputStream) in).closeEntry(); 197 } 198 } 199 200 private boolean prefixMatches(String name) { 201 if (prefix.length() == 0 || prefix.equals(".") || prefix.equals("/")) { 202 return true; 203 } else if (prefix.startsWith("/")) { 204 return name.startsWith(prefix, 1); 205 } else { 206 return name.startsWith(prefix); 207 } 208 } 209 210 private boolean suffixMatches(String name) { 211 return (suffix.length() == 0) ? true : name.endsWith(suffix); 212 } 213 214 private static String addSlash(String name) { 215 if (name == null ) return ""; 216 name = name.trim(); 217 if (name.length() != 0 && !name.endsWith("/")) { 218 name = name + "/"; 219 } 220 return name; 221 } 222 223 public interface ResourceFinderCallback { 224 /** 225 * Resource found in a directory in a bundle. 226 * 227 * @return true to continue scanning, false to abort scanning. 228 */ 229 boolean foundInDirectory(Bundle bundle, String baseDir, URL url) throws Exception; 230 231 /** 232 * Resource found in a jar file in a bundle. 233 * 234 * @return true to continue scanning, false to abort scanning. 235 */ 236 boolean foundInJar(Bundle bundle, String jarName, ZipEntry entry, InputStream in) throws Exception; 237 } 238 239 public static class DefaultResourceFinderCallback implements ResourceFinderCallback { 240 241 private Set<URL> resources; 242 243 public DefaultResourceFinderCallback() { 244 this(new LinkedHashSet<URL>()); 245 } 246 247 public DefaultResourceFinderCallback(Set<URL> resources) { 248 this.resources = resources; 249 } 250 251 public Set<URL> getResources() { 252 return resources; 253 } 254 255 public boolean foundInDirectory(Bundle bundle, String baseDir, URL url) throws Exception { 256 resources.add(url); 257 return true; 258 } 259 260 public boolean foundInJar(Bundle bundle, String jarName, ZipEntry entry, InputStream in) throws Exception { 261 URL jarURL = bundle.getEntry(jarName); 262 URL url = new URL("jar:" + jarURL.toString() + "!/" + entry.getName()); 263 resources.add(url); 264 return true; 265 } 266 267 } 268 269 public static class DummyDiscoveryFilter implements ResourceDiscoveryFilter { 270 271 public boolean rangeDiscoveryRequired(DiscoveryRange discoveryRange) { 272 return true; 273 } 274 275 public boolean directoryDiscoveryRequired(String url) { 276 return true; 277 } 278 279 public boolean zipFileDiscoveryRequired(String url) { 280 return true; 281 } 282 283 } 284}