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.IOException;
023    import java.io.InputStream;
024    import java.net.URL;
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.Enumeration;
028    import java.util.Iterator;
029    import java.util.LinkedHashSet;
030    import java.util.List;
031    import java.util.zip.ZipEntry;
032    
033    import org.osgi.framework.Bundle;
034    import org.osgi.framework.ServiceReference;
035    import 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: 1095603 $ $Date: 2011-04-21 14:29:46 +0800 (Thu, 21 Apr 2011) $
051     */
052    public 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 new IOException("Error discovering resources", 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 = bundle.getEntry(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    }