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.net.URL;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.Enumeration;
027import java.util.Iterator;
028import java.util.LinkedHashSet;
029import java.util.List;
030
031import org.osgi.framework.Bundle;
032import 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 (mar. 27 avril 2010) $
044 */
045public 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}