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.Enumeration;
025
026import org.osgi.framework.Bundle;
027import org.osgi.framework.BundleReference;
028
029/**
030 * ClassLoader for a {@link Bundle}. 
031 * <br/>
032 * In OSGi, resource lookup on resources in the <i>META-INF</i> directory using {@link Bundle#getResource(String)} or 
033 * {@link Bundle#getResources(String)} does not return the resources found in the wired bundles of the bundle 
034 * (wired via <i>Import-Package</i> or <i>DynamicImport-Package</i>). This class loader implementation provides 
035 * {@link #getResource(String) and {@link #getResources(String)} methods that do delegate such resource lookups to
036 * the wired bundles. 
037 * <br/>
038 * The URLs returned by {@link Bundle#getResource(String)} or {@link Bundle#getResources(String)} methods are 
039 * OSGi framework specific &quot;bundle&quot; URLs. This sometimes can cause problems with 3rd party libraries
040 * which do not understand how to interpret the &quot;bundle&quot; URLs. This ClassLoader implementation, if enabled,
041 * can return <tt>jar</tt> URLs for resources found in embedded jars in the bundle. If a resource is found within a
042 * directory in the bundle the URL returned for that resource is unconverted.
043 * 
044 * @version $Rev: 1163514 $ $Date: 2011-08-31 09:37:38 +0200 (Wed, 31 Aug 2011) $
045 */
046public class BundleClassLoader extends ClassLoader implements DelegatingBundleReference {
047
048    protected final Bundle bundle;
049    protected final BundleResourceHelper resourceHelper;
050
051    public BundleClassLoader(Bundle bundle) {
052        this(bundle, 
053             BundleResourceHelper.getSearchWiredBundles(true), 
054             BundleResourceHelper.getConvertResourceUrls(false));
055    }
056        
057    public BundleClassLoader(Bundle bundle, boolean searchWiredBundles) {
058        this(bundle, 
059             searchWiredBundles, 
060             BundleResourceHelper.getConvertResourceUrls(false));
061    }
062    
063    public BundleClassLoader(Bundle bundle, boolean searchWiredBundles, boolean convertResourceUrls) {
064        this.bundle = bundle;
065        this.resourceHelper = new BundleResourceHelper(bundle, searchWiredBundles, convertResourceUrls);
066    }
067
068    @Override
069    public String toString() {
070        return "[BundleClassLoader] " + bundle;
071    }
072    
073    @Override
074    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
075        Class clazz = bundle.loadClass(name);
076        if (resolve) {
077            resolveClass(clazz);
078        }
079        return clazz;
080    }
081    
082    @Override
083    public URL getResource(String name) {
084        return resourceHelper.getResource(name);
085    }
086    
087    @Override
088    public Enumeration<URL> getResources(String name) throws IOException {
089        return resourceHelper.getResources(name);
090    }
091    
092    @Override
093    public Enumeration<URL> findResources (String name) throws IOException {
094        return this.getResources(name);
095    }
096    
097    public void setSearchWiredBundles(boolean search) {
098        resourceHelper.setSearchWiredBundles(search);
099    }
100    
101    public boolean getSearchWiredBundles() {
102        return resourceHelper.getSearchWiredBundles();
103    }
104           
105    public void setConvertResourceUrls(boolean convert) {
106        resourceHelper.setConvertResourceUrls(convert);
107    }
108    
109    public boolean getConvertResourceUrls() {
110        return resourceHelper.getConvertResourceUrls();
111    }
112    
113    /**
114     * Return the bundle associated with this classloader.
115     * 
116     * In most cases the bundle associated with the classloader is a regular framework bundle. 
117     * However, in some cases the bundle associated with the classloader is a {@link DelegatingBundle}.
118     * In such cases, the <tt>unwrap</tt> parameter controls whether this function returns the
119     * {@link DelegatingBundle} instance or the main application bundle backing with the {@link DelegatingBundle}.
120     *
121     * @param unwrap If true and if the bundle associated with this classloader is a {@link DelegatingBundle}, 
122     *        this function will return the main application bundle backing with the {@link DelegatingBundle}. 
123     *        Otherwise, the bundle associated with this classloader is returned as is.
124     * @return The bundle associated with this classloader.
125     */
126    public Bundle getBundle(boolean unwrap) {
127        if (unwrap && bundle instanceof DelegatingBundle) {
128            return ((DelegatingBundle) bundle).getMainBundle();
129        }
130        return bundle;
131    }
132    
133    /**
134     * Return the bundle associated with this classloader.
135     * 
136     * This method calls {@link #getBundle(boolean) getBundle(true)} and therefore always returns a regular 
137     * framework bundle.  
138     * <br><br>
139     * Note: Some libraries use {@link BundleReference#getBundle()} to obtain a bundle for the given 
140     * classloader and expect the returned bundle instance to be work with any OSGi API. Some of these API might
141     * not work if {@link DelegatingBundle} is returned. That is why this function will always return
142     * a regular framework bundle. See {@link #getBundle(boolean)} for more information.
143     *
144     * @return The bundle associated with this classloader.
145     */
146    public Bundle getBundle() {
147        return getBundle(true);
148    }
149
150    @Override
151    public int hashCode() {
152        return bundle.hashCode();
153    }
154
155    @Override
156    public boolean equals(Object other) {
157        if (other == this) {
158            return true;
159        }
160        if (other == null || !other.getClass().equals(getClass())) {
161            return false;
162        }
163        BundleClassLoader otherBundleClassLoader = (BundleClassLoader) other;
164        return this.bundle == otherBundleClassLoader.bundle;
165    }
166
167}