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<>());
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[0]);
054        }
055    }
056
057    public synchronized boolean addStore(ResourceStore store) {
058        if (cp.add(store)) {
059            stores = cp.toArray(new ResourceStore[0]);
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[0]);
068            return true;
069        }
070        return false;
071    }
072
073    @Override
074    public synchronized ResourceStoreClassLoader clone() {
075        return new ResourceStoreClassLoader(getParent(), new LinkedHashSet<>(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    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<>();
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    protected String getId() {
215        return "" + this + "[" + this.getClass().getClassLoader() + "]";
216    }
217
218    /**
219     * org.my.Class -&gt; org/my/Class.class
220     */
221    public static String convertClassToResourcePath(final String pName) {
222        return pName.replace('.', '/') + ".class";
223    }
224
225}