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.net.URL;
024    import java.util.ArrayList;
025    import java.util.Collections;
026    import java.util.Enumeration;
027    import java.util.Iterator;
028    import java.util.LinkedHashSet;
029    import java.util.List;
030    
031    import org.osgi.framework.Bundle;
032    import org.osgi.framework.BundleReference;
033    
034    /**
035     * ClassLoader for a {@link Bundle}. 
036     * <br/>
037     * In OSGi, resource lookup on resources in the <i>META-INF</i> directory using {@link Bundle#getResource(String)} or 
038     * {@link Bundle#getResources(String)} does not return the resources found in the wired bundles of the bundle 
039     * (wired via <i>Import-Package</i> or <i>DynamicImport-Package</i>). This class loader implementation provides 
040     * {@link #getResource(String) and {@link #getResources(String)} methods that do delegate such resource lookups to
041     * the wired bundles. 
042     * 
043     * @version $Rev: 938291 $ $Date: 2010-04-27 03:53:06 +0200 (Tue, 27 Apr 2010) $
044     */
045    public class BundleClassLoader extends ClassLoader implements BundleReference {
046    
047        private final static String META_INF_1 = "META-INF/";
048        private final static String META_INF_2 = "/META-INF/";
049        
050        private final Bundle bundle;
051        private boolean searchWiredBundles;
052    
053        public BundleClassLoader(Bundle bundle) {
054            this(bundle, true);
055        }
056        
057        public BundleClassLoader(Bundle bundle, boolean searchWiredBundles) {
058            this.bundle = bundle;
059            this.searchWiredBundles = searchWiredBundles;
060        }
061    
062        @Override
063        public Class<?> loadClass(String name) throws ClassNotFoundException {
064            return loadClass(name, false);
065        }
066    
067        @Override
068        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
069            Class clazz = bundle.loadClass(name);
070            if (resolve) {
071                resolveClass(clazz);
072            }
073            return clazz;
074        }
075    
076        @Override
077        public String toString() {
078            return "[BundleClassLoader] " + bundle;
079        }
080    
081        @Override
082        public URL getResource(String name) {
083            URL resource = bundle.getResource(name);
084            if (resource == null && isMetaInfResource(name)) {
085                LinkedHashSet<Bundle> wiredBundles = BundleUtils.getWiredBundles(bundle);
086                Iterator<Bundle> iterator = wiredBundles.iterator();
087                while (iterator.hasNext() && resource == null) {                
088                    resource = iterator.next().getResource(name);
089                }
090            }
091            return resource;
092        }
093    
094        @SuppressWarnings("unchecked")
095        @Override
096        public Enumeration<URL> getResources(String name) throws IOException {
097            Enumeration<URL> e = (Enumeration<URL>) bundle.getResources(name);
098            if (isMetaInfResource(name)) {
099                ArrayList<URL> allResources = new ArrayList<URL>();
100                addToList(allResources, e);
101                LinkedHashSet<Bundle> wiredBundles = BundleUtils.getWiredBundles(bundle);
102                for (Bundle wiredBundle : wiredBundles) {
103                    Enumeration<URL> resources = wiredBundle.getResources(name);
104                    addToList(allResources, resources);
105                }
106                return Collections.enumeration(allResources);            
107            } else {
108                if (e == null) {
109                    return Collections.enumeration(Collections.EMPTY_LIST);
110                } else {
111                    return e;
112                }
113            }
114        }
115    
116        public void setSearchWiredBundles(boolean search) {
117            searchWiredBundles = search;
118        }
119        
120        public boolean getSearchWiredBundles() {
121            return searchWiredBundles;
122        }
123        
124        private boolean isMetaInfResource(String name) {
125            return searchWiredBundles && name != null && (name.startsWith(META_INF_1) || name.startsWith(META_INF_2));
126        }
127          
128        private void addToList(List<URL> list, Enumeration<URL> enumeration) {
129            if (enumeration != null) {
130                while (enumeration.hasMoreElements()) {
131                    list.add(enumeration.nextElement());
132                }
133            }
134        }
135            
136        /**
137         * Return the bundle associated with this classloader.
138         * 
139         * In most cases the bundle associated with the classloader is a regular framework bundle. 
140         * However, in some cases the bundle associated with the classloader is a {@link DelegatingBundle}.
141         * In such cases, the <tt>unwrap</tt> parameter controls whether this function returns the
142         * {@link DelegatingBundle} instance or the main application bundle backing with the {@link DelegatingBundle}.
143         *
144         * @param unwrap If true and if the bundle associated with this classloader is a {@link DelegatingBundle}, 
145         *        this function will return the main application bundle backing with the {@link DelegatingBundle}. 
146         *        Otherwise, the bundle associated with this classloader is returned as is.
147         * @return The bundle associated with this classloader.
148         */
149        public Bundle getBundle(boolean unwrap) {
150            if (unwrap && bundle instanceof DelegatingBundle) {
151                return ((DelegatingBundle) bundle).getMainBundle();
152            }
153            return bundle;
154        }
155        
156        /**
157         * Return the bundle associated with this classloader.
158         * 
159         * This method calls {@link #getBundle(boolean) getBundle(true)} and therefore always returns a regular 
160         * framework bundle.  
161         * <br><br>
162         * Note: Some libraries use {@link BundleReference#getBundle()} to obtain a bundle for the given 
163         * classloader and expect the returned bundle instance to be work with any OSGi API. Some of these API might
164         * not work if {@link DelegatingBundle} is returned. That is why this function will always return
165         * a regular framework bundle. See {@link #getBundle(boolean)} for more information.
166         *
167         * @return The bundle associated with this classloader.
168         */
169        public Bundle getBundle() {
170            return getBundle(true);
171        }
172    
173        @Override
174        public int hashCode() {
175            return bundle.hashCode();
176        }
177    
178        @Override
179        public boolean equals(Object other) {
180            if (other == this) {
181                return true;
182            }
183            if (other == null || !other.getClass().equals(getClass())) {
184                return false;
185            }
186            BundleClassLoader otherBundleClassLoader = (BundleClassLoader) other;
187            return this.bundle == otherBundleClassLoader.bundle;
188        }
189    
190    }