/* ClassLocator.java Purpose: Description: History: Tue Aug 30 09:56:06 2005, Created by tomyeh Copyright (C) 2005 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.util.resource; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zkoss.idom.Document; import org.zkoss.idom.Element; import org.zkoss.idom.input.SAXBuilder; import org.zkoss.idom.util.IDOMs; import org.zkoss.util.CollectionsX; /** * The locator searches the current thread's context class loader, * and then this class's class loader. * * <p>It is important to use this locator if you want to load something * in other jar files. * * <p>Since this locator is used frequently, {@link Locators#getDefault} * is provided to return an instance of this class, * * @author tomyeh */ public class ClassLocator implements XMLResourcesLocator { private static final Logger log = LoggerFactory.getLogger(ClassLocator.class); public ClassLocator() { } //XMLResourcesLocator// public Enumeration<URL> getResources(String name) throws IOException { name = resolveName(name); // no need to use Classes.getContextClassLoader() here because of the loading order issue ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl != null) { final Enumeration<URL> en = cl.getResources(name); if (en.hasMoreElements()) return en; } cl = ClassLocator.class.getClassLoader(); if (cl != null) { final Enumeration<URL> en = cl.getResources(name); if (en.hasMoreElements()) return en; } return ClassLoader.getSystemResources(name); } public List<Resource> getDependentXMLResources(String name, String elName, String elDepends) throws IOException { final Map<String, XMLResource> rcmap = new LinkedHashMap<String, XMLResource>(); for (Enumeration<URL> en = getResources(name); en.hasMoreElements();) { final URL url = en.nextElement(); final XMLResource xr = new XMLResource(url, elName, elDepends); final XMLResource old = rcmap.put(xr.name, xr); if (old != null) log.warn("Replicate resource: "+xr.name +"\nOverwrite "+old.url+"\nwith "+xr.url); //it is possible if zcommon.jar is placed in both //WEB-INF/lib and shared/lib, i.e., appear twice in the class path //We overwrite because the order is the parent class loader first //so WEB-INF/lib is placed after } // if (rcmap.isEmpty() && log.isDebugEnabled()) log.debug("No resource is found for "+name); final List<Resource> rcs = new LinkedList<Resource>(); //a list of Document final Set<String> resolving = new LinkedHashSet<String>(); //a set of names used to prevent dead-loop while (!rcmap.isEmpty()) { final Iterator<XMLResource> it = rcmap.values().iterator(); final XMLResource xr = it.next(); it.remove(); resolveDependency(xr, rcs, rcmap, resolving); assert resolving.isEmpty(); } return rcs; } private static void resolveDependency(XMLResource xr, List<Resource> rcs, Map<String, XMLResource> rcmap, Set<String> resolving) { if (!resolving.add(xr.name)) throw new IllegalStateException("Recusrive reference among "+resolving); for (String nm: xr.depends) { final XMLResource dep = rcmap.remove(nm); if (dep != null) //not resolved yet resolveDependency(dep, rcs, rcmap, resolving); //recursively } rcs.add(new Resource(xr.url, xr.document)); resolving.remove(xr.name); if (log.isDebugEnabled()) log.debug("Adding resolved resource: "+xr.name); } /** Info used with getDependentXMLResource. */ private static class XMLResource { private final String name; private final URL url; private final Document document; private final List<String> depends; private XMLResource(URL url, String elName, String elDepends) throws IOException{ if (log.isDebugEnabled()) log.debug("Loading "+url); try { this.document = new SAXBuilder(false, false, true).build(url); } catch (Exception ex) { if (ex instanceof IOException) throw (IOException)ex; if (ex instanceof RuntimeException) throw (RuntimeException)ex; final IOException ioex = new IOException("Unable to load "+url); ioex.initCause(ex); throw ioex; } this.url = url; final Element root = this.document.getRootElement(); this.name = IDOMs.getRequiredElementValue(root, elName); final String deps = root.getElementValue(elDepends, true); if (deps == null || deps.length() == 0) { this.depends = Collections.emptyList(); } else { this.depends = new LinkedList<String>(); CollectionsX.parse(this.depends, deps, ','); if (log.isTraceEnabled()) log.trace(this.name+" depends on "+this.depends); } } public String toString() { return "["+name+": "+url+" depends on "+depends+']'; } }; //-- Locator --// /** Always returns null. */ public String getDirectory() { return null; } public URL getResource(String name) { // no need to use Classes.getContextClassLoader() here because of the loading order issue final ClassLoader cl = Thread.currentThread().getContextClassLoader(); final URL url = cl != null ? cl.getResource(resolveName(name)): null; return url != null ? url: ClassLocator.class.getResource(name); } public InputStream getResourceAsStream(String name) { // no need to use Classes.getContextClassLoader() here because of the loading order issue final ClassLoader cl = Thread.currentThread().getContextClassLoader(); final InputStream is = cl != null ? cl.getResourceAsStream(resolveName(name)): null; return is != null ? is: ClassLocator.class.getResourceAsStream(name); } private static String resolveName(String name) { return name != null && name.startsWith("/") ? name.substring(1): name; } //-- Object --// public int hashCode() { return 1123; } public boolean equals(Object o) { if (this == o) return true; return o instanceof ClassLocator; } }