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