/* * Javolution - Java(TM) Solution for Real-Time and Embedded Systems * Copyright (C) 2005 - Javolution (http://javolution.org/) * All rights reserved. * * Permission to use, copy, modify, and distribute this software is * freely granted, provided that this notice is preserved. */ package javolution.lang; import javolution.context.LogContext; import javolution.context.SecurityContext; import javolution.text.Text; import javolution.text.TextFormat; import javolution.util.FastTable; import javolution.xml.XMLBinding; import javolution.xml.XMLFormat; import javolution.xml.XMLObjectReader; import javolution.xml.stream.XMLStreamException; import java.io.InputStream; import java.util.Enumeration; /** * <p> This class facilitates separation of concerns between the configuration * logic and the application code.</p> * <p> Does your class need to know or has to assume that the configuration is * coming from system properties ??</p> * * <p> The response is obviously NO!</p> * * <p> Let's compare the following examples:[code] * class Document { * private static final Font DEFAULT_FONT * = Font.decode(System.getProperty("DEFAULT_FONT") != null ? * System.getProperty("DEFAULT_FONT") : "Arial-BOLD-18"); * ... * }[/code] * With the following (using this class):[code] * class Document { * public static final Configurable<Font> DEFAULT_FONT * = new Configurable<Font>(new Font("Arial", Font.BOLD, 18)); * ... * }[/code] * Not only the second example is cleaner, but the actual configuration * data can come from anywhere, for example from the OSGI Configuration * Admin package (<code>org.osgi.service.cm</code>). * Low level code does not need to know.</p> * * <p> Configurable instances have the same textual representation as their * current values. For example:[code] * public static final Configurable<String> AIRPORT_TABLE * = new Configurable<String>("Airports"); * ... * String sql = "SELECT * FROM " + AIRPORT_TABLE * // AIRPORT_TABLE.get() is superfluous * + " WHERE State = '" + state + "'";[/code] * </p> * * <p> Unlike system properties (or any static mapping), configuration * parameters may not be known until run-time or may change dynamically. * They may depend upon the current run-time platform, * the number of cpus, etc. Configuration parameters may also be retrieved * from external resources such as databases, XML files, * external servers, system properties, etc.[code] * public abstract class FastComparator<T> implements Comparator<T>, Serializable { * public static final Configurable<Boolean> REHASH_SYSTEM_HASHCODE * = new Configurable<Boolean>(isPoorSystemHash()); // Test system hashcode. * ... * public abstract class ConcurrentContext extends Context { * public static final Configurable<Integer> MAXIMUM_CONCURRENCY * = new Configurable<Integer>(Runtime.getRuntime().availableProcessors() - 1) {}; * // No algorithm parallelization on single-processor machines. * ... * public abstract class XMLInputFactory { * public static final Configurable<Class<? extends XMLInputFactory>> CLASS * = new Configurable<Class<? extends XMLInputFactory>>(XMLInputFactory.Default.class); * // Default class implementation is a private class. * ... * [/code]</p> * * <p> Dynamic {@link #configure configuration} is allowed/disallowed based * upon the current {SecurityContext}. Configurables are automatically * {@link Configurable#notifyChange notified} of * any changes in their configuration values.</p> * * <p> Unlike system properties, configurable can be * used in applets or unsigned webstart applications.</p> * * <p> Here is an example of configuration of a web application from * a property file:[code] * public class Configuration implements ServletContextListener { * public void contextInitialized(ServletContextEvent sce) { * try { * ServletContext ctx = sce.getServletContext(); * * // Loads properties. * Properties properties = new Properties(); * properties.load(ctx.getResourceAsStream("WEB-INF/config/configuration.properties")); * * // Reads properties superceeding default values. * Configurable.read(properties); * * } catch (Exception ex) { * LogContext.error(ex); * } * } * }[/code] * This listener is registered in the <code>web.xml</code> file:[code] * <web-app> * <listener> * <listener-class>mypackage.Configuration</listener-class> * </listener> * </web-app>[/code] * The property file contains the full names of the configurable static * fields and the textual representation of their new values:[code] * # File configuration.properties * javolution.util.FastComparator#REHASH_SYSTEM_HASHCODE = true * javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY = 0 * javolution.xml.stream.XMLInputFactory#CLASS = com.foo.bar.XMLInputFactoryImpl * [/code]</p> * * <p> Here is an example of reconfiguration from a xml file:[code] * FileInputStream xml = new FileInputStream("D:/configuration.xml"); * Configurable.read(xml);[/code] * and the configuration file:[code] * <?xml version="1.0" encoding="UTF-8" ?> * <Configuration> * <Configurable name="javolution.util.FastComparator#REHASH_SYSTEM_HASHCODE"> * <Value class="java.lang.Boolean" value="true"/> * </Configurable> * <Configurable name="javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY"> * <Value class="java.lang.Integer" value="0"/> * </Configurable> * <Configurable name="javolution.xml.stream.XMLInputFactory#CLASS"> * <Value class="java.lang.Class" value="com.foo.MyXMLInputFactory"/> * </Configurable> * </Configuration>[/code]</p> * * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> * @version 5.5, April 20, 2010 */ public class Configurable <T> { /** * Holds the current value (never null). */ private T _value; /** * Holds the default value (never null). */ private final T _default; /** * Holds the class where this configurable is defined. */ private final Class _container; /** * Creates a new configurable having the specified default value. * * @param defaultValue the default value. * @throws IllegalArgumentException if <code>defaultValue</code> is * <code>null</code>. */ public Configurable( T defaultValue) { if (defaultValue == null) throw new IllegalArgumentException("Default value cannot be null"); _default = defaultValue; _value = defaultValue; _container = Configurable.findContainer(); } private static Class findContainer() { /* */ try { StackTraceElement[] stack = new Throwable().getStackTrace(); String className = stack[2].getClassName(); int sep = className.indexOf("$"); if (sep >= 0) { // If inner class, remove suffix. className = className.substring(0, sep); } return Class.forName(className); // We use the caller class loader (and avoid dependency to Reflection utility). } catch (Throwable error) { LogContext.error(error); } /**/ return null; } /** * Returns the current value for this configurable. * * @return the current value (always different from <code>null</code>). */ public T get() { return _value; } /** * Returns the default value for this configurable. * * @return the default value (always different from <code>null</code>). */ public T getDefault() { return _default; } /** * Returns the container class of this configurable (the class * where this configurable is defined as a <code>public static</code> field. * * @return the container class or <code>null</code> if unknown (e.g. J2ME). */ public Class getContainer() { return _container; } /** * Returns the field name of this configurable (for example <code> * "javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY"</code>) * for {@link javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY}. * * @return this configurable name or <code>null</code> if the name * of this configurable is unknown (e.g. J2ME). */ public String getName() { if (_container == null) return null; /* */ try { java.lang.reflect.Field[] fields = _container.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { java.lang.reflect.Field field = fields[i]; if (java.lang.reflect.Modifier.isPublic(field.getModifiers()) && field.get(null) == this) return _container.getName() + '#' + field.getName(); } } catch (Throwable error) { LogContext.error(error); } /**/ return null; } /** * Notifies this configurable that its runtime value is going to be changed. * The default implementation does nothing. * * @param oldValue the previous value. * @param newValue the new value. * @throws UnsupportedOperationException if dynamic reconfiguration of * this configurable is not allowed (regardless of the security * context). */ protected void notifyChange( T oldValue, T newValue) throws java.lang.UnsupportedOperationException { } /** * Returns the string representation of the value of this configurable. * * @return <code>String.valueOf(this.get())</code> */ public String toString() { return String.valueOf(_value); } /** * Returns the configurable instance having the specified name. * For example:[code] * Configurable cfg = Configurable.getInstance("javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY") * [/code] returns {@link javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY}. * * <p><b>Note:</b> OSGI based framework should ensure that class loaders * of configurable instances are known to the {@link Reflection} utility * class.</p> * @param name the name of the configurable to retrieve. * @return the corresponding configurable or <code>null</code> if it * cannot be found. */ public static Configurable getInstance(String name) { int sep = name.lastIndexOf('#'); if (sep < 0) return null; String className = name.substring(0, sep); String fieldName = name.substring(sep + 1); Class cls = Reflection.getInstance().getClass(className); if (cls == null) { LogContext.warning("Class " + className + " not found"); return null; } /* */ try { Configurable cfg = (Configurable) cls.getDeclaredField(fieldName).get(null); if (cfg == null) { LogContext.warning("Configurable " + name + " not found"); } return cfg; } catch (Exception ex) { LogContext.error(ex); } /**/ return null; } /** * Sets the run-time value of the specified configurable. If the * configurable value is different from the previous one, then * {@link #notifyChange} is called. This method * raises a <code>SecurityException</code> if the specified * configurable cannot be {@link SecurityContext#isConfigurable * reconfigured}. * * @param cfg the configurable being configured. * @param newValue the new run-time value. * @throws IllegalArgumentException if <code>value</code> is * <code>null</code>. * @throws SecurityException if the specified configurable cannot * be modified. */ public static <T> void configure(Configurable <T> cfg, T newValue) throws SecurityException { if (newValue == null) throw new IllegalArgumentException("Default value cannot be null"); SecurityContext policy = (SecurityContext) SecurityContext.getCurrentSecurityContext(); if (!policy.isConfigurable(cfg)) throw new SecurityException( "Configuration disallowed by SecurityContext"); T oldValue = cfg._value; if (!newValue.equals(oldValue)) { LogContext.info("Configurable " + cfg.getName() + " set to " + newValue); cfg._value = newValue; cfg.notifyChange(oldValue, newValue); } } /** * Convenience method to read the specified properties and reconfigure * accordingly. For example:[code] * // Load configurables from system properties. * Configurable.read(System.getProperties());[/code] * Configurables are identified by their field names. The textual * representation of their value is defined by * {@link javolution.text.TextFormat#getInstance(Class)} * text format}. For example:[code] * javolution.util.FastComparator#REHASH_SYSTEM_HASHCODE = true * javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY = 0 * javolution.xml.stream.XMLInputFactory#CLASS = com.foo.bar.XMLInputFactoryImpl * [/code] * Conversion of <code>String</code> values to actual object is * performed using {@link javolution.text.TextFormat#getInstance(Class)}. * * <p><b>Note:</b> OSGI based framework should ensure that class loaders * of configurable instances are known to the {@link Reflection} utility * class.</p> * * @param properties the properties. */ public static void read(java.util.Properties properties) { Enumeration e = properties.keys(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); String textValue = properties.getProperty(name); Configurable cfg = Configurable.getInstance(name); if (cfg == null) continue; // Use the default value to retrieve the configurable type // and the associated textual format. Class type = cfg.getDefault().getClass(); TextFormat format = TextFormat.getInstance(type); if (!format.isParsingSupported()) { LogContext.error("Cannot find suitable TextFormat to parse instances of " + type); continue; } Object newValue = format.parse(Configurable.toCsq(textValue)); Configurable.configure(cfg, newValue); } } /** * Convenience method to read configurable values from the specified * XML stream. This method uses * <a href="http://javolution.org/target/site/apidocs/javolution/xml/package-summary.html"> * Javolution XML</a> facility to perform the deserialization. * Here is an example of XML configuration file.[code] * <?xml version="1.0" encoding="UTF-8" ?> * <Configuration> * <Configurable name="javolution.util.FastComparator#REHASH_SYSTEM_HASHCODE"> * <Value class="java.lang.Boolean" value="true"/> * </Configurable> * <Configurable name="javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY"> * <Value class="java.lang.Integer" value="0"/> * </Configurable> * <Configurable name="javolution.xml.stream.XMLInputFactory#CLASS"> * <Value class="java.lang.Class" value="com.foo.MyXMLInputFactory"/> * </Configurable> * </Configuration>[/code] * It can be read directly with the following code:[code] * FileInputStream xml = new FileInputStream("D:/configuration.xml"); * Configurable.read(xml);[/code] * * <p><b>Note:</b> OSGI based framework should ensure that class loaders * of configurable instances are known to the {@link Reflection} utility. * </p> * * @param inputStream the input stream holding the xml configuration. */ public static void read(InputStream inputStream) { try { XMLObjectReader reader = XMLObjectReader.newInstance(inputStream); XMLBinding binding = new XMLBinding() { protected XMLFormat getFormat(Class forClass) throws XMLStreamException { if (Configurable.class.isAssignableFrom(forClass)) return new ConfigurableXMLFormat(); return super.getFormat(forClass); } }; binding.setAlias(Configurable.class, "Configurable"); reader.setBinding(binding); // Reads and configures. reader.read("Configuration", FastTable.class); } catch (Exception ex) { LogContext.error(ex); } } // Local format for read operation. private static class ConfigurableXMLFormat extends XMLFormat { ConfigurableXMLFormat() { super(null); // Unbounded } public Object newInstance(Class cls, InputElement xml) throws XMLStreamException { return Configurable.getInstance(xml.getAttribute("name", "")); } public void write(Object c, OutputElement xml) throws XMLStreamException { throw new java.lang.UnsupportedOperationException(); } public void read(InputElement xml, Object c) throws XMLStreamException { Object value = xml.get("Value"); if (value == null) return; // Optional value not present. Configurable.configure((Configurable) c, value); } }; // For J2ME Compatibility. private static java.lang.CharSequence toCsq(Object str) { /**/ if (true) return (CharSequence) str; /**/ return str == null ? null : Text.valueOf(str); } }