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

ml.alternet.discover.DiscoveryService Maven / Gradle / Ivy

Go to download

Alternet Tools include discovery service tools, concurrent and locking tools, and more

The newest version!
package ml.alternet.discover;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.ServiceLoader;

import javax.naming.InitialContext;

import ml.alternet.util.ClassUtil;
import ml.alternet.util.JNDIUtil;
import ml.alternet.web.WebAvailability;
import ml.alternet.web.WebContext;

/**
 * A simple service discovery component for retrieving classes.
 *
 * 

* The lookup is done once for every class name to discover. *

* *

* An implementation can be render either as a raw {@link Class} or as a * singleton instance (or both across multiple calls). Singletons are * instanciated by their method "newInstance()" if they have one, or by * their default zero argument constructor. If the singleton fails to * instanciate, the user would still have the possibility to get the raw class * and build itself an instance. *

* *

Lookup keys and variants

* *

* The lookup key is usually the fully qualified name of a class (usually * abstract or an interface) : org.acme.Foo, and the resolved class * name should be a concrete implementation of it; the lookup key can also be a * name, even a JNDI name. *

* *

* The lookup key can be supplied with a variant, in order to bind several * implementations (this has to be taken in charge by the caller); for example, * ml.reflex.xml.SerializerFactory/image/png. Basically, it means that * we want a factory that can supply a serializer for "image/png". *

* *

Class localization

* *

* An implementation is found from the key supplied as follows: *

*
    *
  1. The value of the system property with the name of the key supplied if it * exists and is accessible.
  2. *
  3. The value of the JNDI property with the name of the key supplied prepend * with "java:comp/env/" if it exists and is accessible; if the * resolved object is a string, it stands for a class name, otherwise for the * instance to return.
  4. *
  5. The value of the init parameter of the Web application with the name of * the key supplied if it exists and is accessible ( * *
     * <web-app>
     *    <context-param>
     * 
    * * ). During its initialization, the web application must have registered a * filter ml.alternet.web.WebFilter.
  6. *
  7. The contents of the file "[discoveryService.properties]" of the * current directory if it exists.
  8. *
  9. The contents of the file " * $USER_HOME/discovery-service.properties" if it exists.
  10. *
  11. The contents of the file " * $JAVA_HOME/jre/lib/discovery-service.properties" if it exists.
  12. *
  13. The Jar Service Provider discovery mechanism specified in the Jar File * Specification, and ammended by the special use of this class (see below). A * jar file can have a resource (i.e. an embedded file) such as * META-INF/xservices/package.Class (or * META-INF/xservices/package.Class/variant if the key contains a * variant) containing the name of the concrete class to instantiate.
  14. *
  15. The fallback default implementation, which is given by the * META-INF/xservices/ of the user library (services found with a line * of comment before or ending with a comment that contains "default" will be * processed at the end)
  16. *
* *

* The first value found is returned. If one of those method fails, the next is * tried. *

* *

META-INF services and xservices

*

* The location used by this tool is META-INF/xservices/, not * META-INF/services/. Actually, the common META-INF/services/ * directory is ruled by different conventions than those used here * (specifically due to the "variant" of the key). To avoid confusion, another * directory name has been retained, actually "xservices" (that stands for * "extended services"). *

* *

Service names

*

Inner class names contains a $ sign in their name ; all lookup are * performed on keys where the $ sign is replace by a dot. If you define * manually a META-INF/xservices/ entry, don't write file names or directory * names with a dollar, but with a dot instead.

* * @see LookupKey * @see ServiceLoader * * @author Philippe Poulard */ public final class DiscoveryService { /** * A map of {key, classes or singleton}. * * The key is usually the name of a class (an interface), the value a class * that implement or extend it or a singleton of it. * * The key can be of the form package.Class/variant if several * variants are available. */ @SuppressWarnings("rawtypes") private static final Map CLASSES = new HashMap(); /** * A marker that indicates that the class for the given key was already * searched and not found. */ private static final Object NOT_FOUND = new Object(); /** * The file "discoveryService.properties", to load once. */ private static Properties PROPERTIES; private DiscoveryService() { } /** * Load the property file (once) in various locations (current directory, * user home, java home). If a property file has been already loaded, return * it. * * @return The loaded properties, might be empty but not null. */ private static Properties loadProperties() { if (PROPERTIES == null) { PROPERTIES = new Properties(); // Pairs of {sytemProp, fileName} // The sytem property is used to retrieve the base file // from which to resolve the relative file name. String[] propertiesFile = new String[] { null /* current dir */, "discoveryService.properties", "user.home", "discoveryService.properties", "java.home", "lib" + File.separator + "discovery-service.properties" }; // try with the files, the first found is used // and the other are ignored for (int i = 0 ; i < propertiesFile.length ; ) { String systemProperty = propertiesFile[i++]; String fileName = propertiesFile[i++]; File file; if (systemProperty != null) { try { // try to find a system property systemProperty = System.getProperty(systemProperty); if (systemProperty == null) { continue; } } catch (SecurityException se) { continue; } file = new File(systemProperty, fileName); } else { file = new File(fileName); } if (file.exists()) { try { PROPERTIES.load(new FileInputStream(file)); break; } catch (IOException e) { } } } } return PROPERTIES; } /** * Get the available classes of a service defined in jar libraries. * * @param service * A service, something like " * META-INF/xservices/package.Class". Variants are * supported. * * @return A non-null iterator on the class names found. */ @SuppressWarnings("rawtypes") private static Iterator getClasses(String service) { Enumeration e = null; try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = DiscoveryService.class.getClassLoader(); } e = cl.getResources(service); } catch (IOException ioe) { } if (e == null || !e.hasMoreElements()) { return Collections.EMPTY_LIST.iterator(); } else { final Enumeration enumer = e; return new Iterator() { Enumeration e = enumer; // URLs String clazz = null; // class name String defaultClass = null; // default class name, if a comment // "# default" is found InputStream is = null; // current URL content Reader r = null; BufferedReader br = null; @Override public boolean hasNext() { if (clazz == null) { if (is == null) { if (e == null) { if (defaultClass == null) { return false; } else { // use the default class in last resort clazz = defaultClass; defaultClass = null; return true; } } else if (e.hasMoreElements()) { try { URL u = (URL) e.nextElement(); is = u.openStream(); r = new InputStreamReader(is, "UTF-8"); br = new BufferedReader(r); } catch (IOException e) { } } else { e = null; return hasNext(); } } try { clazz = br.readLine(); boolean isDefault = false; while (clazz != null) { // Strip comment int sharp = clazz.indexOf('#'); if (sharp != -1) { if (clazz.substring(sharp).indexOf( "default") != -1) { // this is the default isDefault = true; } clazz = clazz.substring(0, sharp); } clazz = clazz.trim(); if (clazz.length() == 0) { clazz = br.readLine(); continue; } if (isDefault) { // use the default later defaultClass = clazz; isDefault = false; // try to find another implementation clazz = br.readLine(); continue; } break; // found "package.ConcreteClass" } } catch (IOException e) { } if (clazz == null) { // end of stream if (is != null) { try { is.close(); } catch (IOException ioe) { } is = null; } if (r != null) { try { r.close(); } catch (IOException ioe) { } r = null; } if (br == null) { try { br.close(); } catch (IOException ioe) { } br = null; } // try next URL in enum if some available return hasNext(); } else { return true; } } else { return true; } } @Override public Object next() { if (clazz == null && !hasNext()) { throw new NoSuchElementException(); } String next = clazz; clazz = null; return next; } @Override public void remove() { } }; } } /** * Find a key: either by returning the value if a key has been already * processed, or by applying the lookup strategy. * * @param key * The key to find, a class name + an optional variant. * * @return The value bound to the key, either a class or a singleton; * null if not found. * * @throws ClassNotFoundException * When the class name has been resolved but the concrete * implementation was not found. */ @SuppressWarnings("unchecked") private static Object find(String key) throws ClassNotFoundException { Object o = CLASSES.get(key); if (o == null) { String impl = null; try { // try to find a system property impl = System.getProperty(key); } catch (SecurityException se) { } if (impl == null) { // try JNDI java:comp/env/ try { Object resolved = new InitialContext() .lookup(JNDIUtil.JAVA_COMP_ENV + "/" + key); if (resolved instanceof String) { impl = (String) resolved; } else { return resolved; } } catch (Exception e) { } if (impl == null) { // try ... for registered web apps if (WebAvailability.servletAvailable()) { impl = WebContext.getInitParameter(key); } if (impl == null) { // try to find the file "discoveryService.properties" in // various locations Properties properties = loadProperties(); impl = properties.getProperty(key); if (impl == null) { // try to find services in the CLASSPATH // note that ufos-xxx.jar will be the last used // (this is done automatically) String serviceId = "META-INF/xservices/" + key; @SuppressWarnings("rawtypes") Iterator it; for (it = getClasses(serviceId) ; it.hasNext() ; ) { impl = (String) it.next(); // ignore others while (it.hasNext()) { // consume everything (force to close // streams) it.next(); } } } } } } try { if (impl == null || impl.length() == 0) { o = NOT_FOUND; } else { o = ClassUtil.load(impl); } } catch (ClassNotFoundException cnfe) { o = NOT_FOUND; throw cnfe; } finally { CLASSES.put(key, o); } } if (o == NOT_FOUND) { return null; } else { return o; } } /** * Return an implementation of a class. * * @param key * The class name to lookup, with eventually a variant. * @param * The type of the class to lookup. * * @return The implementation of that class. * * @throws ClassNotFoundException * When the class is not found. */ @SuppressWarnings("unchecked") public static Class lookup(String key) throws ClassNotFoundException { key = key.replace('$', '.'); Object o = find(key); if (o == null) { return null; } else if (o instanceof Class) { return (Class) find(key); } else { return (Class) o.getClass(); } } /** * Return the singleton of a class. * * @param key * The class name to lookup, with eventually a variant. * @param * The type of the class to lookup. * * @return The singleton instance of that class. The singleton is built with * the static method "newInstance()" of the class if it exists, * otherwise with the default constructor. * * @throws InstantiationException * When the class can't be instanciated. * @throws IllegalAccessException * When the class can't be accessed. * @throws ClassNotFoundException * When the class is not found. */ @SuppressWarnings("unchecked") public static T lookupSingleton(String key) throws InstantiationException, IllegalAccessException, ClassNotFoundException { key = key.replace('$', '.'); Object o = find(key); if (o instanceof Class) { Class c = (Class) o; try { Method method = c.getMethod("newInstance", (Class) null); o = method.invoke((Object) null, (Object) null); } catch (Exception e) { o = c.newInstance(); } // replace the class by an instance CLASSES.put(key, o); } return (T) o; } /** * Return the singleton of a class. * * @param clazz * The class to lookup, without a variant. * @param * The type of the class to lookup. * * @return The singleton instance of that class. The singleton is built with * the static method "newInstance()" of the class if it exists, * otherwise with the default constructor. * * @throws InstantiationException * When the class can't be instanciated. * @throws IllegalAccessException * When the class can't be accessed. * @throws ClassNotFoundException * When the class is not found. */ public static T lookupSingleton(Class clazz) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return lookupSingleton(clazz.getName()); } /** * Return a new instance of a class. * * @param key * The class name to lookup, with eventually a variant. * @param * The type of the class to return. * * @return The new instance of that class. It is built with the static * method "newInstance()" of the class if it exists, otherwise with * the default constructor. * * @throws InstantiationException * When the class can't be instanciated. * @throws IllegalAccessException * When the class can't be accessed. * @throws ClassNotFoundException * When the class is not found. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public static T newInstance(String key) throws ClassNotFoundException, InstantiationException, IllegalAccessException { key = key.replace('$', '.'); Class clazz = DiscoveryService.lookup(key); if (clazz == null) { // key might be simply an instanciable class clazz = ClassUtil.load(key); CLASSES.put(key, clazz); } return newInstance(clazz); } /** * Return a new instance of a class. * * @param clazz * The class. * @param * The type of the class to lookup. * * @return The new instance of that class. It is built with the static * method "newInstance()" of the class if it exists, otherwise with * the default constructor. If the given class is an interface or is * abstract, it is looked up with no variant. * * @throws InstantiationException * When the class can't be instanciated. * @throws IllegalAccessException * When the class can't be accessed. * @throws ClassNotFoundException * When the class is not found. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static T newInstance(Class clazz) throws ClassNotFoundException, InstantiationException, IllegalAccessException { // check first if the class is instanciable if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) { // lookup for a concrete implementation... String className = clazz.getName(); clazz = DiscoveryService.lookup(className); if (clazz == null) { throw new ClassNotFoundException( "Unable to find a concrete implementation of " + className); } } // get an instance of that class try { Method method = clazz.getMethod("newInstance", (Class) null); return (T) method.invoke((Object) null, (Object) null); } catch (Exception e) { return (T) clazz.newInstance(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy