diff --git a/build.xml b/build.xml
index 149252d03..5e18e1338 100644
--- a/build.xml
+++ b/build.xml
@@ -205,6 +205,7 @@
+
diff --git a/src/java/com/threerings/admin/server/ConfigRegistry.java b/src/java/com/threerings/admin/server/ConfigRegistry.java
index a991b16f0..86d56c94d 100644
--- a/src/java/com/threerings/admin/server/ConfigRegistry.java
+++ b/src/java/com/threerings/admin/server/ConfigRegistry.java
@@ -21,8 +21,31 @@
package com.threerings.admin.server;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+
+import com.samskivert.io.ByteArrayOutInputStream;
+import com.samskivert.util.StringUtil;
+
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+import com.threerings.io.Streamable;
+
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.presents.dobj.DSet;
+import com.threerings.presents.dobj.EntryAddedEvent;
+import com.threerings.presents.dobj.EntryRemovedEvent;
+import com.threerings.presents.dobj.EntryUpdatedEvent;
+import com.threerings.presents.dobj.ObjectAccessException;
+import com.threerings.presents.dobj.SetListener;
+
+import com.threerings.admin.Log;
/**
* Provides a registry of configuration distributed objects. Using distributed
@@ -57,20 +80,286 @@ public abstract class ConfigRegistry
* hierarchical in nature (of the form system.subsystem), for
* example: yohoho.crew.
* @param path The the path in the persistent configuration repository.
+ * This may mean something to the underlying persistent store, for example
+ * in the preferences backed implementation it defines the path to the
+ * preferences node in the package hierarchy.
* @param object the object to be registered.
*/
- public abstract void registerObject (
- String key, String path, DObject object);
+ public void registerObject (String key, String path, DObject object)
+ {
+ ObjectRecord record = createObjectRecord(path, object);
+ record.init();
+ _configs.put(key, record);
+ }
/**
* Returns the config object mapped to the specified key, or null if none
* exists for that key.
*/
- public abstract DObject getObject (String key);
+ public DObject getObject (String key)
+ {
+ ObjectRecord record = (ObjectRecord)_configs.get(key);
+ return (record == null) ? null : record.object;
+ }
/**
* Returns an array containing the keys of all registered configuration
* objects.
*/
- public abstract String[] getKeys ();
+ public String[] getKeys ()
+ {
+ return (String[])_configs.keySet().toArray(new String[_configs.size()]);
+ }
+
+ /**
+ * Creates an object record derivation that will handle the management of
+ * the specified object.
+ */
+ protected abstract ObjectRecord createObjectRecord (
+ String path, DObject object);
+
+ /**
+ * Contains all necessary info for a configuration object registration.
+ */
+ protected abstract class ObjectRecord
+ implements AttributeChangeListener, SetListener
+ {
+ public DObject object;
+
+ public ObjectRecord (DObject object)
+ {
+ this.object = object;
+ }
+
+ public void init ()
+ {
+ // read in the initial configuration settings from the persistent
+ // configuration repository
+ Class cclass = object.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
+ object.addListener(this);
+
+ } catch (SecurityException se) {
+ Log.warning("Unable to reflect on " + cclass.getName() + ": " +
+ se + ". Refusing to monitor object.");
+ }
+ }
+
+ public void entryAdded (EntryAddedEvent event)
+ {
+ serializeAttribute(event.getName());
+ }
+
+ public void entryUpdated (EntryUpdatedEvent event)
+ {
+ serializeAttribute(event.getName());
+ }
+
+ public void entryRemoved (EntryRemovedEvent event)
+ {
+ serializeAttribute(event.getName());
+ }
+
+ public void attributeChanged (AttributeChangedEvent event)
+ {
+ // mirror this configuration update to the persistent config
+ String key = StringUtil.unStudlyName(event.getName());
+ Object value = event.getValue();
+ if (value instanceof Boolean) {
+ setValue(key, ((Boolean)value).booleanValue());
+ } else if (value instanceof Short) {
+ setValue(key, ((Short)value).shortValue());
+ } else if (value instanceof Integer) {
+ setValue(key, ((Integer)value).intValue());
+ } else if (value instanceof Long) {
+ setValue(key, ((Long)value).longValue());
+ } else if (value instanceof Float) {
+ setValue(key, ((Float)value).floatValue());
+ } else if (value instanceof String) {
+ setValue(key, (String)value);
+ } else if (value instanceof float[]) {
+ setValue(key, (float[])value);
+ } else if (value instanceof int[]) {
+ setValue(key, (int[])value);
+ } else if (value instanceof String[]) {
+ setValue(key, (String[])value);
+ } else if (value instanceof long[]) {
+ setValue(key, (long[]) value);
+ } else if (value instanceof DSet) {
+ serializeAttribute(event.getName());
+ } else {
+ Log.info("Unable to flush config object change " +
+ "[cobj=" + object.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 = StringUtil.unStudlyName(field.getName());
+ Class type = field.getType();
+
+ try {
+ if (type.equals(Boolean.TYPE)) {
+ boolean defval = field.getBoolean(object);
+ field.setBoolean(object, getValue(key, defval));
+
+ } else if (type.equals(Short.TYPE)) {
+ short defval = field.getShort(object);
+ field.setShort(object, getValue(key, defval));
+
+ } else if (type.equals(Integer.TYPE)) {
+ int defval = field.getInt(object);
+ field.setInt(object, getValue(key, defval));
+
+ } else if (type.equals(Long.TYPE)) {
+ long defval = field.getLong(object);
+ field.setLong(object, getValue(key, defval));
+
+ } else if (type.equals(Float.TYPE)) {
+ float defval = field.getFloat(object);
+ field.setFloat(object, getValue(key, defval));
+
+ } else if (type.equals(String.class)) {
+ String defval = (String)field.get(object);
+ field.set(object, getValue(key, defval));
+
+ } else if (type.equals(INT_ARRAY_PROTO.getClass())) {
+ int[] defval = (int[])field.get(object);
+ field.set(object, getValue(key, defval));
+
+ } else if (type.equals(FLOAT_ARRAY_PROTO.getClass())) {
+ float[] defval = (float[])field.get(object);
+ field.set(object, getValue(key, defval));
+
+ } else if (type.equals(STRING_ARRAY_PROTO.getClass())) {
+ String[] defval = (String[])field.get(object);
+ field.set(object, getValue(key, defval));
+
+ } else if (type.equals(LONG_ARRAY_PROTO.getClass())) {
+ long[] defval = (long[])field.get(object);
+ field.set(object, getValue(key, defval));
+
+ } else if (Streamable.class.isAssignableFrom(type)) {
+ // don't freak out if the conf is blank.
+ String value = getValue(key, "");
+ if (StringUtil.isBlank(value)) {
+ return;
+ }
+
+ try {
+ ByteArrayInputStream bin = new ByteArrayInputStream(
+ StringUtil.unhexlate(value));
+ ObjectInputStream oin = new ObjectInputStream(bin);
+ field.set(object, oin.readObject());
+ } catch (Exception e) {
+ Log.warning("Failure decoding config value [type=" +
+ type + ", field=" + field + ", exception=" +
+ e + "].");
+ }
+
+ } else {
+ Log.warning("Can't init field of unknown type " +
+ "[cobj=" + object.getClass().getName() +
+ ", key=" + key +
+ ", type=" + type.getName() + "].");
+ }
+
+ } catch (IllegalAccessException iae) {
+ Log.warning("Can't set field " +
+ "[cobj=" + object.getClass().getName() +
+ ", key=" + key + ", error=" + iae + "].");
+ }
+ }
+
+ /**
+ * Get the specified attribute from the configuration object, and
+ * serialize it.
+ */
+ protected void serializeAttribute (String attributeName)
+ {
+ String key = StringUtil.unStudlyName(attributeName);
+ Object value;
+ try {
+ value = object.getAttribute(attributeName);
+ } catch (ObjectAccessException oae) {
+ Log.warning("Exception getting field [name=" + attributeName +
+ "exception=" + oae + "].");
+ return;
+ }
+
+ if (value instanceof Streamable) {
+ serialize(key, value);
+ } else {
+ Log.info("Unable to flush config object change " +
+ "[cobj=" + object.getClass().getName() +
+ ", key=" + key +
+ ", type=" + value.getClass().getName() +
+ ", value=" + value + "].");
+ }
+ }
+
+ /**
+ * Save the specified object as serialized data associated with
+ * the specified key.
+ */
+ protected void serialize (String key, Object value)
+ {
+ ByteArrayOutInputStream out = new ByteArrayOutInputStream();
+ ObjectOutputStream oout = new ObjectOutputStream(out);
+ try {
+ oout.writeObject(value);
+ oout.flush();
+ setValue(key, StringUtil.hexlate(out.toByteArray()));
+ } catch (IOException ioe) {
+ Log.info("Error serializing value " + value);
+ }
+ }
+
+ protected abstract boolean getValue (String field, boolean defval);
+ protected abstract short getValue (String field, short defval);
+ protected abstract int getValue (String field, int defval);
+ protected abstract long getValue (String field, long defval);
+ protected abstract float getValue (String field, float defval);
+ protected abstract String getValue (String field, String defval);
+ protected abstract int[] getValue (String field, int[] defval);
+ protected abstract float[] getValue (String field, float[] defval);
+ protected abstract long[] getValue (String field, long[] defval);
+ protected abstract String[] getValue (String field, String[] defval);
+
+ protected abstract void setValue (String field, boolean value);
+ protected abstract void setValue (String field, short value);
+ protected abstract void setValue (String field, int value);
+ protected abstract void setValue (String field, long value);
+ protected abstract void setValue (String field, float value);
+ protected abstract void setValue (String field, String value);
+ protected abstract void setValue (String field, int[] value);
+ protected abstract void setValue (String field, float[] value);
+ protected abstract void setValue (String field, long[] value);
+ protected abstract void setValue (String field, String[] value);
+ }
+
+ /** A mapping from identifying key to config object. */
+ protected HashMap _configs = new HashMap();
+
+ protected static final int[] INT_ARRAY_PROTO = new int[0];
+ protected static final float[] FLOAT_ARRAY_PROTO = new float[0];
+ protected static final String[] STRING_ARRAY_PROTO = new String[0];
+ protected static final long[] LONG_ARRAY_PROTO = new long[0];
}
diff --git a/src/java/com/threerings/admin/server/DatabaseConfigRegistry.java b/src/java/com/threerings/admin/server/DatabaseConfigRegistry.java
new file mode 100644
index 000000000..3eb04345c
--- /dev/null
+++ b/src/java/com/threerings/admin/server/DatabaseConfigRegistry.java
@@ -0,0 +1,269 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/narya/
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.admin.server;
+
+import java.util.HashMap;
+
+import com.samskivert.io.PersistenceException;
+import com.samskivert.jdbc.ConnectionProvider;
+import com.samskivert.util.Invoker;
+import com.samskivert.util.StringUtil;
+
+import com.threerings.presents.dobj.DObject;
+
+import com.threerings.admin.Log;
+import com.threerings.admin.server.persist.ConfigRepository;
+
+/**
+ * Implements the {@link ConfigRegistry} using a JDBC database as a persistent
+ * store for the configuration information.
+ */
+public class DatabaseConfigRegistry extends ConfigRegistry
+{
+ /**
+ * Creates a configuration registry and prepares it for operation.
+ *
+ * @param conprov will provide access to our JDBC database.
+ * @param invoker this will be used to perform all database activity so as
+ * to avoid blocking the distributed object thread.
+ */
+ public DatabaseConfigRegistry (ConnectionProvider conprov, Invoker invoker)
+ throws PersistenceException
+ {
+ _repo = new ConfigRepository(conprov);
+ _invoker = invoker;
+ }
+
+ // documentation inherited
+ protected ObjectRecord createObjectRecord (String path, DObject object)
+ {
+ return null;
+ }
+
+ protected class DatabaseObjectRecord extends ObjectRecord
+ {
+ public DatabaseObjectRecord (String path, DObject object)
+ {
+ super(object);
+ _path = path;
+ }
+
+ public void init ()
+ {
+ // load up our persistent data and then allow the normal
+ // initialization process to take place
+ _invoker.postUnit(new Invoker.Unit() {
+ public boolean invoke () {
+ try {
+ _data = _repo.loadConfig(_path);
+ } catch (PersistenceException pe) {
+ Log.warning("Failed to load object configuration " +
+ "[path=" + _path + "].");
+ Log.logStackTrace(pe);
+ _data = new HashMap();
+ }
+ return true;
+ }
+
+ public void handleResult () {
+ DatabaseObjectRecord.super.init();
+ }
+ });
+ }
+
+ protected boolean getValue (String field, boolean defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ return Boolean.parseBoolean(value);
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected short getValue (String field, short defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ return Short.parseShort(value);
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected int getValue (String field, int defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ return Integer.parseInt(value);
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected long getValue (String field, long defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ return Long.parseLong(value);
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected float getValue (String field, float defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ return Float.parseFloat(value);
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected String getValue (String field, String defval) {
+ String value = (String)_data.get(field);
+ return (value == null) ? defval : value;
+ }
+
+ protected int[] getValue (String field, int[] defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ int[] avalue = StringUtil.parseIntArray(value);
+ if (avalue != null) {
+ return avalue;
+ }
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected float[] getValue (String field, float[] defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ float[] avalue = StringUtil.parseFloatArray(value);
+ if (avalue != null) {
+ return avalue;
+ }
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected long[] getValue (String field, long[] defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ long[] avalue = StringUtil.parseLongArray(value);
+ if (avalue != null) {
+ return avalue;
+ }
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected String[] getValue (String field, String[] defval) {
+ String value = (String)_data.get(field);
+ try {
+ if (value != null) {
+ return StringUtil.parseStringArray(value);
+ }
+ } catch (Exception e) {
+ // ignore bogus values and return the default
+ }
+ return defval;
+ }
+
+ protected void setValue (String field, boolean value) {
+ setAndFlush(field, String.valueOf(value));
+ }
+ protected void setValue (String field, short value) {
+ setAndFlush(field, String.valueOf(value));
+ }
+ protected void setValue (String field, int value) {
+ setAndFlush(field, String.valueOf(value));
+ }
+ protected void setValue (String field, long value) {
+ setAndFlush(field, String.valueOf(value));
+ }
+ protected void setValue (String field, float value) {
+ setAndFlush(field, String.valueOf(value));
+ }
+ protected void setValue (String field, String value) {
+ setAndFlush(field, value);
+ }
+ protected void setValue (String field, int[] value) {
+ setAndFlush(field, StringUtil.toString(value, "", ""));
+ }
+ protected void setValue (String field, float[] value) {
+ setAndFlush(field, StringUtil.toString(value, "", ""));
+ }
+ protected void setValue (String field, long[] value) {
+ setAndFlush(field, StringUtil.toString(value, "", ""));
+ }
+ protected void setValue (String field, String[] value) {
+ setAndFlush(field, StringUtil.joinEscaped(value));
+ }
+
+ protected void setAndFlush (final String field, final String value) {
+ _data.put(field, value);
+ _invoker.postUnit(new Invoker.Unit() {
+ public boolean invoke () {
+ try {
+ _repo.updateConfig(_path, field, value);
+ } catch (PersistenceException pe) {
+ Log.warning("Failed to update object configuration " +
+ "[path=" + _path + ", field=" + field +
+ ", value=" + value + "].");
+ Log.logStackTrace(pe);
+ }
+ return false;
+ }
+ });
+ }
+
+ protected String _path;
+ protected HashMap _data;
+ }
+
+ protected ConfigRepository _repo;
+ protected Invoker _invoker;
+}
diff --git a/src/java/com/threerings/admin/server/PrefsConfigRegistry.java b/src/java/com/threerings/admin/server/PrefsConfigRegistry.java
index 84eb6e553..87f2e4c81 100644
--- a/src/java/com/threerings/admin/server/PrefsConfigRegistry.java
+++ b/src/java/com/threerings/admin/server/PrefsConfigRegistry.java
@@ -21,34 +21,8 @@
package com.threerings.admin.server;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-import com.samskivert.io.ByteArrayOutInputStream;
import com.samskivert.util.Config;
-import com.samskivert.util.StringUtil;
-
-import com.threerings.io.ObjectInputStream;
-import com.threerings.io.ObjectOutputStream;
-import com.threerings.io.Streamable;
-
-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.presents.dobj.DSet;
-import com.threerings.presents.dobj.EntryAddedEvent;
-import com.threerings.presents.dobj.EntryRemovedEvent;
-import com.threerings.presents.dobj.EntryUpdatedEvent;
-import com.threerings.presents.dobj.SetListener;
-
-import com.threerings.admin.Log;
-import com.threerings.presents.dobj.ObjectAccessException;
/**
* Implements the {@link ConfigRegistry} using the Java preferences system as a
@@ -58,280 +32,81 @@ import com.threerings.presents.dobj.ObjectAccessException;
public class PrefsConfigRegistry extends ConfigRegistry
{
// documentation inherited
- public void registerObject (String key, String path, DObject object)
+ protected ObjectRecord createObjectRecord (String path, DObject object)
{
- // create a new config record for this object
- _configs.put(key, new ConfObjRecord(path, object));
+ return null;
}
- // documentation inherited
- public DObject getObject (String key)
+ protected class PrefsObjectRecord extends ObjectRecord
{
- ConfObjRecord record = (ConfObjRecord)_configs.get(key);
- return (record == null) ? null : record.confObj;
- }
-
- // documentation inherited
- public String[] getKeys ()
- {
- 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, SetListener
- {
- public DObject confObj;
public Config config;
- public ConfObjRecord (String path, DObject confObj)
+ public PrefsObjectRecord (String path, DObject object)
{
+ super(object);
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.");
- }
}
- // documentation inherited
- public void entryAdded (EntryAddedEvent event)
- {
- serializeAttribute(event.getName());
+ protected boolean getValue (String field, boolean defval) {
+ return config.getValue(field, defval);
+ }
+ protected short getValue (String field, short defval) {
+ return (short)config.getValue(field, defval);
+ }
+ protected int getValue (String field, int defval) {
+ return config.getValue(field, defval);
+ }
+ protected long getValue (String field, long defval) {
+ return config.getValue(field, defval);
+ }
+ protected float getValue (String field, float defval) {
+ return config.getValue(field, defval);
+ }
+ protected String getValue (String field, String defval) {
+ return config.getValue(field, defval);
+ }
+ protected int[] getValue (String field, int[] defval) {
+ return config.getValue(field, defval);
+ }
+ protected float[] getValue (String field, float[] defval) {
+ return config.getValue(field, defval);
+ }
+ protected long[] getValue (String field, long[] defval) {
+ return config.getValue(field, defval);
+ }
+ protected String[] getValue (String field, String[] defval) {
+ return config.getValue(field, defval);
}
- // documentation inherited
- public void entryUpdated (EntryUpdatedEvent event)
- {
- serializeAttribute(event.getName());
+ protected void setValue (String field, boolean value) {
+ config.setValue(field, value);
}
-
- // documentation inherited
- public void entryRemoved (EntryRemovedEvent event)
- {
- serializeAttribute(event.getName());
+ protected void setValue (String field, short value) {
+ config.setValue(field, value);
}
-
- public void attributeChanged (AttributeChangedEvent event)
- {
- // mirror this configuration update to the on-disk config
- String key = fieldToKey(event.getName());
- Object value = event.getValue();
-
- if (value instanceof Boolean) {
- config.setValue(key, ((Boolean)value).booleanValue());
- } else if (value instanceof Short) {
- config.setValue(key, ((Short)value).shortValue());
- } else 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 Float) {
- config.setValue(key, ((Float)value).floatValue());
- } else if (value instanceof String) {
- config.setValue(key, (String)value);
- } else if (value instanceof float[]) {
- config.setValue(key, (float[])value);
- } else if (value instanceof int[]) {
- config.setValue(key, (int[])value);
- } else if (value instanceof String[]) {
- config.setValue(key, (String[])value);
- } else if (value instanceof long[]) {
- config.setValue(key, (long[]) value);
- } else if (value instanceof DSet) {
- serializeAttribute(event.getName());
- } else {
- Log.info("Unable to flush config object change " +
- "[cobj=" + confObj.getClass().getName() +
- ", key=" + key +
- ", type=" + value.getClass().getName() +
- ", value=" + value + "].");
- }
+ protected void setValue (String field, int value) {
+ config.setValue(field, 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(Short.TYPE)) {
- short defval = field.getShort(confObj);
- defval = (short)config.getValue(key, defval);
- field.setShort(confObj, 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(Float.TYPE)) {
- float defval = field.getFloat(confObj);
- field.setFloat(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(FLOAT_ARRAY_PROTO.getClass())) {
- float[] defval = (float[])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 if (type.equals(LONG_ARRAY_PROTO.getClass())) {
- long[] defval = (long[])field.get(confObj);
- field.set(confObj, config.getValue(key, defval));
-
- } else if (Streamable.class.isAssignableFrom(type)) {
- // don't freak out if the conf is blank.
- String value = config.getValue(key, "");
- if (StringUtil.isBlank(value)) {
- return;
- }
-
- try {
- ByteArrayInputStream bin = new ByteArrayInputStream(
- StringUtil.unhexlate(value));
- ObjectInputStream oin = new ObjectInputStream(bin);
- field.set(confObj, oin.readObject());
- } catch (Exception e) {
- Log.warning("Failure decoding config value [type=" +
- type + ", field=" + field + ", exception=" +
- e + "].");
- }
-
- } 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 + "].");
- }
+ protected void setValue (String field, long value) {
+ config.setValue(field, value);
}
-
- /**
- * 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 void setValue (String field, float value) {
+ config.setValue(field, value);
}
-
- /**
- * Get the specified attribute from the configuration object, and
- * serialize it.
- */
- protected void serializeAttribute (String attributeName)
- {
- String key = fieldToKey(attributeName);
- Object value;
- try {
- value = confObj.getAttribute(attributeName);
- } catch (ObjectAccessException oae) {
- Log.warning("Exception getting field [name=" + attributeName +
- "exception=" + oae + "].");
- return;
- }
-
- if (value instanceof Streamable) {
- serialize(key, value);
- } else {
- Log.info("Unable to flush config object change " +
- "[cobj=" + confObj.getClass().getName() +
- ", key=" + key +
- ", type=" + value.getClass().getName() +
- ", value=" + value + "].");
- }
+ protected void setValue (String field, String value) {
+ config.setValue(field, value);
}
-
- /**
- * Save the specified object as serialized data associated with
- * the specified key.
- */
- protected void serialize (String key, Object value)
- {
- ByteArrayOutInputStream out = new ByteArrayOutInputStream();
- ObjectOutputStream oout = new ObjectOutputStream(out);
- try {
- oout.writeObject(value);
- oout.flush();
- config.setValue(key, StringUtil.hexlate(out.toByteArray()));
- } catch (IOException ioe) {
- Log.info("Error serializing value " + value);
- }
+ protected void setValue (String field, int[] value) {
+ config.setValue(field, value);
+ }
+ protected void setValue (String field, float[] value) {
+ config.setValue(field, value);
+ }
+ protected void setValue (String field, long[] value) {
+ config.setValue(field, value);
+ }
+ protected void setValue (String field, String[] value) {
+ config.setValue(field, value);
}
-
- protected static final int[] INT_ARRAY_PROTO = new int[0];
- protected static final float[] FLOAT_ARRAY_PROTO = new float[0];
- protected static final String[] STRING_ARRAY_PROTO = new String[0];
- protected static final long[] LONG_ARRAY_PROTO = new long[0];
}
-
- /** A mapping from identifying key to config object. */
- protected HashMap _configs = new HashMap();
}
diff --git a/src/java/com/threerings/admin/server/persist/ConfigDatum.java b/src/java/com/threerings/admin/server/persist/ConfigDatum.java
new file mode 100644
index 000000000..5eb930a88
--- /dev/null
+++ b/src/java/com/threerings/admin/server/persist/ConfigDatum.java
@@ -0,0 +1,36 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/narya/
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.admin.server.persist;
+
+/**
+ * Contains a single datum of configuration information.
+ */
+public class ConfigDatum
+{
+ public String object;
+ public String field;
+ public String value;
+
+ public String toString () {
+ return object + "." + field + "=" + value + "]";
+ }
+}
diff --git a/src/java/com/threerings/admin/server/persist/ConfigRepository.java b/src/java/com/threerings/admin/server/persist/ConfigRepository.java
new file mode 100644
index 000000000..4a6064d67
--- /dev/null
+++ b/src/java/com/threerings/admin/server/persist/ConfigRepository.java
@@ -0,0 +1,107 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
+// http://www.threerings.net/code/narya/
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.admin.server.persist;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.samskivert.io.PersistenceException;
+import com.samskivert.jdbc.ConnectionProvider;
+import com.samskivert.jdbc.DatabaseLiaison;
+import com.samskivert.jdbc.JDBCUtil;
+import com.samskivert.jdbc.JORARepository;
+import com.samskivert.jdbc.jora.Session;
+import com.samskivert.jdbc.jora.Table;
+
+import com.threerings.admin.Log;
+
+/**
+ * Stores configuration information in a database table.
+ */
+public class ConfigRepository extends JORARepository
+{
+ /** The identifier used when establishing a database connection. */
+ public static final String CONFIG_DB_IDENT = "configdb";
+
+ /**
+ * Constructs a new config repository with the specified connection
+ * provider.
+ */
+ public ConfigRepository (ConnectionProvider conprov)
+ throws PersistenceException
+ {
+ super(conprov, CONFIG_DB_IDENT);
+ }
+
+ /**
+ * Loads up the configuration data for the specified object.
+ *
+ * @return a map containing field/value pairs for all stored configuration
+ * data.
+ */
+ public HashMap loadConfig (String object)
+ throws PersistenceException
+ {
+ ArrayList list = loadAll(
+ _ctable, "where OBJECT = " + JDBCUtil.escape(object));
+ HashMap data = new HashMap();
+ for (int ii = 0, ll = list.size(); ii < ll; ii++) {
+ ConfigDatum datum = (ConfigDatum)list.get(ii);
+ data.put(datum.field, datum.value);
+ }
+ return data;
+ }
+
+ /**
+ * Updates the specified configuration datum.
+ */
+ public void updateConfig (String object, String field, String value)
+ throws PersistenceException
+ {
+ ConfigDatum datum = new ConfigDatum();
+ datum.object = object;
+ datum.field = field;
+ datum.value = value;
+ store(_ctable, datum);
+ }
+
+ // documentation inherited
+ protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
+ throws SQLException, PersistenceException
+ {
+ if (!JDBCUtil.tableExists(conn, "CONFIG")) {
+ Log.info("Creating admin.config schema...");
+ JDBCUtil.loadSchema(conn, "admin/config.sql");
+ }
+ }
+
+ // documentation inherited
+ protected void createTables (Session session)
+ {
+ _ctable = new Table(
+ ConfigDatum.class.getName(), "CONFIG", session, "OBJECT", true);
+ }
+
+ protected Table _ctable;
+}
diff --git a/src/sql/admin/config.sql b/src/sql/admin/config.sql
new file mode 100644
index 000000000..4334dbb6b
--- /dev/null
+++ b/src/sql/admin/config.sql
@@ -0,0 +1,25 @@
+/**
+ * $Id$
+ *
+ * Schema for the Admin services configuration table.
+ */
+
+drop table if exists CONFIG;
+
+/**
+ * Contains all registered configuration data.
+ */
+CREATE TABLE CONFIG
+(
+ /** The configuration object with which this datum is associated. */
+ OBJECT VARCHAR(255) NOT NULL,
+
+ /** The configuration object field name. */
+ FIELD VARCHAR(255) NOT NULL,
+
+ /** The value of the object field. */
+ VALUE TEXT NOT NULL,
+
+ /** Defines our table keys. */
+ PRIMARY KEY (OBJECT, FIELD)
+);