/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.loader; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLClassLoader; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.config.ConfigException; import com.caucho.lifecycle.Lifecycle; import com.caucho.loader.enhancer.ScanListener; import com.caucho.loader.enhancer.ScanManager; import com.caucho.loader.module.ArtifactManager; import com.caucho.management.server.EnvironmentMXBean; import com.caucho.util.Alarm; import com.caucho.util.Crc64; import com.caucho.util.LruCache; import com.caucho.util.ResinThreadPoolExecutor; import com.caucho.vfs.Path; import com.caucho.vfs.Vfs; /** * Class loader which checks for changes in class files and automatically * picks up new jars. * * <p>DynamicClassLoaders can be chained creating one virtual class loader. * From the perspective of the JDK, it's all one classloader. Internally, * the class loader chain searches like a classpath. */ public class EnvironmentClassLoader extends DynamicClassLoader { private static Logger _log; private static final Object _childListenerLock = new Object(); // listeners invoked at the start of any child environment private static EnvironmentLocal<ArrayList<EnvironmentListener>> _childListeners; // listeners invoked when a Loader is added private static EnvironmentLocal<ArrayList<AddLoaderListener>> _addLoaderListeners; // The owning bean private EnvironmentBean _owner; // Class loader specific attributes private ConcurrentHashMap<String,Object> _attributes = new ConcurrentHashMap<String,Object>(8); private ArrayList<ScanListener> _scanListeners; private ArrayList<ScanRoot> _pendingScanRoots = new ArrayList<ScanRoot>(); private AtomicReference<ArtifactManager> _artifactManagerRef = new AtomicReference<ArtifactManager>(); private ArtifactManager _artifactManager; private ArrayList<String> _packageList = new ArrayList<String>(); // Array of listeners // server/306i - can't be weak reference, instead create WeakStopListener private ArrayList<EnvironmentListener> _listeners = new ArrayList<EnvironmentListener>(); private Map<String,String> _resourceAliasMap; private LruCache<String,ResourceEntry> _resourceCacheMap = new LruCache<String,ResourceEntry>(256); private WeakStopListener _stopListener; // The state of the environment private volatile Lifecycle _lifecycle = new Lifecycle(); private boolean _isConfigComplete; private EnvironmentAdmin _admin; private Throwable _configException; /** * Creates a new environment class loader. */ protected EnvironmentClassLoader(ClassLoader parent, String id) { super(parent); if (id != null) setId(id); // initializeEnvironment(); initListeners(); } /** * Creates a new environment class loader. */ public static EnvironmentClassLoader create() { ClassLoader parent = null; String id = null; return create(parent, id); } /** * Creates a new environment class loader. */ public static EnvironmentClassLoader create(String id) { ClassLoader parent = null; return create(parent, id); } /** * Creates a new environment class loader. */ public static EnvironmentClassLoader create(ClassLoader parent) { String id = null; return create(parent, id); } /** * Creates a new environment class loader. */ public static EnvironmentClassLoader create(ClassLoader parent, String id) { if (parent == null) parent = Thread.currentThread().getContextClassLoader(); ClassLoader systemClassLoader = getSystemClassLoaderSafe(); if (parent == null || isParent(parent, systemClassLoader)) parent = systemClassLoader; String className = System.getProperty("caucho.environment.class.loader"); if (className != null) { try { Class<?> cl = Thread.currentThread().getContextClassLoader().loadClass(className); Constructor<?> ctor = cl.getConstructor(new Class[] { ClassLoader.class, String.class}); Object instance = ctor.newInstance(parent, id); return (EnvironmentClassLoader) instance; } catch (Exception e) { e.printStackTrace(); } } return new EnvironmentClassLoader(parent, id); } private static boolean isParent(ClassLoader parent, ClassLoader child) { if (parent == null) return true; else if (child == null) return false; else if (parent.equals(child)) return true; else return isParent(parent, child.getParent()); } /** * Returns the environment's owner. */ public EnvironmentBean getOwner() { return _owner; } /** * Sets the environment's owner. */ public void setOwner(EnvironmentBean owner) { _owner = owner; } /** * Sets the config exception. */ public void setConfigException(Throwable e) { if (_configException == null) _configException = e; } /** * Gets the config exception. */ public Throwable getConfigException() { return _configException; } /** * Returns true if the environment is active */ public boolean isActive() { return _lifecycle.isActive(); } /** * Returns the admin */ public EnvironmentMXBean getAdmin() { return _admin; } /** * Initialize the environment. */ @Override public void init() { super.init(); initEnvironment(); } protected void initEnvironment() { } /** * Returns the named attributes */ public Object getAttribute(String name) { if (_attributes != null) return _attributes.get(name); else return null; } /** * Sets the named attributes */ public Object setAttribute(String name, Object obj) { if (obj == null) { if (_attributes == null) return null; else return _attributes.remove(name); } if (_attributes == null) _attributes = new ConcurrentHashMap<String,Object>(8); return _attributes.put(name, obj); } /** * Sets the named attributes */ public Object putIfAbsent(String name, Object obj) { if (obj == null) throw new NullPointerException(); if (_attributes == null) _attributes = new ConcurrentHashMap<String,Object>(8); return _attributes.putIfAbsent(name, obj); } /** * Removes the named attributes */ public Object removeAttribute(String name) { if (_attributes == null) return null; else return _attributes.remove(name); } // // resources // /** * Overrides getResource to implement caching. */ @Override public URL getResource(String name) { ResourceEntry entry = _resourceCacheMap.get(name); if (entry == null || entry.isModified()) { URL resource = super.getResource(name); entry = new ResourceEntry(resource); _resourceCacheMap.put(name, entry); } return entry.getResource(); } /** * Overrides getResource to implement caching. */ @Override public InputStream getResourceAsStream(String name) { ResourceEntry entry = _resourceCacheMap.get(name); if (entry == null || entry.isModified()) { URL resource = super.getResource(name); entry = new ResourceEntry(resource); _resourceCacheMap.put(name, entry); } return entry.getResourceAsStream(); } // // resource aliases // public void putResourceAlias(String name, String actualName) { if (_resourceAliasMap == null) _resourceAliasMap = new ConcurrentHashMap<String,String>(); _resourceAliasMap.put(name, actualName); } @Override public String getResourceAlias(String name) { if (_resourceAliasMap != null) { String actualName = _resourceAliasMap.get(name); if (actualName != null) return actualName; } return null; } /** * Adds a listener to detect environment lifecycle changes. */ public void addListener(EnvironmentListener listener) { synchronized (_listeners) { for (int i = _listeners.size() - 1; i >= 0; i--) { EnvironmentListener oldListener = _listeners.get(i); if (listener == oldListener) { return; } else if (oldListener == null) _listeners.remove(i); } _listeners.add(listener); } if (_lifecycle.isStarting()) { listener.environmentBind(this); } if (_lifecycle.isStarting() && _isConfigComplete) { listener.environmentStart(this); } } /** * Adds self as a listener. */ private void initListeners() { ClassLoader parent = getParent(); for (; parent != null; parent = parent.getParent()) { if (parent instanceof EnvironmentClassLoader) { EnvironmentClassLoader loader = (EnvironmentClassLoader) parent; if (_stopListener == null) _stopListener = new WeakStopListener(this); loader.addListener(_stopListener); return; } } } /** * Adds a listener to detect environment lifecycle changes. */ public void removeListener(EnvironmentListener listener) { ArrayList<EnvironmentListener> listeners = _listeners; if (_listeners == null) return; synchronized (listeners) { for (int i = listeners.size() - 1; i >= 0; i--) { EnvironmentListener oldListener = listeners.get(i); if (listener == oldListener) { listeners.remove(i); return; } else if (oldListener == null) { listeners.remove(i); } } } } /** * Adds a child listener. */ void addChildListener(EnvironmentListener listener) { synchronized (_childListenerLock) { if (_childListeners == null) _childListeners = new EnvironmentLocal<ArrayList<EnvironmentListener>>(); ArrayList<EnvironmentListener> listeners = _childListeners.getLevel(this); if (listeners == null) { listeners = new ArrayList<EnvironmentListener>(); _childListeners.set(listeners, this); } listeners.add(listener); } if (_lifecycle.isStarting() && _isConfigComplete) { listener.environmentStart(this); } } /** * Removes a child listener. */ void removeChildListener(EnvironmentListener listener) { synchronized (_childListenerLock) { if (_childListeners == null) return; ArrayList<EnvironmentListener> listeners = _childListeners.getLevel(this); if (listeners != null) listeners.remove(listener); } } /** * Returns the listeners. */ protected ArrayList<EnvironmentListener> getEnvironmentListeners() { ArrayList<EnvironmentListener> listeners; listeners = new ArrayList<EnvironmentListener>(); // add the descendant listeners if (_childListeners != null) { synchronized (_childListenerLock) { ClassLoader loader; for (loader = this; loader != null; loader = loader.getParent()) { if (loader instanceof EnvironmentClassLoader) { ArrayList<EnvironmentListener> childListeners; childListeners = _childListeners.getLevel(loader); if (childListeners != null) listeners.addAll(childListeners); } } } } if (_listeners == null) return listeners; ArrayList<EnvironmentListener> envListeners = _listeners; if (envListeners != null) { synchronized (envListeners) { for (int i = 0; i < envListeners.size(); i++) { EnvironmentListener listener = envListeners.get(i); if (listener != null) listeners.add(listener); else { envListeners.remove(i); i--; } } } } return listeners; } /** * Adds a child listener. */ public void addLoaderListener(AddLoaderListener listener) { synchronized (_childListenerLock) { if (_addLoaderListeners == null) _addLoaderListeners = new EnvironmentLocal<ArrayList<AddLoaderListener>>(); ArrayList<AddLoaderListener> listeners = _addLoaderListeners.getLevel(this); if (listeners == null) { listeners = new ArrayList<AddLoaderListener>(); _addLoaderListeners.set(listeners, this); } if (! listeners.contains(listener)) { listeners.add(listener); } } listener.addLoader(this); } /** * Returns the listeners. */ protected ArrayList<AddLoaderListener> getLoaderListeners() { ArrayList<AddLoaderListener> listeners; listeners = new ArrayList<AddLoaderListener>(); if (_addLoaderListeners == null) return listeners; // add the descendent listeners synchronized (_childListenerLock) { ClassLoader loader; for (loader = this; loader != null; loader = loader.getParent()) { if (loader instanceof EnvironmentClassLoader) { ArrayList<AddLoaderListener> childListeners; childListeners = _addLoaderListeners.getLevel(loader); if (childListeners != null) listeners.addAll(childListeners); } } } return listeners; } /** * Adds a listener to detect class loader changes. */ @Override protected void configureEnhancerEvent() { ArrayList<AddLoaderListener> listeners = getLoaderListeners(); for (int i = 0; listeners != null && i < listeners.size(); i++) { AddLoaderListener listener = listeners.get(i); if (listener.isEnhancer()) listeners.get(i).addLoader(this); } } /** * Adds a listener to detect class loader changes. */ @Override protected void configurePostEnhancerEvent() { ArrayList<AddLoaderListener> listeners = getLoaderListeners(); for (int i = 0; listeners != null && i < listeners.size(); i++) { AddLoaderListener listener = listeners.get(i); if (! listener.isEnhancer()) listeners.get(i).addLoader(this); } } /** * Adds the URL to the URLClassLoader. */ @Override public void addURL(URL url) { if (containsURL(url)) return; super.addURL(url); _pendingScanRoots.add(new ScanRoot(url, null)); } /** * Adds a virtual module root for scanning. * * @param rootPackage */ public void addScanPackage(URL url, String rootPackage) { if (! containsURL(url)) { super.addURL(url); } _packageList.add(rootPackage); _pendingScanRoots.add(new ScanRoot(url, rootPackage)); sendAddLoaderEvent(); } /** * Add the custom packages to the classloader hash. */ @Override public String getHash() { String superHash = super.getHash(); // ioc/0p61 - package needed for hash to enable scan long crc = Crc64.generate(superHash); for (String pkg : _packageList) { crc = Crc64.generate(crc, pkg); } return Long.toHexString(crc); } /** * Tells the classloader to scan the root classpath. */ @Override public void addScanRoot() { super.addScanRoot(); addScanRoot(getParent()); } private void addScanRoot(ClassLoader loader) { if (loader == null) return; addScanRoot(loader.getParent()); if (loader instanceof URLClassLoader) { URLClassLoader urlParent = (URLClassLoader) loader; for (URL url : urlParent.getURLs()) { // String name = url.toString(); // ejb/0e01 //if (name.endsWith(".jar")) { _pendingScanRoots.add(new ScanRoot(url, null)); //} } } } /** * Adds a scan listener. */ public void addScanListener(ScanListener listener) { if (_scanListeners == null) _scanListeners = new ArrayList<ScanListener>(); int i = 0; for (; i < _scanListeners.size(); i++) { if (listener.getScanPriority() < _scanListeners.get(i).getScanPriority()) break; } _scanListeners.add(i, listener); ArrayList<URL> urlList = new ArrayList<URL>(); for (URL url : getURLs()) { if (isScanRootAvailable(url)) urlList.add(url); } if (urlList.size() > 0) { try { make(); } catch (Exception e) { log().log(Level.WARNING, e.toString(), e); if (_configException == null) _configException = e; } ArrayList<ScanListener> selfList = new ArrayList<ScanListener>(); selfList.add(listener); ScanManager scanManager = new ScanManager(selfList); for (URL url : urlList) { scanManager.scan(this, url, null); } } } private boolean isScanRootAvailable(URL url) { for (ScanRoot scanRoot : _pendingScanRoots) { if (url.equals(scanRoot.getUrl())) return false; } return true; } /** * Returns the artifact manager */ public ArtifactManager createArtifactManager() { if (_artifactManager == null) { ArtifactManager manager = new ArtifactManager(this); _artifactManagerRef.compareAndSet(null, manager); _artifactManager = _artifactManagerRef.get(); } return _artifactManager; } /** * Returns the artifact manager */ public ArtifactManager getArtifactManager() { return _artifactManager; } /** * Returns any import class, e.g. from an artifact */ @Override protected Class<?> findImportClass(String name) { if (_artifactManager != null) return _artifactManager.findImportClass(name); else return null; } /** * Get resource from an artifact */ @Override protected URL getImportResource(String name) { if (_artifactManager != null) return _artifactManager.getImportResource(name); else return null; } @Override protected void buildImportClassPath(ArrayList<String> cp) { if (_artifactManager != null) _artifactManager.buildImportClassPath(cp); } /** * Applies the action to all visible environment modules. The * action may apply to the same environment more than once. */ public void applyVisibleModules(EnvironmentApply apply) { apply.apply(this); for (ClassLoader parent = getParent(); parent != null; parent = parent.getParent()) { if (parent instanceof EnvironmentClassLoader) { EnvironmentClassLoader env = (EnvironmentClassLoader) parent; env.applyVisibleModules(apply); break; } } if (_artifactManager != null) _artifactManager.applyVisibleModules(apply); } /** * Called when the <class-loader> completes. */ @Override public void validate() { super.validate(); } @Override public void scan() { configureEnhancerEvent(); ArrayList<ScanRoot> rootList = new ArrayList<ScanRoot>(_pendingScanRoots); _pendingScanRoots.clear(); try { int rootListSize = rootList.size(); if (_scanListeners != null && rootListSize > 0) { try { make(); } catch (Exception e) { log().log(Level.WARNING, e.toString(), e); if (_configException == null) _configException = e; } ScanManager scanManager = new ScanManager(_scanListeners); for (int i = 0; i < rootListSize; i++) { ScanRoot root = rootList.get(i); scanManager.scan(this, root.getUrl(), root.getPackageName()); } } // configureEnhancerEvent(); } catch (Exception e) { if (_configException == null) _configException = e; throw ConfigException.create(e); } } /** * Starts the config phase of the environment. */ private void config() { sendAddLoaderEvent(); ArrayList<EnvironmentListener> listeners = getEnvironmentListeners(); int size = listeners.size(); for (int i = 0; listeners != null && i < size; i++) { EnvironmentListener listener = listeners.get(i); if (listener instanceof EnvironmentEnhancerListener) { EnvironmentEnhancerListener enhancerListener = (EnvironmentEnhancerListener) listener; enhancerListener.environmentConfigureEnhancer(this); } } for (int i = 0; listeners != null && i < size; i++) { EnvironmentListener listener = listeners.get(i); listener.environmentConfigure(this); } // _isConfigComplete = true; } /** * Starts the config phase of the environment. */ private void bind() { config(); ArrayList<EnvironmentListener> listeners = getEnvironmentListeners(); int size = listeners.size(); for (int i = 0; listeners != null && i < size; i++) { EnvironmentListener listener = listeners.get(i); listener.environmentBind(this); } _isConfigComplete = true; } /** * Marks the environment of the class loader as started. The * class loader itself doesn't use this, but a callback might. */ public void start() { if (! _lifecycle.toStarting()) { startListeners(); return; } sendAddLoaderEvent(); bind(); if (_artifactManager != null) _artifactManager.start(); startListeners(); _admin = new EnvironmentAdmin(this); _admin.register(); _lifecycle.toActive(); } private void startListeners() { ArrayList<EnvironmentListener> listeners = getEnvironmentListeners(); int size = listeners.size(); for (int i = 0; listeners != null && i < size; i++) { EnvironmentListener listener = listeners.get(i); listener.environmentStart(this); } } /** * Stops the environment, closing down any resources. * * The resources are closed in the reverse order that they're started */ @Override public void stop() { if (! _lifecycle.toStop()) return; ArrayList<EnvironmentListener> listeners = getEnvironmentListeners(); Thread thread = Thread.currentThread(); ClassLoader oldLoader = thread.getContextClassLoader(); thread.setContextClassLoader(this); try { // closing down in reverse if (listeners != null) { for (int i = listeners.size() - 1; i >= 0; i--) { EnvironmentListener listener = listeners.get(i); try { listener.environmentStop(this); } catch (Throwable e) { log().log(Level.WARNING, e.toString(), e); } } } super.stop(); } finally { thread.setContextClassLoader(oldLoader); // drain the thread pool for GC ResinThreadPoolExecutor.getThreadPool().stopEnvironment(this); } } /** * Destroys the class loader. */ @Override public void destroy() { try { WeakStopListener stopListener = _stopListener; _stopListener = null; super.destroy(); ClassLoader parent = getParent(); for (; parent != null; parent = parent.getParent()) { if (parent instanceof EnvironmentClassLoader) { EnvironmentClassLoader loader = (EnvironmentClassLoader) parent; loader.removeListener(stopListener); } } } finally { _owner = null; _attributes = null; _listeners = null; _scanListeners = null; _artifactManager = null; _stopListener = null; EnvironmentAdmin admin = _admin; _admin = null; if (admin != null) admin.unregister(); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); sb.append("["); sb.append(getId()); if (! _lifecycle.isActive()) { sb.append(","); sb.append(_lifecycle.getStateName()); } sb.append("]"); return sb.toString(); } private static final Logger log() { if (_log == null) _log = Logger.getLogger(EnvironmentClassLoader.class.getName()); return _log; } private static ClassLoader getSystemClassLoaderSafe() { try { return ClassLoader.getSystemClassLoader(); } catch (Exception e) { } return EnvironmentClassLoader.class.getClassLoader(); } static class ScanRoot { private final URL _url; private final String _pkg; ScanRoot(URL url, String pkg) { _url = url; _pkg = pkg; } URL getUrl() { return _url; } String getPackageName() { return _pkg; } @Override public String toString() { return getClass().getSimpleName() + "[" + _url + "," + _pkg + "]"; } } class ResourceEntry { private URL _url; private long _expireTime; private Path _path; private boolean _isPathChecked; ResourceEntry(URL url) { _url = url; if (isDirectoryLoader()) _expireTime = Alarm.getCurrentTime() + getDependencyCheckInterval(); else _expireTime = Long.MAX_VALUE / 2; } public boolean isModified() { return _expireTime < Alarm.getCurrentTime(); } public URL getResource() { return _url; } public InputStream getResourceAsStream() { if (_url == null) return null; if (! _isPathChecked) { String urlString = URLDecoder.decode(_url.toString()); if (urlString.startsWith("file:") || urlString.startsWith("jar:")) _path = Vfs.getPwd(EnvironmentClassLoader.this).lookup(urlString); _isPathChecked = true; } try { if (_path != null) return _path.openRead(); else return _url.openStream(); } catch (IOException e) { log().log(Level.FINE, e.toString(), e); return null; } } } }