001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu
016 */
017package org.nuxeo.ecm.webengine.loader.store;
018
019import java.io.IOException;
020import java.net.URL;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Enumeration;
024import java.util.LinkedHashSet;
025import java.util.List;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030/**
031 * The class loader allows modifying the stores (adding/removing). Mutable operations are thread safe.
032 *
033 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
034 */
035public class ResourceStoreClassLoader extends ClassLoader implements Cloneable {
036
037    private final Log log = LogFactory.getLog(ResourceStoreClassLoader.class);
038
039    private volatile ResourceStore[] stores;
040
041    private final LinkedHashSet<ResourceStore> cp; // class path
042
043    public ResourceStoreClassLoader(final ClassLoader pParent) {
044        this(pParent, new LinkedHashSet<ResourceStore>());
045    }
046
047    protected ResourceStoreClassLoader(final ClassLoader pParent, LinkedHashSet<ResourceStore> cp) {
048        super(pParent);
049        this.cp = cp;
050        if (!cp.isEmpty()) {
051            stores = cp.toArray(new ResourceStore[cp.size()]);
052        }
053    }
054
055    public synchronized boolean addStore(ResourceStore store) {
056        if (cp.add(store)) {
057            stores = cp.toArray(new ResourceStore[cp.size()]);
058            return true;
059        }
060        return false;
061    }
062
063    public synchronized boolean removeStore(ResourceStore store) {
064        if (cp.remove(store)) {
065            stores = cp.toArray(new ResourceStore[cp.size()]);
066            return true;
067        }
068        return false;
069    }
070
071    @Override
072    public synchronized ResourceStoreClassLoader clone() {
073        return new ResourceStoreClassLoader(getParent(), new LinkedHashSet<ResourceStore>(cp));
074    }
075
076    public ResourceStore[] getStores() {
077        return stores;
078    }
079
080    protected Class<?> fastFindClass(final String name) {
081        ResourceStore[] _stores = stores; // use a local variable
082        if (_stores != null) {
083            for (final ResourceStore store : _stores) {
084                final byte[] clazzBytes = store.getBytes(convertClassToResourcePath(name));
085                if (clazzBytes != null) {
086                    if (log.isTraceEnabled()) {
087                        log.trace(getId() + " found class: " + name + " (" + clazzBytes.length + " bytes)");
088                    }
089                    doDefinePackage(name);
090                    return defineClass(name, clazzBytes, 0, clazzBytes.length);
091                }
092            }
093        }
094        return null;
095    }
096
097    /**
098     * Without this method getPackage() returns null
099     *
100     * @param name
101     */
102    protected void doDefinePackage(String name) {
103        int i = name.lastIndexOf('.');
104        if (i > -1) {
105            String pkgname = name.substring(0, i);
106            Package pkg = getPackage(pkgname);
107            if (pkg == null) {
108                definePackage(pkgname, null, null, null, null, null, null, null);
109            }
110        }
111    }
112
113    @Override
114    protected URL findResource(String name) {
115        ResourceStore[] _stores = stores; // use a local variable
116        if (_stores != null) {
117            for (final ResourceStore store : _stores) {
118                final URL url = store.getURL(name);
119                if (url != null) {
120                    if (log.isTraceEnabled()) {
121                        log.trace(getId() + " found resource: " + name);
122                    }
123                    return url;
124                }
125            }
126        }
127        return null;
128    }
129
130    @Override
131    protected Enumeration<URL> findResources(String name) throws IOException {
132        ResourceStore[] _stores = stores; // use a local variable
133        if (_stores != null) {
134            List<URL> result = new ArrayList<URL>();
135            for (final ResourceStore store : _stores) {
136                final URL url = store.getURL(name);
137                if (url != null) {
138                    if (log.isTraceEnabled()) {
139                        log.trace(getId() + " found resource: " + name);
140                    }
141                    result.add(url);
142                }
143            }
144            return Collections.enumeration(result);
145        }
146        return null;
147    }
148
149    @Override
150    public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
151        // log.debug(getId() + " looking for: " + name);
152        Class<?> clazz = findLoadedClass(name);
153
154        if (clazz == null) {
155            clazz = fastFindClass(name);
156
157            if (clazz == null) {
158
159                final ClassLoader parent = getParent();
160                if (parent != null) {
161                    clazz = parent.loadClass(name);
162                    // log.debug(getId() + " delegating loading to parent: " + name);
163                } else {
164                    throw new ClassNotFoundException(name);
165                }
166
167            } else {
168                if (log.isDebugEnabled()) {
169                    log.debug(getId() + " loaded from store: " + name);
170                }
171            }
172        }
173
174        if (resolve) {
175            resolveClass(clazz);
176        }
177
178        return clazz;
179    }
180
181    @Override
182    protected Class<?> findClass(final String name) throws ClassNotFoundException {
183        final Class<?> clazz = fastFindClass(name);
184        if (clazz == null) {
185            throw new ClassNotFoundException(name);
186        }
187        return clazz;
188    }
189
190    @Override
191    public Enumeration<URL> getResources(String name) throws IOException {
192        Enumeration<URL> urls = findResources(name);
193        if (urls == null) {
194            final ClassLoader parent = getParent();
195            if (parent != null) {
196                urls = parent.getResources(name);
197            }
198        }
199        return urls;
200    }
201
202    @Override
203    public URL getResource(String name) {
204        URL url = findResource(name);
205        if (url == null) {
206            final ClassLoader parent = getParent();
207            if (parent != null) {
208                url = parent.getResource(name);
209            }
210        }
211        return url;
212    }
213
214    // TODO implement this method if you want packages to be supported by this loader
215    @Override
216    protected Package getPackage(String name) {
217        return super.getPackage(name);
218    }
219
220    @Override
221    protected Package[] getPackages() {
222        return super.getPackages();
223    }
224
225    protected String getId() {
226        return "" + this + "[" + this.getClass().getClassLoader() + "]";
227    }
228
229    /**
230     * org.my.Class -> org/my/Class.class
231     */
232    public static String convertClassToResourcePath(final String pName) {
233        return pName.replace('.', '/') + ".class";
234    }
235
236}