/** * The contents of this file are subject to the OpenMRS Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.module; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandlerFactory; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.api.APIException; import org.openmrs.util.OpenmrsClassLoader; import org.openmrs.util.OpenmrsUtil; /** * Standard implementation of module class loader. <br/> * Code adapted from the Java Plug-in Framework (JPF) - LGPL - Copyright (C)<br/> * 2004-2006 Dmitry Olshansky */ public class ModuleClassLoader extends URLClassLoader { static Log log = LogFactory.getLog(ModuleClassLoader.class); private final Module module; private Module[] requiredModules; private Module[] awareOfModules; private Map<URL, File> libraryCache; private boolean probeParentLoaderLast = true; private Set<String> additionalPackages = new LinkedHashSet<String>(); /** * @param module Module * @param urls resources "managed" by this class loader * @param parent parent class loader * @param factory URL stream handler factory * @see URLClassLoader#URLClassLoader(java.net.URL[], java.lang.ClassLoader, * java.net.URLStreamHandlerFactory) */ protected ModuleClassLoader(final Module module, final List<URL> urls, final ClassLoader parent, final URLStreamHandlerFactory factory) { super(urls.toArray(new URL[urls.size()]), parent, factory); if (log.isDebugEnabled()) log.debug("URLs length: " + urls.size()); this.module = module; collectRequiredModuleImports(); collectAwareOfModuleImports(); collectFilters(); libraryCache = new WeakHashMap<URL, File>(); } /** * @param module the <code>Module</code> to load * @param urls <code>List<URL></code> of the resources "managed" by this class loader * @param parent parent <code>ClassLoader</code> * @see URLClassLoader#URLClassLoader(java.net.URL[], java.lang.ClassLoader) */ protected ModuleClassLoader(final Module module, final List<URL> urls, final ClassLoader parent) { this(module, urls, parent, null); for (URL url : urls) { addAllAdditionalPackages(ModuleUtil.getPackagesFromFile(OpenmrsUtil.url2file(url))); } } /** * @param module the <code>Module</code> to load * @param urls <code>List<URL></code> of thee resources "managed" by this class loader * @see URLClassLoader#URLClassLoader(java.net.URL[]) */ protected ModuleClassLoader(final Module module, final List<URL> urls) { this(module, urls, null); } /** * Creates class instance configured to load classes and resources for given module. * * @param module the <code>Module</code> to load * @param parent parent <code>ClassLoader</code> */ public ModuleClassLoader(final Module module, final ClassLoader parent) { this(module, getUrls(module), parent); } /** * @return returns this classloader's module */ public Module getModule() { return module; } /** * Get the base class url of the given <code>cls</code>. Used for checking against system class * loads vs classloader loads * * @param cls Class name * @return URL to the class */ private static URL getClassBaseUrl(final Class<?> cls) { ProtectionDomain pd = cls.getProtectionDomain(); if (pd != null) { CodeSource cs = pd.getCodeSource(); if (cs != null) { return cs.getLocation(); } } return null; } /** * Get all urls for all files in the given <code>module</code> * * @param module Module in which to look * @return List<URL> of all urls found (and cached) in the module */ private static List<URL> getUrls(final Module module) { List<URL> result = new LinkedList<URL>(); File tmpModuleDir = getLibCacheFolderForModule(module); File tmpModuleJar = new File(tmpModuleDir, module.getModuleId() + ".jar"); if (!tmpModuleJar.exists()) { try { tmpModuleJar.createNewFile(); } catch (IOException io) { log.warn("Unable to create tmpModuleFile", io); } } // copy the module jar into that temporary folder FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream(module.getFile()); out = new FileOutputStream(tmpModuleJar); OpenmrsUtil.copyFile(in, out); } catch (IOException io) { log.warn("Unable to copy tmpModuleFile", io); } finally { try { in.close(); } catch (Exception e) { /* pass */} try { out.close(); } catch (Exception e) { /* pass */} } // add the module jar as a url in the classpath of the classloader URL moduleFileURL = null; try { moduleFileURL = ModuleUtil.file2url(tmpModuleJar); result.add(moduleFileURL); } catch (MalformedURLException e) { log.warn("Unable to add files from module to URL list: " + module.getModuleId(), e); } // add each defined jar in the /lib folder, add as a url in the classpath of the classloader try { if (log.isDebugEnabled()) log.debug("Expanding /lib folder in module"); ModuleUtil.expandJar(module.getFile(), tmpModuleDir, "lib", true); File libdir = new File(tmpModuleDir, "lib"); if (libdir != null && libdir.exists()) { // recursively get files Collection<File> files = (Collection<File>) FileUtils.listFiles(libdir, new String[] { "jar" }, true); for (File file : files) { if (log.isDebugEnabled()) log.debug("Adding file to results: " + file.getAbsolutePath()); result.add(ModuleUtil.file2url(file)); } } } catch (MalformedURLException e) { log.warn("Error while adding module 'lib' folder to URL result list"); } catch (IOException io) { log.warn("Error while expanding lib folder", io); } // add each xml document to the url list return result; } /** * Get the library cache folder for the given module. Each module has a different cache folder * to ease cleanup when unloading a module while openmrs is running * * @param module Module which the cache will be used for * @return File directory where the files will be placed */ public static File getLibCacheFolderForModule(Module module) { File tmpModuleDir = new File(OpenmrsClassLoader.getLibCacheFolder(), module.getModuleId()); // each module gets its own folder named /moduleId/ if (!tmpModuleDir.exists()) { tmpModuleDir.mkdir(); tmpModuleDir.deleteOnExit(); } return tmpModuleDir; } /** * Get all urls for the given <code>module</code> that are not already in the * <code>existingUrls</code> * * @param module Module in which to get urls * @param existingUrls Array of URLs to skip * @return List<URL> of new unique urls * @see #getUrls(Module) */ private static List<URL> getUrls(final Module module, final URL[] existingUrls) { List<URL> urls = Arrays.asList(existingUrls); List<URL> result = new LinkedList<URL>(); for (URL url : getUrls(module)) { if (!urls.contains(url)) { result.add(url); } } return result; } /** * Get and cache the imports for this module. The imports should just be the modules that set as * "required" by this module */ protected void collectRequiredModuleImports() { // collect imported modules (exclude duplicates) Map<String, Module> publicImportsMap = new WeakHashMap<String, Module>(); //<module ID, Module> for (String moduleId : ModuleConstants.CORE_MODULES.keySet()) { Module module = ModuleFactory.getModuleById(moduleId); if (module == null && !ModuleUtil.ignoreCoreModules()) { log.error("Unable to find an openmrs core loaded module with id: " + moduleId); throw new APIException( "Should not be here. All 'core' required modules by the api should be started and their classloaders should be available"); } // if this is already the classloader for one of the core modules, don't put it on the import list if (module != null && !moduleId.equals(this.getModule().getModuleId())) { publicImportsMap.put(moduleId, module); } } for (String requiredPackage : getModule().getRequiredModules()) { Module requiredModule = ModuleFactory.getModuleByPackage(requiredPackage); if (ModuleFactory.isModuleStarted(requiredModule)) { publicImportsMap.put(requiredModule.getModuleId(), requiredModule); } } requiredModules = publicImportsMap.values().toArray(new Module[publicImportsMap.size()]); } /** * Get and cache the imports for this module. The imports should just be the modules that set as * "aware of" by this module */ protected void collectAwareOfModuleImports() { // collect imported modules (exclude duplicates) Map<String, Module> publicImportsMap = new WeakHashMap<String, Module>(); //<module ID, Module> for (String awareOfPackage : getModule().getAwareOfModules()) { Module awareOfModule = ModuleFactory.getModuleByPackage(awareOfPackage); if (ModuleFactory.isModuleStarted(awareOfModule)) { publicImportsMap.put(awareOfModule.getModuleId(), awareOfModule); } } awareOfModules = publicImportsMap.values().toArray(new Module[publicImportsMap.size()]); } /** * Get and cache the filters for this module (not currently implemented) */ protected void collectFilters() { // if (resourceFilters == null) { // resourceFilters = new WeakHashMap<URL, ResourceFilter>(); // } else { // resourceFilters.clear(); // } // TODO even need to iterate over libraries here? //for (Library lib : getModule().getLibraries()) { //resourceFilters.put( // ModuleFactory.getPathResolver().resolvePath(lib, // lib.getPath()), new ResourceFilter(lib)); //} } /** * @see org.openmrs.module.ModuleClassLoader#modulesSetChanged() */ protected void modulesSetChanged() { List<URL> newUrls = getUrls(getModule(), getURLs()); for (URL u : newUrls) { addURL(u); } if (log.isDebugEnabled()) { StringBuffer buf = new StringBuffer(); buf.append("New code URL's populated for module " + getModule() + ":\r\n"); for (URL u : newUrls) { buf.append("\t"); buf.append(u); buf.append("\r\n"); } log.debug(buf.toString()); } collectRequiredModuleImports(); collectAwareOfModuleImports(); // repopulate resource URLs //resourceLoader = ModuleResourceLoader.get(getModule()); collectFilters(); for (Iterator<Map.Entry<URL, File>> it = libraryCache.entrySet().iterator(); it.hasNext();) { if (it.next().getValue() == null) { it.remove(); } } } /** * @see org.openmrs.module.ModuleFactory#stopModule(Module,boolean) */ public void dispose() { if (log.isDebugEnabled()) log.debug("Disposing of ModuleClassLoader: " + this); for (Iterator<File> it = libraryCache.values().iterator(); it.hasNext();) { it.next().delete(); } libraryCache.clear(); //resourceFilters.clear(); requiredModules = null; awareOfModules = null; //resourceLoader = null; } /** * Allow the probe parent loader last variable to be set. Usually this is set to true to allow * modules to override and create their own classes * * @param value boolean true/false whether or not to look at the parent classloader last */ public void setProbeParentLoaderLast(final boolean value) { probeParentLoaderLast = value; } /** * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean) */ @Override protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException { Class<?> result = null; if (probeParentLoaderLast) { try { result = loadClass(name, resolve, this, null); } catch (ClassNotFoundException cnfe) { if (getParent() != null) result = getParent().loadClass(name); } catch (NullPointerException e) { log.debug("Error while attempting to load class: " + name + " from: " + this.toString()); } if (result == null) { if (getParent() != null) result = getParent().loadClass(name); } } else { try { if (getParent() != null) result = getParent().loadClass(name); } catch (ClassNotFoundException cnfe) { result = loadClass(name, resolve, this, null); } } if (result != null) return result; throw new ClassNotFoundException(name); } /** * Custom loadClass implementation to allow for loading from a given ModuleClassLoader and skip * the modules that have been tried already * * @param name String path and name of the class to load * @param resolve boolean whether or not to resolve this class before returning * @param requestor ModuleClassLoader with which to try loading * @param seenModules Set<String> moduleIds that have been tried already * @return Class that has been loaded or null if none * @throws ClassNotFoundException if no class found */ protected Class<?> loadClass(final String name, final boolean resolve, final ModuleClassLoader requestor, Set<String> seenModules) throws ClassNotFoundException { if (log.isTraceEnabled()) { log.trace("loading " + name + " " + getModule() + " seenModules: " + seenModules + " requestor: " + requestor + " resolve? " + resolve); StringBuilder output = new StringBuilder(); for (StackTraceElement element : Thread.currentThread().getStackTrace()) { if (element.getClassName().contains("openmrs")) output.append("+ "); output.append(element); output.append("\n"); } log.trace("stacktrace: " + output.toString()); } if ((seenModules != null) && seenModules.contains(getModule().getModuleId())) { return null; } // make sure the module is started if ((this != requestor) && !ModuleFactory.isModuleStarted(getModule())) { String msg = "can't load class " + name + ", module " + getModule() + " is not started yet"; log.warn(msg); throw new ClassNotFoundException(msg); } // the class ultimately returned (if found) Class<?> result = null; result = findLoadedClass(name); if (result != null) { checkClassVisibility(result, requestor); /*if (resolve) { resolveClass(result); }*/ // found an already loaded class in this moduleclassloader return result; } synchronized (this) { // we didn't find a loaded class and this isn't a class // from another module try { result = findClass(name); } catch (LinkageError le) { throw le; } catch (ClassNotFoundException cnfe) { // ignore } // we were able to "find" a class if (result != null) { checkClassVisibility(result, requestor); if (resolve) { resolveClass(result); } return result; // found class in this module } } // initialize the array if need be if (seenModules == null) seenModules = new HashSet<String>(); // add this module to the list of modules we've tried already seenModules.add(getModule().getModuleId()); // look through this module's imports to see if the class // can be loaded from them if (requiredModules != null) { for (Module publicImport : requiredModules) { if (seenModules.contains(publicImport.getModuleId())) continue; ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport); // the mcl will be null if a required module isn't started yet (like at openmrs startup) if (mcl != null) { result = mcl.loadClass(name, resolve, requestor, seenModules); } if (result != null) { /*if (resolve) { resolveClass(result); }*/ break; // found class in publicly imported module } } } // look through this module's aware of imports to see if the class // can be loaded from them. for (Module publicImport : awareOfModules) { if (seenModules.contains(publicImport.getModuleId())) continue; ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport); // the mcl will be null if an aware of module isn't started yet (like at openmrs startup) if (mcl != null) { result = mcl.loadClass(name, resolve, requestor, seenModules); } if (result != null) { /*if (resolve) { resolveClass(result); }*/ break; // found class in publicly imported module } } return result; } /** * Checking the given class's visibility in this module * * @param cls Class to check * @param requestor ModuleClassLoader to check against * @throws ClassNotFoundException */ protected void checkClassVisibility(final Class<?> cls, final ModuleClassLoader requestor) throws ClassNotFoundException { if (this == requestor) return; URL lib = getClassBaseUrl(cls); if (lib == null) return; // cls is a system class ClassLoader loader = cls.getClassLoader(); if (!(loader instanceof ModuleClassLoader)) return; if (loader != this) { ((ModuleClassLoader) loader).checkClassVisibility(cls, requestor); } else { // ResourceFilter filter = (ResourceFilter) resourceFilters.get(lib); // if (filter == null) { // log.warn("class not visible, no class filter found, lib=" + lib // + ", class=" + cls + ", this=" + this // + ", requestor=" + requestor); // throw new ClassNotFoundException("class " // + cls.getName() + " is not visible for module " // + requestor.getModule().getModuleId() // + ", no filter found for library " + lib); // } // if (!filter.isClassVisible(cls.getName())) { // log.warn("class not visible, lib=" + lib // + ", class=" + cls + ", this=" + this // + ", requestor=" + requestor); // throw new ClassNotFoundException("class " // + cls.getName() + " is not visible for module " // + requestor.getModule().getModuleId()); // } } } /** * @see java.lang.ClassLoader#findLibrary(java.lang.String) */ @Override protected String findLibrary(final String name) { if ((name == null) || "".equals(name.trim())) return null; if (log.isTraceEnabled()) { log.trace("findLibrary(String): name=" + name + ", this=" + this); } String libname = System.mapLibraryName(name); String result = null; //TODO //PathResolver pathResolver = ModuleFactory.getPathResolver(); // for (Library lib : getModule().getLibraries()) { // if (lib.isCodeLibrary()) { // continue; // } // URL libUrl = null; //pathResolver.resolvePath(lib, lib.getPath() + libname); // if (log.isDebugEnabled()) { // log.debug("findLibrary(String): trying URL " + libUrl); // } // File libFile = OpenmrsUtil.url2file(libUrl); // if (libFile != null) { // if (log.isDebugEnabled()) { // log.debug("findLibrary(String): URL " + libUrl // + " resolved as local file " + libFile); // } // if (libFile.isFile()) { // result = libFile.getAbsolutePath(); // break; // } // continue; // } // // we have some kind of non-local URL // // try to copy it to local temporary file // libFile = (File) libraryCache.get(libUrl); // if (libFile != null) { // if (libFile.isFile()) { // result = libFile.getAbsolutePath(); // break; // } // libraryCache.remove(libUrl); // } // if (libraryCache.containsKey(libUrl)) { // // already tried to cache this library // break; // } // libFile = cacheLibrary(libUrl, libname); // if (libFile != null) { // result = libFile.getAbsolutePath(); // break; // } // } if (log.isTraceEnabled()) { log .trace("findLibrary(String): name=" + name + ", libname=" + libname + ", result=" + result + ", this=" + this); } return result; } /** * Saves the given library in the openmrs cache. This prevents locking of jars/files by servlet * container * * @param libUrl URL to the library/jar file * @param libname name of the jar that will be the name of the cached file * @return file that is now copied and cached */ protected File cacheLibrary(final URL libUrl, final String libname) { File cacheFolder = OpenmrsClassLoader.getLibCacheFolder(); if (libraryCache.containsKey(libUrl)) { return libraryCache.get(libUrl); } File result = null; try { if (cacheFolder == null) { throw new IOException("can't initialize libraries cache folder"); } // create the directory to hold the jar's files File libCacheModuleFolder = new File(cacheFolder, getModule().getModuleId()); // error while creating the file if (!libCacheModuleFolder.exists() && !libCacheModuleFolder.mkdirs()) { throw new IOException("can't create cache folder " + libCacheModuleFolder); } // directory within the specific folder within the cache result = new File(libCacheModuleFolder, libname); // copy the file over to the cache InputStream in = OpenmrsUtil.getResourceInputStream(libUrl); try { FileOutputStream fileOut = new FileOutputStream(result); OutputStream out = new BufferedOutputStream(fileOut); try { OpenmrsUtil.copyFile(in, out); } finally { try { out.close(); } catch (Exception e) { /* pass */} try { fileOut.close(); } catch (Exception e) {} } } finally { try { in.close(); } catch (Exception e) { /* pass */} } // save a link to the cached file libraryCache.put(libUrl, result); if (log.isDebugEnabled()) { log.debug("library " + libname + " successfully cached from URL " + libUrl + " and saved to local file " + result); } } catch (IOException ioe) { log.error("can't cache library " + libname + " from URL " + libUrl, ioe); libraryCache.put(libUrl, null); result = null; } return result; } /** * If a resource is found within a jar, that jar URL is converted to a temporary file and a URL * to that is returned * * @see java.lang.ClassLoader#findResource(java.lang.String) */ @Override public URL findResource(final String name) { URL result = findResource(name, this, null); return expandIfNecessary(result); } /** * @see java.lang.ClassLoader#findResources(java.lang.String) */ @Override public Enumeration<URL> findResources(final String name) throws IOException { List<URL> result = new LinkedList<URL>(); findResources(result, name, this, null); // expand all of the "jar" urls for (URL url : result) { url = expandIfNecessary(url); } return Collections.enumeration(result); } /** * Find a resource (image, file, etc) in the module structure * * @param name String path and name of the file * @param requestor ModuleClassLoader in which to look * @param seenModules Set<String> modules that have been checked already * @return URL to resource * @see #findResource(String) */ protected URL findResource(final String name, final ModuleClassLoader requestor, Set<String> seenModules) { if (log.isTraceEnabled()) { if (name != null && name.contains("starter")) { if (seenModules != null) log.trace("seenModules.size: " + seenModules.size()); log.trace("name: " + name); for (URL url : getURLs()) { log.trace("url: " + url); } } } if ((seenModules != null) && seenModules.contains(getModule().getModuleId())) return null; URL result = super.findResource(name); if (result != null) { // found resource in this module class path if (isResourceVisible(name, result, requestor)) { return result; } log.debug("Resource is not visible"); return null; } // if (resourceLoader != null) { // result = resourceLoader.findResource(name); // log.debug("Result from resourceLoader: " + result); // if (result != null) { // found resource in this module resource libraries // if (isResourceVisible(name, result, requestor)) { // return result; // } // log.debug("result from resourceLoader is not visible"); // return null; // } // } if (seenModules == null) seenModules = new HashSet<String>(); seenModules.add(getModule().getModuleId()); if (requiredModules != null) { for (Module publicImport : requiredModules) { if (seenModules.contains(publicImport.getModuleId())) continue; ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport); if (mcl != null) result = mcl.findResource(name, requestor, seenModules); if (result != null) { break; // found resource in publicly imported module } } } else { // do something here so I can put a breakpoint in result = result; } //look through the aware of modules. for (Module publicImport : awareOfModules) { if (seenModules.contains(publicImport.getModuleId())) continue; ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport); if (mcl != null) result = mcl.findResource(name, requestor, seenModules); if (result != null) { break; // found resource in publicly imported module } } return result; } /** * Find all occurrences of a resource (image, file, etc) in the module structure * * @param result URL of the file found * @param name String path and name of the file to find * @param requestor ModuleClassLoader in which to start * @param seenModules Set<String> moduleIds that have been checked already * @throws IOException * @see #findResources(String) * @see #findResource(String, ModuleClassLoader, Set) */ protected void findResources(final List<URL> result, final String name, final ModuleClassLoader requestor, Set<String> seenModules) throws IOException { if ((seenModules != null) && seenModules.contains(getModule().getModuleId())) { return; } for (Enumeration<URL> enm = super.findResources(name); enm.hasMoreElements();) { URL url = enm.nextElement(); if (isResourceVisible(name, url, requestor)) { result.add(url); } } // if (resourceLoader != null) { // for (Enumeration enm = resourceLoader.findResources(name); // enm.hasMoreElements();) { // URL url = (URL) enm.nextElement(); // if (isResourceVisible(name, url, requestor)) { // result.add(url); // } // } // } if (seenModules == null) { seenModules = new HashSet<String>(); } seenModules.add(getModule().getModuleId()); if (requiredModules != null) { for (Module publicImport : requiredModules) { if (seenModules.contains(publicImport.getModuleId())) continue; ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport); if (mcl != null) mcl.findResources(result, name, requestor, seenModules); } } //look through the aware of modules. for (Module publicImport : awareOfModules) { if (seenModules.contains(publicImport.getModuleId())) continue; ModuleClassLoader mcl = ModuleFactory.getModuleClassLoader(publicImport); if (mcl != null) mcl.findResources(result, name, requestor, seenModules); } } /** * Check if the given resource (image, file, etc) is visible by this classloader * * @param name String path and name to check * @param url URL of the library file * @param requestor ModuleClassLoader in which to look * @return true/false whether this resource is visibile by this classloader */ protected boolean isResourceVisible(final String name, final URL url, final ModuleClassLoader requestor) { /*log.debug("isResourceVisible(URL, ModuleClassLoader): URL=" + url + ", requestor=" + requestor);*/ if (this == requestor) { return true; } @SuppressWarnings("unused") URL lib; try { String file = url.getFile(); lib = new URL(url.getProtocol(), url.getHost(), file.substring(0, file.length() - name.length())); } catch (MalformedURLException mue) { log.error("can't get resource library URL", mue); return false; } // ResourceFilter filter = (ResourceFilter) resourceFilters.get(lib); // if (filter == null) { // log.warn("no resource filter found for library " // + lib + ", name=" + name // + ", URL=" + url + ", this=" + this // + ", requestor=" + requestor); // return false; // } // if (!filter.isResourceVisible(name)) { // log.warn("resource not visible, name=" + name // + ", URL=" + url + ", this=" + this // + ", requestor=" + requestor); // return false; // } return true; } /** * Expands the URL into the temporary folder if the URL points to a resource inside of a jar * file * * @param result * @return URL to the expanded result or null if an error occurred */ private URL expandIfNecessary(URL result) { if (result == null || !"jar".equals(result.getProtocol())) return result; File tmpFolder = getLibCacheFolderForModule(module); return OpenmrsClassLoader.expandURL(result, tmpFolder); } /** * Package names that this module should try to load. All classes/packages within the omod and * the lib folder are already checked, this method/variable are used for extreme circumstances * where an omod needs to know about another after being loaded * * @return the additionalPackages */ public Set<String> getAdditionalPackages() { return additionalPackages; } /** * @param additionalPackages the package names to set that this module contains that are outside * the normal omod and omod/lib folders */ public void setAdditionalPackages(Set<String> additionalPackages) { this.additionalPackages = additionalPackages; } /** * Convenience method to add another package name to the list of packages provided by this * module * * @param additionalPackage string package name * @see #setProvidedPackages(Set) */ public void addAdditionalPackage(String additionalPackage) { if (this.additionalPackages == null) this.additionalPackages = new LinkedHashSet<String>(); // its pointless to add a package that is below the module's package // name because we are automatically looking at that in the classloader if (!additionalPackage.startsWith(module.getPackageName())) this.additionalPackages.add(additionalPackage); } /** * Convenience method to add a bunch of package names to the list of packages provided by this * module * * @param providedPackages list/set of strings that are package names * @see #setProvidedPackages(Set) */ public void addAllAdditionalPackages(Collection<String> providedPackages) { if (this.additionalPackages == null) this.additionalPackages = new LinkedHashSet<String>(); for (String provPackage : providedPackages) // its pointless to add a package that is below the module's package // name because we are automatically looking at that in the classloader addAdditionalPackage(provPackage); } /** * @see java.lang.Object#toString() */ @Override public String toString() { return "{ModuleClassLoader: uid=" + System.identityHashCode(this) + "; " + module + "}"; } }