001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.mx.loading;
023:
024: import java.io.BufferedReader;
025: import java.io.File;
026: import java.io.FileFilter;
027: import java.io.FileInputStream;
028: import java.io.InputStream;
029: import java.io.InputStreamReader;
030: import java.io.IOException;
031: import java.lang.reflect.Method;
032: import java.net.URL;
033: import java.net.URLClassLoader;
034: import java.security.CodeSource;
035: import java.security.ProtectionDomain;
036: import java.util.ArrayList;
037: import java.util.Arrays;
038: import java.util.Comparator;
039: import java.util.HashSet;
040: import java.util.LinkedList;
041: import java.util.Map;
042: import java.util.Set;
043: import java.util.TreeSet;
044: import java.util.zip.ZipEntry;
045: import java.util.zip.ZipInputStream;
046:
047: import org.jboss.logging.Logger;
048: import org.jboss.mx.loading.LoadMgr3.PkgClassLoader;
049:
050: /** Utility methods for class loader to package names, etc.
051: *
052: * @author Scott.Stark@jboss.org
053: * @version $Revision: 57200 $
054: */
055: public class ClassLoaderUtils {
056: private static Logger log = Logger
057: .getLogger(ClassLoaderUtils.class);
058:
059: /** The singleton instance of repository classloader comparator */
060: private static final Comparator repostiroyClassLoaderComparator = new RepositoryClassLoaderComparator();
061:
062: /** Format a string buffer containing the Class, Interfaces, CodeSource,
063: and ClassLoader information for the given object clazz.
064:
065: @param clazz the Class
066: @param results - the buffer to write the info to
067: */
068: public static void displayClassInfo(Class clazz,
069: StringBuffer results) {
070: // Print out some codebase info for the ProbeHome
071: ClassLoader cl = clazz.getClassLoader();
072: results.append("\n" + clazz.getName() + "("
073: + Integer.toHexString(clazz.hashCode())
074: + ").ClassLoader=" + cl);
075: ClassLoader parent = cl;
076: while (parent != null) {
077: results.append("\n.." + parent);
078: URL[] urls = getClassLoaderURLs(parent);
079: int length = urls != null ? urls.length : 0;
080: for (int u = 0; u < length; u++) {
081: results.append("\n...." + urls[u]);
082: }
083: if (parent != null)
084: parent = parent.getParent();
085: }
086: CodeSource clazzCS = clazz.getProtectionDomain()
087: .getCodeSource();
088: if (clazzCS != null)
089: results.append("\n++++CodeSource: " + clazzCS);
090: else
091: results.append("\n++++Null CodeSource");
092:
093: results.append("\nImplemented Interfaces:");
094: Class[] ifaces = clazz.getInterfaces();
095: for (int i = 0; i < ifaces.length; i++) {
096: Class iface = ifaces[i];
097: results.append("\n++" + iface + "("
098: + Integer.toHexString(iface.hashCode()) + ")");
099: ClassLoader loader = ifaces[i].getClassLoader();
100: results.append("\n++++ClassLoader: " + loader);
101: ProtectionDomain pd = ifaces[i].getProtectionDomain();
102: CodeSource cs = pd.getCodeSource();
103: if (cs != null)
104: results.append("\n++++CodeSource: " + cs);
105: else
106: results.append("\n++++Null CodeSource");
107: }
108: }
109:
110: /** Use reflection to access a URL[] getURLs or URL[] getClasspath method so
111: that non-URLClassLoader class loaders, or class loaders that override
112: getURLs to return null or empty, can provide the true classpath info.
113: */
114: public static URL[] getClassLoaderURLs(ClassLoader cl) {
115: URL[] urls = {};
116: try {
117: Class returnType = urls.getClass();
118: Class[] parameterTypes = {};
119: Class clClass = cl.getClass();
120: Method getURLs = clClass.getMethod("getURLs",
121: parameterTypes);
122: if (returnType.isAssignableFrom(getURLs.getReturnType())) {
123: Object[] args = {};
124: urls = (URL[]) getURLs.invoke(cl, args);
125: }
126: if (urls == null || urls.length == 0) {
127: Method getCp = clClass.getMethod("getClasspath",
128: parameterTypes);
129: if (returnType.isAssignableFrom(getCp.getReturnType())) {
130: Object[] args = {};
131: urls = (URL[]) getCp.invoke(cl, args);
132: }
133: }
134: } catch (Exception ignore) {
135: }
136: return urls;
137: }
138:
139: /** Get all of the URLClassLoaders from cl on up the hierarchy
140: *
141: * @param cl the class loader to start from
142: * @return The possibly empty array of URLClassLoaders from cl through
143: * its parent class loaders
144: */
145: public static URLClassLoader[] getClassLoaderStack(ClassLoader cl) {
146: ArrayList stack = new ArrayList();
147: while (cl != null) {
148: if (cl instanceof URLClassLoader) {
149: stack.add(cl);
150: }
151: cl = cl.getParent();
152: }
153: URLClassLoader[] ucls = new URLClassLoader[stack.size()];
154: stack.toArray(ucls);
155: return ucls;
156: }
157:
158: /** Translates a dot class name (java.lang.String) into a path form
159: * suitable for a jar entry (java/lang/String.class)
160: *
161: * @param className java.lang.String
162: * @return java/lang/String.class
163: */
164: public static String getJarClassName(String className) {
165: String jarClassName = className.replace('.', '/');
166: return jarClassName + ".class";
167: }
168:
169: /** Parse a class name into its package prefix. This has to handle
170: array classes whose name is prefixed with [L.
171: */
172: public static String getPackageName(String className) {
173: int startIndex = 0;
174: // Strip any leading "[+L" found in array class names
175: if (className.length() > 0 && className.charAt(0) == '[') {
176: // Move beyond the [...[L prefix
177: startIndex = className.indexOf('L') + 1;
178: }
179: // Now extract the package name
180: String pkgName = "";
181: int endIndex = className.lastIndexOf('.');
182: if (endIndex > 0)
183: pkgName = className.substring(startIndex, endIndex);
184: return pkgName;
185: }
186:
187: /** Parse a class name into its resource form. This has to handle
188: array classes whose name is prefixed with [L.
189: */
190: public static String getResourceName(String className) {
191: int startIndex = 0;
192: // Strip any leading "[+L" found in array class names
193: if (className.length() > 0 && className.charAt(0) == '[') {
194: // Move beyond the [...[L prefix
195: startIndex = className.indexOf('L') + 1;
196: }
197: // Now extract the package name
198: String resName = "";
199: int endIndex = className.lastIndexOf('.');
200: if (endIndex > 0)
201: resName = className.substring(startIndex, endIndex);
202: return resName.replace('.', '/');
203: }
204:
205: /**
206: * Create a new package set
207: *
208: * @return the new package set
209: */
210: public static Set newPackageSet() {
211: return new TreeSet(repostiroyClassLoaderComparator);
212: }
213:
214: /**
215: * Clone a package set
216: *
217: * @param the set to clone
218: * @return the cloned package set
219: */
220: public static Set clonePackageSet(Object toClone) {
221: TreeSet original = (TreeSet) toClone;
222: return (Set) original.clone();
223: }
224:
225: /** Given a UCL this method determine what packages
226: it contains and create a mapping from the package names to the cl.
227: * @param cl the UCL that loads from url
228: * @param packagesMap the Map<cl, String[]> to update
229: * @return the updated unique set of package names
230: * @throws Exception
231: */
232: public static String[] updatePackageMap(RepositoryClassLoader cl,
233: Map packagesMap) throws Exception {
234: URL url = cl.getURL();
235: ClassPathIterator cpi = new ClassPathIterator(url);
236: HashSet pkgNameSet = new HashSet();
237: return updatePackageMap(cl, packagesMap, cpi, pkgNameSet);
238: }
239:
240: /** Augment the package name associated with a UCL.
241: * @param cl the UCL that loads from url
242: * @param packagesMap the Map<cl, String[]> to update
243: * @param url the URL to parse for package names
244: * @param prevPkgNames the set of pckage names already associated with cl
245: * @return the updated unique set of package names
246: * @throws Exception
247: */
248: public static String[] updatePackageMap(RepositoryClassLoader cl,
249: Map packagesMap, URL url, String[] prevPkgNames)
250: throws Exception {
251: ClassPathIterator cpi = new ClassPathIterator(url);
252: HashSet pkgNameSet = null;
253: if (prevPkgNames == null)
254: pkgNameSet = new HashSet();
255: else
256: pkgNameSet = new HashSet(Arrays.asList(prevPkgNames));
257: return updatePackageMap(cl, packagesMap, cpi, pkgNameSet);
258: }
259:
260: /** Given a UCL this method determine what classes it contains
261: and create a mapping from the class names to the cl.
262: * @param cl the UCL that loads from url
263: * @param classNamesMap the Map<cl, String[]> to update
264: * @return the class names directly visible to the cl
265: * @throws Exception
266: */
267: public static String[] updateClassNamesMap(
268: RepositoryClassLoader cl, Map classNamesMap)
269: throws Exception {
270: URL url = cl.getURL();
271: ClassPathIterator cpi = new ClassPathIterator(url);
272: HashSet classNameSet = new HashSet();
273: return updateClassNamesMap(cl, classNamesMap, cpi, classNameSet);
274: }
275:
276: /** Augment the class names associated with a UCL.
277: * @param cl the UCL that loads from url
278: * @param classNamesMap the Map<cl, String[]> to update
279: * @param url the URL to parse for class names
280: * @param prevClassNames the set of pckage names already associated with cl
281: * @return the updated list of class names
282: * @throws Exception
283: */
284: public static String[] updateClassNamesMap(
285: RepositoryClassLoader cl, Map classNamesMap, URL url,
286: String[] prevClassNames) throws Exception {
287: ClassPathIterator cpi = new ClassPathIterator(url);
288: HashSet classNameSet = null;
289: if (prevClassNames == null)
290: classNameSet = new HashSet();
291: else
292: classNameSet = new HashSet(Arrays.asList(prevClassNames));
293: return updateClassNamesMap(cl, classNamesMap, cpi, classNameSet);
294: }
295:
296: static String[] updatePackageMap(RepositoryClassLoader cl,
297: Map packagesMap, ClassPathIterator cpi, HashSet pkgNameSet)
298: throws Exception {
299: boolean trace = log.isTraceEnabled();
300: ClassPathEntry entry;
301: while ((entry = cpi.getNextEntry()) != null) {
302: String name = entry.getName();
303: // First look for a META-INF/INDEX.LIST entry
304: if (name.equals("META-INF/INDEX.LIST")) {
305: readJarIndex(cl, cpi, packagesMap, pkgNameSet);
306: // We are done
307: break;
308: }
309:
310: // Skip empty directory entries
311: if (entry.isDirectory() == true)
312: continue;
313:
314: String pkgName = entry.toPackageName();
315: addPackage(pkgName, packagesMap, pkgNameSet, cl, trace);
316: }
317: cpi.close();
318:
319: // Return an array of the package names
320: String[] pkgNames = new String[pkgNameSet.size()];
321: pkgNameSet.toArray(pkgNames);
322: return pkgNames;
323: }
324:
325: static String[] updateClassNamesMap(RepositoryClassLoader cl,
326: Map classNamesMap, ClassPathIterator cpi,
327: HashSet classNameSet) throws Exception {
328: boolean trace = log.isTraceEnabled();
329: ClassPathEntry entry;
330: while ((entry = cpi.getNextEntry()) != null) {
331: String name = entry.getName();
332: // Skip empty directory entries
333: if (entry.isDirectory() == true)
334: continue;
335: // Skip non .class files
336: if (name.endsWith(".class") == false)
337: continue;
338:
339: addClass(name, classNamesMap, cl, trace);
340: classNameSet.add(name);
341: }
342: cpi.close();
343:
344: // Return an array of the package names
345: String[] classNames = new String[classNameSet.size()];
346: classNameSet.toArray(classNames);
347: return classNames;
348: }
349:
350: /** Read the JDK 1.3+ META-INF/INDEX.LIST entry to obtain the package
351: names without having to iterate through all entries in the jar.
352: */
353: private static void readJarIndex(RepositoryClassLoader cl,
354: ClassPathIterator cpi, Map packagesMap, Set pkgNameSet)
355: throws Exception {
356: boolean trace = log.isTraceEnabled();
357: InputStream zis = cpi.getInputStream();
358: BufferedReader br = new BufferedReader(new InputStreamReader(
359: zis));
360: String line;
361: // Skip the jar index header
362: while ((line = br.readLine()) != null) {
363: if (line.length() == 0)
364: break;
365: }
366:
367: // Read the main jar section
368: String jarName = br.readLine();
369: if (trace)
370: log.trace("Reading INDEX.LIST for jar: " + jarName);
371: while ((line = br.readLine()) != null) {
372: if (line.length() == 0)
373: break;
374: String pkgName = line.replace('/', '.');
375: addPackage(pkgName, packagesMap, pkgNameSet, cl, trace);
376: }
377: br.close();
378: }
379:
380: private static void addPackage(String pkgName, Map packagesMap,
381: Set pkgNameSet, RepositoryClassLoader cl, boolean trace) {
382: // Skip the standard J2EE archive directories
383: if (pkgName.startsWith("META-INF")
384: || pkgName.startsWith("WEB-INF"))
385: return;
386:
387: Set pkgSet = (Set) packagesMap.get(pkgName);
388: if (pkgSet == null) {
389: pkgSet = newPackageSet();
390: packagesMap.put(pkgName, pkgSet);
391: }
392: if (pkgSet.contains(cl) == false) {
393: pkgSet.add(cl);
394: pkgNameSet.add(pkgName);
395: // Anytime more than one class loader exists this may indicate a problem
396: if (pkgSet.size() > 1) {
397: log.debug("Multiple class loaders found for pkg: "
398: + pkgName);
399: }
400: if (trace)
401: log.trace("Indexed pkg: " + pkgName + ", UCL: " + cl);
402: }
403: }
404:
405: /** Add a class name to the UCL map.
406: * @param jarClassName the class name in the jar (java/lang/String.class)
407: * @param classNamesMap the UCL class name mappings
408: * @param cl the UCL
409: * @param trace the logging trace level flag
410: */
411: private static void addClass(String jarClassName,
412: Map classNamesMap, RepositoryClassLoader cl, boolean trace) {
413: LinkedList ucls = (LinkedList) classNamesMap.get(jarClassName);
414: if (ucls == null) {
415: ucls = new LinkedList();
416: ucls.add(cl);
417: classNamesMap.put(jarClassName, ucls);
418: } else {
419: boolean uclIsMapped = ucls.contains(cl);
420: if (uclIsMapped == false) {
421: log.debug("Multiple class loaders found for class: "
422: + jarClassName + ", duplicate UCL: " + cl);
423: ucls.add(cl);
424: }
425: }
426: if (trace)
427: log.trace("Indexed class: " + jarClassName + ", UCL: "
428: + ucls.get(0));
429: }
430:
431: /**
432: */
433: static class FileIterator {
434: LinkedList subDirectories = new LinkedList();
435: FileFilter filter;
436: File[] currentListing;
437: int index = 0;
438:
439: FileIterator(File start) {
440: String name = start.getName();
441: // Don't recurse into wars
442: boolean isWar = name.endsWith(".war");
443: if (isWar)
444: currentListing = new File[0];
445: else
446: currentListing = start.listFiles();
447: }
448:
449: FileIterator(File start, FileFilter filter) {
450: String name = start.getName();
451: // Don't recurse into wars
452: boolean isWar = name.endsWith(".war");
453: if (isWar)
454: currentListing = new File[0];
455: else
456: currentListing = start.listFiles(filter);
457: this .filter = filter;
458: }
459:
460: File getNextEntry() {
461: File next = null;
462: if (index >= currentListing.length
463: && subDirectories.size() > 0) {
464: do {
465: File nextDir = (File) subDirectories.removeFirst();
466: currentListing = nextDir.listFiles(filter);
467: } while (currentListing.length == 0
468: && subDirectories.size() > 0);
469: index = 0;
470: }
471: if (index < currentListing.length) {
472: next = currentListing[index++];
473: if (next.isDirectory())
474: subDirectories.addLast(next);
475: }
476: return next;
477: }
478: }
479:
480: /**
481: */
482: static class ClassPathEntry {
483: String name;
484: ZipEntry zipEntry;
485: File fileEntry;
486:
487: ClassPathEntry(ZipEntry zipEntry) {
488: this .zipEntry = zipEntry;
489: this .name = zipEntry.getName();
490: }
491:
492: ClassPathEntry(File fileEntry, int rootLength) {
493: this .fileEntry = fileEntry;
494: this .name = fileEntry.getPath().substring(rootLength);
495: }
496:
497: String getName() {
498: return name;
499: }
500:
501: /** Convert the entry path to a package name
502: */
503: String toPackageName() {
504: String pkgName = name;
505: char separatorChar = zipEntry != null ? '/'
506: : File.separatorChar;
507: int index = name.lastIndexOf(separatorChar);
508: if (index > 0) {
509: pkgName = name.substring(0, index);
510: pkgName = pkgName.replace(separatorChar, '.');
511: } else {
512: // This must be an entry in the default package (e.g., X.class)
513: pkgName = "";
514: }
515: return pkgName;
516: }
517:
518: boolean isDirectory() {
519: boolean isDirectory = false;
520: if (zipEntry != null)
521: isDirectory = zipEntry.isDirectory();
522: else
523: isDirectory = fileEntry.isDirectory();
524: return isDirectory;
525: }
526: }
527:
528: /** An iterator for jar entries or directory structures.
529: */
530: static class ClassPathIterator {
531: ZipInputStream zis;
532: FileIterator fileIter;
533: File file;
534: int rootLength;
535:
536: ClassPathIterator(URL url) throws IOException {
537: String protocol = url != null ? url.getProtocol() : null;
538: if (protocol == null) {
539: } else if (protocol.equals("file")) {
540: File tmp = new File(url.getFile());
541: if (tmp.isDirectory()) {
542: rootLength = tmp.getPath().length() + 1;
543: fileIter = new FileIterator(tmp);
544: } else {
545: // Assume this is a jar archive
546: InputStream is = new FileInputStream(tmp);
547: zis = new ZipInputStream(is);
548: }
549: } else {
550: // Assume this points to a jar
551: InputStream is = url.openStream();
552: zis = new ZipInputStream(is);
553: }
554: }
555:
556: ClassPathEntry getNextEntry() throws IOException {
557: ClassPathEntry entry = null;
558: if (zis != null) {
559: ZipEntry zentry = zis.getNextEntry();
560: if (zentry != null)
561: entry = new ClassPathEntry(zentry);
562: } else if (fileIter != null) {
563: File fentry = fileIter.getNextEntry();
564: if (fentry != null)
565: entry = new ClassPathEntry(fentry, rootLength);
566: file = fentry;
567: }
568:
569: return entry;
570: }
571:
572: InputStream getInputStream() throws IOException {
573: InputStream is = zis;
574: if (zis == null) {
575: is = new FileInputStream(file);
576: }
577: return is;
578: }
579:
580: void close() throws IOException {
581: if (zis != null)
582: zis.close();
583: }
584:
585: }
586:
587: /**
588: * A comparator for comparing repository classloaders
589: */
590: private static class RepositoryClassLoaderComparator implements
591: Comparator {
592: /**
593: * Compares two repository classloaders, they are ordered by:
594: * 1) parent->child delegation rules in the loader repository
595: * 2) added order inside the loader repository
596: */
597: public int compare(Object o1, Object o2) {
598: if (o1 instanceof PkgClassLoader) {
599: PkgClassLoader pkg1 = (PkgClassLoader) o1;
600: PkgClassLoader pkg2 = (PkgClassLoader) o2;
601: RepositoryClassLoader rcl1 = pkg1.ucl;
602: RepositoryClassLoader rcl2 = pkg2.ucl;
603: // We use the package classloader ordering before the repository order
604: int test = (pkg1.order - pkg2.order);
605: if (test != 0)
606: return test;
607: else
608: return rcl1.getAddedOrder() - rcl2.getAddedOrder();
609: } else {
610: RepositoryClassLoader rcl1 = (RepositoryClassLoader) o1;
611: RepositoryClassLoader rcl2 = (RepositoryClassLoader) o2;
612: return rcl1.getAddedOrder() - rcl2.getAddedOrder();
613:
614: // REVIEW: Alternative to using the pkgClassLoader is
615: // ordering based on the loader repository
616:
617: //LoaderRepository lr1 = rcl1.getLoaderRepository();
618: //LoaderRepository lr2 = rcl2.getLoaderRepository();
619:
620: // Are the loader repositories ordered?
621: //int test = lr1.compare(lr2);
622: //if (test != 0)
623: // return test;
624: //else
625: // return rcl1.getAddedOrder() - rcl2.getAddedOrder();
626: }
627: }
628: }
629: }
|