001: /*
002: * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
003: * Copyright (C) 2005 - Javolution (http://javolution.org/)
004: * All rights reserved.
005: *
006: * Permission to use, copy, modify, and distribute this software is
007: * freely granted, provided that this notice is preserved.
008: */
009: package javolution.lang;
010:
011: import javolution.context.SecurityContext;
012: import javolution.text.TextFormat;
013:
014: /**
015: * <p> This class facilitates separation of concerns between the configuration
016: * logic and the application code.</p>
017: *
018: * <p> Does your class need to know or has to assume that the configuration is
019: * coming from system properties ??</p>
020: *
021: * <p> The response is obviously NO!</p>
022: *
023: * <p> Let's compare the following examples:[code]
024: * class Document {
025: * private static final Font DEFAULT_FONT
026: * = Font.decode(System.getProperty("DEFAULT_FONT") != null ? System.getProperty("DEFAULT_FONT") : "Arial-BOLD-18");
027: * ...
028: * }[/code]
029: * With the following (using this class):[code]
030: * class Document {
031: * public static final Configurable<Font> DEFAULT_FONT = new Configurable<Font>(new Font("Arial", Font.BOLD, 18));
032: * ...
033: * }[/code]
034: * Not only the second example is cleaner, but the actual configuration
035: * data can come from anywhere (even remotely). Low level code does not
036: * need to know.</p>
037: *
038: * <p> Furthermore, with the second example the configurable data is
039: * automatically documented in the JavaDoc (public). Still only instances
040: * of {@link Logic} may set this data. There is no chance
041: * for the user to modify the configuration by accident.</p>
042: *
043: * <p> Configurable instances have the same textual representation as their
044: * current values. For example:[code]
045: * public static final Configurable<String> AIRPORT_TABLE
046: * = new Configurable<String>("Airports");
047: * ...
048: * String sql = "SELECT * FROM " + AIRPORT_TABLE // AIRPORT_TABLE.get() is superfluous
049: * + " WHERE State = '" + state + "'";[/code]
050: * </p>
051: *
052: * <p> Unlike system properties (or any static mapping), configuration
053: * parameters may not be known until run-time or may change dynamically.
054: * They may depend upon the current run-time platform,
055: * the number of cpus, etc. Configuration parameters may also be retrieved
056: * from external resources such as databases, XML files,
057: * external servers, system properties, etc.[code]
058: * public abstract class FastComparator<T> implements Comparator<T>, Serializable {
059: * public static final Configurable<Boolean> REHASH_SYSTEM_HASHCODE
060: * = new Configurable<Boolean>(isPoorSystemHash()); // Test system hashcode.
061: * ...
062: * public abstract class ConcurrentContext extends Context {
063: * public static final Configurable<Integer> MAXIMUM_CONCURRENCY
064: * = new Configurable<Integer>(Runtime.getRuntime().availableProcessors() - 1);
065: * // No algorithm parallelization on single-processor machines.
066: * ...
067: * public abstract class XMLInputFactory {
068: * public static final Configurable<Class<? extends XMLInputFactory>> DEFAULT
069: * = new Configurable<Class<? extends XMLInputFactory>>(XMLInputFactory.Default.class);
070: * // Default class implementation is a private class.
071: * ...
072: * [/code]</p>
073: *
074: * <p> Reconfiguration is allowed at run-time as configurable can be
075: * {@link Configurable#notifyChange() notified} of changes in their
076: * configuration values. Unlike system properties, configurable can be
077: * used in applets or unsigned webstart applications.</p>
078: *
079: * <p> Here is an example of configuration of a web application from
080: * a property file:[code]
081: * public class Configuration extends Configurable.Logic implements ServletContextListener {
082: * public void contextInitialized(ServletContextEvent sce) {
083: * try {
084: * ServletContext ctx = sce.getServletContext();
085: *
086: * // Loads properties.
087: * Properties properties = new Properties();
088: * properties.load(ctx.getResourceAsStream("WEB-INF/config/configuration.properties"));
089: *
090: * // Reads properties superceeding default values.
091: * Configurable.read(properties);
092: *
093: * } catch (Exception ex) {
094: * LogContext.error(ex);
095: * }
096: * }
097: * }[/code]
098: * This listener is registered in the <code>web.xml</code> file:[code]
099: * <web-app>
100: * <listener>
101: * <listener-class>mypackage.Configuration</listener-class>
102: * </listener>
103: * </web-app>[/code]
104: * The property file contains the full names of the configurables static
105: * fields and the textual representation of their new values:[code]
106: * # File configuration.properties
107: * javolution.util.FastComparator#REHASH_SYSTEM_HASHCODE = true
108: * javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY = 0
109: * javolution.xml.stream.XMLInputFactory#DEFAULT = com.foo.bar.XMLInputFactoryImpl
110: * [/code]</p>
111: *
112: * <p> Configuration settings are global (affect all threads). For thread-local
113: * environment settings {@link javolution.context.LocalContext.Reference
114: * LocalContext.Reference} instances are recommended.</p>
115: *
116: * <p> <i>Note:</i> Any type for which a text format is
117: * {@link TextFormat#getInstance(Class) known} can be configured from
118: * <code>String</code> properties.</p>
119: *
120: * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
121: * @version 5.1, July 4, 2007
122: */
123: public class Configurable/*<T>*/{
124:
125: /**
126: * Holds the current value.
127: */
128: private Object/*{T}*/_value;
129:
130: /**
131: * Default constructor.
132: */
133: public Configurable(Object/*{T}*/defaultValue) {
134: _value = defaultValue;
135: }
136:
137: /**
138: * Returns the current value for this configurable.
139: *
140: * @return the current value.
141: */
142: public final Object/*{T}*/get() {
143: return _value;
144: }
145:
146: /**
147: * Returns the string representation of the value of this configurable.
148: *
149: * @return <code>String.valueOf(this.get())</code>
150: */
151: public String toString() {
152: return String.valueOf(_value);
153: }
154:
155: /**
156: * Convenience method to read the specified properties (key/value mapping)
157: * and reconfigures accordingly. The configurables are identified by their
158: * field names (e.g. <code>
159: * "javolution.context.ConcurrentContext#MAXIMUM_CONCURRENCY"</code>).
160: * Conversion of <code>String</code> values is performed
161: * using {@link javolution.text.TextFormat#getInstance(Class)}.
162: *@JVM-1.1+@
163: public static void read(j2me.util.Map properties) {
164: j2me.util.Iterator i = properties.entrySet().iterator();
165: while (i.hasNext()) {
166: j2me.util.Map.Entry entry = (j2me.util.Map.Entry) i.next();
167: String key = String.valueOf(entry.getKey());
168: Object value = entry.getValue();
169: try {
170: int sep = key.indexOf('#');
171: if (sep < 0) // Not a configurable property.
172: continue;
173:
174: // Found a configurable property being superseded.
175: javolution.context.LogContext.info("Configure " + key + " to " + value);
176: String className = key.substring(0, sep);
177: String fieldName = key.substring(sep + 1);
178:
179: Class cls = Reflection.getClass(className);
180: Configurable cfg = (Configurable) cls.getDeclaredField(
181: fieldName).get(null);
182: Object previous = cfg.get();
183: if ((previous == null) || !(value instanceof String)) {
184: // No automatic conversion, use value directly.
185: LOGIC.configure(cfg, value);
186: continue;
187: }
188: String str = (String) value;
189: if (previous instanceof String) {
190: LOGIC.configure(cfg, value);
191: continue;
192: }
193:
194: javolution.text.TextFormat format = javolution.text.TextFormat.getInstance(previous.getClass());
195: if (format != null) {
196: LOGIC.configure(cfg, format.parse(javolution.Javolution.j2meToCharSeq(str)));
197: continue;
198: }
199:
200: javolution.context.LogContext.warning(javolution.text.Text.valueOf(
201: "No text format found for type "
202: + previous.getClass() + " (" + key + "), please register the text format" +
203: " using TextFormat.setInstance(Class, TextFormat) static method"));
204:
205: } catch (Exception ex) {
206: javolution.context.LogContext.warning(javolution.text.Text.valueOf("Cannot set property " + key
207: + "(" + ex.toString() + ")"));
208: }
209: }
210: }
211: /**/
212:
213: static final Logic LOGIC = new Logic() {
214: }; // To access configuration setting.
215:
216: /**
217: * Notifies this configurable that its runtime value has been changed.
218: * The default implementation does nothing.
219: */
220: protected void notifyChange() {
221: // Does nothing.
222: }
223:
224: /**
225: * This class represents a configuration logic capable of setting
226: * {@link Configurable} values. For example:[code]
227: * class MyApplication {
228: * private static final Configuration CONFIGURATION = new Configuration();
229: * public static void main(String[] args) {
230: * CONFIGURATION.run();
231: * ...
232: * }
233: * static class Configuration extends Configurable.Logic implements Runnable {
234: * public void run() {
235: * Properties properties = System.getProperties();
236: * // Properties could be loaded automatically if the property names
237: * // were the full names of the configurable fields (ref. Configurable.read(Map)).
238: * String concurrency = properties.get("MAXIMUM_CONCURRENCY");
239: * if (concurrency != null) {
240: * configure(ConcurrentContext.MAXIMUM_CONCURRENCY, TypeFormat.parseInt(concurrency));
241: * }
242: * ...
243: * }
244: * }
245: * }[/code]
246: * Applications can prevent configuration modifications through
247: * {@link SecurityContext}. For example:[code]
248: * public class MyPolicy extends SecurityContext {
249: * public boolean isModifiable (Configurable cfg) {
250: * return false;
251: * }
252: * }
253: * ...
254: * configure(SecurityContext.DEFAULT, MyPolicy.class); // Global setting.
255: * [/code]
256: */
257: public static abstract class Logic {
258:
259: /**
260: * Sets the run-time value of the specified configurable. If
261: * configurable value is different from the previous one, then
262: * {@link Configurable#notifyChange()} is called. This method
263: * raises <code>SecurityException</code> if the specified
264: * configurable is not {@link SecurityContext#isModifiable(Configurable)
265: * modifiable}.
266: *
267: * @param cfg the configurable being configurated.
268: * @param value the new run-time value.
269: * @throws SecurityException if the specified configurable cannot
270: * be modified.
271: */
272: protected final/*<T>*/void configure(Configurable/*<T>*/cfg,
273: Object/*{T}*/value) {
274: SecurityContext policy = (SecurityContext) SecurityContext
275: .getCurrent();
276: if (!policy.isModifiable(cfg))
277: throw new SecurityException(
278: "Configurable modification disallowed by SecurityContext");
279: Object previous = cfg._value;
280: cfg._value = value;
281: boolean change = (value == null) ? previous != null
282: : !value.equals(previous);
283: if (change) {
284: cfg.notifyChange();
285: }
286: }
287: }
288:
289: }
|