Package javolution.lang

Source Code of javolution.lang.Configurable

/*
* 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);

    }
}
TOP

Related Classes of javolution.lang.Configurable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.