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: 1160131 $ $Date: 2011-08-22 15:07:20 +0800 (Mon, 22 Aug 2011) $
043 */
044 public 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 try {
169 ZipInputStream in = new ZipInputStream(zipEntry.openStream());
170 ZipEntry entry;
171 while ((entry = in.getNextEntry()) != null) {
172 String name = entry.getName();
173 if (prefixMatches(name) && suffixMatches(name)) {
174 if (!callback.foundInJar(bundle, zipName, entry, new ZipEntryInputStream(in))) {
175 return false;
176 }
177 }
178 }
179 } catch (IOException e) {
180 e.printStackTrace();
181 }
182 return true;
183 }
184
185 private static class ZipEntryInputStream extends FilterInputStream {
186 public ZipEntryInputStream(ZipInputStream in) {
187 super(in);
188 }
189 public void close() throws IOException {
190 // not really necessary
191 // ((ZipInputStream) in).closeEntry();
192 }
193 }
194
195 private boolean prefixMatches(String name) {
196 if (prefix.length() == 0 || prefix.equals(".") || prefix.equals("/")) {
197 return true;
198 } else if (prefix.startsWith("/")) {
199 return name.startsWith(prefix, 1);
200 } else {
201 return name.startsWith(prefix);
202 }
203 }
204
205 private boolean suffixMatches(String name) {
206 return (suffix.length() == 0) ? true : name.endsWith(suffix);
207 }
208
209 private static String addSlash(String name) {
210 if (name == null ) return "";
211 name = name.trim();
212 if (!name.isEmpty() && !name.endsWith("/")) {
213 name = name + "/";
214 }
215 return name;
216 }
217
218 public interface ResourceFinderCallback {
219 /**
220 * Resource found in a directory in a bundle.
221 *
222 * @return true to continue scanning, false to abort scanning.
223 */
224 boolean foundInDirectory(Bundle bundle, String baseDir, URL url) throws Exception;
225
226 /**
227 * Resource found in a jar file in a bundle.
228 *
229 * @return true to continue scanning, false to abort scanning.
230 */
231 boolean foundInJar(Bundle bundle, String jarName, ZipEntry entry, InputStream in) throws Exception;
232 }
233
234 public static class DefaultResourceFinderCallback implements ResourceFinderCallback {
235
236 private Set<URL> resources;
237
238 public DefaultResourceFinderCallback() {
239 this(new LinkedHashSet<URL>());
240 }
241
242 public DefaultResourceFinderCallback(Set<URL> resources) {
243 this.resources = resources;
244 }
245
246 public Set<URL> getResources() {
247 return resources;
248 }
249
250 public boolean foundInDirectory(Bundle bundle, String baseDir, URL url) throws Exception {
251 resources.add(url);
252 return true;
253 }
254
255 public boolean foundInJar(Bundle bundle, String jarName, ZipEntry entry, InputStream in) throws Exception {
256 URL jarURL = bundle.getEntry(jarName);
257 URL url = new URL("jar:" + jarURL.toString() + "!/" + entry.getName());
258 resources.add(url);
259 return true;
260 }
261
262 }
263
264 public static class DummyDiscoveryFilter implements ResourceDiscoveryFilter {
265
266 public boolean rangeDiscoveryRequired(DiscoveryRange discoveryRange) {
267 return true;
268 }
269
270 public boolean directoryDiscoveryRequired(String url) {
271 return true;
272 }
273
274 public boolean zipFileDiscoveryRequired(String url) {
275 return true;
276 }
277
278 }
279 }