/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2007, Red Hat, Inc. and/or it's affiliates or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors.  All third-party contributions are * distributed under license by Red Hat, Inc. and/or it's affiliates. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program 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. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.hibernate.test.cache.infinispan.functional.classloader; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Map; import org.jboss.logging.Logger; /** * A ClassLoader that loads classes whose classname begins with one of a given set of strings, without attempting first to delegate * to its parent loader. * <p> * This class is intended to allow emulation of 2 different types of common J2EE classloading situations. * <ul> * <li>Servlet-style child-first classloading, where this class is the child loader.</li> * <li>Parent-first classloading where the parent does not have access to certain classes</li> * </ul> * </p> * <p> * This class can also be configured to raise a ClassNotFoundException if asked to load certain classes, thus allowing classes on * the classpath to be hidden from a test environment. * </p> * * @author Brian Stansberry */ public class SelectedClassnameClassLoader extends ClassLoader { private static final Logger log = Logger.getLogger( SelectedClassnameClassLoader.class ); private String[] includedClasses = null; private String[] excludedClasses = null; private String[] notFoundClasses = null; private Map<String, Class> classes = new java.util.HashMap<String, Class>(); /** * Creates a new classloader that loads the given classes. * * @param includedClasses array of class or package names that should be directly loaded by this loader. Classes whose name * starts with any of the strings in this array will be loaded by this class, unless their name appears in * <code>excludedClasses</code>. Can be <code>null</code> * @param excludedClasses array of class or package names that should NOT be directly loaded by this loader. Loading of classes * whose name starts with any of the strings in this array will be delegated to <code>parent</code>, even if the classes * package or classname appears in <code>includedClasses</code>. Typically this parameter is used to exclude loading one * or more classes in a package whose other classes are loaded by this object. * @param parent ClassLoader to which loading of classes should be delegated if necessary */ public SelectedClassnameClassLoader( String[] includedClasses, String[] excludedClasses, ClassLoader parent ) { this(includedClasses, excludedClasses, null, parent); } /** * Creates a new classloader that loads the given classes. * * @param includedClasses array of class or package names that should be directly loaded by this loader. Classes whose name * starts with any of the strings in this array will be loaded by this class, unless their name appears in * <code>excludedClasses</code>. Can be <code>null</code> * @param excludedClasses array of class or package names that should NOT be directly loaded by this loader. Loading of classes * whose name starts with any of the strings in this array will be delegated to <code>parent</code>, even if the classes * package or classname appears in <code>includedClasses</code>. Typically this parameter is used to exclude loading one * or more classes in a package whose other classes are loaded by this object. * @param notFoundClasses array of class or package names for which this should raise a ClassNotFoundException * @param parent ClassLoader to which loading of classes should be delegated if necessary */ public SelectedClassnameClassLoader( String[] includedClasses, String[] excludedClasses, String[] notFoundClasses, ClassLoader parent ) { super(parent); this.includedClasses = includedClasses; this.excludedClasses = excludedClasses; this.notFoundClasses = notFoundClasses; log.debug("created " + this); } @Override protected synchronized Class<?> loadClass( String name, boolean resolve ) throws ClassNotFoundException { log.trace("loadClass(" + name + "," + resolve + ")"); if (isIncluded(name) && (isExcluded(name) == false)) { Class c = findClass(name); if (resolve) { resolveClass(c); } return c; } else if (isNotFound(name)) { throw new ClassNotFoundException(name + " is discarded"); } else { return super.loadClass(name, resolve); } } @Override protected Class<?> findClass( String name ) throws ClassNotFoundException { log.trace("findClass(" + name + ")"); Class result = classes.get(name); if (result != null) { return result; } if (isIncluded(name) && (isExcluded(name) == false)) { result = createClass(name); } else if (isNotFound(name)) { throw new ClassNotFoundException(name + " is discarded"); } else { result = super.findClass(name); } classes.put(name, result); return result; } protected Class createClass( String name ) throws ClassFormatError, ClassNotFoundException { log.info("createClass(" + name + ")"); try { InputStream is = getResourceAsStream(name.replace('.', '/').concat(".class")); byte[] bytes = new byte[1024]; ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); int read; while ((read = is.read(bytes)) > -1) { baos.write(bytes, 0, read); } bytes = baos.toByteArray(); return this.defineClass(name, bytes, 0, bytes.length); } catch (FileNotFoundException e) { throw new ClassNotFoundException("cannot find " + name, e); } catch (IOException e) { throw new ClassNotFoundException("cannot read " + name, e); } } protected boolean isIncluded( String className ) { if (includedClasses != null) { for (int i = 0; i < includedClasses.length; i++) { if (className.startsWith(includedClasses[i])) { return true; } } } return false; } protected boolean isExcluded( String className ) { if (excludedClasses != null) { for (int i = 0; i < excludedClasses.length; i++) { if (className.startsWith(excludedClasses[i])) { return true; } } } return false; } protected boolean isNotFound( String className ) { if (notFoundClasses != null) { for (int i = 0; i < notFoundClasses.length; i++) { if (className.startsWith(notFoundClasses[i])) { return true; } } } return false; } @Override public String toString() { String s = getClass().getName(); s += "[includedClasses="; s += listClasses(includedClasses); s += ";excludedClasses="; s += listClasses(excludedClasses); s += ";notFoundClasses="; s += listClasses(notFoundClasses); s += ";parent="; s += getParent(); s += "]"; return s; } private static String listClasses( String[] classes ) { if (classes == null) return null; String s = ""; for (int i = 0; i < classes.length; i++) { if (i > 0) s += ","; s += classes[i]; } return s; } }