Beginnings of runtime control panel stuff.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1428 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2002-06-07 06:22:24 +00:00
parent b81ab2f176
commit 6202c1a463
8 changed files with 506 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
//
// $Id: Log.java,v 1.1 2002/06/07 06:22:24 mdb Exp $
package com.threerings.admin;
/**
* A placeholder class that contains a reference to the log object used by
* the admin services.
*/
public class Log
{
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("admin");
/** Convenience function. */
public static void debug (String message)
{
log.debug(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
@@ -0,0 +1,34 @@
//
// $Id: AdminService.java,v 1.1 2002/06/07 06:22:24 mdb Exp $
package com.threerings.admin.client;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationDirector;
import com.threerings.admin.Log;
import com.threerings.admin.data.AdminCodes;
/**
* Handles the client side of the admin invocation services.
*/
public class AdminService
implements AdminCodes
{
/**
* Requests the list of config objects. This will result in a call to
*
* <pre>
* public void handleConfigInfoResponse (int invid, String[] keys,
* int[] oids)
* </pre>
*
* on the response target.
*/
public static void getConfigInfo (Client client, Object rsptarget)
{
InvocationDirector invdir = client.getInvocationDirector();
invdir.invoke(MODULE_NAME, GET_CONFIG_INFO_REQUEST, null, rsptarget);
Log.debug("Sent getConfigInfo request.");
}
}
@@ -0,0 +1,79 @@
//
// $Id: ConfigEditorPanel.java,v 1.1 2002/06/07 06:22:24 mdb Exp $
package com.threerings.admin.client;
import javax.swing.JTabbedPane;
import javax.swing.event.AncestorEvent;
import com.samskivert.swing.event.AncestorAdapter;
import com.samskivert.util.StringUtil;
import com.threerings.media.SafeScrollPane;
import com.threerings.presents.util.PresentsContext;
import com.threerings.admin.Log;
/**
* Fetches a list of the configuration objects in use by the server and
* displays their fields in a tree widget to be viewed and edited.
*/
public class ConfigEditorPanel extends JTabbedPane
{
/**
* Constructs an editor panel which will use the supplied context to
* access the distributed object services.
*/
public ConfigEditorPanel (PresentsContext ctx)
{
_ctx = ctx;
// when we're hidden, we want to clear out our subscriptions
addAncestorListener(new AncestorAdapter () {
public void ancestorRemoved (AncestorEvent event) {
cleanup();
}
});
// if we have no children, ship off a getConfigInfo request to
// find out what config objects are available for editing
if (getComponentCount() == 0) {
Log.info("Sending get config info.");
AdminService.getConfigInfo(_ctx.getClient(), this);
}
}
/**
* Called in response to our getConfigInfo server-side service
* request.
*/
public void handleConfigInfo (int invid, String[] keys, int[] oids)
{
Log.info("Got config info: " + StringUtil.toString(keys));
// create object editor panels for each of the categories
for (int ii = 0; ii < keys.length; ii++) {
ObjectEditorPanel panel =
new ObjectEditorPanel(_ctx, keys[ii], oids[ii]);
SafeScrollPane scrolly = new SafeScrollPane(panel);
addTab(keys[ii], scrolly);
}
}
/**
* Called when the panel is hidden; this instructs all of our object
* editors to clear out their subscriptions.
*/
protected void cleanup ()
{
int ccount = getComponentCount();
for (int ii = 0; ii < ccount; ii++) {
SafeScrollPane scrolly = (SafeScrollPane)getComponent(ii);
ObjectEditorPanel opanel = (ObjectEditorPanel)
scrolly.getViewport().getView();
opanel.cleanup();
}
}
/** Our client context. */
protected PresentsContext _ctx;
}
@@ -0,0 +1,97 @@
//
// $Id: FieldEditor.java,v 1.1 2002/06/07 06:22:24 mdb Exp $
package com.threerings.admin.client;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Field;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import com.samskivert.swing.HGroupLayout;
import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.util.PresentsContext;
/**
* Used to display and edit a particular distributed object field.
*/
public class FieldEditor extends JPanel
implements AttributeChangeListener, ActionListener
{
public FieldEditor (PresentsContext ctx, Field field, DObject object)
{
_ctx = ctx;
_field = field;
_object = object;
// create our interface elements
setLayout(new HGroupLayout(HGroupLayout.STRETCH));
// set up our default border
updateBorder(false);
// a label to display the field name
add(_label = new JLabel(_field.getName()), HGroupLayout.FIXED);
// and a text entry field to display the field value
String value = "";
try {
value = String.valueOf(_field.get(_object));
} catch (IllegalAccessException iae) {
value = "<error>";
}
add(_value = new JTextField(value));
_value.addActionListener(this);
}
// documentation inherited from interface
public void attributeChanged (AttributeChangedEvent event)
{
_value.setText(String.valueOf(event.getValue()));
updateBorder(false);
}
// documentation inherited from interface
public void actionPerformed (ActionEvent event)
{
if (_field.getType().equals(Integer.class) ||
_field.getType().equals(Integer.TYPE)) {
try {
Integer value = new Integer(_value.getText());
AttributeChangedEvent ace = new AttributeChangedEvent(
_object.getOid(), _field.getName(), value);
_ctx.getDObjectManager().postEvent(ace);
} catch (NumberFormatException nfe) {
updateBorder(true);
}
}
}
/**
* Sets the appropriate border on this field editor based on whether
* or not the field is modified.
*/
protected void updateBorder (boolean modified)
{
if (modified) {
setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.red));
} else {
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
}
}
protected PresentsContext _ctx;
protected Field _field;
protected DObject _object;
protected JLabel _label;
protected JTextField _value;
}
@@ -0,0 +1,119 @@
//
// $Id: ObjectEditorPanel.java,v 1.1 2002/06/07 06:22:24 mdb Exp $
package com.threerings.admin.client;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import javax.swing.BorderFactory;
import javax.swing.event.AncestorEvent;
import com.samskivert.swing.VGroupLayout;
import com.samskivert.swing.ScrollablePanel;
import com.samskivert.swing.event.AncestorAdapter;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.util.PresentsContext;
import com.threerings.admin.Log;
/**
* Used to edit the distributed object fields of a particular
* configuration object. When the panel is first shown, it will subscribe
* to the object and display its fields. It will not automatically
* unsubscribe when it is hidden, but rather {@link #cleanup} must be
* called to let it know that it's not going to be shown again soon and it
* is safe for it to clear out its subscription.
*
* @see ConfigEditorPanel
*/
public class ObjectEditorPanel extends ScrollablePanel
implements Subscriber
{
/**
* Creates an object editor panel for the specified configuration
* object.
*/
public ObjectEditorPanel (PresentsContext ctx, String key, int oid)
{
super(new VGroupLayout(VGroupLayout.NONE, VGroupLayout.STRETCH,
5, VGroupLayout.TOP));
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
// keep this business around
_ctx = ctx;
_key = key;
_oid = oid;
// when we're hidden, we want to clear out our subscriptions
addAncestorListener(new AncestorAdapter () {
public void ancestorAdded (AncestorEvent event) {
// subscribe to our object the first time we're shown
if (_object == null) {
_ctx.getDObjectManager().subscribeToObject(
_oid, ObjectEditorPanel.this);
}
}
});
}
// documentation inherited from interface
public boolean getScrollableTracksViewportWidth ()
{
return true;
}
/**
* This method must be called to let the object editor panel know that
* it's OK for it to remove its subscription to its config object.
*/
public void cleanup ()
{
if (_object != null) {
_ctx.getDObjectManager().unsubscribeFromObject(_oid, this);
_object = null;
}
// clear out our field editors
removeAll();
}
// documentation inherited from interface
public void objectAvailable (DObject object)
{
// keep this for later
_object = object;
// create our field editors
try {
Field[] fields = object.getClass().getFields();
for (int ii = 0; ii < fields.length; ii++) {
// if the field is anything but a plain old public field,
// we don't want to edit it
if (fields[ii].getModifiers() == Modifier.PUBLIC) {
add(new FieldEditor(_ctx, fields[ii], _object));
}
}
} catch (SecurityException se) {
Log.warning("Unable to introspect DObject!? " + se);
}
revalidate();
repaint();
}
// documentation inherited from interface
public void requestFailed (int oid, ObjectAccessException cause)
{
Log.warning("Unable to subscribe to config object: " + cause);
}
protected PresentsContext _ctx;
protected String _key;
protected int _oid;
protected DObject _object;
}
@@ -0,0 +1,21 @@
//
// $Id: AdminCodes.java,v 1.1 2002/06/07 06:22:24 mdb Exp $
package com.threerings.admin.data;
import com.threerings.presents.data.InvocationCodes;
/**
* Contains codes used by the admin invocation services.
*/
public interface AdminCodes extends InvocationCodes
{
/** The module name for the admin services. */
public static final String MODULE_NAME = "admin";
/** The message identifier for a getConfigInfo request. */
public static final String GET_CONFIG_INFO_REQUEST = "GetConfigInfo";
/** The response identifier for a successful getConfigInfo request. */
public static final String CONFIG_INFO_RESPONSE = "ConfigInfo";
}
@@ -0,0 +1,51 @@
//
// $Id: AdminProvider.java,v 1.1 2002/06/07 06:22:24 mdb Exp $
package com.threerings.admin.server;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.presents.server.ServiceFailedException;
import com.threerings.crowd.data.BodyObject;
import com.threerings.admin.Log;
import com.threerings.admin.data.AdminCodes;
/**
* Provides the server-side interface to various administrator services.
*/
public class AdminProvider extends InvocationProvider
implements AdminCodes
{
/**
* Constructs an admin provider and registers it with the invocation
* manager to handle admin services. This must be called by any server
* that wishes to make use of the admin services.
*/
public static void init (InvocationManager invmgr)
{
invmgr.registerProvider(MODULE_NAME, new AdminProvider());
}
/**
* Processes a request from a client to obtain the configuration keys
* and oids for all registered configuration objects.
*/
public void handleGetConfigInfoRequest (BodyObject source, int invid)
throws ServiceFailedException
{
// we don't have to validate the request because the user can't do
// anything with the keys or oids unless they're an admin (we put
// the burden of doing that checking on the creator of the config
// object because we would otherwise need some mechanism to
// determine whether a user is an admin and we don't want to force
// some primitive system on the service user)
String[] keys = ConfObjRegistry.getKeys();
int[] oids = new int[keys.length];
for (int ii = 0; ii < keys.length; ii++) {
oids[ii] = ConfObjRegistry.getObject(keys[ii]).getOid();
}
sendResponse(source, invid, CONFIG_INFO_RESPONSE, keys, oids);
}
}
@@ -0,0 +1,67 @@
//
// $Id: ConfObjRegistry.java,v 1.1 2002/06/07 06:22:24 mdb Exp $
package com.threerings.admin.server;
import java.util.HashMap;
import java.util.Iterator;
import com.threerings.presents.dobj.AccessController;
import com.threerings.presents.dobj.DObject;
/**
* 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 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.
*/
public class ConfObjRegistry
{
/**
* 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 object the object to be registered.
*/
public static void registerObject (String key, DObject object)
{
_confobjs.put(key, object);
}
/**
* Returns the config object mapped to the specified key, or null if
* none exists for that key.
*/
public static DObject getObject (String key)
{
return (DObject)_confobjs.get(key);
}
/**
* Returns an array containing the keys of all registered
* configuration objects.
*/
public static String[] getKeys ()
{
String[] keys = new String[_confobjs.size()];
Iterator iter = _confobjs.keySet().iterator();
for (int ii = 0; iter.hasNext(); ii++) {
keys[ii] = (String)iter.next();
}
return keys;
}
/** A mapping from identifying key to config object. */
protected static HashMap _confobjs = new HashMap();
}