Files
narya/src/java/com/threerings/admin/server/ConfigRegistry.java
T
Michael Bayne f90a39207f Created a peer-aware DatabaseConfigRegistry that broadcasts changed runtime
configuration information to other peers so that they all remain in sync.

Also nixed constructor arguments to the peer manager so that it can be created
at server construct time like all other managers and be available for the
twisty maze of inter-registration that takes place during the manager init
process.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4814 542714f4-19e9-0310-aa3c-eee0fc999fb1
2007-08-16 20:53:17 +00:00

378 lines
15 KiB
Java

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 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.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import javax.swing.JEditorPane;
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.ElementUpdateListener;
import com.threerings.presents.dobj.ElementUpdatedEvent;
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 object to store
* runtime configuration data can be exceptionally useful in that clients (with admin privileges)
* can view and update the running server's configuration parameters on the fly.
*
* <p> Users of the service are responsible for creating their own configuration objects which are
* then registered via this class. The config object registry then performs a few functions:
*
* <ul>
* <li> It populates the config object with values from the persistent configuration information.
* <li> It mirrors object updates out to the persistent configuration repository.
* <li> It makes the set of registered objects available for inspection and modification via the
* admin client interface.
* </ul>
*
* <p> 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 abstract class ConfigRegistry
{
/**
* Registers the supplied configuration object with the system.
*
* @param key a string that identifies this object. These are generally hierarchical in nature
* (of the form <code>system.subsystem</code>), for example: <code>yohoho.crew</code>.
* @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 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 DObject getObject (String key)
{
ObjectRecord record = _configs.get(key);
return (record == null) ? null : record.object;
}
/**
* Returns an array containing the keys of all registered configuration objects.
*/
public String[] getKeys ()
{
return _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, ElementUpdateListener
{
public DObject object;
public ObjectRecord (DObject object)
{
this.object = object;
}
public void init ()
{
// read in the initial configuration settings from the persistent config 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.");
}
}
// from SetListener
public void entryAdded (EntryAddedEvent event)
{
serializeAttribute(event.getName());
}
// from SetListener
public void entryUpdated (EntryUpdatedEvent event)
{
serializeAttribute(event.getName());
}
// from SetListener
public void entryRemoved (EntryRemovedEvent event)
{
serializeAttribute(event.getName());
}
// from ElementUpdateListener
public void elementUpdated (ElementUpdatedEvent event)
{
Object value;
try {
value = object.getAttribute(event.getName());
} catch (ObjectAccessException oae) {
Log.warning("Exception getting field [name=" + event.getName() +
", exception=" + oae + "].");
return;
}
updateValue(event.getName(), value);
}
// from AttributeChangeListener
public void attributeChanged (AttributeChangedEvent event)
{
// mirror this configuration update to the persistent config
Object value = event.getValue();
if (value instanceof DSet) {
serializeAttribute(event.getName());
} else {
updateValue(event.getName(), value);
}
}
protected void updateValue (String name, Object value)
{
String key = nameToKey(name);
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 {
Log.info("Unable to flush config obj 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 = nameToKey(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[].class)) {
int[] defval = (int[])field.get(object);
field.set(object, getValue(key, defval));
} else if (type.equals(float[].class)) {
float[] defval = (float[])field.get(object);
field.set(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(long[].class)) {
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 = nameToKey(attributeName);
Object value;
try {
value = object.getAttribute(attributeName);
} catch (ObjectAccessException oae) {
Log.warning("Exception getting field [name=" + attributeName +
", error=" + oae + "].");
return;
}
if (value instanceof Streamable) {
serialize(attributeName, key, value);
} else {
Log.info("Unable to flush config obj 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 name, 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);
}
}
/**
* Converts a config object field name (someConfigMember) to a configuration key
* (some_config_member).
*/
protected String nameToKey (String attributeName)
{
return StringUtil.unStudlyName(attributeName).toLowerCase();
}
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<String,ObjectRecord> _configs = new HashMap<String,ObjectRecord>();
}