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 020 package org.apache.xbean.osgi.bundle.util; 021 022 import java.io.FilterInputStream; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.net.URL; 026 import java.util.Enumeration; 027 import java.util.LinkedHashSet; 028 import java.util.List; 029 import java.util.Set; 030 import java.util.zip.ZipEntry; 031 import java.util.zip.ZipInputStream; 032 033 import org.apache.xbean.osgi.bundle.util.BundleDescription.HeaderEntry; 034 import org.osgi.framework.Bundle; 035 import 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: 942661 $ $Date: 2010-05-10 07:17:20 +0200 (Mon, 10 May 2010) $ 043 */ 044 public class BundleResourceFinder { 045 046 public static final ResourceDiscoveryFilter FULL_DISCOVERY_FILTER = new DummyDiscoveryFilter(); 047 private final Bundle bundle; 048 private final PackageAdmin packageAdmin; 049 private final String prefix; 050 private final String suffix; 051 private ResourceDiscoveryFilter discoveryFilter; 052 053 public BundleResourceFinder(PackageAdmin packageAdmin, Bundle bundle, String prefix, String suffix) { 054 this(packageAdmin, bundle, prefix, suffix, FULL_DISCOVERY_FILTER); 055 } 056 057 public BundleResourceFinder(PackageAdmin packageAdmin, Bundle bundle, String prefix, String suffix, ResourceDiscoveryFilter discoveryFilter) { 058 this.packageAdmin = packageAdmin; 059 this.bundle = bundle; 060 this.prefix = prefix.trim(); 061 this.suffix = suffix.trim(); 062 this.discoveryFilter = discoveryFilter; 063 } 064 065 public void find(ResourceFinderCallback callback) throws Exception { 066 if (discoveryFilter.rangeDiscoveryRequired(DiscoveryRange.BUNDLE_CLASSPATH)) { 067 scanBundleClassPath(callback, bundle); 068 } 069 if (packageAdmin != null && discoveryFilter.rangeDiscoveryRequired(DiscoveryRange.FRAGMENT_BUNDLES)) { 070 Bundle[] fragments = packageAdmin.getFragments(bundle); 071 if (fragments != null) { 072 for (Bundle fragment : fragments) { 073 scanBundleClassPath(callback, fragment); 074 } 075 } 076 } 077 } 078 079 public Set<URL> find() { 080 Set<URL> resources = new LinkedHashSet<URL>(); 081 try { 082 find(new DefaultResourceFinderCallback(resources)); 083 } catch (Exception e) { 084 // this should not happen 085 throw new RuntimeException("Resource discovery failed", e); 086 } 087 return resources; 088 } 089 090 private void scanBundleClassPath(ResourceFinderCallback callback, Bundle bundle) throws Exception { 091 BundleDescription desc = new BundleDescription(bundle.getHeaders()); 092 List<HeaderEntry> paths = desc.getBundleClassPath(); 093 if (paths.isEmpty()) { 094 scanDirectory(callback, bundle, prefix); 095 } else { 096 for (HeaderEntry path : paths) { 097 String name = path.getName(); 098 if (name.equals(".") || name.equals("/")) { 099 // scan root 100 scanDirectory(callback, bundle, prefix); 101 } else if (name.endsWith(".jar") || name.endsWith(".zip")) { 102 // scan embedded jar/zip 103 scanZip(callback, bundle, name); 104 } else { 105 // assume it's a directory 106 scanDirectory(callback, bundle, addSlash(prefix) + name); 107 } 108 } 109 } 110 } 111 112 private void scanDirectory(ResourceFinderCallback callback, Bundle bundle, String basePath) throws Exception { 113 if (!discoveryFilter.directoryDiscoveryRequired(basePath)) { 114 return; 115 } 116 Enumeration e = bundle.findEntries(basePath, "*" + suffix, true); 117 if (e != null) { 118 while (e.hasMoreElements()) { 119 callback.foundInDirectory(bundle, basePath, (URL) e.nextElement()); 120 } 121 } 122 } 123 124 private void scanZip(ResourceFinderCallback callback, Bundle bundle, String zipName) throws Exception { 125 if (!discoveryFilter.zipFileDiscoveryRequired(zipName)) { 126 return; 127 } 128 URL zipEntry = bundle.getEntry(zipName); 129 if (zipEntry == null) { 130 return; 131 } 132 try { 133 ZipInputStream in = new ZipInputStream(zipEntry.openStream()); 134 ZipEntry entry; 135 while ((entry = in.getNextEntry()) != null) { 136 String name = entry.getName(); 137 if (prefixMatches(name) && suffixMatches(name)) { 138 callback.foundInJar(bundle, zipName, entry, new ZipEntryInputStream(in)); 139 } 140 } 141 } catch (IOException e) { 142 e.printStackTrace(); 143 } 144 } 145 146 private static class ZipEntryInputStream extends FilterInputStream { 147 public ZipEntryInputStream(ZipInputStream in) { 148 super(in); 149 } 150 public void close() throws IOException { 151 // not really necessary 152 // ((ZipInputStream) in).closeEntry(); 153 } 154 } 155 156 private boolean prefixMatches(String name) { 157 if (prefix.length() == 0 || prefix.equals(".") || prefix.equals("/")) { 158 return true; 159 } else if (prefix.startsWith("/")) { 160 return name.startsWith(prefix, 1); 161 } else { 162 return name.startsWith(prefix); 163 } 164 } 165 166 private boolean suffixMatches(String name) { 167 return (suffix.length() == 0) ? true : name.endsWith(suffix); 168 } 169 170 private static String addSlash(String name) { 171 if (!name.endsWith("/")) { 172 name = name + "/"; 173 } 174 return name; 175 } 176 177 public interface ResourceFinderCallback { 178 void foundInDirectory(Bundle bundle, String baseDir, URL url) throws Exception; 179 180 void foundInJar(Bundle bundle, String jarName, ZipEntry entry, InputStream in) throws Exception; 181 } 182 183 public static class DefaultResourceFinderCallback implements ResourceFinderCallback { 184 185 private Set<URL> resources; 186 187 public DefaultResourceFinderCallback() { 188 this(new LinkedHashSet<URL>()); 189 } 190 191 public DefaultResourceFinderCallback(Set<URL> resources) { 192 this.resources = resources; 193 } 194 195 public Set<URL> getResources() { 196 return resources; 197 } 198 199 public void foundInDirectory(Bundle bundle, String baseDir, URL url) throws Exception { 200 resources.add(url); 201 } 202 203 public void foundInJar(Bundle bundle, String jarName, ZipEntry entry, InputStream in) throws Exception { 204 URL jarURL = bundle.getEntry(jarName); 205 URL url = new URL("jar:" + jarURL.toString() + "!/" + entry.getName()); 206 resources.add(url); 207 } 208 209 } 210 211 public static class DummyDiscoveryFilter implements ResourceDiscoveryFilter { 212 213 214 public boolean directoryDiscoveryRequired(String url) { 215 return true; 216 } 217 218 219 public boolean rangeDiscoveryRequired(DiscoveryRange discoveryRange) { 220 return true; 221 } 222 223 224 public boolean zipFileDiscoveryRequired(String url) { 225 return true; 226 } 227 228 } 229 }