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:
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user