diff --git a/src/java/com/threerings/admin/server/ConfObjRegistry.java b/src/java/com/threerings/admin/server/ConfObjRegistry.java index 7f1c7d576..d67e7daf2 100644 --- a/src/java/com/threerings/admin/server/ConfObjRegistry.java +++ b/src/java/com/threerings/admin/server/ConfObjRegistry.java @@ -1,14 +1,24 @@ // -// $Id: ConfObjRegistry.java,v 1.1 2002/06/07 06:22:24 mdb Exp $ +// $Id: ConfObjRegistry.java,v 1.2 2002/09/23 01:34:32 mdb Exp $ package com.threerings.admin.server; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + import java.util.HashMap; import java.util.Iterator; +import com.samskivert.util.Config; +import com.samskivert.util.StringUtil; + import com.threerings.presents.dobj.AccessController; +import com.threerings.presents.dobj.AttributeChangeListener; +import com.threerings.presents.dobj.AttributeChangedEvent; import com.threerings.presents.dobj.DObject; +import com.threerings.admin.Log; + /** * Provides a registry of configuration distributed objects. Using * distributed object to store runtime configuration data can be @@ -16,12 +26,22 @@ import com.threerings.presents.dobj.DObject; * and update the running server's configuration parameters on the fly. * *
Users of the service are responsible for creating their own - * configuration objects which are then registered with this class which - * simply makes available a list of all registered configuration objects - * to the client. The service users will want to use {@link - * AccessController}s on their configuration distributed objects to - * prevent non-administrators from subscribing to or modifying the - * objects. + * configuration objects which are then registered via this class. The + * config object registry then performs a few functions: + * + *
Users of this service will want to use {@link AccessController}s on
+ * their configuration distributed objects to prevent non-administrators
+ * from subscribing to or modifying the objects.
*/
public class ConfObjRegistry
{
@@ -32,11 +52,14 @@ public class ConfObjRegistry
* generally hierarchical in nature (of the form
* system.subsystem), for example:
* yohoho.crew.
+ * @param path The the path in the persistent configuration repository
+ * (see {@link Config} for more info).
* @param object the object to be registered.
*/
- public static void registerObject (String key, DObject object)
+ public static void registerObject (String key, String path, DObject object)
{
- _confobjs.put(key, object);
+ // create a new config record for this object
+ _configs.put(key, new ConfObjRecord(path, object));
}
/**
@@ -45,7 +68,8 @@ public class ConfObjRegistry
*/
public static DObject getObject (String key)
{
- return (DObject)_confobjs.get(key);
+ ConfObjRecord record = (ConfObjRecord)_configs.get(key);
+ return (record == null) ? null : record.confObj;
}
/**
@@ -54,14 +78,159 @@ public class ConfObjRegistry
*/
public static String[] getKeys ()
{
- String[] keys = new String[_confobjs.size()];
- Iterator iter = _confobjs.keySet().iterator();
+ String[] keys = new String[_configs.size()];
+ Iterator iter = _configs.keySet().iterator();
for (int ii = 0; iter.hasNext(); ii++) {
keys[ii] = (String)iter.next();
}
return keys;
}
+ /**
+ * Contains all necessary info for a configuration object
+ * registration.
+ */
+ protected static class ConfObjRecord
+ implements AttributeChangeListener
+ {
+ public DObject confObj;
+ public Config config;
+
+ public ConfObjRecord (String path, DObject confObj)
+ {
+ this.config = new Config(path);
+ this.confObj = confObj;
+
+ // read in the initial configuration settings from the
+ // persistent configuration repository
+ Class cclass = confObj.getClass();
+ try {
+ Field[] fields = cclass.getFields();
+ for (int ii = 0; ii < fields.length; ii++) {
+ int mods = fields[ii].getModifiers();
+ if ((mods & Modifier.STATIC) != 0 ||
+ (mods & Modifier.PUBLIC) == 0 ||
+ (mods & Modifier.TRANSIENT) != 0) {
+ continue;
+ }
+ initField(fields[ii]);
+ }
+
+ // listen for attribute updates
+ confObj.addListener(this);
+
+ } catch (SecurityException se) {
+ Log.warning("Unable to reflect on " + cclass.getName() + ": " +
+ se + ". Refusing to monitor object.");
+ }
+ }
+
+ public void attributeChanged (AttributeChangedEvent event)
+ {
+ // mirror this configuration update to the on-disk config
+ String key = fieldToKey(event.getName());
+ Object value = event.getValue();
+
+ Log.info("Mirroring value [key=" + key + ", value=" + value + "].");
+
+ if (value instanceof Integer) {
+ config.setValue(key, ((Integer)value).intValue());
+ } else if (value instanceof Long) {
+ config.setValue(key, ((Long)value).longValue());
+ } else if (value instanceof Boolean) {
+ config.setValue(key, ((Boolean)value).booleanValue());
+ } else if (value instanceof String) {
+ config.setValue(key, (String)value);
+ } else if (value instanceof int[]) {
+ config.setValue(key, (int[])value);
+ } else if (value instanceof String[]) {
+ config.setValue(key, (String[])value);
+
+ } else {
+ Log.info("Unable to flush config object change " +
+ "[cobj=" + confObj.getClass().getName() +
+ ", key=" + key +
+ ", type=" + value.getClass().getName() +
+ ", value=" + value + "].");
+ }
+ }
+
+ /** Initializes a single field of a config distributed object from
+ * its corresponding value in the associated config repository. */
+ protected void initField (Field field)
+ {
+ String key = fieldToKey(field.getName());
+ Class type = field.getType();
+
+ try {
+ if (type.equals(Boolean.TYPE)) {
+ boolean defval = field.getBoolean(confObj);
+ field.setBoolean(confObj, config.getValue(key, defval));
+
+ } else if (type.equals(Integer.TYPE)) {
+ int defval = field.getInt(confObj);
+ field.setInt(confObj, config.getValue(key, defval));
+
+ } else if (type.equals(Long.TYPE)) {
+ long defval = field.getLong(confObj);
+ field.setLong(confObj, config.getValue(key, defval));
+
+ } else if (type.equals(String.class)) {
+ String defval = (String)field.get(confObj);
+ field.set(confObj, config.getValue(key, defval));
+
+ } else if (type.equals(INT_ARRAY_PROTO.getClass())) {
+ int[] defval = (int[])field.get(confObj);
+ field.set(confObj, config.getValue(key, defval));
+
+ } else if (type.equals(STRING_ARRAY_PROTO.getClass())) {
+ String[] defval = (String[])field.get(confObj);
+ field.set(confObj, config.getValue(key, defval));
+
+ } else {
+ Log.warning("Can't init field of unknown type " +
+ "[cobj=" + confObj.getClass().getName() +
+ ", key=" + key +
+ ", type=" + type.getName() + "].");
+ }
+
+ } catch (IllegalAccessException iae) {
+ Log.warning("Can't set field " +
+ "[cobj=" + confObj.getClass().getName() +
+ ", key=" + key + ", error=" + iae + "].");
+ }
+ }
+
+ /**
+ * Converts a mixed case field name (for example,
+ * millisPerTick) to a lower case, underscored key
+ * name (in this case, millis_per_tick).
+ */
+ protected String fieldToKey (String fieldName)
+ {
+ StringBuffer key = new StringBuffer();
+ int flength = fieldName.length();
+ boolean seenLower = false;
+ for (int ii = 0; ii < flength; ii++) {
+ char c = fieldName.charAt(ii);
+ if (Character.isUpperCase(c)) {
+ if (seenLower) {
+ key.append("_");
+ }
+ key.append(Character.toLowerCase(c));
+ seenLower = false;
+ } else {
+ key.append(c);
+ seenLower = true;
+ }
+ }
+ return key.toString();
+ }
+
+ protected static final int[] INT_ARRAY_PROTO = new int[0];
+ protected static final String[] STRING_ARRAY_PROTO = new String[0];
+ }
+
/** A mapping from identifying key to config object. */
- protected static HashMap _confobjs = new HashMap();
+ protected static HashMap _configs = new HashMap();
}