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