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.IOException;
023import java.io.InputStream;
024import java.net.URL;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Enumeration;
028import java.util.Iterator;
029import java.util.LinkedHashSet;
030import java.util.List;
031import java.util.zip.ZipEntry;
032
033import org.osgi.framework.Bundle;
034import org.osgi.framework.ServiceReference;
035import org.osgi.service.packageadmin.PackageAdmin;
036
037/**
038 * Helper for finding resources in a {@link Bundle}. 
039 * <br/>
040 * In OSGi, resource lookup on resources in the <i>META-INF</i> directory using {@link Bundle#getResource(String)} or 
041 * {@link Bundle#getResources(String)} does not return the resources found in the wired bundles of the bundle 
042 * (wired via <i>Import-Package</i> or <i>DynamicImport-Package</i>). This class loader implementation provides 
043 * {@link #getResource(String) and {@link #getResources(String)} methods that do delegate <i>META-INF</i> resource lookups
044 * to the wired bundles. 
045 * <br/>
046 * The URLs returned by {@link Bundle#getResource(String)} or {@link Bundle#getResources(String)} methods are 
047 * OSGi framework specific &quot;bundle&quot; URLs. If enabled, this helper can convert the framework specific URLs into 
048 * regular <tt>jar</tt> URLs. 
049 * 
050 * @version $Rev: 1331428 $ $Date: 2012-04-27 15:39:19 +0200 (Fri, 27 Apr 2012) $
051 */
052public class BundleResourceHelper {
053
054    public static final String SEARCH_WIRED_BUNDLES = BundleResourceHelper.class.getName() + ".searchWiredBundles";
055    public static final String CONVERT_RESOURCE_URLS = BundleResourceHelper.class.getName() + ".convertResourceUrls";
056    
057    private final static String META_INF_1 = "META-INF/";
058    private final static String META_INF_2 = "/META-INF/";
059    
060    protected final Bundle bundle;
061    private LinkedHashSet<Bundle> wiredBundles = null;
062    protected boolean searchWiredBundles;
063    protected boolean convertResourceUrls;
064  
065    public BundleResourceHelper(Bundle bundle) {
066        this(bundle,      
067             BundleResourceHelper.getSearchWiredBundles(false), 
068             BundleResourceHelper.getConvertResourceUrls(false));
069    }
070    
071    public BundleResourceHelper(Bundle bundle, boolean searchWiredBundles, boolean convertResourceUrls) {
072        this.bundle = bundle;
073        this.searchWiredBundles = searchWiredBundles;
074        this.convertResourceUrls = convertResourceUrls;
075    }
076
077    public void setSearchWiredBundles(boolean search) {
078        searchWiredBundles = search;
079    }
080    
081    public boolean getSearchWiredBundles() {
082        return searchWiredBundles;
083    }
084  
085    public void setConvertResourceUrls(boolean convert) {
086        convertResourceUrls = convert;
087    }
088    
089    public boolean getConvertResourceUrls() {
090        return convertResourceUrls;
091    }
092        
093    public URL getResource(String name) {
094        if (convertResourceUrls) {
095            return convertedFindResource(name);
096        } else {
097            return findResource(name);
098        }
099    }
100    
101    public Enumeration<URL> getResources(String name) throws IOException {
102        if (convertResourceUrls) {
103            return convertedFindResources(name);
104        } else {
105            return findResources(name);
106        }
107    }
108    
109    protected URL convert(URL url) {
110        return url;
111    }
112    
113    private synchronized LinkedHashSet<Bundle> getWiredBundles() {
114        if (wiredBundles == null) {
115            wiredBundles = BundleUtils.getWiredBundles((bundle instanceof DelegatingBundle) ? ((DelegatingBundle) bundle).getMainBundle() : bundle);
116        }
117        return wiredBundles;
118    }
119    
120    private boolean isMetaInfResource(String name) {
121        return searchWiredBundles && name != null && (name.startsWith(META_INF_1) || name.startsWith(META_INF_2));
122    }
123      
124    private List<URL> getList() {
125        if (convertResourceUrls) {
126            return new ArrayList<URL>() {
127                public boolean add(URL u) {
128                    return super.add(convert(u));
129                }
130            };
131        } else {
132            return new ArrayList<URL>();
133        }
134    }
135    
136    private void addToList(List<URL> list, Enumeration<URL> enumeration) {
137        if (enumeration != null) {
138            while (enumeration.hasMoreElements()) {
139                list.add(enumeration.nextElement());
140            }
141        }
142    }
143           
144    protected URL findResource(String name) {
145        URL resource = bundle.getResource(name);
146        if (resource == null && isMetaInfResource(name)) {
147            LinkedHashSet<Bundle> wiredBundles = getWiredBundles();
148            Iterator<Bundle> iterator = wiredBundles.iterator();
149            while (iterator.hasNext() && resource == null) {                
150                resource = iterator.next().getResource(name);
151            }
152        }
153        if (resource != null && convertResourceUrls) {
154            resource = convert(resource);
155        }
156        return resource;
157    }
158
159    protected Enumeration<URL> findResources(String name) throws IOException {
160        Enumeration<URL> e = (Enumeration<URL>) bundle.getResources(name);
161        if (isMetaInfResource(name)) {
162            List<URL> allResources = getList();
163            addToList(allResources, e);
164            LinkedHashSet<Bundle> wiredBundles = getWiredBundles();
165            for (Bundle wiredBundle : wiredBundles) {
166                Enumeration<URL> resources = wiredBundle.getResources(name);
167                addToList(allResources, resources);
168            }
169            return Collections.enumeration(allResources);            
170        } else if (e == null) {
171            return Collections.enumeration(Collections.<URL>emptyList());
172        } else if (convertResourceUrls) {
173            List<URL> allResources = getList();
174            addToList(allResources, e);
175            return Collections.enumeration(allResources);
176        } else {
177            return e;            
178        }
179    }    
180    
181    /**
182     * Lookup resource and return converted URL (in a generic way).
183     * 
184     * @param name
185     * @return
186     */
187    protected URL convertedFindResource(String name) {
188        ServiceReference reference = bundle.getBundleContext().getServiceReference(PackageAdmin.class.getName());
189        PackageAdmin packageAdmin = (PackageAdmin) bundle.getBundleContext().getService(reference);
190        try {
191            List<URL> resources = findResources(packageAdmin, bundle, name, false);
192            if (resources.isEmpty() && isMetaInfResource(name)) {
193                LinkedHashSet<Bundle> wiredBundles = getWiredBundles();
194                Iterator<Bundle> iterator = wiredBundles.iterator();
195                while (iterator.hasNext() && resources.isEmpty()) {    
196                    Bundle wiredBundle = iterator.next();
197                    resources = findResources(packageAdmin, wiredBundle, name, false);
198                }
199            }
200            return (resources.isEmpty()) ? null : resources.get(0);
201        } catch (Exception e) {
202            return null;
203        } finally {
204            bundle.getBundleContext().ungetService(reference);
205        }
206    }
207
208    /**
209     * Lookup resources and return converted URLs (in a generic way).
210     * 
211     * @param name
212     * @return
213     */
214    protected Enumeration<URL> convertedFindResources(String name) throws IOException {
215        ServiceReference reference = bundle.getBundleContext().getServiceReference(PackageAdmin.class.getName());
216        PackageAdmin packageAdmin = (PackageAdmin) bundle.getBundleContext().getService(reference);
217        try {
218            List<URL> resources = findResources(packageAdmin, bundle, name, true);
219            if (isMetaInfResource(name)) {
220                LinkedHashSet<Bundle> wiredBundles = getWiredBundles();
221                for (Bundle wiredBundle : wiredBundles) {
222                    resources.addAll(findResources(packageAdmin, wiredBundle, name, true));
223                }
224            }
225            return Collections.enumeration(resources);
226        } catch (Exception e) {
227            throw (IOException) new IOException("Error discovering resources: " + e).initCause(e);
228        } finally {
229            bundle.getBundleContext().ungetService(reference);
230        }
231    }
232    
233    private static List<URL> findResources(PackageAdmin packageAdmin, 
234                                           Bundle bundle, 
235                                           String name, 
236                                           final boolean continueScanning) throws Exception {
237        BundleResourceFinder finder = new BundleResourceFinder(packageAdmin, bundle, "", name);
238        final List<URL> resources = new ArrayList<URL>();
239        finder.find(new BundleResourceFinder.ResourceFinderCallback() {
240
241            public boolean foundInDirectory(Bundle bundle, String baseDir, URL url) throws Exception {
242                resources.add(url);
243                return continueScanning;
244            }
245
246            public boolean foundInJar(Bundle bundle, String jarName, ZipEntry entry, InputStream inputStream) throws Exception {
247                URL jarURL = BundleUtils.getEntry(bundle, jarName);
248                URL url = new URL("jar:" + jarURL.toString() + "!/" + entry.getName());
249                resources.add(url);
250                return continueScanning;
251            }
252        });                   
253        return resources;           
254    }
255    
256    public static boolean getSearchWiredBundles(boolean defaultValue) {
257        String value = System.getProperty(SEARCH_WIRED_BUNDLES);
258        return (value == null) ? defaultValue : Boolean.parseBoolean(value);        
259    }
260    
261    public static boolean getConvertResourceUrls(boolean defaultValue) {
262        String value = System.getProperty(CONVERT_RESOURCE_URLS);
263        return (value == null) ? defaultValue : Boolean.parseBoolean(value);        
264    }
265}