All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.dev.introspect.ClassPathResourceDiscoverer Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */
package com.tangosol.dev.introspect;

import com.oracle.coherence.common.base.Logger;

import com.tangosol.io.ClassLoaderAware;

import com.tangosol.util.Base;
import com.tangosol.util.Filter;
import com.tangosol.util.SimpleEnumerator;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;

import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import java.util.regex.Pattern;

/**
 * ClassPathResourceDiscoverer is {@link ResourceDiscoverer} implementation
 * that uses its {@link ClassLoader} ({@link #getContextClassLoader()}) to
 * determine appropriate artifacts based on the discriminator passed to
 * {@link #discoverResource(String, String)}. The resource discovery
 * mechanism employed by this class resides in
 * {@link #findUrls(String, String)} and by default uses
 * {@link ClassLoader#getResources(String)}. This has some limitations
 * including the inability refer to a resource prefix in the case of a jar.
 * Thus the discriminator must be a resource that can be referenced in all
 * artifacts that may have resources complying to the regular expression.
 * 

* Each of these derived artifacts * ({@link URL}s) are evaluated against a resource discoverer chain * {@link #getDiscovererChain()}. *

* Each resource discoverer is given the opportunity discover all * leaf-resources in the current artifact. ResourceDiscoverer's that * implement {@link Filter} are provided the URL of the artifact to either * disregard the artifact or confirm acceptance to interrogate said artifact. * The chain of ResourceDiscoverer's are provided both the URL * and a string expression. The expression is the same passed to * {@link #discoverResource(String, String)} and could represent a package. * * @author hr 2011.10.18 * * @since Coherence 12.1.2 * * @see ResourceDiscoverer * @see InformedResourceDiscoverer * @see FileBasedResourceDiscoverer * @see JarFileBasedResourceDiscoverer */ public class ClassPathResourceDiscoverer extends AbstractFilteredResourceDiscoverer { // ----- constructors --------------------------------------------------- /** * Construct a ClassPathResourceDiscoverer with default configuration. */ public ClassPathResourceDiscoverer() { this(null, null); } /** * Construct a ClassPathResourceDiscoverer with the provided list of * {@link ResourceDiscoverer}s and a set of acceptable file types. * * @param setFileTypes the set of acceptable file types * @param listDiscoverers the {@link ResourceDiscoverer} chain to call * for each discovered resource */ public ClassPathResourceDiscoverer(Set setFileTypes, List> listDiscoverers) { m_listDiscoverers = listDiscoverers == null ? m_listDiscoverers : listDiscoverers; m_setFileTypes = setFileTypes == null ? m_setFileTypes : setFileTypes; initialize(); } // ----- AbstractFilteredResourceDiscoverer methods --------------------- /** * Returns an {@link Enumeration} of classpath relative resources that * comply to the provided {@code sExpression}. This implementation uses * a chain of {@link ResourceDiscoverer}s to determine leaf-resources. * * Note:A null or empty discriminator will result in using the * {@literal sExpression} as the discriminator. This default logic can * be overridden by providing a custom implementation of * {@link #findUrls(String, String)}. * * @param sDiscriminator a string representing the resource that allows * the discovery of all artifacts (jar files or * directories in the class path) that should be * interrogated for resources complying to the * {@literal sExpression} * @param sExpression the regular expression applied to all child * resources of the discovered resource via * {@literal sDiscriminator} * * @return an Enumeration of resources that comply to provided * {@literal sExpression} */ @Override public Collection discoverResource(String sDiscriminator, String sExpression) { sExpression = sExpression == null ? "" : sExpression; Set setResources = new HashSet<>(); String sAppendExpr = buildFileTypeExpression(); try { for (Enumeration enumResources = findUrls(sDiscriminator, sExpression); enumResources.hasMoreElements(); ) { URL url = enumResources.nextElement(); for (ResourceDiscoverer discoverer : getDiscovererChain()) { if (discoverer instanceof Filter && !((Filter) discoverer).evaluate(url)) { continue; } Enumeration enumStringPaths = discoverer.discover(".*" + sExpression + ".*" + sAppendExpr, url); while (enumStringPaths != null && enumStringPaths.hasMoreElements()) { URI uri = toURI(enumStringPaths.nextElement()); if (uri != null) { setResources.add(uri); } } } } } catch (IOException e) { throw Base.ensureRuntimeException(e); } return setResources; } /** * {@inheritDoc} */ @Override protected Set getPermittedProtocols() { return new HashSet(); } /** * Based on the set of file types this implementation was instantiated * with build a regular expression that results in an OR of the file * types. * * @return regular expression permitting certain file types */ protected String buildFileTypeExpression() { Set setFileTypes = m_setFileTypes; StringBuilder sbldrExpr = new StringBuilder("("); for (Iterator iterFileTypes = setFileTypes.iterator(); iterFileTypes.hasNext(); ) { String sFileType = iterFileTypes.next(); sbldrExpr.append("\\.") .append(sFileType) .append(iterFileTypes.hasNext() ? "|" : ")$"); } return sbldrExpr.length() == 1 ? "" : sbldrExpr.toString(); } /** * Based on the provided {@literal sDiscriminator} and * {@literal sExpression} derive an {@link Enumeration} of {@link URL}s * that are likely candidates of satisfying {@literal sExpression}. These * {@link URL}s will be passed down a chain of {@link ResourceDiscoverer}s * for interrogation. * * @param sDiscriminator a string representing the resource that allows * the discovery of all artifacts (jar files or * directories in the class path) that should be * interrogated for resources complying to the * {@literal sExpression} * @param sExpression the regular expression applied to all child * resources of the discovered resource via * {@literal sDiscriminator} * * @return an Enumeration or URLs that may comply to the {@literal sExpression} * * @throws IOException */ protected Enumeration findUrls(String sDiscriminator, String sExpression) throws IOException { return getContextClassLoader().getResources(sDiscriminator == null || sDiscriminator.isEmpty() ? sExpression : sDiscriminator); } // ----- ClassLoaderHolder methods -------------------------------------- /** * Ensure all {@link ClassLoaderAware} resources this class is aware of * are updated with the new {@link ClassLoader}. * * @param loader the context ClassLoader for this object */ @Override public void setContextClassLoader(ClassLoader loader) { super.setContextClassLoader(loader); List> listDiscoverers = getDiscovererChain(); if (listDiscoverers == null) { return; } for (ResourceDiscoverer discoverer : listDiscoverers) { discoverer.setContextClassLoader(getContextClassLoader()); } } // ----- accessors ------------------------------------------------------ /** * Returns a list of {@link ResourceDiscoverer}s this class is configured * to use. * * @return a list of ResourceDiscoverer's this class is configured * to use */ public List> getDiscovererChain() { return m_listDiscoverers; } /** * Sets a list of {@link ResourceDiscoverer}s this class is configured * to use. * * @param listDiscoverers the list of ResourceDiscoverer's this class * should use to determine leaf-resources */ public void setDiscoverers(List> listDiscoverers) { Base.azzert(listDiscoverers != null, "ClassPathResourceDiscoverer requires a non-null set of child discoverers"); m_listDiscoverers = listDiscoverers; } // ----- helpers -------------------------------------------------------- /** * Based on the state of the class initialize it with sensible defaults. */ protected void initialize() { List> listDiscoverers = m_listDiscoverers; if (listDiscoverers.size() == 0) { listDiscoverers.add(new FileBasedResourceDiscoverer()); listDiscoverers.add(new JarFileBasedResourceDiscoverer()); } } // ----- inner class: InformedResourceDiscoverer ------------------------ /** * An InformedResourceDiscoverer is provided a number of {@link URL}s to * be interrogated by a chain of {@link ResourceDiscoverer} * implementations. *

* This ResourceDiscoverer implementation is useful when the * resources are known upfront precluding the need of a discovery * mechanism such as a {@link ClassLoader#getResources(String)} request. */ public static class InformedResourceDiscoverer extends ClassPathResourceDiscoverer { // ----- constructors ----------------------------------------------- /** * Construct as InformedResourceDiscoverer with the provided array of * {@link URL}s. * * @param aUrl array of URLs passed down the chain of * {@link ResourceDiscoverer}s */ public InformedResourceDiscoverer(URL[] aUrl) { this(null, null, aUrl); } /** * Construct an InformedResourceDiscoverer with the passed chain of * {@link ResourceDiscoverer}s and an array of {@link URL}s. * * @param setFileTypes the set of acceptable file types * @param listDiscoverers chain of ResourceDiscoverer's * @param aUrl array of URLs passed down the chain * of ResourceDiscoverer's */ public InformedResourceDiscoverer(Set setFileTypes, List> listDiscoverers, URL[] aUrl) { super(setFileTypes, listDiscoverers); m_aUrl = aUrl; } /** * {@inheritDoc} */ @Override protected Enumeration findUrls(String sDiscriminator, String sExpression) throws IOException { return new SimpleEnumerator(m_aUrl); } // ----- data members ----------------------------------------------- /** * Array of {@link URL}s passed down the chain of * {@link ResourceDiscoverer}s. */ protected URL[] m_aUrl; } // ----- inner class: FileBasedResourceDiscoverer ----------------------- /** * A {@link ResourceDiscoverer} implementation that works against "file" * based resources, such that the {@link URL} provided has a "file" * protocol. This implementation will traverse the directory tree * discovering all complying leaf-resources. * * @author hr 2011.10.18 */ protected static class FileBasedResourceDiscoverer extends AbstractFilteredResourceDiscoverer { // ----- AbstractFilteredResourceDiscoverer methods ----------------- /** * {@inheritDoc} */ protected Set getPermittedProtocols() { return s_setProtocols; } // ----- ResourceDiscoverer interface ------------------------------- /** * Starting from the {@code urlRoot} traverse the directory tree * evaluating all leaf-resources against {@code sFileExpression} * returning complying resources. This method will return the full * path of the resource. * * @param sFileExpression the expression that each leaf-resource * must match * @param urlRoot the root element to start traversing from * * @return a number of resources that complied with the * {@code sFileExpression} and belong to {@code urlRoot} */ @Override public Collection discoverResource(String sFileExpression, URL urlRoot) { Set setResources = new HashSet<>(); URI uriRoot = toURI(urlRoot); if (uriRoot == null) { Logger.warn("FileBasedResourceDiscoverer requires a non-null and non-empty root directory"); return setResources; } File root = ensureDirectory(new File(uriRoot)); if (root == null) { return setResources; } if (root.isFile() && root.canRead()) { setResources.add(uriRoot); return setResources; } Pattern regEx = Pattern.compile(sFileExpression); findFiles(root, regEx, setResources); return setResources; } // ----- helpers ---------------------------------------------------- /** * From the current directory ({@code root}), find all files that * match the {@code regEx} parameter adding them to the * {@code setResources}. This method will be called recursively if * any of the files discovered are directories. * * @param root the directory to start from * @param regEx the pattern to match file names against * @param setResources the set of resources to add complying files * to */ protected void findFiles(File root, final Pattern regEx, Set setResources) { File[] complyingFiles = root.listFiles(new FileFilter() { public boolean accept(File pathname) { // ensure path separators use the java & unix format String sPath = pathname.getAbsolutePath(); return pathname.isDirectory() || regEx.matcher(sPath.contains("\\") ? sPath.replaceAll("\\\\", "/") : sPath).matches(); } }); for (int i = 0; i< complyingFiles.length; ++i) { File complyingFile = complyingFiles[i]; if (complyingFile.isFile()) { setResources.add(complyingFile.toURI()); } else if (complyingFile.isDirectory()) { if (ensureDirectory(complyingFile) == null) { continue; } findFiles(complyingFile, regEx, setResources); } } } /** * Based on {@code root} ensure it is a directory and can be * read. Display a warning message if not the case and return null. * * @param root the file resource * * @return the file reference */ protected File ensureDirectory(File root) { if (!root.isDirectory()) { Logger.warn(String.format( "FileBasedResourceDiscoverer root directory should be a valid directory [rootDir: %s]", root.getAbsolutePath())); return null; } if (!root.canRead()) { Logger.warn(String.format( "FileBasedResourceDiscoverer root directory should be a readable directory [rootDir: %s]", root.getAbsolutePath())); return null; } return root; } // ----- constants -------------------------------------------------- /** * All protocols this {@link ResourceDiscoverer} can operate against. */ protected static final Set s_setProtocols = new HashSet(Arrays.asList("file")); } // ----- inner class: JarFileBasedResourceDiscoverer -------------------- /** * A {@link ResourceDiscoverer} implementation that works against "jar", * "zip", "wsjar", "code-source", based resources, such that the * {@link URL} provided has one of the mentioned protocols. This * implementation will traverse the directory tree discovering all * complying leaf-resources. * * @author hr 2011.10.18 */ protected static class JarFileBasedResourceDiscoverer extends AbstractFilteredResourceDiscoverer { // ----- AbstractFilteredResourceDiscoverer methods ----------------- /** * {@inheritDoc} */ @Override protected Set getPermittedProtocols() { return s_setProtocols; } /** * Starting from the {@code urlRoot} traverse the directory tree * evaluating all leaf-resources against {@code sFileExpression} * returning complying resources. This method will return the full * path of the resource. * * @param urlRoot the root element to start traversing from * @param sFileExpression the expression that each leaf-resource * must match * * @return a number of resources that complied with the * {@code sFileExpression} and belong to {@code urlRoot} */ @Override public Collection discoverResource(String sFileExpression, URL urlRoot) { Set setResources = new HashSet<>(); Pattern regEx = Pattern.compile(sFileExpression); try { URLConnection urlCon = urlRoot.openConnection(); JarFile jarFile; if (urlCon instanceof JarURLConnection) { JarURLConnection jarUrl = (JarURLConnection) urlCon; jarUrl.setUseCaches(false); jarFile = jarUrl.getJarFile(); } else { // attempt to create a File reference removing any notation // specific to the jar protocol String sFullLocation = urlRoot.getFile(); int iEmbeddedRef = sFullLocation.indexOf("!/"); String sLocation = iEmbeddedRef == -1 ? sFullLocation : sFullLocation.substring(0, iEmbeddedRef); // try to create a URI out using the location // if not possible assume the format of sLocation to be // "file:/foo" or "someother:/" // in case of "/dir1/dir:2/" pass this as the fs location try { jarFile = new JarFile(new URI(sLocation.replace(" ", "%20")).getSchemeSpecificPart()); } catch (URISyntaxException e) { int iColon = sLocation.indexOf(":"); jarFile = new JarFile(iColon == -1 || sLocation.substring(0, iColon).contains(File.separator) ? sLocation : sLocation.substring(iColon + 1)); } } // scan the entries adding all matched values to the results for (Enumeration enumEntries = jarFile.entries(); enumEntries.hasMoreElements(); ) { JarEntry entry = enumEntries.nextElement(); String sResource = entry.getName(); if (regEx.matcher(sResource).matches()) { URI uri = toURI(new URL(urlRoot, "/" + sResource)); if (uri != null) { setResources.add(uri); } } } } catch (IOException e) { Logger.warn(String.format( "JarFileBasedResourceDiscoverer could not open a connection to the resource [url: %s]" + ", hence this resource will not be used", urlRoot.toExternalForm())); } return setResources; } // ----- constants -------------------------------------------------- /** * All protocols this {@link ResourceDiscoverer} can operate against. */ protected static final Set s_setProtocols = new HashSet(Arrays.asList("jar", "zip", "wsjar", "code-source")); } // ----- data member ---------------------------------------------------- /** * The set of accepted file types to return as a result of * {@link #discoverResource(String, String)}. */ protected Set m_setFileTypes = new HashSet(Arrays.asList("class")); /** * A {@link List} of {@link ResourceDiscoverer}s the * ClassPathResourceDiscoverer will use to discover leaf-resources from * different types of resources. */ protected List> m_listDiscoverers = new ArrayList>(2); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy