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) +);