Convert Narya (most of the way) over to a Maven Ant task based build. The
ActionScript bits remain belligerent, but the Java stuff is mostly shipshape. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6222 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
|
||||
/**
|
||||
* Contains a reference to the log object used by this project.
|
||||
*/
|
||||
public class NaryaLog
|
||||
{
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static Logger log = Logger.getLogger("com.threerings.narya");
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
|
||||
/**
|
||||
* Contains a reference to the log object used by the Admin services.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static Logger log = Logger.getLogger("com.threerings.narya.admin");
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
/**
|
||||
* Defines the client side of the admin invocation services.
|
||||
*/
|
||||
public interface AdminService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Used to communicate a response to a {@link AdminService#getConfigInfo} request.
|
||||
*/
|
||||
public static interface ConfigInfoListener extends InvocationListener
|
||||
{
|
||||
/**
|
||||
* Delivers a successful response to a {@link AdminService#getConfigInfo} request.
|
||||
*/
|
||||
void gotConfigInfo (String[] keys, int[] oids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the list of config objects.
|
||||
*/
|
||||
void getConfigInfo (Client client, ConfigInfoListener listener);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
|
||||
import static com.threerings.admin.Log.log;
|
||||
|
||||
/**
|
||||
* Used to display and edit a particular distributed object field.
|
||||
*/
|
||||
public class AsStringFieldEditor extends FieldEditor
|
||||
{
|
||||
public AsStringFieldEditor (PresentsContext ctx, Field field, DObject object)
|
||||
{
|
||||
super(ctx, field, object);
|
||||
|
||||
// and a text entry field to display the field value
|
||||
add(_value = new JTextField());
|
||||
_value.addActionListener(this);
|
||||
|
||||
// we want to let the user know if they remove focus from a text
|
||||
// box without changing a field that it's not saved
|
||||
_value.addFocusListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getDisplayValue ()
|
||||
throws Exception
|
||||
{
|
||||
String text = _value.getText();
|
||||
if (_field.getType().equals(Integer.class) ||
|
||||
_field.getType().equals(Integer.TYPE)) {
|
||||
return new Integer(text);
|
||||
|
||||
} else if (_field.getType().equals(Short.class) ||
|
||||
_field.getType().equals(Short.TYPE)) {
|
||||
return new Short(text);
|
||||
|
||||
} else if (_field.getType().equals(Byte.class) ||
|
||||
_field.getType().equals(Byte.TYPE)) {
|
||||
return new Byte(text);
|
||||
|
||||
} else if (_field.getType().equals(Long.class) ||
|
||||
_field.getType().equals(Long.TYPE)) {
|
||||
return new Long(text);
|
||||
|
||||
} else if (_field.getType().equals(Float.class) ||
|
||||
_field.getType().equals(Float.TYPE)) {
|
||||
return new Float(text);
|
||||
|
||||
} else if (_field.getType().equals(Double.class) ||
|
||||
_field.getType().equals(Double.TYPE)) {
|
||||
return new Double(text);
|
||||
|
||||
} else if (_field.getType().equals(String.class)) {
|
||||
return text;
|
||||
|
||||
} else if (_field.getType().equals(String[].class)) {
|
||||
return StringUtil.parseStringArray(_value.getText());
|
||||
|
||||
} else if (_field.getType().equals(int[].class)) {
|
||||
return StringUtil.parseIntArray(_value.getText());
|
||||
|
||||
} else if (_field.getType().equals(float[].class)) {
|
||||
return StringUtil.parseFloatArray(_value.getText());
|
||||
|
||||
} else if (_field.getType().equals(long[].class)) {
|
||||
return StringUtil.parseLongArray(_value.getText());
|
||||
|
||||
} else if (_field.getType().equals(Boolean.TYPE)) {
|
||||
return new Boolean(_value.getText().equalsIgnoreCase("true"));
|
||||
|
||||
} else {
|
||||
log.warning("Unknown field type '" + _field.getName() + "': " +
|
||||
_field.getType().getName() + ".");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayValue (Object value)
|
||||
{
|
||||
_value.setText(StringUtil.toString(value, "", ""));
|
||||
}
|
||||
|
||||
protected JTextField _value;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import com.samskivert.swing.GroupLayout;
|
||||
import com.samskivert.swing.HGroupLayout;
|
||||
import com.samskivert.swing.Spacer;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
|
||||
/**
|
||||
* Provides "editing" of boolean fields.
|
||||
*/
|
||||
public class BooleanFieldEditor extends FieldEditor
|
||||
{
|
||||
public BooleanFieldEditor (PresentsContext ctx, Field field, DObject object)
|
||||
{
|
||||
super(ctx, field, object);
|
||||
|
||||
JPanel jpan = new JPanel(new HGroupLayout(HGroupLayout.STRETCH));
|
||||
|
||||
// add a checkbox to display the field value
|
||||
jpan.add(_value = new JCheckBox(), GroupLayout.FIXED);
|
||||
// add a spacer so that clicks to the right of the checkbox
|
||||
// don't toggle it
|
||||
jpan.add(new Spacer(1, 1));
|
||||
_value.addActionListener(this);
|
||||
|
||||
add(jpan);
|
||||
|
||||
// we want to let the user know if they remove focus from a text
|
||||
// box without changing a field that it's not saved
|
||||
_value.addFocusListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getDisplayValue ()
|
||||
throws Exception
|
||||
{
|
||||
return Boolean.valueOf(_value.isSelected());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayValue (Object value)
|
||||
{
|
||||
_value.setSelected(Boolean.TRUE.equals(value));
|
||||
}
|
||||
|
||||
protected JCheckBox _value;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Comparator;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import com.samskivert.util.QuickSort;
|
||||
import com.samskivert.swing.VGroupLayout;
|
||||
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
|
||||
import static com.threerings.admin.Log.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 JPanel
|
||||
implements AdminService.ConfigInfoListener
|
||||
{
|
||||
/**
|
||||
* Constructs an editor panel which will use the supplied context to
|
||||
* access the distributed object services.
|
||||
*/
|
||||
public ConfigEditorPanel (PresentsContext ctx)
|
||||
{
|
||||
this(ctx, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an editor panel with the specified pane defaulting to
|
||||
* selected.
|
||||
*/
|
||||
public ConfigEditorPanel (PresentsContext ctx, String defaultPane)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_defaultPane = defaultPane;
|
||||
|
||||
setLayout(new VGroupLayout(VGroupLayout.STRETCH, VGroupLayout.STRETCH,
|
||||
VGroupLayout.DEFAULT_GAP, VGroupLayout.CENTER));
|
||||
|
||||
// create our objects tabbed pane
|
||||
add(_oeditors = new JTabbedPane(JTabbedPane.LEFT));
|
||||
|
||||
// If they don't fit, make them scroll, since wrapped vertical tabs eats insane sceen space
|
||||
_oeditors.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||
|
||||
// add a handy label at the bottom
|
||||
add(new JLabel("Fields outline in red have been modified but not yet committed."),
|
||||
VGroupLayout.FIXED);
|
||||
add(new JLabel("Press return in a modified field to commit the change."),
|
||||
VGroupLayout.FIXED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
|
||||
// ship off a getConfigInfo request to find out what config
|
||||
// objects are available for editing
|
||||
AdminService service = _ctx.getClient().requireService(AdminService.class);
|
||||
service.getConfigInfo(_ctx.getClient(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify ()
|
||||
{
|
||||
super.removeNotify();
|
||||
|
||||
// when we're hidden, we want to clear out our subscriptions
|
||||
int ccount = _oeditors.getComponentCount();
|
||||
for (int ii = 0; ii < ccount; ii++) {
|
||||
Component comp = _oeditors.getComponent(ii);
|
||||
if (comp instanceof JScrollPane) {
|
||||
JScrollPane scrolly = (JScrollPane)comp;
|
||||
ObjectEditorPanel opanel = (ObjectEditorPanel)scrolly.getViewport().getView();
|
||||
opanel.cleanup();
|
||||
}
|
||||
}
|
||||
_oeditors.removeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in response to our getConfigInfo server-side service request.
|
||||
*/
|
||||
public void gotConfigInfo (final String[] keys, final int[] oids)
|
||||
{
|
||||
// make sure we're still added
|
||||
if (!isDisplayable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Integer indexes[] = new Integer[keys.length];
|
||||
for (int ii = 0; ii < indexes.length; ii++) {
|
||||
indexes[ii] = ii;
|
||||
}
|
||||
|
||||
QuickSort.sort(indexes, new Comparator<Integer>() {
|
||||
public int compare (Integer i1, Integer i2) {
|
||||
return keys[i1].compareTo(keys[i2]);
|
||||
}
|
||||
});
|
||||
|
||||
// create object editor panels for each of the categories
|
||||
for (Integer ii : indexes) {
|
||||
ObjectEditorPanel panel = new ObjectEditorPanel(_ctx, keys[ii], oids[ii]);
|
||||
JScrollPane scrolly = new JScrollPane(panel);
|
||||
_oeditors.addTab(keys[ii], scrolly);
|
||||
if (keys[ii].equals(_defaultPane)) {
|
||||
_oeditors.setSelectedComponent(scrolly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void requestFailed (String reason)
|
||||
{
|
||||
log.warning("Failed to get config info", "reason", reason);
|
||||
}
|
||||
|
||||
/** Our client context. */
|
||||
protected PresentsContext _ctx;
|
||||
|
||||
/** Holds our object editors. */
|
||||
protected JTabbedPane _oeditors;
|
||||
|
||||
/** Our default tab pane. */
|
||||
protected String _defaultPane;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.ClientAdapter;
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
|
||||
import com.threerings.admin.data.AdminCodes;
|
||||
import com.threerings.admin.data.ConfigObject;
|
||||
|
||||
import static com.threerings.admin.Log.log;
|
||||
|
||||
/**
|
||||
* Handles subscribing to admin config objects.
|
||||
*/
|
||||
public class ConfigObjectManager implements AdminService.ConfigInfoListener
|
||||
{
|
||||
public ConfigObjectManager (Client client)
|
||||
{
|
||||
_serverconfig = Maps.newHashMap();
|
||||
_client = client;
|
||||
_client.addClientObserver(new ClientAdapter() {
|
||||
@Override
|
||||
public void clientWillLogon (Client client) {
|
||||
client.addServiceGroup(AdminCodes.ADMIN_GROUP);
|
||||
}
|
||||
@Override
|
||||
public void clientDidLogon (Client client) {
|
||||
_dobjmgr = _client.getDObjectManager();
|
||||
_service = client.requireService(AdminService.class);
|
||||
getConfigInfo();
|
||||
}
|
||||
@Override
|
||||
public void clientDidLogoff (Client client) {
|
||||
// Clean up our subscription to the server's configuration
|
||||
for (ConfigObjectSubscriber _csubscriber : _csubscribers) {
|
||||
_csubscriber.cleanup();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ConfigObject identified by the given key.
|
||||
*/
|
||||
public ConfigObject getServerConfig (String key)
|
||||
{
|
||||
return _serverconfig.get(key);
|
||||
}
|
||||
|
||||
// documentation inherited from interface AdminService.ConfigInfoListener
|
||||
public void gotConfigInfo (String[] keys, int[] oids)
|
||||
{
|
||||
_csubscribers = new ConfigObjectSubscriber[keys.length];
|
||||
for (int ii = 0; ii < keys.length; ii++) {
|
||||
_csubscribers[ii] = new ConfigObjectSubscriber();
|
||||
_csubscribers[ii].subscribeConfig(keys[ii], oids[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface AdminService.ConfigInfoListener
|
||||
public void requestFailed (String reason)
|
||||
{
|
||||
log.warning("Oh bugger, we didn't get the config data: " + reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: generate a getConfigInfo request to the AdminService from the external class,
|
||||
* instead from within the anonymous inner class.
|
||||
*/
|
||||
protected void getConfigInfo ()
|
||||
{
|
||||
_service.getConfigInfo(_client, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class takes care of the details of subscribing to and placing an individual
|
||||
* ConfigObject that the server knows about into a HashMap.
|
||||
*/
|
||||
protected class ConfigObjectSubscriber implements Subscriber<ConfigObject>
|
||||
{
|
||||
/**
|
||||
* This method requests that we place a subscription to the ConfigObject with the given
|
||||
* oid, identified by the key; when the object becomes available, it's added to our
|
||||
* serverconfig map.
|
||||
*/
|
||||
public void subscribeConfig (String key, int oid) {
|
||||
_key = key;
|
||||
_oid = oid;
|
||||
_dobjmgr.subscribeToObject(_oid, this);
|
||||
}
|
||||
|
||||
// documentation inherited from interface Subscriber
|
||||
public void objectAvailable (ConfigObject object) {
|
||||
_cobj = object;
|
||||
_serverconfig.put(_key, _cobj);
|
||||
}
|
||||
|
||||
// documentation inherited from interface Subscriber
|
||||
public void requestFailed (int oid, ObjectAccessException cause) {
|
||||
log.warning("Unable to subscribe to config object " + _key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that we should stop subscribing to our ConfigObject, and flush out the entry
|
||||
* from the serverconfig map.
|
||||
*/
|
||||
public void cleanup () {
|
||||
// clear out our subscription
|
||||
_dobjmgr.unsubscribeFromObject(_oid, this);
|
||||
_cobj = null;
|
||||
_serverconfig.remove(_key);
|
||||
}
|
||||
|
||||
/** The object that we are tracking. */
|
||||
protected ConfigObject _cobj;
|
||||
|
||||
/** The name of the config object to which we are subscribing. */
|
||||
protected String _key;
|
||||
|
||||
/** The oid of the object that we're tracking. */
|
||||
protected int _oid;
|
||||
}
|
||||
|
||||
/** An array of handlers that each subscribe to a single ConfigObject. */
|
||||
protected ConfigObjectSubscriber[] _csubscribers;
|
||||
|
||||
/** Our local copy of the server-side runtime configuration. */
|
||||
protected HashMap<String, ConfigObject> _serverconfig;
|
||||
|
||||
/** Our distributed object manager. */
|
||||
protected DObjectManager _dobjmgr;
|
||||
|
||||
/** Our admin service that we're using to fetch data. */
|
||||
protected AdminService _service;
|
||||
|
||||
/** Our client object. */
|
||||
protected Client _client;
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.util.ComparableArrayList;
|
||||
|
||||
import com.samskivert.swing.ObjectEditorTable;
|
||||
import com.samskivert.swing.event.CommandEvent;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Allows simple editing of DSets within a distributed object.
|
||||
*/
|
||||
public class DSetEditor<E extends DSet.Entry> extends JPanel
|
||||
implements ActionListener
|
||||
{
|
||||
/**
|
||||
* An interface for a plugin defining how the editor interacts with its underlying data.
|
||||
*/
|
||||
public interface Accessor<F extends DSet.Entry>
|
||||
{
|
||||
void added ();
|
||||
void removed ();
|
||||
void updateEntry (String setName, DSet.Entry entry);
|
||||
ObjectEditorTable.FieldInterpreter getInterp (ObjectEditorTable.FieldInterpreter interp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a DSet editor to merely display the specified set.
|
||||
*
|
||||
* @param setter The object that contains the set.
|
||||
* @param setName The name of the set in the object.
|
||||
* @param entryClass The Class of the DSet.Entry elements contained in the set.
|
||||
*/
|
||||
public DSetEditor (DObject setter, String setName, Class<?> entryClass)
|
||||
{
|
||||
this(setter, setName, entryClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a DSetEditor, allowing the specified fields to be edited.
|
||||
*
|
||||
* @param setter The object that contains the set.
|
||||
* @param setName The name of the set in the object.
|
||||
* @param entryClass The Class of the DSet.Entry elements contained in the set.
|
||||
* @param editableFields the names of the fields in the entryClass that should be editable.
|
||||
*/
|
||||
public DSetEditor (
|
||||
DObject setter, String setName, Class<?> entryClass, String[] editableFields)
|
||||
{
|
||||
this(setter, setName, entryClass, editableFields, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a DSetEditor with a custom FieldInterpreter.
|
||||
*
|
||||
* @param setter The object that contains the set.
|
||||
* @param setName The name of the set in the object.
|
||||
* @param entryClass the Class of the DSet.Entry elements contained in the set.
|
||||
* @param editableFields The names of the fields in the entryClass that should be editable.
|
||||
* @param interp The FieldInterpreter to use.
|
||||
*/
|
||||
public DSetEditor (
|
||||
DObject setter, String setName, Class<?> entryClass, String[] editableFields,
|
||||
ObjectEditorTable.FieldInterpreter interp)
|
||||
{
|
||||
this(setter, setName, entryClass, editableFields, interp, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a DSetEditor that only displays entries that match the given Predicate.
|
||||
*
|
||||
* @param setter The object that contains the set.
|
||||
* @param setName The name of the set in the object.
|
||||
* @param entryClass The Class of the DSet.Entry elements contained in the set.
|
||||
* @param editableFields The names of the fields in the entryClass that should be editable.
|
||||
* @param interp The FieldInterpreter to use.
|
||||
* @param displayFields The fields to display, or null for all.
|
||||
* @param entryFilter The Predicate to use.
|
||||
*/
|
||||
public DSetEditor (
|
||||
DObject setter, String setName, Class<?> entryClass, String[] editableFields,
|
||||
ObjectEditorTable.FieldInterpreter interp, String[] displayFields, Predicate<E> entryFilter)
|
||||
{
|
||||
super(new BorderLayout());
|
||||
|
||||
_setName = setName;
|
||||
_entryFilter = entryFilter;
|
||||
|
||||
_entryClass = entryClass;
|
||||
_editableFields = editableFields;
|
||||
_interp = interp;
|
||||
_displayFields = displayFields;
|
||||
|
||||
setAccessor(new DObjectAccessor<E>(setter, setName));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the logic for how this editor interacts with its underlying data.
|
||||
*/
|
||||
public void setAccessor (Accessor<E> accessor)
|
||||
{
|
||||
removeAll();
|
||||
_accessor = accessor;
|
||||
_table = new ObjectEditorTable(_entryClass, _editableFields, _accessor.getInterp(_interp),
|
||||
_displayFields);
|
||||
add(new JScrollPane(_table), BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table being used to display the set.
|
||||
*/
|
||||
public JTable getTable ()
|
||||
{
|
||||
return _table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected entry.
|
||||
*/
|
||||
public DSet.Entry getSelectedEntry ()
|
||||
{
|
||||
return (DSet.Entry)_table.getSelectedObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize ()
|
||||
{
|
||||
Dimension d = super.getPreferredSize();
|
||||
d.height = Math.min(d.height, MIN_HEIGHT);
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
_accessor.added();
|
||||
_table.addActionListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify ()
|
||||
{
|
||||
_accessor.removed();
|
||||
_table.removeActionListener(this);
|
||||
super.removeNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the addition of an entry, assuming our filter allows it.
|
||||
*/
|
||||
protected void addEntry (E entry)
|
||||
{
|
||||
if (_entryFilter == null || _entryFilter.apply(entry)) {
|
||||
int index = _keys.insertSorted(getKey(entry));
|
||||
_table.insertDatum(entry, index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of removing a key from
|
||||
*/
|
||||
protected void removeKey (Comparable<?> key)
|
||||
{
|
||||
int index = _keys.indexOf(key);
|
||||
if (index != -1) {
|
||||
_keys.remove(index);
|
||||
_table.removeDatum(index);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface ActionListener
|
||||
public void actionPerformed (ActionEvent event)
|
||||
{
|
||||
CommandEvent ce = (CommandEvent)event;
|
||||
_accessor.updateEntry(_setName, (DSet.Entry)ce.getArgument());
|
||||
}
|
||||
|
||||
public void setData (ComparableArrayList<Comparable<Object>> keys, Object[] data)
|
||||
{
|
||||
_keys = keys;
|
||||
_table.setData(data);
|
||||
}
|
||||
|
||||
public Predicate<E> getFilter ()
|
||||
{
|
||||
return _entryFilter;
|
||||
}
|
||||
|
||||
public String getSetName ()
|
||||
{
|
||||
return _setName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static Comparable<Object> getKey (DSet.Entry entry)
|
||||
{
|
||||
return (Comparable<Object>)entry.getKey();
|
||||
}
|
||||
|
||||
protected class DObjectAccessor<F extends E>
|
||||
implements AttributeChangeListener, SetListener<F>, Accessor<F>
|
||||
{
|
||||
public DObjectAccessor (DObject obj, String setName)
|
||||
{
|
||||
_obj = obj;
|
||||
_setName = setName;
|
||||
}
|
||||
|
||||
public ObjectEditorTable.FieldInterpreter getInterp (
|
||||
ObjectEditorTable.FieldInterpreter interp)
|
||||
{
|
||||
return interp;
|
||||
}
|
||||
|
||||
public void added ()
|
||||
{
|
||||
_obj.addListener(this);
|
||||
refreshSet();
|
||||
refreshData();
|
||||
}
|
||||
|
||||
public void removed ()
|
||||
{
|
||||
_obj.removeListener(this);
|
||||
}
|
||||
|
||||
public void refreshSet ()
|
||||
{
|
||||
_set = _obj.getSet(_setName);
|
||||
}
|
||||
|
||||
public void updateEntry (String setName, DSet.Entry entry)
|
||||
{
|
||||
_obj.updateSet(setName, entry);
|
||||
}
|
||||
|
||||
// documentation inherited from interface SetListener
|
||||
public void entryAdded (EntryAddedEvent<F> event)
|
||||
{
|
||||
if (event.getName().equals(_setName)) {
|
||||
addEntry(event.getEntry());
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface SetListener
|
||||
public void entryRemoved (EntryRemovedEvent<F> event)
|
||||
{
|
||||
if (event.getName().equals(_setName)) {
|
||||
removeKey(event.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
protected void refreshData ()
|
||||
{
|
||||
ComparableArrayList<Comparable<Object>> keys =
|
||||
new ComparableArrayList<Comparable<Object>>();
|
||||
E[] entries;
|
||||
|
||||
if (_entryFilter == null) {
|
||||
entries = createArray();
|
||||
|
||||
} else {
|
||||
// Do some shuffling to get out a filtered array.
|
||||
Iterator<F> itr = Iterators.filter(iterator(), _entryFilter);
|
||||
ArrayList<F> list = Lists.newArrayList();
|
||||
Iterators.addAll(list, itr);
|
||||
|
||||
@SuppressWarnings("unchecked") F[] tmp = (F[])new DSet.Entry[list.size()];
|
||||
entries = tmp;
|
||||
list.toArray(entries);
|
||||
}
|
||||
|
||||
for (E entry : entries) {
|
||||
keys.insertSorted(getKey(entry));
|
||||
}
|
||||
setData(keys, entries); // this works because DSet itself is sorted
|
||||
}
|
||||
|
||||
// documentation inherited from interface SetListener
|
||||
public void entryUpdated (EntryUpdatedEvent<F> event)
|
||||
{
|
||||
if (event.getName().equals(_setName)) {
|
||||
E entry = event.getEntry();
|
||||
int index = _keys.indexOf(entry.getKey());
|
||||
if (index != -1) {
|
||||
// We have it, so either update or remove
|
||||
if (_entryFilter == null || _entryFilter.apply(entry)) {
|
||||
_table.updateDatum(entry, index);
|
||||
|
||||
} else {
|
||||
removeKey(entry.getKey());
|
||||
}
|
||||
} else {
|
||||
// We DON'T have it, so try to add it in case we care about it
|
||||
addEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface SetListener
|
||||
public void attributeChanged (AttributeChangedEvent event)
|
||||
{
|
||||
if (event.getName().equals(_setName)) {
|
||||
// the whole set changed so we need to refetch it from the object
|
||||
refreshSet();
|
||||
refreshData();
|
||||
}
|
||||
}
|
||||
|
||||
public E[] createArray ()
|
||||
{
|
||||
@SuppressWarnings("unchecked") F[] tmp = (F[])new DSet.Entry[_set.size()];
|
||||
F[] entries = tmp;
|
||||
_set.toArray(entries);
|
||||
return entries;
|
||||
}
|
||||
|
||||
public Iterator<F> iterator ()
|
||||
{
|
||||
return _set.iterator();
|
||||
}
|
||||
|
||||
protected DObject _obj;
|
||||
|
||||
protected DSet<F> _set;
|
||||
|
||||
protected String _setName;
|
||||
}
|
||||
|
||||
/** The name of the set in that object. */
|
||||
protected String _setName;
|
||||
|
||||
/** An optional predicate to decide whether actually care about displaying a given entry. */
|
||||
protected Predicate<E> _entryFilter;
|
||||
|
||||
/** Provides access to our data we're editing. */
|
||||
protected Accessor<E> _accessor;
|
||||
|
||||
/** The table used to edit. */
|
||||
protected ObjectEditorTable _table;
|
||||
|
||||
/** An array we use to track our entries' positions by key. */
|
||||
protected ComparableArrayList<Comparable<Object>> _keys;
|
||||
|
||||
protected Class<?> _entryClass;
|
||||
protected String[] _editableFields;
|
||||
protected ObjectEditorTable.FieldInterpreter _interp;
|
||||
protected String[] _displayFields;
|
||||
|
||||
/** The minimum height for our editor UI. */
|
||||
protected static final int MIN_HEIGHT = 200;
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
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.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
|
||||
import static com.threerings.admin.Log.log;
|
||||
|
||||
/**
|
||||
* Used to display and edit a particular distributed object field.
|
||||
*/
|
||||
public abstract class FieldEditor extends JPanel
|
||||
implements AttributeChangeListener, ActionListener, FocusListener
|
||||
{
|
||||
/** The interface defining how the editor interacts with its data. */
|
||||
public interface Accessor
|
||||
{
|
||||
void added ();
|
||||
void removed ();
|
||||
void set (Field field, Object value);
|
||||
Object get (Field field);
|
||||
}
|
||||
|
||||
public FieldEditor (PresentsContext ctx, Field field, DObject object)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_field = field;
|
||||
setAccessor(new DObjectAccessor(object));
|
||||
|
||||
// create our interface elements
|
||||
setLayout(new HGroupLayout(HGroupLayout.STRETCH));
|
||||
|
||||
// a label to display the field name
|
||||
add(_label = new JLabel(_field.getName()));
|
||||
|
||||
// set up our default border
|
||||
updateBorder(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the plugin for how we interact with our data.
|
||||
*/
|
||||
public void setAccessor (Accessor accessor)
|
||||
{
|
||||
_accessor = accessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
|
||||
// listen to the object while we're visible
|
||||
_accessor.added();
|
||||
displayValue(getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify ()
|
||||
{
|
||||
super.removeNotify();
|
||||
|
||||
// stop listening when we're hidden
|
||||
_accessor.removed();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void attributeChanged (AttributeChangedEvent event)
|
||||
{
|
||||
noteUpdatedExternally();
|
||||
}
|
||||
|
||||
/** Update ourselves to reflect a change from outside the editor. */
|
||||
public void noteUpdatedExternally ()
|
||||
{
|
||||
displayValue(getValue());
|
||||
updateBorder(false);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void actionPerformed (ActionEvent event)
|
||||
{
|
||||
Object value = null;
|
||||
try {
|
||||
value = getDisplayValue();
|
||||
} catch (Exception e) {
|
||||
updateBorder(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// submit an attribute changed event with the new value
|
||||
if (!Objects.equal(value, getValue())) {
|
||||
_accessor.set(_field, value);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void focusGained (FocusEvent event)
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void focusLost (FocusEvent event)
|
||||
{
|
||||
// make sure the value is not changed from the value in the
|
||||
// object; if it is, set a modified border
|
||||
Object dvalue = null;
|
||||
try {
|
||||
dvalue = getDisplayValue();
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to parse display value " + e + ".");
|
||||
displayValue(getValue());
|
||||
}
|
||||
updateBorder(!Objects.equal(dvalue, getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently displayed value.
|
||||
*/
|
||||
protected abstract Object getDisplayValue ()
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Reads the value from the distributed object field and updates the
|
||||
* display with it.
|
||||
*/
|
||||
protected abstract void displayValue (Object value);
|
||||
|
||||
/**
|
||||
* Returns the current object value.
|
||||
*/
|
||||
protected Object getValue ()
|
||||
{
|
||||
return _accessor.get(_field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple accessor that knows how to interact with a DObject - this is normally what is used.
|
||||
*/
|
||||
protected class DObjectAccessor
|
||||
implements Accessor
|
||||
{
|
||||
public DObjectAccessor (DObject obj)
|
||||
{
|
||||
_obj = obj;
|
||||
}
|
||||
|
||||
public void added ()
|
||||
{
|
||||
_obj.addListener(FieldEditor.this);
|
||||
}
|
||||
|
||||
public void removed ()
|
||||
{
|
||||
_obj.removeListener(FieldEditor.this);
|
||||
}
|
||||
|
||||
public void set (Field field, Object value)
|
||||
{
|
||||
try {
|
||||
_obj.changeAttribute(field.getName(), value);
|
||||
} catch (ObjectAccessException oae) {
|
||||
log.warning("Failed to update field " + field.getName() + ": " + oae);
|
||||
}
|
||||
}
|
||||
|
||||
public Object get (Field field)
|
||||
{
|
||||
try {
|
||||
return field.get(_obj);
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to fetch field", "field", field, "object", _obj, "error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected DObject _obj;
|
||||
}
|
||||
|
||||
protected PresentsContext _ctx;
|
||||
protected Field _field;
|
||||
protected Accessor _accessor;
|
||||
protected JLabel _label;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
|
||||
import com.samskivert.swing.ScrollablePanel;
|
||||
import com.samskivert.swing.VGroupLayout;
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
import com.threerings.presents.util.SafeSubscriber;
|
||||
|
||||
import com.threerings.admin.data.ConfigObject;
|
||||
|
||||
import static com.threerings.admin.Log.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<ConfigObject>
|
||||
{
|
||||
/**
|
||||
* 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,
|
||||
VGroupLayout.DEFAULT_GAP, VGroupLayout.TOP));
|
||||
setBorder(BorderFactory.createEmptyBorder(BORDER, BORDER, BORDER, BORDER));
|
||||
|
||||
// keep this business around
|
||||
_ctx = ctx;
|
||||
_key = key;
|
||||
|
||||
// we'll use this to safely subscribe to and unsubscribe from the
|
||||
// config object
|
||||
_safesub = new SafeSubscriber<ConfigObject>(oid, this);
|
||||
_safesub.subscribe(_ctx.getDObjectManager());
|
||||
}
|
||||
|
||||
@Override
|
||||
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 ()
|
||||
{
|
||||
// clear out our subscription
|
||||
_safesub.unsubscribe(_ctx.getDObjectManager());
|
||||
_object = null;
|
||||
|
||||
// clear out our field editors
|
||||
removeAll();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void objectAvailable (ConfigObject object)
|
||||
{
|
||||
// keep this for later
|
||||
_object = object;
|
||||
|
||||
// create our field editors
|
||||
try {
|
||||
Field[] fields = object.getClass().getFields();
|
||||
for (Field field : fields) {
|
||||
// if the field is anything but a plain old public field,
|
||||
// we don't want to edit it
|
||||
if (field.getModifiers() == Modifier.PUBLIC) {
|
||||
add(_object.getEditor(_ctx, field));
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SecurityException se) {
|
||||
log.warning("Unable to introspect DObject!? " + se);
|
||||
}
|
||||
|
||||
SwingUtil.refresh(this);
|
||||
}
|
||||
|
||||
// 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 SafeSubscriber<ConfigObject> _safesub;
|
||||
protected ConfigObject _object;
|
||||
|
||||
protected static final int BORDER = 5;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.swing.JComboBox;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
|
||||
import static com.threerings.admin.Log.log;
|
||||
|
||||
/**
|
||||
* Allows editing of a dobj field using a pulldown.
|
||||
*/
|
||||
public class PulldownFieldEditor extends FieldEditor
|
||||
{
|
||||
/**
|
||||
* An object that nicely represents a pulldown choice.
|
||||
*/
|
||||
public static class Choice
|
||||
{
|
||||
/** The value we represent. */
|
||||
public Object value;
|
||||
|
||||
/**
|
||||
* Construct a pulldown choice.
|
||||
*/
|
||||
public Choice (String displayName, Object val)
|
||||
{
|
||||
if (displayName == null) {
|
||||
throw new NullPointerException("displayName cannot be null.");
|
||||
}
|
||||
_name = displayName;
|
||||
value = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/** The string representation of this choice. */
|
||||
protected String _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a PulldownFieldEditor.
|
||||
*/
|
||||
public PulldownFieldEditor (PresentsContext ctx, Field field, DObject obj)
|
||||
{
|
||||
super(ctx, field, obj);
|
||||
|
||||
add(_value = new JComboBox());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a PulldownChoice object as a choice for the pulldown.
|
||||
*/
|
||||
public void addChoice (Choice choice)
|
||||
{
|
||||
_value.addItem(choice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified object as a choice. The name will be the
|
||||
* toString() of the object.
|
||||
*/
|
||||
public void addChoice (Object choice)
|
||||
{
|
||||
String name = (choice == null) ? "null" : choice.toString();
|
||||
addChoice(new Choice(name, choice));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
_value.addActionListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify ()
|
||||
{
|
||||
_value.removeActionListener(this);
|
||||
super.removeNotify();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getDisplayValue ()
|
||||
throws Exception
|
||||
{
|
||||
Object obj = _value.getSelectedItem();
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
return ((Choice)obj).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void displayValue (Object value)
|
||||
{
|
||||
for (int ii = _value.getItemCount() - 1; ii >= 0; ii--) {
|
||||
Choice choice = (Choice)_value.getItemAt(ii);
|
||||
if (Objects.equal(value, choice.value)) {
|
||||
_value.setSelectedIndex(ii);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// cause shit to blow up minorly
|
||||
log.warning("Value in dobj is not settable, disabling choice.", new Exception());
|
||||
_value.setEnabled(false);
|
||||
}
|
||||
|
||||
/** Holds the value we're editing. */
|
||||
protected JComboBox _value;
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.client;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
import com.samskivert.util.QuickSort;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.samskivert.swing.ObjectEditorTable;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Allows simple editing of DSets within a distributed object and easily groups entries into tabs
|
||||
* based on the content of some field.
|
||||
*/
|
||||
public class TabbedDSetEditor<E extends DSet.Entry> extends JPanel
|
||||
implements AttributeChangeListener, SetListener<E>
|
||||
{
|
||||
/**
|
||||
* Defines how DSetEditor data-access plugins should be created.
|
||||
*/
|
||||
public interface AccessorFactory
|
||||
{
|
||||
public <E extends DSet.Entry> DSetEditor.Accessor<E> createAccessor (DSetEditor<E> editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to divide various entires into different groups.
|
||||
*/
|
||||
public static abstract class EntryGrouper<E extends DSet.Entry>
|
||||
{
|
||||
/**
|
||||
* Subclasses implement the actual logic to figure out a group names from an entry here.
|
||||
*/
|
||||
protected abstract String[] computeGroups (E entry);
|
||||
|
||||
/**
|
||||
* Returns a predicate that returns true if the given entry is in the given group.
|
||||
*/
|
||||
protected Predicate<E> getPredicate (final String group) {
|
||||
return new Predicate<E>() {
|
||||
public boolean apply (E entry) {
|
||||
String[] groups = computeGroups(entry);
|
||||
for (String g: groups) {
|
||||
if (g.equals(group)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grinds through the
|
||||
* @param entries
|
||||
*/
|
||||
public void computeGroups (Iterable<E> entries) {
|
||||
for (E entry : entries) {
|
||||
String[] groups = computeGroups(entry);
|
||||
for (String group : groups) {
|
||||
_allGroups.add(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the groups we know about, ordered as they should be displayed.
|
||||
*/
|
||||
public String[] getAllGroups () {
|
||||
String[] list = _allGroups.toArray(new String[_allGroups.size()]);
|
||||
QuickSort.sort(list, getComparator());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
protected Comparator<Object> getComparator () {
|
||||
return LEXICAL_CASE_INSENSITIVE;
|
||||
}
|
||||
|
||||
protected Set<String> _allGroups = Sets.newHashSet();
|
||||
|
||||
protected static final Comparator<Object> LEXICAL_CASE_INSENSITIVE = Ordering
|
||||
.from(String.CASE_INSENSITIVE_ORDER)
|
||||
.onResultOf(Functions.toStringFunction())
|
||||
.nullsLast();
|
||||
}
|
||||
|
||||
public static class FieldGrouper<E extends DSet.Entry> extends EntryGrouper<E>
|
||||
{
|
||||
public FieldGrouper (String fieldName, Class<?> entryClass) {
|
||||
try {
|
||||
_field = entryClass.getField(fieldName);
|
||||
} catch (NoSuchFieldException nsfe) {
|
||||
throw new IllegalArgumentException(Logger.format(
|
||||
"Group field not found in prototype class!",
|
||||
"proto", entryClass, "groupField", fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] computeGroups (E entry) {
|
||||
try {
|
||||
return new String[] { StringUtil.toString(_field.get(entry)) };
|
||||
} catch (IllegalAccessException iae) {
|
||||
// This ain't good, but let's soldier on.
|
||||
return new String[] { "<bogus>" };
|
||||
}
|
||||
}
|
||||
|
||||
protected final Field _field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to make an edittor that groups based on the values of a given field.
|
||||
*/
|
||||
public TabbedDSetEditor (
|
||||
DObject setter, String setName, Class<?> entryClass, String[] editableFields,
|
||||
ObjectEditorTable.FieldInterpreter interp, String groupField)
|
||||
{
|
||||
this(setter, setName, entryClass, editableFields, interp,
|
||||
new FieldGrouper<E>(groupField, entryClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of tabs containing DSetEditors grouping entries by the String value stored in
|
||||
* a given field of the Entry.
|
||||
*/
|
||||
public TabbedDSetEditor (
|
||||
DObject setter, String setName, Class<?> entryClass, String[] editableFields,
|
||||
ObjectEditorTable.FieldInterpreter interp, EntryGrouper<E> grouper)
|
||||
{
|
||||
// Stash all this for later
|
||||
_setter = setter;
|
||||
_setName = setName;
|
||||
_entryClass = entryClass;
|
||||
_editableFields = editableFields;
|
||||
_interp = interp;
|
||||
_grouper = grouper;
|
||||
|
||||
_tabs = new JTabbedPane();
|
||||
add(_tabs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the factory that creates data-access plugins for our set our DSetEditors.
|
||||
*/
|
||||
public void setAccessorFactory (AccessorFactory accessorFactory)
|
||||
{
|
||||
_accessorFactory = accessorFactory;
|
||||
}
|
||||
|
||||
protected void computeTabs ()
|
||||
{
|
||||
_grouper.computeGroups(_setter.<E>getSet(_setName));
|
||||
String[] groups = _grouper.getAllGroups();
|
||||
|
||||
for (String group : groups) {
|
||||
if (!_editors.containsKey(group)) {
|
||||
DSetEditor<E> editor = createEditor(
|
||||
_setter, _setName, _entryClass, _editableFields, _interp, _grouper, group);
|
||||
if (_accessorFactory != null) {
|
||||
editor.setAccessor(_accessorFactory.createAccessor(editor));
|
||||
}
|
||||
_tabs.add(group, editor);
|
||||
_editors.put(group, editor);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Prune any now-empty tabs
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DSetEditor for displaying the given group.
|
||||
*/
|
||||
protected DSetEditor<E> createEditor (
|
||||
DObject setter, String setName, Class<?> entryClass, String[] editableFields,
|
||||
ObjectEditorTable.FieldInterpreter interp, EntryGrouper<E> grouper, String group)
|
||||
{
|
||||
return new DSetEditor<E>(setter, setName, entryClass, editableFields,
|
||||
interp, getDisplayFields(group), grouper.getPredicate(group));
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose which fields to display for the given group.
|
||||
*/
|
||||
protected String[] getDisplayFields (String group)
|
||||
{
|
||||
return null; // Override to display only a subset
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
_setter.addListener(this);
|
||||
|
||||
// populate our tabs
|
||||
computeTabs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify ()
|
||||
{
|
||||
_setter.removeListener(this);
|
||||
super.removeNotify();
|
||||
}
|
||||
|
||||
public void attributeChanged (AttributeChangedEvent event)
|
||||
{
|
||||
if (event.getName().equals(_setName)) {
|
||||
computeTabs();
|
||||
}
|
||||
}
|
||||
|
||||
public void entryAdded (EntryAddedEvent<E> event)
|
||||
{
|
||||
if (event.getName().equals(_setName)) {
|
||||
computeTabs();
|
||||
}
|
||||
}
|
||||
|
||||
public void entryRemoved (EntryRemovedEvent<E> event)
|
||||
{
|
||||
if (event.getName().equals(_setName)) {
|
||||
computeTabs();
|
||||
}
|
||||
}
|
||||
|
||||
public void entryUpdated (EntryUpdatedEvent<E> event)
|
||||
{
|
||||
if (event.getName().equals(_setName)) {
|
||||
computeTabs();
|
||||
}
|
||||
}
|
||||
|
||||
protected final DObject _setter;
|
||||
protected final String _setName;
|
||||
protected final Class<?> _entryClass;
|
||||
protected final String[] _editableFields;
|
||||
protected final ObjectEditorTable.FieldInterpreter _interp;
|
||||
protected final EntryGrouper<E> _grouper;
|
||||
protected AccessorFactory _accessorFactory;
|
||||
|
||||
protected JTabbedPane _tabs;
|
||||
protected HashMap<String, DSetEditor<E>> _editors = Maps.newHashMap();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.data;
|
||||
|
||||
import com.threerings.presents.data.InvocationCodes;
|
||||
|
||||
/**
|
||||
* Codes and consants relating to the admin services.
|
||||
*/
|
||||
public interface AdminCodes extends InvocationCodes
|
||||
{
|
||||
/** Defines our invocation service group. */
|
||||
public static final String ADMIN_GROUP = "presents.admin";
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.admin.client.AdminService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
import com.threerings.presents.dobj.InvocationResponseEvent;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link AdminService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from AdminService.java.")
|
||||
public class AdminMarshaller extends InvocationMarshaller
|
||||
implements AdminService
|
||||
{
|
||||
/**
|
||||
* Marshalls results to implementations of {@link AdminService.ConfigInfoListener}.
|
||||
*/
|
||||
public static class ConfigInfoMarshaller extends ListenerMarshaller
|
||||
implements ConfigInfoListener
|
||||
{
|
||||
/** The method id used to dispatch {@link #gotConfigInfo}
|
||||
* responses. */
|
||||
public static final int GOT_CONFIG_INFO = 1;
|
||||
|
||||
// from interface ConfigInfoMarshaller
|
||||
public void gotConfigInfo (String[] arg1, int[] arg2)
|
||||
{
|
||||
_invId = null;
|
||||
omgr.postEvent(new InvocationResponseEvent(
|
||||
callerOid, requestId, GOT_CONFIG_INFO,
|
||||
new Object[] { arg1, arg2 }, transport));
|
||||
}
|
||||
|
||||
@Override // from InvocationMarshaller
|
||||
public void dispatchResponse (int methodId, Object[] args)
|
||||
{
|
||||
switch (methodId) {
|
||||
case GOT_CONFIG_INFO:
|
||||
((ConfigInfoListener)listener).gotConfigInfo(
|
||||
(String[])args[0], (int[])args[1]);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchResponse(methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #getConfigInfo} requests. */
|
||||
public static final int GET_CONFIG_INFO = 1;
|
||||
|
||||
// from interface AdminService
|
||||
public void getConfigInfo (Client arg1, AdminService.ConfigInfoListener arg2)
|
||||
{
|
||||
AdminMarshaller.ConfigInfoMarshaller listener2 = new AdminMarshaller.ConfigInfoMarshaller();
|
||||
listener2.listener = arg2;
|
||||
sendRequest(arg1, GET_CONFIG_INFO, new Object[] {
|
||||
listener2
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.data;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
|
||||
import com.threerings.admin.client.AsStringFieldEditor;
|
||||
import com.threerings.admin.client.BooleanFieldEditor;
|
||||
|
||||
/**
|
||||
* Base class for runtime config distributed objects. Used to allow
|
||||
* config objects to supply custom object editing UI.
|
||||
*/
|
||||
public class ConfigObject extends DObject
|
||||
{
|
||||
/**
|
||||
* Returns the editor panel for the specified field.
|
||||
*/
|
||||
public JPanel getEditor (PresentsContext ctx, Field field)
|
||||
{
|
||||
if (field.getType().equals(Boolean.TYPE)) {
|
||||
return new BooleanFieldEditor(ctx, field, this);
|
||||
} else {
|
||||
return new AsStringFieldEditor(ctx, field, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 javax.annotation.Generated;
|
||||
|
||||
import com.threerings.admin.client.AdminService;
|
||||
import com.threerings.admin.data.AdminMarshaller;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link AdminProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from AdminService.java.")
|
||||
public class AdminDispatcher extends InvocationDispatcher<AdminMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public AdminDispatcher (AdminProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminMarshaller createMarshaller ()
|
||||
{
|
||||
return new AdminMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case AdminMarshaller.GET_CONFIG_INFO:
|
||||
((AdminProvider)provider).getConfigInfo(
|
||||
source, (AdminService.ConfigInfoListener)args[0]
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
|
||||
import com.threerings.admin.client.AdminService;
|
||||
import com.threerings.admin.data.AdminCodes;
|
||||
|
||||
/**
|
||||
* Handles admin stuffs.
|
||||
*/
|
||||
@Singleton
|
||||
public class AdminManager
|
||||
implements AdminProvider
|
||||
{
|
||||
@Inject public AdminManager (InvocationManager invmgr)
|
||||
{
|
||||
invmgr.registerDispatcher(new AdminDispatcher(this), AdminCodes.ADMIN_GROUP);
|
||||
}
|
||||
|
||||
// from interface AdminProvider
|
||||
public void getConfigInfo (ClientObject caller, AdminService.ConfigInfoListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
// 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 = _registry.getKeys();
|
||||
int[] oids = new int[keys.length];
|
||||
for (int ii = 0; ii < keys.length; ii++) {
|
||||
oids[ii] = _registry.getObject(keys[ii]).getOid();
|
||||
}
|
||||
listener.gotConfigInfo(keys, oids);
|
||||
}
|
||||
|
||||
@Inject protected ConfigRegistry _registry;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 javax.annotation.Generated;
|
||||
|
||||
import com.threerings.admin.client.AdminService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
/**
|
||||
* Defines the server-side of the {@link AdminService}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from AdminService.java.")
|
||||
public interface AdminProvider extends InvocationProvider
|
||||
{
|
||||
/**
|
||||
* Handles a {@link AdminService#getConfigInfo} request.
|
||||
*/
|
||||
void getConfigInfo (ClientObject caller, AdminService.ConfigInfoListener arg1)
|
||||
throws InvocationException;
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
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 static com.threerings.admin.Log.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
|
||||
{
|
||||
/**
|
||||
* Creates a ConfigRegistry that isn't transitioning.
|
||||
*/
|
||||
public ConfigRegistry ()
|
||||
{
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ConfigRegistry.
|
||||
*
|
||||
* @param transitioning if true, serialized Streamable instances stored in the registry will
|
||||
* be written back out immediately to allow them to be transitioned to new class names.
|
||||
*/
|
||||
public ConfigRegistry (boolean transitioning)
|
||||
{
|
||||
_transitioning = transitioning;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Create an ObjectInputStream to read serialized config entries.
|
||||
*/
|
||||
protected ObjectInputStream createObjectInputStream (InputStream bin)
|
||||
{
|
||||
return new ObjectInputStream(bin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ObjectOutputStream to write serialized config entries.
|
||||
*/
|
||||
protected ObjectOutputStream createObjectOutputStream (OutputStream bin)
|
||||
{
|
||||
return new ObjectOutputStream(bin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains all necessary info for a configuration object registration.
|
||||
*/
|
||||
protected abstract class ObjectRecord
|
||||
implements AttributeChangeListener, SetListener<DSet.Entry>, ElementUpdateListener
|
||||
{
|
||||
public DObject object;
|
||||
|
||||
public ObjectRecord (DObject obj)
|
||||
{
|
||||
object = obj;
|
||||
}
|
||||
|
||||
public void init ()
|
||||
{
|
||||
// read in the initial configuration settings from the persistent config repository
|
||||
Class<?> cclass = object.getClass();
|
||||
try {
|
||||
Field[] fields = cclass.getFields();
|
||||
for (Field field : fields) {
|
||||
int mods = field.getModifiers();
|
||||
if ((mods & Modifier.STATIC) != 0 || (mods & Modifier.PUBLIC) == 0 ||
|
||||
(mods & Modifier.TRANSIENT) != 0) {
|
||||
continue;
|
||||
}
|
||||
initField(field);
|
||||
}
|
||||
|
||||
// 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<DSet.Entry> event)
|
||||
{
|
||||
serializeAttribute(event.getName());
|
||||
}
|
||||
|
||||
// from SetListener
|
||||
public void entryUpdated (EntryUpdatedEvent<DSet.Entry> event)
|
||||
{
|
||||
serializeAttribute(event.getName());
|
||||
}
|
||||
|
||||
// from SetListener
|
||||
public void entryRemoved (EntryRemovedEvent<DSet.Entry> 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 Byte) {
|
||||
setValue(key, ((Byte)value).byteValue());
|
||||
} 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(Byte.TYPE)) {
|
||||
byte defval = field.getByte(object);
|
||||
field.setByte(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 = createObjectInputStream(bin);
|
||||
Object deserializedValue = oin.readObject();
|
||||
field.set(object, deserializedValue);
|
||||
if (_transitioning) {
|
||||
// Use serialize rather than serializeAttribute so we don't get
|
||||
// ObjectAccessExceptions
|
||||
serialize(key, nameToKey(key), deserializedValue);
|
||||
}
|
||||
} 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 = createObjectOutputStream(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 byte getValue (String field, byte 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, byte 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 = Maps.newHashMap();
|
||||
|
||||
/** If we need to transition serialized Streamables to a new class format in init.. */
|
||||
protected boolean _transitioning;
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.google.common.collect.Maps;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.samskivert.jdbc.WriteOnlyUnit;
|
||||
import com.samskivert.depot.DatabaseException;
|
||||
import com.samskivert.depot.PersistenceContext;
|
||||
|
||||
import com.threerings.presents.annotation.MainInvoker;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
|
||||
import com.threerings.admin.server.persist.ConfigRepository;
|
||||
|
||||
import static com.threerings.admin.Log.log;
|
||||
|
||||
/**
|
||||
* Implements the {@link ConfigRegistry} using a JDBC database as a persistent store for the
|
||||
* configuration information. <em>Note:</em> config objects should only be created during server
|
||||
* startup because they will result in synchronous requests to load up the initial configuration
|
||||
* data from the database. This ensures that systems initialized after the config registry can
|
||||
* safely make use of configuration information.
|
||||
*/
|
||||
@Singleton
|
||||
public class DatabaseConfigRegistry extends ConfigRegistry
|
||||
{
|
||||
/**
|
||||
* Creates a configuration registry and prepares it for operation.
|
||||
*
|
||||
* @param ctx will provide access to our database.
|
||||
* @param invoker this will be used to perform all database activity (except first time
|
||||
* initialization) so as to avoid blocking the distributed object thread.
|
||||
*/
|
||||
@Inject public DatabaseConfigRegistry (PersistenceContext ctx, @MainInvoker Invoker invoker)
|
||||
{
|
||||
this(ctx, invoker, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration registry and prepares it for operation.
|
||||
*
|
||||
* @param ctx will provide access to our database.
|
||||
* @param invoker this will be used to perform all database activity (except first time
|
||||
* initialization) so as to avoid blocking the distributed object thread.
|
||||
* @param transitioning if the values in the database need to be transitioned to a new format
|
||||
*/
|
||||
public DatabaseConfigRegistry (PersistenceContext ctx, Invoker invoker, boolean transitioning)
|
||||
{
|
||||
this(ctx, invoker, "", transitioning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration registry and prepares it for operation.
|
||||
*
|
||||
* @param ctx will provide access to our database.
|
||||
* @param invoker this will be used to perform all database activity (except first time
|
||||
* initialization) so as to avoid blocking the distributed object thread.
|
||||
* @param node if this config registry is accessed by multiple servers which wish to maintain
|
||||
* separate configs, then specify a node for each server
|
||||
*/
|
||||
public DatabaseConfigRegistry (PersistenceContext ctx, Invoker invoker, String node)
|
||||
{
|
||||
this(ctx, invoker, node, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration registry and prepares it for operation.
|
||||
*
|
||||
* @param ctx will provide access to our database.
|
||||
* @param invoker this will be used to perform all database activity (except first time
|
||||
* initialization) so as to avoid blocking the distributed object thread.
|
||||
* @param node if this config registry is accessed by multiple servers which wish to maintain
|
||||
* separate configs, then specify a node for each server
|
||||
* @param transitioning if the values in the database need to be transitioned to a new format
|
||||
*/
|
||||
public DatabaseConfigRegistry (PersistenceContext ctx, Invoker invoker, String node,
|
||||
boolean transitioning)
|
||||
{
|
||||
super(transitioning);
|
||||
_repo = new ConfigRepository(ctx);
|
||||
_invoker = invoker;
|
||||
_node = StringUtil.isBlank(node) ? "" : node;
|
||||
}
|
||||
|
||||
@Override // from ConfigRegistry
|
||||
protected ObjectRecord createObjectRecord (String path, DObject object)
|
||||
{
|
||||
return new DatabaseObjectRecord(path, object);
|
||||
}
|
||||
|
||||
/** Stores settings in a database. */
|
||||
protected class DatabaseObjectRecord extends ObjectRecord
|
||||
{
|
||||
public DatabaseObjectRecord (String path, DObject object)
|
||||
{
|
||||
super(object);
|
||||
_path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init ()
|
||||
{
|
||||
// load up our persistent data synchronously because we should be in the middle of
|
||||
// server startup when it's OK to do database access on the main thread and we need to
|
||||
// be completely initialized when we return from this call so that subsequent systems
|
||||
// can predictably make use of the configuration information that we load
|
||||
try {
|
||||
_data = _repo.loadConfig(_node, _path);
|
||||
} catch (DatabaseException pe) {
|
||||
log.warning("Failed to load object configuration", "path", _path, pe);
|
||||
_data = Maps.newHashMap();
|
||||
}
|
||||
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean getValue (String field, boolean defval) {
|
||||
String value = _data.get(field);
|
||||
if (value != null) {
|
||||
return "true".equalsIgnoreCase(value);
|
||||
}
|
||||
return defval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte getValue (String field, byte defval) {
|
||||
String value = _data.get(field);
|
||||
try {
|
||||
if (value != null) {
|
||||
return Byte.parseByte(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore bogus values and return the default
|
||||
}
|
||||
return defval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short getValue (String field, short defval) {
|
||||
String value = _data.get(field);
|
||||
try {
|
||||
if (value != null) {
|
||||
return Short.parseShort(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore bogus values and return the default
|
||||
}
|
||||
return defval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getValue (String field, int defval) {
|
||||
String value = _data.get(field);
|
||||
try {
|
||||
if (value != null) {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore bogus values and return the default
|
||||
}
|
||||
return defval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getValue (String field, long defval) {
|
||||
String value = _data.get(field);
|
||||
try {
|
||||
if (value != null) {
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore bogus values and return the default
|
||||
}
|
||||
return defval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getValue (String field, float defval) {
|
||||
String value = _data.get(field);
|
||||
try {
|
||||
if (value != null) {
|
||||
return Float.parseFloat(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore bogus values and return the default
|
||||
}
|
||||
return defval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getValue (String field, String defval) {
|
||||
String value = _data.get(field);
|
||||
return (value == null) ? defval : value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] getValue (String field, int[] defval) {
|
||||
String value = _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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float[] getValue (String field, float[] defval) {
|
||||
String value = _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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long[] getValue (String field, long[] defval) {
|
||||
String value = _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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getValue (String field, String[] defval) {
|
||||
String value = _data.get(field);
|
||||
try {
|
||||
if (value != null) {
|
||||
return StringUtil.parseStringArray(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore bogus values and return the default
|
||||
}
|
||||
return defval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setValue (String field, boolean value) {
|
||||
setAndFlush(field, String.valueOf(value));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, byte value) {
|
||||
setAndFlush(field, String.valueOf(value));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, short value) {
|
||||
setAndFlush(field, String.valueOf(value));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, int value) {
|
||||
setAndFlush(field, String.valueOf(value));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, long value) {
|
||||
setAndFlush(field, String.valueOf(value));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, float value) {
|
||||
setAndFlush(field, String.valueOf(value));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, String value) {
|
||||
setAndFlush(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, int[] value) {
|
||||
setAndFlush(field, StringUtil.toString(value, "", ""));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, float[] value) {
|
||||
setAndFlush(field, StringUtil.toString(value, "", ""));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, long[] value) {
|
||||
setAndFlush(field, StringUtil.toString(value, "", ""));
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, String[] value) {
|
||||
setAndFlush(field, StringUtil.joinEscaped(value));
|
||||
}
|
||||
|
||||
protected void setAndFlush (String field, String value) {
|
||||
_data.put(field, value);
|
||||
flush(field, value);
|
||||
}
|
||||
|
||||
protected void flush (final String field, final String value) {
|
||||
String iname = "updateConfig(" + _path + ", " + field + ", value=" + value + ")";
|
||||
_invoker.postUnit(new WriteOnlyUnit(iname) {
|
||||
@Override
|
||||
public void invokePersist () throws Exception {
|
||||
_repo.updateConfig(_node, _path, field, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected String _path;
|
||||
protected HashMap<String, String> _data;
|
||||
}
|
||||
|
||||
protected ConfigRepository _repo;
|
||||
protected Invoker _invoker;
|
||||
protected String _node;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.ArrayList;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Invoker;
|
||||
|
||||
import com.samskivert.depot.PersistenceContext;
|
||||
|
||||
import com.threerings.io.Streamable;
|
||||
|
||||
import com.threerings.util.StreamableTuple;
|
||||
|
||||
import com.threerings.presents.annotation.MainInvoker;
|
||||
import com.threerings.presents.dobj.AttributeChangedEvent;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.peer.server.PeerManager;
|
||||
|
||||
import static com.threerings.admin.Log.log;
|
||||
|
||||
/**
|
||||
* A database backed config registry that registers with the peer system and synchronizes with its
|
||||
* peers when configuration fields are changed.
|
||||
*/
|
||||
@Singleton
|
||||
public class PeeredDatabaseConfigRegistry extends DatabaseConfigRegistry
|
||||
{
|
||||
@Inject public PeeredDatabaseConfigRegistry (
|
||||
PersistenceContext ctx, @MainInvoker Invoker invoker, PeerManager peermgr)
|
||||
{
|
||||
super(ctx, invoker, "");
|
||||
_peermgr = peermgr;
|
||||
}
|
||||
|
||||
@Override // from ConfigRegistry
|
||||
protected ObjectRecord createObjectRecord (String path, DObject object)
|
||||
{
|
||||
return new PeerDatabaseObjectRecord(path, object);
|
||||
}
|
||||
|
||||
/** Stores settings in a database and broadcasts changes to peers. */
|
||||
protected class PeerDatabaseObjectRecord extends DatabaseObjectRecord
|
||||
implements PeerManager.StaleCacheObserver
|
||||
{
|
||||
public PeerDatabaseObjectRecord (String path, DObject object)
|
||||
{
|
||||
super(path, object);
|
||||
_peermgr.addStaleCacheObserver(PEER_CACHE_PREFIX + _path, this);
|
||||
}
|
||||
|
||||
// from interface PeerManager.StaleCacheObserver
|
||||
public void changedCacheData (Streamable data)
|
||||
{
|
||||
@SuppressWarnings("unchecked") StreamableTuple<String, Object> change =
|
||||
(StreamableTuple<String, Object>)data;
|
||||
|
||||
// note that we should ignore the attribute change event we're about to generate
|
||||
// because it is not a real configuration change but rather a sync
|
||||
try {
|
||||
object.changeAttribute(change.left, change.right);
|
||||
_pendingSyncs.add(change.left);
|
||||
} catch (Exception e) {
|
||||
log.warning("Config attribute sync failed " + change + ".", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from ObjectRecord
|
||||
public void attributeChanged (AttributeChangedEvent event)
|
||||
{
|
||||
// if this was a pending sync event, don't pass it to our parent as it is not a real
|
||||
// configuration change event
|
||||
if (!_pendingSyncs.remove(event.getName())) {
|
||||
super.attributeChanged(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from ObjectRecord
|
||||
protected void updateValue (String name, Object value)
|
||||
{
|
||||
super.updateValue(name, value);
|
||||
fieldUpdated(name, value);
|
||||
}
|
||||
|
||||
@Override // from ObjectRecord
|
||||
protected void serialize (String name, String key, Object value)
|
||||
{
|
||||
super.serialize(name, key, value);
|
||||
fieldUpdated(name, value);
|
||||
}
|
||||
|
||||
protected void fieldUpdated (String field, Object value)
|
||||
{
|
||||
// broadcast to the other nodes that this value has changed
|
||||
_peermgr.broadcastStaleCacheData(
|
||||
PEER_CACHE_PREFIX + _path, new StreamableTuple<String, Object>(field, value));
|
||||
}
|
||||
|
||||
protected ArrayList<String> _pendingSyncs = Lists.newArrayList();
|
||||
}
|
||||
|
||||
protected PeerManager _peermgr;
|
||||
|
||||
/** Prefixed to our cache invalidation notifications. */
|
||||
protected static final String PEER_CACHE_PREFIX = "PeerConfigCache:";
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Config;
|
||||
import com.samskivert.util.PrefsConfig;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
|
||||
/**
|
||||
* Implements the {@link ConfigRegistry} using the Java preferences system as a persistent store
|
||||
* for the configuration information (see {@link Config} for more information on how that works).
|
||||
*/
|
||||
@Singleton
|
||||
public class PrefsConfigRegistry extends ConfigRegistry
|
||||
{
|
||||
@Override // from ConfigRegistry
|
||||
protected ObjectRecord createObjectRecord (String path, DObject object)
|
||||
{
|
||||
return new PrefsObjectRecord(path, object);
|
||||
}
|
||||
|
||||
/** Stores preferences using the Java preferences system. */
|
||||
protected class PrefsObjectRecord extends ObjectRecord
|
||||
{
|
||||
public PrefsConfig config;
|
||||
|
||||
public PrefsObjectRecord (String path, DObject object)
|
||||
{
|
||||
super(object);
|
||||
this.config = new PrefsConfig(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean getValue (String field, boolean defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected byte getValue (String field, byte defval) {
|
||||
return (byte)config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected short getValue (String field, short defval) {
|
||||
return (short)config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected int getValue (String field, int defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected long getValue (String field, long defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected float getValue (String field, float defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected String getValue (String field, String defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected int[] getValue (String field, int[] defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected float[] getValue (String field, float[] defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected long[] getValue (String field, long[] defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
@Override
|
||||
protected String[] getValue (String field, String[] defval) {
|
||||
return config.getValue(field, defval);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setValue (String field, boolean value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, byte value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, short value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, int value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, long value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, float value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, String value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, int[] value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, float[] value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, long[] value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
@Override
|
||||
protected void setValue (String field, String[] value) {
|
||||
config.setValue(field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 node;
|
||||
public String object;
|
||||
public String field;
|
||||
public String value;
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return node + "." + object + "." + field + "=" + value + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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 com.samskivert.depot.Key;
|
||||
import com.samskivert.depot.PersistentRecord;
|
||||
import com.samskivert.depot.annotation.Column;
|
||||
import com.samskivert.depot.annotation.Entity;
|
||||
import com.samskivert.depot.annotation.Id;
|
||||
import com.samskivert.depot.expression.ColumnExp;
|
||||
|
||||
/**
|
||||
* Stores information about a configuration entry in the database.
|
||||
*/
|
||||
@Entity(name="CONFIG")
|
||||
public class ConfigRecord extends PersistentRecord
|
||||
{
|
||||
// AUTO-GENERATED: FIELDS START
|
||||
public static final Class<ConfigRecord> _R = ConfigRecord.class;
|
||||
public static final ColumnExp NODE = colexp(_R, "node");
|
||||
public static final ColumnExp OBJECT = colexp(_R, "object");
|
||||
public static final ColumnExp FIELD = colexp(_R, "field");
|
||||
public static final ColumnExp VALUE = colexp(_R, "value");
|
||||
// AUTO-GENERATED: FIELDS END
|
||||
|
||||
public static final int SCHEMA_VERSION = 2;
|
||||
|
||||
@Id
|
||||
@Column(name="NODE", length=64)
|
||||
public String node;
|
||||
|
||||
@Id
|
||||
@Column(name="OBJECT", length=128)
|
||||
public String object;
|
||||
|
||||
@Id
|
||||
@Column(name="FIELD", length=64)
|
||||
public String field;
|
||||
|
||||
@Column(name="VALUE", length=65535)
|
||||
public String value;
|
||||
|
||||
/**
|
||||
* An empty constructor for unmarshalling.
|
||||
*/
|
||||
public ConfigRecord ()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public ConfigRecord (String node, String object, String field, String value)
|
||||
{
|
||||
super();
|
||||
this.node = node;
|
||||
this.object = object;
|
||||
this.field = field;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
// AUTO-GENERATED: METHODS START
|
||||
/**
|
||||
* Create and return a primary {@link Key} to identify a {@link ConfigRecord}
|
||||
* with the supplied key values.
|
||||
*/
|
||||
public static Key<ConfigRecord> getKey (String node, String object, String field)
|
||||
{
|
||||
return new Key<ConfigRecord>(
|
||||
ConfigRecord.class,
|
||||
new ColumnExp[] { NODE, OBJECT, FIELD },
|
||||
new Comparable[] { node, object, field });
|
||||
}
|
||||
// AUTO-GENERATED: METHODS END
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.depot.DepotRepository;
|
||||
import com.samskivert.depot.PersistenceContext;
|
||||
import com.samskivert.depot.PersistentRecord;
|
||||
import com.samskivert.depot.clause.Where;
|
||||
|
||||
/**
|
||||
* Stores configuration information in a database table.
|
||||
*/
|
||||
public class ConfigRepository extends DepotRepository
|
||||
{
|
||||
/**
|
||||
* Constructs a new config repository with the specified persistence context.
|
||||
*/
|
||||
public ConfigRepository (PersistenceContext ctx)
|
||||
{
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up the configuration data for the specified object.
|
||||
*
|
||||
* @return a map containing field/value pairs for all stored configuration data.
|
||||
*/
|
||||
public HashMap<String, String> loadConfig (String node, String object)
|
||||
{
|
||||
HashMap<String, String> data = Maps.newHashMap();
|
||||
Where where = new Where(ConfigRecord.OBJECT, object, ConfigRecord.NODE, node);
|
||||
for (ConfigRecord record : findAll(ConfigRecord.class, where)) {
|
||||
data.put(record.field, record.value);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the specified configuration datum.
|
||||
*/
|
||||
public void updateConfig (String node, String object, String field, String value)
|
||||
{
|
||||
store(new ConfigRecord(node, object, field, value));
|
||||
}
|
||||
|
||||
@Override // from DepotRepository
|
||||
protected void getManagedRecords (Set<Class<? extends PersistentRecord>> classes)
|
||||
{
|
||||
classes.add(ConfigRecord.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<module>
|
||||
<inherits name="com.threerings.web.Base"/>
|
||||
|
||||
<source path="client"/>
|
||||
<source path="gwt"/>
|
||||
</module>
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.web.client;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.rpc.ServiceDefTarget;
|
||||
import com.google.gwt.user.client.ui.TabPanel;
|
||||
|
||||
import com.threerings.admin.web.client.ConfigEditorTab.ConfigAccessor;
|
||||
import com.threerings.admin.web.gwt.ConfigField;
|
||||
import com.threerings.admin.web.gwt.ConfigService;
|
||||
import com.threerings.admin.web.gwt.ConfigService.ConfigurationRecord;
|
||||
import com.threerings.admin.web.gwt.ConfigService.ConfigurationResult;
|
||||
import com.threerings.admin.web.gwt.ConfigServiceAsync;
|
||||
import com.threerings.gwt.util.PopupCallback;
|
||||
|
||||
|
||||
/**
|
||||
* The main panel of the configuration editor. All service calls are routed through here.
|
||||
* Subclass this class in your project.
|
||||
*/
|
||||
public abstract class ConfigEditorPanel extends TabPanel
|
||||
implements ConfigAccessor
|
||||
{
|
||||
public ConfigEditorPanel ()
|
||||
{
|
||||
addStyleName("configEditorPanel");
|
||||
((ServiceDefTarget)_configsvc).setServiceEntryPoint(getServiceEntryPoint());
|
||||
_configsvc.getConfiguration(new PopupCallback<ConfigurationResult>() {
|
||||
public void onSuccess (ConfigurationResult result) {
|
||||
gotData(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void submitChanges (String key, ConfigField[] modified,
|
||||
AsyncCallback<ConfigurationRecord> callback)
|
||||
{
|
||||
_configsvc.updateConfiguration(key, modified, callback);
|
||||
}
|
||||
|
||||
protected void gotData (ConfigurationResult result)
|
||||
{
|
||||
clear();
|
||||
|
||||
if (result.records.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Entry<String, ConfigurationRecord> tab : result.records.entrySet()) {
|
||||
String tabKey = tab.getKey();
|
||||
ConfigEditorTab widget = new ConfigEditorTab(this, tabKey, tab.getValue());
|
||||
_tabs.put(tabKey, widget);
|
||||
add(widget, tabKey);
|
||||
}
|
||||
selectTab(0);
|
||||
}
|
||||
|
||||
/** Should return the absolute path of the servlet that implements {@link ConfigService}. */
|
||||
protected abstract String getServiceEntryPoint();
|
||||
|
||||
protected Map<String, ConfigEditorTab> _tabs = Maps.newHashMap();
|
||||
|
||||
protected static final ConfigServiceAsync _configsvc = GWT.create(ConfigService.class);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.web.client;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.google.gwt.user.client.Command;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.ui.Button;
|
||||
|
||||
import com.threerings.admin.web.gwt.ConfigField;
|
||||
import com.threerings.gwt.ui.InfoPopup;
|
||||
import com.threerings.gwt.ui.SmartTable;
|
||||
|
||||
import com.threerings.admin.web.gwt.ConfigService.ConfigurationRecord;
|
||||
|
||||
import com.threerings.gwt.util.ClickCallback;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ConfigEditorTab extends SmartTable
|
||||
{
|
||||
public interface ConfigAccessor
|
||||
{
|
||||
void submitChanges (String key, ConfigField[] modified,
|
||||
AsyncCallback<ConfigurationRecord> callback);
|
||||
}
|
||||
|
||||
public ConfigEditorTab (ConfigAccessor parent, String key, ConfigurationRecord record)
|
||||
{
|
||||
super("configEditorTab", 5, 5);
|
||||
|
||||
_parent = parent;
|
||||
_key = key;
|
||||
|
||||
_submit = new Button("Submit Changes");
|
||||
|
||||
// wire up saving the code on click
|
||||
new ClickCallback<ConfigurationRecord>(_submit) {
|
||||
protected boolean callService () {
|
||||
List<ConfigField> modified = Lists.newArrayList();
|
||||
for (ConfigFieldEditor editor : _editors) {
|
||||
ConfigField field = editor.getModifiedField();
|
||||
if (field != null) {
|
||||
modified.add(field);
|
||||
}
|
||||
}
|
||||
_parent.submitChanges(
|
||||
_key, modified.toArray(new ConfigField[modified.size()]), this);
|
||||
return true;
|
||||
}
|
||||
protected boolean gotResult (ConfigurationRecord result) {
|
||||
new InfoPopup("Updated " + result.updates + " fields.").show();
|
||||
updateTable(result);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
cell(1, 1).alignRight().widget(_submit);
|
||||
|
||||
updateTable(record);
|
||||
}
|
||||
|
||||
protected void updateTable (ConfigurationRecord record)
|
||||
{
|
||||
SmartTable table = new SmartTable(5, 5);
|
||||
table.setStyleName("configEditorTable");
|
||||
|
||||
int row = 0;
|
||||
for (ConfigField field : record.fields) {
|
||||
ConfigFieldEditor editor = ConfigFieldEditor.getEditorFor(field, UPDATE_BUTTON);
|
||||
_editors.add(editor);
|
||||
table.cell(row, 0).alignRight().widget(editor.getNameWidget());
|
||||
table.cell(row, 1).alignLeft().widget(editor.getValueWidget());
|
||||
table.cell(row, 2).alignLeft().widget(editor.getResetWidget());
|
||||
row ++;
|
||||
}
|
||||
|
||||
cell(0, 0).colSpan(2).widget(table);
|
||||
|
||||
UPDATE_BUTTON.execute();
|
||||
}
|
||||
|
||||
protected List<ConfigFieldEditor> _editors = Lists.newArrayList();
|
||||
|
||||
protected ConfigAccessor _parent;
|
||||
protected String _key;
|
||||
protected Button _submit;
|
||||
|
||||
protected Command UPDATE_BUTTON = new Command () {
|
||||
public void execute () {
|
||||
// search for any modified field; if found, enable submissions & exit
|
||||
for (ConfigFieldEditor editor : _editors) {
|
||||
if (editor.getModifiedField() != null) {
|
||||
_submit.setEnabled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_submit.setEnabled(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.web.client;
|
||||
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.threerings.admin.web.gwt.ConfigField;
|
||||
import com.threerings.admin.web.gwt.ConfigField.FieldType;
|
||||
|
||||
import com.google.gwt.dom.client.Style;
|
||||
|
||||
import com.google.gwt.event.dom.client.ChangeEvent;
|
||||
import com.google.gwt.event.dom.client.ChangeHandler;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
|
||||
import com.google.gwt.event.logical.shared.ValueChangeEvent;
|
||||
import com.google.gwt.event.logical.shared.ValueChangeHandler;
|
||||
|
||||
import com.google.gwt.user.client.Command;
|
||||
import com.google.gwt.user.client.ui.CheckBox;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
|
||||
/**
|
||||
* A class responsible for constructing and exporting the widgets involved in manipulating
|
||||
* one specific configuration field.
|
||||
*/
|
||||
public abstract class ConfigFieldEditor
|
||||
{
|
||||
public static ConfigFieldEditor getEditorFor (ConfigField field, Command onChange)
|
||||
{
|
||||
if (field.type == FieldType.BOOLEAN) {
|
||||
return new CheckboxFieldEditor(field, onChange);
|
||||
}
|
||||
return new StringFieldEditor(field, onChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* This editor represents values as strings.
|
||||
*/
|
||||
protected static class StringFieldEditor extends ConfigFieldEditor
|
||||
{
|
||||
public StringFieldEditor (ConfigField field, Command onChange) {
|
||||
super(field, onChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Widget buildWidget (ConfigField field) {
|
||||
_box = new TextBox();
|
||||
_box.setStyleName("configStringEditor");
|
||||
_box.setVisibleLength(40);
|
||||
resetField();
|
||||
|
||||
_box.addChangeHandler(new ChangeHandler() {
|
||||
public void onChange (ChangeEvent changeEvent) {
|
||||
// if the string fails conversion, just reset to the old value
|
||||
if (_field.type.toValue(_box.getText().trim()) == null) {
|
||||
_box.setText(_field.valStr);
|
||||
}
|
||||
updateModificationState();
|
||||
}
|
||||
});
|
||||
return _box;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigField getModifiedField () {
|
||||
Object newValue = _field.type.toValue(_box.getText().trim());
|
||||
if (newValue == null) {
|
||||
return null;
|
||||
}
|
||||
String newValStr = newValue.toString();
|
||||
if ((newValStr.length() == 0 && _field.valStr == null)
|
||||
|| newValStr.equals(_field.valStr)) {
|
||||
return null;
|
||||
}
|
||||
return new ConfigField(_field.name, _field.type, newValStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetField () {
|
||||
_box.setText(_field.valStr);
|
||||
}
|
||||
|
||||
protected TextBox _box;
|
||||
}
|
||||
|
||||
/**
|
||||
* This editor represents boolean values as checkboxes.
|
||||
*/
|
||||
protected static class CheckboxFieldEditor extends ConfigFieldEditor
|
||||
{
|
||||
public CheckboxFieldEditor (ConfigField field, Command onChange) {
|
||||
super(field, onChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Widget buildWidget (ConfigField field) {
|
||||
_box = new CheckBox();
|
||||
_box.setStyleName("configCheckBoxEditor");
|
||||
resetField();
|
||||
|
||||
_box.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
|
||||
public void onValueChange (ValueChangeEvent<Boolean> changeEvent) {
|
||||
updateModificationState();
|
||||
}
|
||||
});
|
||||
return _box;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigField getModifiedField () {
|
||||
String newValStr = Boolean.toString(_box.getValue());
|
||||
if (newValStr.equals(_field.valStr)) {
|
||||
return null;
|
||||
}
|
||||
return new ConfigField(_field.name, _field.type, newValStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetField () {
|
||||
_box.setValue(Boolean.valueOf(_field.valStr));
|
||||
}
|
||||
|
||||
protected CheckBox _box;
|
||||
}
|
||||
|
||||
public ConfigFieldEditor (ConfigField field, Command onChange)
|
||||
{
|
||||
_field = field;
|
||||
_onChange = onChange;
|
||||
|
||||
_value = buildWidget(field);
|
||||
|
||||
_name = new Label(field.name);
|
||||
_name.setStyleName("fieldName");
|
||||
|
||||
_reset = new Label("X");
|
||||
_reset.setStyleName("resetButton");
|
||||
_reset.addClickHandler(new ClickHandler() {
|
||||
public void onClick (ClickEvent event) {
|
||||
resetField();
|
||||
updateModificationState();
|
||||
}
|
||||
});
|
||||
_reset.setVisible(false);
|
||||
}
|
||||
|
||||
protected void updateModificationState ()
|
||||
{
|
||||
Style style = _value.getElement().getStyle();
|
||||
if (getModifiedField() != null) {
|
||||
style.setBackgroundColor("red");
|
||||
_reset.setVisible(true);
|
||||
|
||||
} else {
|
||||
style.clearBackgroundColor();
|
||||
_reset.setVisible(false);
|
||||
}
|
||||
_onChange.execute();
|
||||
}
|
||||
|
||||
public Widget getNameWidget ()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public Widget getValueWidget ()
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
public Widget getResetWidget ()
|
||||
{
|
||||
return _reset;
|
||||
}
|
||||
|
||||
public abstract ConfigField getModifiedField ();
|
||||
|
||||
protected abstract Widget buildWidget (ConfigField field);
|
||||
protected abstract void resetField ();
|
||||
|
||||
protected ConfigField _field;
|
||||
protected Command _onChange;
|
||||
|
||||
protected Label _name, _reset;
|
||||
protected Widget _value;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.web.gwt;
|
||||
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
|
||||
/**
|
||||
* A GWT-friendly representation of a configuration tuple, consisting of the name of the entry,
|
||||
* a type enum, and the toString() of the value.
|
||||
*/
|
||||
public class ConfigField
|
||||
implements IsSerializable, Comparable<ConfigField>
|
||||
{
|
||||
public enum FieldType
|
||||
implements IsSerializable
|
||||
{
|
||||
INTEGER,
|
||||
SHORT,
|
||||
BYTE,
|
||||
LONG,
|
||||
FLOAT,
|
||||
BOOLEAN,
|
||||
DOUBLE,
|
||||
STRING;
|
||||
|
||||
/**
|
||||
* Create a string representation of the given value, which should be of the type
|
||||
* reflected in this enum.
|
||||
*/
|
||||
public String toString (Object value)
|
||||
{
|
||||
return (value != null) ? value.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given string, which should have been created by {@link #toString(Object)},
|
||||
* back into its raw value form.
|
||||
*/
|
||||
public Object toValue (String text)
|
||||
{
|
||||
switch(this) {
|
||||
case INTEGER:
|
||||
return new Integer(text);
|
||||
case SHORT:
|
||||
return new Short(text);
|
||||
case BYTE:
|
||||
return new Byte(text);
|
||||
case LONG:
|
||||
return new Long(text);
|
||||
case FLOAT:
|
||||
return new Float(text);
|
||||
case DOUBLE:
|
||||
return new Double(text);
|
||||
case BOOLEAN:
|
||||
return new Boolean(text);
|
||||
case STRING:
|
||||
return text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String name;
|
||||
public FieldType type;
|
||||
public String valStr;
|
||||
|
||||
/** Deserialization constructor. */
|
||||
public ConfigField ()
|
||||
{
|
||||
}
|
||||
|
||||
/** Construct a new ConfigField with the given values. */
|
||||
public ConfigField (String name, FieldType type, String valStr)
|
||||
{
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.valStr = valStr;
|
||||
}
|
||||
|
||||
// from Comparable<ConfigField>
|
||||
public int compareTo (ConfigField o)
|
||||
{
|
||||
return ComparisonChain.start().compare(name, o.name).result();
|
||||
}
|
||||
|
||||
@Override // from Object
|
||||
public boolean equals (Object o)
|
||||
{
|
||||
return compareTo((ConfigField) o) == 0;
|
||||
}
|
||||
|
||||
@Override // from Object
|
||||
public int hashCode ()
|
||||
{
|
||||
return name.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.web.gwt;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.google.gwt.user.client.rpc.RemoteService;
|
||||
|
||||
import com.threerings.web.gwt.ServiceException;
|
||||
|
||||
/**
|
||||
* Defines remote services available to admins.
|
||||
*/
|
||||
public interface ConfigService extends RemoteService
|
||||
{
|
||||
/**
|
||||
* The current runtime configuration of a server, a collection of {@link ConfigurationRecord}
|
||||
* objects indexed by key.
|
||||
*/
|
||||
public static class ConfigurationResult
|
||||
implements IsSerializable
|
||||
{
|
||||
public Map<String, ConfigurationRecord> records;
|
||||
}
|
||||
|
||||
/**
|
||||
* The runtime configuration of a single {@link com.threerings.admin.data.ConfigObject}.
|
||||
*/
|
||||
public static class ConfigurationRecord
|
||||
implements IsSerializable
|
||||
{
|
||||
public ConfigField[] fields;
|
||||
public int updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the runtime configuration held by the server and return it in a format
|
||||
* that is digestible by GWT.
|
||||
*/
|
||||
public ConfigurationResult getConfiguration () throws ServiceException;
|
||||
|
||||
/**
|
||||
* Submit a collection of updated fields to the server for application to its runtime
|
||||
* configuration. A new snapshot of the configuration state is returned for sanity checking
|
||||
* purposes.
|
||||
*/
|
||||
public ConfigurationRecord updateConfiguration (String key, ConfigField[] updates)
|
||||
throws ServiceException;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.web.gwt;
|
||||
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
|
||||
import com.threerings.admin.web.gwt.ConfigService.ConfigurationRecord;
|
||||
import com.threerings.admin.web.gwt.ConfigService.ConfigurationResult;
|
||||
|
||||
/**
|
||||
* Provides the asynchronous version of {@link com.threerings.admin.web.gwt.AdminService}.
|
||||
*/
|
||||
public interface ConfigServiceAsync
|
||||
{
|
||||
/**
|
||||
* The async version of {@link com.threerings.admin.web.gwt.ConfigService#getConfig}.
|
||||
*/
|
||||
public void getConfiguration (AsyncCallback<ConfigurationResult> callback);
|
||||
|
||||
/**
|
||||
* The async version of {@link com.threerings.admin.web.gwt.ConfigService#updateConfiguration}.
|
||||
*/
|
||||
public void updateConfiguration (
|
||||
String key, ConfigField[] updates, AsyncCallback<ConfigurationRecord> callback);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.web.server;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.threerings.presents.data.InvocationCodes;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
|
||||
import com.threerings.web.gwt.ServiceException;
|
||||
import com.threerings.web.server.ServletWaiter;
|
||||
|
||||
import com.threerings.admin.server.ConfigRegistry;
|
||||
import com.threerings.admin.web.gwt.ConfigField;
|
||||
import com.threerings.admin.web.gwt.ConfigField.FieldType;
|
||||
import com.threerings.admin.web.gwt.ConfigService;
|
||||
|
||||
import static com.threerings.admin.Log.log;
|
||||
|
||||
/**
|
||||
* Provides the server implementation of {@link ConfigService}.
|
||||
*/
|
||||
public abstract class ConfigServlet extends RemoteServiceServlet
|
||||
implements ConfigService
|
||||
{
|
||||
// from interface ConfigService
|
||||
public ConfigurationResult getConfiguration ()
|
||||
throws ServiceException
|
||||
{
|
||||
requireAdminUser();
|
||||
|
||||
final ServletWaiter<ConfigurationResult> waiter =
|
||||
new ServletWaiter<ConfigurationResult>("getConfiguration");
|
||||
|
||||
_omgr.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
Map<String, ConfigurationRecord> tabs = Maps.newHashMap();
|
||||
for (String key : _confReg.getKeys()) {
|
||||
ConfigurationRecord record = buildRecord(key);
|
||||
if (record == null) {
|
||||
waiter.requestFailed(
|
||||
new ServiceException(InvocationCodes.E_INTERNAL_ERROR));
|
||||
return;
|
||||
}
|
||||
tabs.put(key, record);
|
||||
}
|
||||
|
||||
ConfigurationResult result = new ConfigurationResult();
|
||||
result.records = tabs;
|
||||
waiter.requestCompleted(result);
|
||||
}
|
||||
});
|
||||
|
||||
return waiter.waitForResult();
|
||||
}
|
||||
|
||||
// from interface ConfigService
|
||||
public ConfigurationRecord updateConfiguration (final String key, final ConfigField[] updates)
|
||||
throws ServiceException
|
||||
{
|
||||
requireAdminUser();
|
||||
|
||||
final ServletWaiter<ConfigurationRecord> waiter =
|
||||
new ServletWaiter<ConfigurationRecord>("updateConfiguration");
|
||||
|
||||
_omgr.postRunnable(new Runnable() {
|
||||
public void run () {
|
||||
DObject object = _confReg.getObject(key);
|
||||
object.startTransaction();
|
||||
|
||||
int updateCount = 0;
|
||||
for (ConfigField update : updates) {
|
||||
try {
|
||||
object.changeAttribute(update.name, update.type.toValue(update.valStr));
|
||||
updateCount ++;
|
||||
} catch (ObjectAccessException oae) {
|
||||
log.warning("Failed to update field", "field", update.name, oae);
|
||||
}
|
||||
}
|
||||
object.commitTransaction();
|
||||
|
||||
ConfigurationRecord record = buildRecord(key);
|
||||
record.updates = updateCount;
|
||||
waiter.requestCompleted(record);
|
||||
}
|
||||
});
|
||||
|
||||
return waiter.waitForResult();
|
||||
}
|
||||
|
||||
protected ConfigurationRecord buildRecord (String key)
|
||||
{
|
||||
DObject object = _confReg.getObject(key);
|
||||
List<ConfigField> configFields = Lists.newArrayList();
|
||||
Field[] fields = object.getClass().getFields();
|
||||
for (Field field : fields) {
|
||||
if (field.getModifiers() != Modifier.PUBLIC) {
|
||||
continue;
|
||||
}
|
||||
FieldType type = TYPES.get(field.getType());
|
||||
if (type == null) {
|
||||
log.warning("Unknown field type", "field", field.getName(),
|
||||
"type", field.getType());
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Object value = field.get(object);
|
||||
String valStr = type.toString(value);
|
||||
configFields.add(new ConfigField(field.getName(), type, valStr));
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
log.warning("Failure reflecting on configuration object", "key", key,
|
||||
"object", object, "field", field, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ConfigurationRecord record = new ConfigurationRecord();
|
||||
record.fields = Iterables.toArray(configFields, ConfigField.class);
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented on a project by project basis to provide a security fence for configuration
|
||||
* editing powers.
|
||||
*/
|
||||
protected abstract void requireAdminUser ()
|
||||
throws ServiceException;
|
||||
|
||||
@Inject protected ConfigRegistry _confReg;
|
||||
@Inject protected RootDObjectManager _omgr;
|
||||
|
||||
protected static Map<Class<?>, FieldType> TYPES = ImmutableMap.<Class<?>, FieldType>builder()
|
||||
.put(Integer.class, FieldType.INTEGER)
|
||||
.put(Integer.TYPE, FieldType.INTEGER)
|
||||
.put(Short.class, FieldType.SHORT)
|
||||
.put(Short.TYPE, FieldType.SHORT)
|
||||
.put(Long.class, FieldType.LONG)
|
||||
.put(Long.TYPE, FieldType.LONG)
|
||||
.put(Float.class, FieldType.FLOAT)
|
||||
.put(Float.TYPE, FieldType.FLOAT)
|
||||
.put(Double.class, FieldType.DOUBLE)
|
||||
.put(Double.TYPE, FieldType.DOUBLE)
|
||||
.put(Boolean.class, FieldType.BOOLEAN)
|
||||
.put(Boolean.TYPE, FieldType.BOOLEAN)
|
||||
.put(String.class, FieldType.STRING)
|
||||
.build();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
|
||||
/**
|
||||
* Contains a reference to the log object used by the Bureau services.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static Logger log = Logger.getLogger("com.threerings.bureau");
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.client;
|
||||
|
||||
import com.threerings.bureau.data.AgentObject;
|
||||
|
||||
/**
|
||||
* Represents an agent running within a bureau client.
|
||||
*/
|
||||
public abstract class Agent
|
||||
{
|
||||
/**
|
||||
* Initializes the Agent with the distributed agent object.
|
||||
*/
|
||||
public void init (AgentObject agentObj)
|
||||
{
|
||||
_agentObj = agentObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the code running in the agent.
|
||||
*/
|
||||
public abstract void start ();
|
||||
|
||||
/**
|
||||
* Stops the code running in the agent.
|
||||
*/
|
||||
public abstract void stop ();
|
||||
|
||||
/**
|
||||
* The shared agent object.
|
||||
*/
|
||||
protected AgentObject _agentObj;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.client;
|
||||
|
||||
import com.samskivert.util.Config;
|
||||
import com.samskivert.util.RunQueue;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.dobj.DObjectManager;
|
||||
|
||||
import com.threerings.bureau.data.BureauCredentials;
|
||||
import com.threerings.bureau.util.BureauContext;
|
||||
|
||||
/**
|
||||
* Represents a client embedded in a bureau.
|
||||
*/
|
||||
public abstract class BureauClient extends Client
|
||||
{
|
||||
/**
|
||||
* Creates a new client.
|
||||
* @param runQueue the place to post tasks required by clients
|
||||
*/
|
||||
public BureauClient (String bureauId, String sharedSecret, RunQueue runQueue)
|
||||
{
|
||||
super(null, runQueue);
|
||||
_bureauId = bureauId;
|
||||
_creds = new BureauCredentials(_bureauId, sharedSecret);
|
||||
_ctx = createContext();
|
||||
_director = createDirector();
|
||||
}
|
||||
|
||||
protected abstract BureauDirector createDirector ();
|
||||
|
||||
protected BureauContext createContext ()
|
||||
{
|
||||
return new BureauContext() {
|
||||
public BureauDirector getBureauDirector () {
|
||||
return _director;
|
||||
}
|
||||
public DObjectManager getDObjectManager () {
|
||||
return _omgr;
|
||||
}
|
||||
public Client getClient () {
|
||||
return BureauClient.this;
|
||||
}
|
||||
public Config getConfig () {
|
||||
return _config;
|
||||
}
|
||||
public String getBureauId () {
|
||||
return _bureauId;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected BureauContext _ctx;
|
||||
protected String _bureauId;
|
||||
protected BureauDirector _director;
|
||||
protected Config _config = new Config("bureau");
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.client;
|
||||
|
||||
import com.threerings.presents.client.InvocationDecoder;
|
||||
|
||||
/**
|
||||
* Dispatches calls to a {@link BureauReceiver} instance.
|
||||
*/
|
||||
public class BureauDecoder extends InvocationDecoder
|
||||
{
|
||||
/** The generated hash code used to identify this receiver class. */
|
||||
public static final String RECEIVER_CODE = "3e98f7a30deb5a8e25e05c71c6081bf4";
|
||||
|
||||
/** The method id used to dispatch {@link BureauReceiver#createAgent}
|
||||
* notifications. */
|
||||
public static final int CREATE_AGENT = 1;
|
||||
|
||||
/** The method id used to dispatch {@link BureauReceiver#destroyAgent}
|
||||
* notifications. */
|
||||
public static final int DESTROY_AGENT = 2;
|
||||
|
||||
/**
|
||||
* Creates a decoder that may be registered to dispatch invocation
|
||||
* service notifications to the specified receiver.
|
||||
*/
|
||||
public BureauDecoder (BureauReceiver receiver)
|
||||
{
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReceiverCode ()
|
||||
{
|
||||
return RECEIVER_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchNotification (int methodId, Object[] args)
|
||||
{
|
||||
switch (methodId) {
|
||||
case CREATE_AGENT:
|
||||
((BureauReceiver)receiver).createAgent(
|
||||
((Integer)args[0]).intValue()
|
||||
);
|
||||
return;
|
||||
|
||||
case DESTROY_AGENT:
|
||||
((BureauReceiver)receiver).destroyAgent(
|
||||
((Integer)args[0]).intValue()
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchNotification(methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.client;
|
||||
|
||||
import com.samskivert.util.IntMap;
|
||||
import com.samskivert.util.IntMaps;
|
||||
|
||||
import com.threerings.presents.client.BasicDirector;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
import com.threerings.presents.util.SafeSubscriber;
|
||||
|
||||
import com.threerings.bureau.data.AgentObject;
|
||||
import com.threerings.bureau.data.BureauCodes;
|
||||
import com.threerings.bureau.server.BureauRegistry;
|
||||
import com.threerings.bureau.util.BureauContext;
|
||||
|
||||
import static com.threerings.bureau.Log.log;
|
||||
|
||||
/**
|
||||
* Allows the server to create and destroy agents on a client.
|
||||
* @see BureauRegistry
|
||||
*/
|
||||
public abstract class BureauDirector extends BasicDirector
|
||||
{
|
||||
/**
|
||||
* Creates a new BureauDirector.
|
||||
*/
|
||||
public BureauDirector (BureauContext ctx)
|
||||
{
|
||||
super(ctx);
|
||||
_ctx = ctx;
|
||||
}
|
||||
|
||||
@Override // from BasicDirector
|
||||
public void clientDidLogon (Client client)
|
||||
{
|
||||
super.clientDidLogon(client);
|
||||
_bureauService.bureauInitialized(_ctx.getClient(), _ctx.getBureauId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new agent when the server requests it.
|
||||
*/
|
||||
protected synchronized void createAgent (int agentId)
|
||||
{
|
||||
Subscriber<AgentObject> delegator = new Subscriber<AgentObject>() {
|
||||
public void objectAvailable (AgentObject agentObject) {
|
||||
BureauDirector.this.objectAvailable(agentObject);
|
||||
}
|
||||
public void requestFailed (int oid, ObjectAccessException cause) {
|
||||
BureauDirector.this.requestFailed(oid, cause);
|
||||
}
|
||||
};
|
||||
|
||||
log.info("Subscribing to object " + agentId);
|
||||
|
||||
SafeSubscriber<AgentObject> subscriber =
|
||||
new SafeSubscriber<AgentObject>(agentId, delegator);
|
||||
_subscribers.put(agentId, subscriber);
|
||||
subscriber.subscribe(_ctx.getDObjectManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys an agent at the server's request.
|
||||
*/
|
||||
protected synchronized void destroyAgent (int agentId)
|
||||
{
|
||||
Agent agent = null;
|
||||
agent = _agents.remove(agentId);
|
||||
|
||||
if (agent == null) {
|
||||
log.warning("Lost an agent, id " + agentId);
|
||||
} else {
|
||||
try {
|
||||
agent.stop();
|
||||
} catch (Throwable t) {
|
||||
log.warning("Stopping an agent caused an exception", t);
|
||||
}
|
||||
SafeSubscriber<AgentObject> subscriber = _subscribers.remove(agentId);
|
||||
if (subscriber == null) {
|
||||
log.warning("Lost a subscriber for agent " + agent);
|
||||
} else {
|
||||
subscriber.unsubscribe(_ctx.getDObjectManager());
|
||||
}
|
||||
_bureauService.agentDestroyed(_ctx.getClient(), agentId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the a request to subscribe to an object finishes and the object is
|
||||
* available.
|
||||
*/
|
||||
protected synchronized void objectAvailable (AgentObject agentObject)
|
||||
{
|
||||
int oid = agentObject.getOid();
|
||||
|
||||
log.info("Object " + oid + " now available");
|
||||
|
||||
Agent agent;
|
||||
try {
|
||||
agent = createAgent(agentObject);
|
||||
agent.init(agentObject);
|
||||
agent.start();
|
||||
} catch (Throwable t) {
|
||||
log.warning("Could not create agent", "obj", agentObject, t);
|
||||
_bureauService.agentCreationFailed(_ctx.getClient(), oid);
|
||||
return;
|
||||
}
|
||||
|
||||
_agents.put(oid, agent);
|
||||
_bureauService.agentCreated(_ctx.getClient(), oid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the a request to subscribe to an object fails.
|
||||
*/
|
||||
protected synchronized void requestFailed (int oid, ObjectAccessException cause)
|
||||
{
|
||||
log.warning("Could not subscribe to agent", "oid", oid, cause);
|
||||
}
|
||||
|
||||
@Override // from BasicDirector
|
||||
protected void registerServices (Client client)
|
||||
{
|
||||
super.registerServices(client);
|
||||
|
||||
// Require the bureau services
|
||||
client.addServiceGroup(BureauCodes.BUREAU_GROUP);
|
||||
|
||||
// Set up our decoder so we can receive method calls from the server
|
||||
BureauReceiver receiver = new BureauReceiver() {
|
||||
public void createAgent (int agentId) {
|
||||
BureauDirector.this.createAgent(agentId);
|
||||
}
|
||||
public void destroyAgent (int agentId) {
|
||||
BureauDirector.this.destroyAgent(agentId);
|
||||
}
|
||||
};
|
||||
|
||||
client.getInvocationDirector().
|
||||
registerReceiver(new BureauDecoder(receiver));
|
||||
}
|
||||
|
||||
@Override // from BasicDirector
|
||||
protected void fetchServices (Client client)
|
||||
{
|
||||
super.fetchServices(client);
|
||||
|
||||
_bureauService = client.getService(BureauService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it is time to create an Agent. Subclasses should read the
|
||||
* <code>agentObject</code>'s type and/or properties to determine what kind of Agent to
|
||||
* create.
|
||||
* @param agentObj the distributed and object
|
||||
* @return a new Agent that will govern the distributed object
|
||||
*/
|
||||
protected abstract Agent createAgent (AgentObject agentObj);
|
||||
|
||||
protected BureauContext _ctx;
|
||||
protected BureauService _bureauService;
|
||||
protected IntMap<Agent> _agents = IntMaps.newHashIntMap();
|
||||
protected IntMap<SafeSubscriber<AgentObject>> _subscribers =
|
||||
IntMaps.newHashIntMap();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.client;
|
||||
|
||||
import com.threerings.presents.client.InvocationReceiver;
|
||||
|
||||
import com.threerings.bureau.data.AgentObject;
|
||||
|
||||
/**
|
||||
* Hooks for controlling a previously launched bureau client.
|
||||
*/
|
||||
public interface BureauReceiver extends InvocationReceiver
|
||||
{
|
||||
/**
|
||||
* Creates a new agent. Implementors should create a new {@link Agent} and give it access to
|
||||
* the {@link AgentObject} referred to by the <code>agentId</code> parameter and must notify
|
||||
* the service that the agent has been created using {@link BureauService#agentCreated}.
|
||||
* @param agentId the id of the <code>AgentObject</code> that needs an <code>Agent</code>
|
||||
*/
|
||||
void createAgent (int agentId);
|
||||
|
||||
/**
|
||||
* Destroys a previously created agent. Implementors should destroy the agent that was created
|
||||
* by the call to <code>createAgent</code> with the same agent id and must notify
|
||||
* the service that the agent has been created using {@link BureauService#agentDestroyed}.
|
||||
* @param agentId the id of the <code>AgentObject</code> whose <code>Agent</code>
|
||||
* should be destroyed
|
||||
*/
|
||||
void destroyAgent (int agentId);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
/**
|
||||
* Interface for the bureau to communicate with the server.
|
||||
*/
|
||||
public interface BureauService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Notifies the server that the bureau is up and running and ready to receive
|
||||
* requests via the <code>BureauReceiver</code>.
|
||||
* @see BureauReceiver
|
||||
*/
|
||||
void bureauInitialized (Client client, String bureauId);
|
||||
|
||||
/**
|
||||
* Notifies the server that this bureau has encountered a critical error and needs to be shut
|
||||
* down.
|
||||
*/
|
||||
void bureauError (Client client, String message);
|
||||
|
||||
/**
|
||||
* Notify the server that a previosuly requested agent is now created and ready to use.
|
||||
* @see BureauReceiver#createAgent
|
||||
*/
|
||||
void agentCreated (Client client, int agentId);
|
||||
|
||||
/**
|
||||
* Notify the server that a previosuly requested agent could not be created.
|
||||
* @see BureauReceiver#createAgent
|
||||
*/
|
||||
void agentCreationFailed (Client client, int agentId);
|
||||
|
||||
/**
|
||||
* Notify the server that an agent is no longer running. Normally called in response
|
||||
* to a call to <code>destroyAgent</code>
|
||||
* @see BureauReceiver#destroyAgent
|
||||
*/
|
||||
void agentDestroyed (Client client, int agentId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
|
||||
/**
|
||||
* Contains information for configuring and communicating with an agent.
|
||||
*/
|
||||
public class AgentObject extends DObject
|
||||
{
|
||||
// AUTO-GENERATED: FIELDS START
|
||||
/** The field name of the <code>bureauId</code> field. */
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public static final String BUREAU_ID = "bureauId";
|
||||
|
||||
/** The field name of the <code>bureauType</code> field. */
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public static final String BUREAU_TYPE = "bureauType";
|
||||
|
||||
/** The field name of the <code>code</code> field. */
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public static final String CODE = "code";
|
||||
|
||||
/** The field name of the <code>className</code> field. */
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public static final String CLASS_NAME = "className";
|
||||
|
||||
/** The field name of the <code>clientOid</code> field. */
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public static final String CLIENT_OID = "clientOid";
|
||||
// AUTO-GENERATED: FIELDS END
|
||||
|
||||
/** The id of the bureau the agent is running in. This is normally a unique id corresponding
|
||||
* to the game or item that requires some server-side processing. */
|
||||
public String bureauId;
|
||||
|
||||
/** The type of bureau that the agent is running in. This is normally derived from the kind
|
||||
* of media that the game or item has specified for its code and determines the method of
|
||||
* launching the bureau when the first agent is requested. */
|
||||
public String bureauType;
|
||||
|
||||
/** The location of the code for the agent. This could be a URL to an action script file or
|
||||
* some other description that the bureau can use to load and execute the agent's code. */
|
||||
public String code;
|
||||
|
||||
/** The main class within the code to use when launching an agent. Whether this value is
|
||||
* used depends on the type of bureau and will be resolve in the bureau client. */
|
||||
public String className;
|
||||
|
||||
/** The id of the client running this agent (only set after the agent is assigned to a
|
||||
* bureau and run). */
|
||||
public int clientOid;
|
||||
|
||||
/**
|
||||
* Returns a brief string that identifies this agent. Use this instead of
|
||||
* {@link Object#toString} when you wish to report an agent object in a log message.
|
||||
*/
|
||||
@Override
|
||||
public String which ()
|
||||
{
|
||||
return "[bid=" + bureauId + ", type=" + bureauType + "]";
|
||||
}
|
||||
|
||||
// AUTO-GENERATED: METHODS START
|
||||
/**
|
||||
* Requests that the <code>bureauId</code> field be set to the
|
||||
* specified value. The local value will be updated immediately and an
|
||||
* event will be propagated through the system to notify all listeners
|
||||
* that the attribute did change. Proxied copies of this object (on
|
||||
* clients) will apply the value change when they received the
|
||||
* attribute changed notification.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public void setBureauId (String value)
|
||||
{
|
||||
String ovalue = this.bureauId;
|
||||
requestAttributeChange(
|
||||
BUREAU_ID, value, ovalue);
|
||||
this.bureauId = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the <code>bureauType</code> field be set to the
|
||||
* specified value. The local value will be updated immediately and an
|
||||
* event will be propagated through the system to notify all listeners
|
||||
* that the attribute did change. Proxied copies of this object (on
|
||||
* clients) will apply the value change when they received the
|
||||
* attribute changed notification.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public void setBureauType (String value)
|
||||
{
|
||||
String ovalue = this.bureauType;
|
||||
requestAttributeChange(
|
||||
BUREAU_TYPE, value, ovalue);
|
||||
this.bureauType = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the <code>code</code> field be set to the
|
||||
* specified value. The local value will be updated immediately and an
|
||||
* event will be propagated through the system to notify all listeners
|
||||
* that the attribute did change. Proxied copies of this object (on
|
||||
* clients) will apply the value change when they received the
|
||||
* attribute changed notification.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public void setCode (String value)
|
||||
{
|
||||
String ovalue = this.code;
|
||||
requestAttributeChange(
|
||||
CODE, value, ovalue);
|
||||
this.code = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the <code>className</code> field be set to the
|
||||
* specified value. The local value will be updated immediately and an
|
||||
* event will be propagated through the system to notify all listeners
|
||||
* that the attribute did change. Proxied copies of this object (on
|
||||
* clients) will apply the value change when they received the
|
||||
* attribute changed notification.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public void setClassName (String value)
|
||||
{
|
||||
String ovalue = this.className;
|
||||
requestAttributeChange(
|
||||
CLASS_NAME, value, ovalue);
|
||||
this.className = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the <code>clientOid</code> field be set to the
|
||||
* specified value. The local value will be updated immediately and an
|
||||
* event will be propagated through the system to notify all listeners
|
||||
* that the attribute did change. Proxied copies of this object (on
|
||||
* clients) will apply the value change when they received the
|
||||
* attribute changed notification.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
|
||||
public void setClientOid (int value)
|
||||
{
|
||||
int ovalue = this.clientOid;
|
||||
requestAttributeChange(
|
||||
CLIENT_OID, Integer.valueOf(value), Integer.valueOf(ovalue));
|
||||
this.clientOid = value;
|
||||
}
|
||||
// AUTO-GENERATED: METHODS END
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Represents an authenticated bureau client.
|
||||
*/
|
||||
public class BureauAuthName extends Name
|
||||
{
|
||||
public BureauAuthName (String bureauId)
|
||||
{
|
||||
super(bureauId);
|
||||
}
|
||||
|
||||
// used when unserializing
|
||||
public BureauAuthName ()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.data;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
|
||||
/**
|
||||
* An object representing a Bureau connection. This is currently just a marker class.
|
||||
*/
|
||||
public class BureauClientObject extends ClientObject
|
||||
{
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return "BUREAU_CLIENT_OBJECT(" + super.toString() + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.data;
|
||||
|
||||
import com.threerings.presents.data.InvocationCodes;
|
||||
|
||||
/**
|
||||
* Codes and constants global to the Bureau services.
|
||||
*/
|
||||
public interface BureauCodes extends InvocationCodes
|
||||
{
|
||||
/** Defines our invocation services group. */
|
||||
public static final String BUREAU_GROUP = "bureau";
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.data;
|
||||
|
||||
import com.threerings.presents.net.ServiceCreds;
|
||||
|
||||
/**
|
||||
* Extends the basic credentials to provide bureau-specific fields.
|
||||
*/
|
||||
public class BureauCredentials extends ServiceCreds
|
||||
{
|
||||
/**
|
||||
* Creates new credentials for a specific bureau.
|
||||
*/
|
||||
public BureauCredentials (String bureauId, String sharedSecret)
|
||||
{
|
||||
super(bureauId, sharedSecret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty credentials for streaming. Should not be used directly.
|
||||
*/
|
||||
public BureauCredentials ()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.bureau.client.BureauService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link BureauService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from BureauService.java.")
|
||||
public class BureauMarshaller extends InvocationMarshaller
|
||||
implements BureauService
|
||||
{
|
||||
/** The method id used to dispatch {@link #agentCreated} requests. */
|
||||
public static final int AGENT_CREATED = 1;
|
||||
|
||||
// from interface BureauService
|
||||
public void agentCreated (Client arg1, int arg2)
|
||||
{
|
||||
sendRequest(arg1, AGENT_CREATED, new Object[] {
|
||||
Integer.valueOf(arg2)
|
||||
});
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #agentCreationFailed} requests. */
|
||||
public static final int AGENT_CREATION_FAILED = 2;
|
||||
|
||||
// from interface BureauService
|
||||
public void agentCreationFailed (Client arg1, int arg2)
|
||||
{
|
||||
sendRequest(arg1, AGENT_CREATION_FAILED, new Object[] {
|
||||
Integer.valueOf(arg2)
|
||||
});
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #agentDestroyed} requests. */
|
||||
public static final int AGENT_DESTROYED = 3;
|
||||
|
||||
// from interface BureauService
|
||||
public void agentDestroyed (Client arg1, int arg2)
|
||||
{
|
||||
sendRequest(arg1, AGENT_DESTROYED, new Object[] {
|
||||
Integer.valueOf(arg2)
|
||||
});
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #bureauError} requests. */
|
||||
public static final int BUREAU_ERROR = 4;
|
||||
|
||||
// from interface BureauService
|
||||
public void bureauError (Client arg1, String arg2)
|
||||
{
|
||||
sendRequest(arg1, BUREAU_ERROR, new Object[] {
|
||||
arg2
|
||||
});
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #bureauInitialized} requests. */
|
||||
public static final int BUREAU_INITIALIZED = 5;
|
||||
|
||||
// from interface BureauService
|
||||
public void bureauInitialized (Client arg1, String arg2)
|
||||
{
|
||||
sendRequest(arg1, BUREAU_INITIALIZED, new Object[] {
|
||||
arg2
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.server;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.ClientResolver;
|
||||
|
||||
import com.threerings.bureau.data.BureauClientObject;
|
||||
|
||||
/**
|
||||
* Used to configure crowd-specific client object data.
|
||||
*/
|
||||
public class BureauClientResolver extends ClientResolver
|
||||
{
|
||||
@Override // from ClientResolver
|
||||
public ClientObject createClientObject ()
|
||||
{
|
||||
return new BureauClientObject();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.server;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.bureau.data.BureauMarshaller;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link BureauProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from BureauService.java.")
|
||||
public class BureauDispatcher extends InvocationDispatcher<BureauMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public BureauDispatcher (BureauProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BureauMarshaller createMarshaller ()
|
||||
{
|
||||
return new BureauMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case BureauMarshaller.AGENT_CREATED:
|
||||
((BureauProvider)provider).agentCreated(
|
||||
source, ((Integer)args[0]).intValue()
|
||||
);
|
||||
return;
|
||||
|
||||
case BureauMarshaller.AGENT_CREATION_FAILED:
|
||||
((BureauProvider)provider).agentCreationFailed(
|
||||
source, ((Integer)args[0]).intValue()
|
||||
);
|
||||
return;
|
||||
|
||||
case BureauMarshaller.AGENT_DESTROYED:
|
||||
((BureauProvider)provider).agentDestroyed(
|
||||
source, ((Integer)args[0]).intValue()
|
||||
);
|
||||
return;
|
||||
|
||||
case BureauMarshaller.BUREAU_ERROR:
|
||||
((BureauProvider)provider).bureauError(
|
||||
source, (String)args[0]
|
||||
);
|
||||
return;
|
||||
|
||||
case BureauMarshaller.BUREAU_INITIALIZED:
|
||||
((BureauProvider)provider).bureauInitialized(
|
||||
source, (String)args[0]
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.server;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.bureau.client.BureauService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
/**
|
||||
* Defines the server-side of the {@link BureauService}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from BureauService.java.")
|
||||
public interface BureauProvider extends InvocationProvider
|
||||
{
|
||||
/**
|
||||
* Handles a {@link BureauService#agentCreated} request.
|
||||
*/
|
||||
void agentCreated (ClientObject caller, int arg1);
|
||||
|
||||
/**
|
||||
* Handles a {@link BureauService#agentCreationFailed} request.
|
||||
*/
|
||||
void agentCreationFailed (ClientObject caller, int arg1);
|
||||
|
||||
/**
|
||||
* Handles a {@link BureauService#agentDestroyed} request.
|
||||
*/
|
||||
void agentDestroyed (ClientObject caller, int arg1);
|
||||
|
||||
/**
|
||||
* Handles a {@link BureauService#bureauError} request.
|
||||
*/
|
||||
void bureauError (ClientObject caller, String arg1);
|
||||
|
||||
/**
|
||||
* Handles a {@link BureauService#bureauInitialized} request.
|
||||
*/
|
||||
void bureauInitialized (ClientObject caller, String arg1);
|
||||
}
|
||||
@@ -0,0 +1,804 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.server;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.Invoker;
|
||||
import com.samskivert.util.RunQueue;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.presents.annotation.MainInvoker;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
import com.threerings.presents.server.ClientManager;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
import com.threerings.presents.server.PresentsSession;
|
||||
import com.threerings.presents.server.ServiceAuthenticator;
|
||||
import com.threerings.presents.server.SessionFactory;
|
||||
import com.threerings.presents.server.net.ConnectionManager;
|
||||
|
||||
import com.threerings.bureau.data.AgentObject;
|
||||
import com.threerings.bureau.data.BureauAuthName;
|
||||
import com.threerings.bureau.data.BureauCodes;
|
||||
import com.threerings.bureau.data.BureauCredentials;
|
||||
import com.threerings.bureau.util.BureauLogRedirector;
|
||||
|
||||
import static com.threerings.bureau.Log.log;
|
||||
|
||||
/**
|
||||
* Abstracts the launching and termination of external processes (bureaus) that host instances of
|
||||
* server-side code (agents).
|
||||
*/
|
||||
@Singleton
|
||||
public class BureauRegistry
|
||||
{
|
||||
/**
|
||||
* Defines how a bureau is launched. Instances are associated to bureau types by the server on
|
||||
* startup. The instances are used whenever the registry needs to launch a bureau for an agent
|
||||
* with the associated bureau type.
|
||||
*/
|
||||
public static interface Launcher
|
||||
{
|
||||
/**
|
||||
* Kicks off a new bureau. This method will always be called on the unit invocation
|
||||
* thread since it may do extensive I/O.
|
||||
* @param bureauId the id of the bureau being launched
|
||||
* @param token the secret string for the bureau to use in its credentials
|
||||
*/
|
||||
void launchBureau (String bureauId, String token)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how to generate a command to launch a bureau in a local process.
|
||||
* @see #setCommandGenerator(String,CommandGenerator,int)
|
||||
* @see Launcher
|
||||
*/
|
||||
public static interface CommandGenerator
|
||||
{
|
||||
/**
|
||||
* Creates the command line to launch a new bureau using the given information.
|
||||
* Called by the registry when a new bureau is needed whose type was registered
|
||||
* with <code>setCommandGenerator</code>.
|
||||
* @param bureauId the id of the bureau being launched
|
||||
* @param token the token string to use for the credentials when logging in
|
||||
* @return command line arguments, including executable name
|
||||
*/
|
||||
String[] createCommand (String bureauId, String token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an uninitialized registry.
|
||||
*/
|
||||
@Inject public BureauRegistry (
|
||||
InvocationManager invmgr, ConnectionManager conmgr, ClientManager clmgr)
|
||||
{
|
||||
invmgr.registerDispatcher(new BureauDispatcher(new BureauProvider() {
|
||||
public void bureauInitialized (ClientObject client, String bureauId) {
|
||||
BureauRegistry.this.bureauInitialized(client, bureauId);
|
||||
}
|
||||
public void bureauError (ClientObject caller, String message) {
|
||||
BureauRegistry.this.bureauError(caller, message);
|
||||
}
|
||||
public void agentCreated (ClientObject client, int agentId) {
|
||||
BureauRegistry.this.agentCreated(client, agentId);
|
||||
}
|
||||
public void agentCreationFailed (ClientObject client, int agentId) {
|
||||
BureauRegistry.this.agentCreationFailed(client, agentId);
|
||||
}
|
||||
public void agentDestroyed (ClientObject client, int agentId) {
|
||||
BureauRegistry.this.agentDestroyed(client, agentId);
|
||||
}
|
||||
}), BureauCodes.BUREAU_GROUP);
|
||||
conmgr.addChainedAuthenticator(new ServiceAuthenticator<BureauCredentials>(
|
||||
BureauCredentials.class, BureauAuthName.class) {
|
||||
@Override protected boolean areValid (BureauCredentials creds) {
|
||||
return checkToken(creds) == null;
|
||||
}
|
||||
});
|
||||
clmgr.addSessionFactory(
|
||||
SessionFactory.newSessionFactory(BureauCredentials.class, getSessionClass(),
|
||||
BureauAuthName.class, getClientResolverClass()));
|
||||
clmgr.addClientObserver(new ClientManager.ClientObserver() {
|
||||
public void clientSessionDidStart (PresentsSession client) {
|
||||
if (client.getCredentials() instanceof BureauCredentials) {
|
||||
sessionDidStart(client, ((BureauCredentials)client.getCredentials()).clientId);
|
||||
}
|
||||
}
|
||||
public void clientSessionDidEnd (PresentsSession client) {
|
||||
if (client.getCredentials() instanceof BureauCredentials) {
|
||||
sessionDidEnd(client, ((BureauCredentials)client.getCredentials()).clientId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the credentials to make sure this is one of our bureaus.
|
||||
* @return null if all's well, otherwise a string describing the authentication failure
|
||||
*/
|
||||
public String checkToken (BureauCredentials creds)
|
||||
{
|
||||
Bureau bureau = _bureaus.get(creds.clientId);
|
||||
if (bureau == null) {
|
||||
return "Bureau " + creds.clientId + " not found";
|
||||
}
|
||||
if (bureau.clientObj != null) {
|
||||
return "Bureau " + creds.clientId + " already logged in";
|
||||
}
|
||||
if (!creds.areValid(bureau.token)) {
|
||||
return "Bureau " + creds.clientId + " does not match credentials token";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a command generator for a given type. When an agent is started and no bureaus are
|
||||
* running, the <code>bureauType</code> is used to determine the <code>CommandGenerator</code>
|
||||
* instance to call. The registry will wait indefinitely for the bureau to connect back.
|
||||
* @param bureauType the type of bureau that will be launched
|
||||
* @param cmdGenerator the generator to be used for bureaus of <code>bureauType</code>
|
||||
*/
|
||||
public void setCommandGenerator (String bureauType, final CommandGenerator cmdGenerator)
|
||||
{
|
||||
setCommandGenerator(bureauType, cmdGenerator, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a command generator for a given type. When an agent is started and no bureaus are
|
||||
* running, the <code>bureauType</code> is used to determine the <code>CommandGenerator</code>
|
||||
* instance to call. If the launched bureau does not connect within the given number of
|
||||
* milliseconds, it will be logged as an error and future attempts to launch the bureau
|
||||
* will try launching the command again.
|
||||
* @param bureauType the type of bureau that will be launched
|
||||
* @param cmdGenerator the generator to be used for bureaus of <code>bureauType</code>
|
||||
* @param timeout milliseconds to wait for the bureau or 0 to wait forever
|
||||
*/
|
||||
public void setCommandGenerator (
|
||||
String bureauType, final CommandGenerator cmdGenerator, int timeout)
|
||||
{
|
||||
setLauncher(bureauType, new Launcher() {
|
||||
public void launchBureau (String bureauId, String token)
|
||||
throws IOException {
|
||||
ProcessBuilder builder = new ProcessBuilder(
|
||||
cmdGenerator.createCommand(bureauId, token));
|
||||
builder.redirectErrorStream(true);
|
||||
Process process = builder.start();
|
||||
// log the output of the process and prefix with bureau id
|
||||
new BureauLogRedirector(bureauId, process.getInputStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return "DefaultLauncher for " + cmdGenerator;
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a launcher for a given type. When an agent is started and no bureaus are
|
||||
* running, the <code>bureauType</code> is used to determine the <code>Launcher</code>
|
||||
* instance to call. The registry will wait indefinitely for the launched bureau
|
||||
* to connect back.
|
||||
* @param bureauType the type of bureau that will be launched
|
||||
* @param launcher the launcher to be used for bureaus of <code>bureauType</code>
|
||||
*/
|
||||
public void setLauncher (String bureauType, Launcher launcher)
|
||||
{
|
||||
setLauncher(bureauType, launcher, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a launcher for a given type. When an agent is started and no bureaus are
|
||||
* running, the <code>bureauType</code> is used to determine the <code>Launcher</code>
|
||||
* instance to call. If the launched bureau does not connect within the given number of
|
||||
* milliseconds, it will be logged as an error and future attempts to launch the bureau
|
||||
* will invoke the <code>launch</code> method again.
|
||||
* @param bureauType the type of bureau that will be launched
|
||||
* @param launcher the launcher to be used for bureaus of <code>bureauType</code>
|
||||
* @param timeout milliseconds to wait for the bureau or 0 to wait forever
|
||||
*/
|
||||
public void setLauncher (String bureauType, Launcher launcher, int timeout)
|
||||
{
|
||||
if (_launchers.get(bureauType) != null) {
|
||||
log.warning("Launcher for type already exists", "type", bureauType);
|
||||
return;
|
||||
}
|
||||
|
||||
_launchers.put(bureauType, new LauncherEntry(launcher, timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new agent using the data in the given object, creating a new bureau if necessary.
|
||||
*/
|
||||
public void startAgent (AgentObject agent)
|
||||
{
|
||||
agent.setLocal(AgentData.class, new AgentData());
|
||||
|
||||
Bureau bureau = _bureaus.get(agent.bureauId);
|
||||
if (bureau != null && bureau.ready()) {
|
||||
_omgr.registerObject(agent);
|
||||
|
||||
log.info("Bureau ready, sending createAgent", "agent", agent.which());
|
||||
BureauSender.createAgent(bureau.clientObj, agent.getOid());
|
||||
bureau.agentStates.put(agent, AgentState.STARTED);
|
||||
bureau.summarize();
|
||||
return;
|
||||
}
|
||||
|
||||
if (bureau == null) {
|
||||
LauncherEntry launcherEntry = _launchers.get(agent.bureauType);
|
||||
if (launcherEntry == null) {
|
||||
log.warning("Launcher not found", "agent", agent.which());
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Creating new bureau", "bureauId", agent.bureauId, "launcher", launcherEntry);
|
||||
bureau = new Bureau();
|
||||
bureau.bureauId = agent.bureauId;
|
||||
bureau.token = generateToken(bureau.bureauId);
|
||||
bureau.launcherEntry = launcherEntry;
|
||||
_invoker.postUnit(new LauncherUnit(bureau, _omgr));
|
||||
_bureaus.put(agent.bureauId, bureau);
|
||||
}
|
||||
|
||||
_omgr.registerObject(agent);
|
||||
bureau.agentStates.put(agent, AgentState.PENDING);
|
||||
|
||||
log.info("Bureau not ready, pending agent", "agent", agent.which());
|
||||
bureau.summarize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys a previously started agent using the data in the given object.
|
||||
*/
|
||||
public void destroyAgent (AgentObject agent)
|
||||
{
|
||||
FoundAgent found = resolve(null, agent.getOid(), "destroyAgent");
|
||||
if (found == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Destroying agent", "agent", agent.which());
|
||||
|
||||
// transition the agent to a new state and perform the effect of the transition
|
||||
if (found.state == AgentState.PENDING) {
|
||||
found.bureau.agentStates.remove(found.agent);
|
||||
_omgr.destroyObject(found.agent.getOid());
|
||||
|
||||
} else if (found.state == AgentState.STARTED) {
|
||||
found.bureau.agentStates.put(found.agent, AgentState.STILL_BORN);
|
||||
|
||||
} else if (found.state == AgentState.RUNNING) {
|
||||
// TODO: have a timeout for this in case the client is misbehaving or hung
|
||||
BureauSender.destroyAgent(found.bureau.clientObj, agent.getOid());
|
||||
found.bureau.agentStates.put(found.agent, AgentState.DESTROYED);
|
||||
|
||||
} else if (found.state == AgentState.DESTROYED ||
|
||||
found.state == AgentState.STILL_BORN) {
|
||||
log.warning("Ignoring request to destroy agent in unexpected state",
|
||||
"state", found.state, "agent", found.agent.which());
|
||||
}
|
||||
|
||||
found.bureau.summarize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active session for a bureau of the given id.
|
||||
*/
|
||||
public PresentsSession lookupClient (String bureauId)
|
||||
{
|
||||
Bureau bureau = _bureaus.get(bureauId);
|
||||
if (bureau == null) {
|
||||
return null;
|
||||
}
|
||||
return bureau.client;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this agent's bureau encountered an error on launch, return it.
|
||||
*/
|
||||
public Exception getLaunchError (AgentObject agentObj)
|
||||
{
|
||||
AgentData data = agentObj.getLocal(AgentData.class);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return data.launchError;
|
||||
}
|
||||
|
||||
protected void sessionDidStart (PresentsSession client, String id)
|
||||
{
|
||||
Bureau bureau = _bureaus.get(id);
|
||||
if (bureau == null) {
|
||||
log.warning("Starting session for unknown bureau", "id", id, "client", client);
|
||||
return;
|
||||
}
|
||||
if (bureau.client != null) {
|
||||
log.warning("Multiple sessions for the same bureau", "id", id, "client", client,
|
||||
"bureau", bureau);
|
||||
}
|
||||
bureau.client = client;
|
||||
}
|
||||
|
||||
protected void sessionDidEnd (PresentsSession client, String id)
|
||||
{
|
||||
Bureau bureau = _bureaus.get(id);
|
||||
if (bureau == null) {
|
||||
log.warning("Ending session for unknown bureau", "id", id, "client", client);
|
||||
return;
|
||||
}
|
||||
if (bureau.client == null) {
|
||||
log.warning("Multiple logouts from the same bureau", "id", id, "client", client,
|
||||
"bureau", bureau);
|
||||
}
|
||||
bureau.client = null;
|
||||
|
||||
clientDestroyed(bureau);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the bureau client acknowledges starting up. Starts all pending agents and
|
||||
* causes subsequent agent start requests to be sent directly to the bureau.
|
||||
*/
|
||||
protected void bureauInitialized (ClientObject client, String bureauId)
|
||||
{
|
||||
final Bureau bureau = _bureaus.get(bureauId);
|
||||
if (bureau == null) {
|
||||
log.warning("Initialization of non-existent bureau", "bureauId", bureauId);
|
||||
return;
|
||||
}
|
||||
|
||||
bureau.clientObj = client;
|
||||
|
||||
log.info("Bureau created, launching pending agents", "bureau", bureau);
|
||||
|
||||
// find all pending agents
|
||||
Set<AgentObject> pending = Sets.newHashSet();
|
||||
|
||||
for (Map.Entry<AgentObject, AgentState> entry :
|
||||
bureau.agentStates.entrySet()) {
|
||||
|
||||
if (entry.getValue() == AgentState.PENDING) {
|
||||
pending.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
// create them
|
||||
for (AgentObject agent : pending) {
|
||||
log.info("Creating agent", "agent", agent.which());
|
||||
BureauSender.createAgent(bureau.clientObj, agent.getOid());
|
||||
bureau.agentStates.put(agent, AgentState.STARTED);
|
||||
}
|
||||
|
||||
bureau.summarize();
|
||||
}
|
||||
|
||||
protected void bureauError (ClientObject caller, String message)
|
||||
{
|
||||
for (Bureau bureau : _bureaus.values()) {
|
||||
if (bureau.clientObj == caller) {
|
||||
log.info(
|
||||
"Bureau error occurred", "caller", caller.who(), "message", message,
|
||||
"bureau", bureau.bureauId);
|
||||
bureau.client.endSession();
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.warning(
|
||||
"Bureau error occurred in unregistered bureau", "caller", caller.who(),
|
||||
"message", message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the bureau client acknowledges the creation of an agent.
|
||||
*/
|
||||
protected void agentCreated (ClientObject client, int agentId)
|
||||
{
|
||||
FoundAgent found = resolve(client, agentId, "agentCreated");
|
||||
if (found == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Agent creation confirmed", "agent", found.agent.which());
|
||||
|
||||
if (found.state == AgentState.STARTED) {
|
||||
found.bureau.agentStates.put(found.agent, AgentState.RUNNING);
|
||||
found.agent.setClientOid(client.getOid());
|
||||
|
||||
} else if (found.state == AgentState.STILL_BORN) {
|
||||
// TODO: have a timeout for this in case the client is misbehaving or hung
|
||||
BureauSender.destroyAgent(found.bureau.clientObj, agentId);
|
||||
found.bureau.agentStates.put(found.agent, AgentState.DESTROYED);
|
||||
|
||||
} else if (found.state == AgentState.PENDING ||
|
||||
found.state == AgentState.RUNNING ||
|
||||
found.state == AgentState.DESTROYED) {
|
||||
log.warning("Ignoring confirmation of creation of an agent in an unexpected state",
|
||||
"state", found.state, "agent", found.agent.which());
|
||||
}
|
||||
|
||||
found.bureau.summarize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the bureau client acknowledges the failure to create an agent.
|
||||
*/
|
||||
protected void agentCreationFailed (ClientObject client, int agentId)
|
||||
{
|
||||
FoundAgent found = resolve(client, agentId, "agentCreationFailed");
|
||||
if (found == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Agent creation failed", "agent", found.agent.which());
|
||||
|
||||
if (found.state == AgentState.STARTED ||
|
||||
found.state == AgentState.STILL_BORN) {
|
||||
found.bureau.agentStates.remove(found.agent);
|
||||
_omgr.destroyObject(found.agent.getOid());
|
||||
|
||||
} else if (found.state == AgentState.PENDING ||
|
||||
found.state == AgentState.RUNNING ||
|
||||
found.state == AgentState.DESTROYED) {
|
||||
log.warning("Ignoring failure of creation of an agent in an unexpected state",
|
||||
"state", found.state, "agent", found.agent.which());
|
||||
}
|
||||
|
||||
found.bureau.summarize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the bureau client acknowledges the destruction of an agent.
|
||||
*/
|
||||
protected void agentDestroyed (ClientObject client, int agentId)
|
||||
{
|
||||
FoundAgent found = resolve(client, agentId, "agentDestroyed");
|
||||
if (found == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Agent destruction confirmed", "agent", found.agent.which());
|
||||
|
||||
if (found.state == AgentState.DESTROYED) {
|
||||
found.bureau.agentStates.remove(found.agent);
|
||||
_omgr.destroyObject(found.agent.getOid());
|
||||
|
||||
} else if (found.state == AgentState.PENDING ||
|
||||
found.state == AgentState.STARTED ||
|
||||
found.state == AgentState.RUNNING ||
|
||||
found.state == AgentState.STILL_BORN) {
|
||||
log.warning("Ignoring confirmation of destruction of agent in unexpected state",
|
||||
"state", found.state, "agent", found.agent.which());
|
||||
}
|
||||
|
||||
found.bureau.summarize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when a client is destroyed.
|
||||
*/
|
||||
protected void clientDestroyed (Bureau bureau)
|
||||
{
|
||||
log.info("Client destroyed, destroying all agents", "bureau", bureau);
|
||||
|
||||
// clean up any agents attached to this bureau
|
||||
for (AgentObject agent : bureau.agentStates.keySet()) {
|
||||
_omgr.destroyObject(agent.getOid());
|
||||
}
|
||||
bureau.agentStates.clear();
|
||||
|
||||
if (_bureaus.remove(bureau.bureauId) == null) {
|
||||
log.info("Bureau not found to remove", "bureau", bureau);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does lots of null checks and lookups and resolves the given information into FoundAgent.
|
||||
*/
|
||||
protected FoundAgent resolve (ClientObject client, int agentId, String resolver)
|
||||
{
|
||||
com.threerings.presents.dobj.DObject dobj = _omgr.getObject(agentId);
|
||||
if (dobj == null) {
|
||||
log.warning("Non-existent agent", "function", resolver, "agentId", agentId);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(dobj instanceof AgentObject)) {
|
||||
log.warning("Object not an agent", "function", resolver, "obj", dobj.getClass());
|
||||
return null;
|
||||
}
|
||||
|
||||
AgentObject agent = (AgentObject)dobj;
|
||||
Bureau bureau = _bureaus.get(agent.bureauId);
|
||||
if (bureau == null) {
|
||||
log.warning("Bureau not found for agent", "function", resolver, "agent", agent.which());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!bureau.agentStates.containsKey(agent)) {
|
||||
log.warning("Bureau does not have agent", "function", resolver, "agent", agent.which());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (client != null && bureau.clientObj != client) {
|
||||
log.warning("Masquerading request", "function", resolver, "agent", agent.which(),
|
||||
"client", bureau.clientObj, "client", client);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FoundAgent(bureau, agent, bureau.agentStates.get(agent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a hard-to-guess token that the bureau can use to authenticate itself when it tries
|
||||
* to log in.
|
||||
*/
|
||||
protected String generateToken (String bureauId)
|
||||
{
|
||||
String tokenSource = bureauId + "@" + System.currentTimeMillis() + "r" + Math.random();
|
||||
return StringUtil.md5hex(tokenSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the launcher unit timeout time after launching.
|
||||
* @param bureau bureau whose launch occurred
|
||||
*/
|
||||
protected void launchTimeoutExpired (Bureau bureau)
|
||||
{
|
||||
if (bureau.clientObj != null) {
|
||||
return; // all's well, ignore
|
||||
}
|
||||
|
||||
if (!_bureaus.containsKey(bureau.bureauId)) {
|
||||
// bureau has already managed to get destroyed before the launch timeout, ignore
|
||||
return;
|
||||
}
|
||||
|
||||
handleLaunchError(bureau, null, "timeout");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when something goes wrong with launching a bureau.
|
||||
*/
|
||||
protected void handleLaunchError (Bureau bureau, Exception error, String cause)
|
||||
{
|
||||
if (cause == null && error != null) {
|
||||
cause = error.getMessage();
|
||||
}
|
||||
log.info("Bureau failed to launch", "bureau", bureau, "cause", cause);
|
||||
|
||||
// clean up any agents attached to this bureau
|
||||
for (AgentObject agent : bureau.agentStates.keySet()) {
|
||||
agent.getLocal(AgentData.class).launchError = error;
|
||||
_omgr.destroyObject(agent.getOid());
|
||||
}
|
||||
bureau.agentStates.clear();
|
||||
|
||||
_bureaus.remove(bureau.bureauId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class used to handle bureau sessions.
|
||||
*/
|
||||
protected Class<? extends BureauSession> getSessionClass ()
|
||||
{
|
||||
return BureauSession.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class used to resolve bureau client data.
|
||||
*/
|
||||
protected Class<? extends BureauClientResolver> getClientResolverClass ()
|
||||
{
|
||||
return BureauClientResolver.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoker unit to launch a bureau's process, then assign the result on the main thread.
|
||||
*/
|
||||
protected class LauncherUnit extends Invoker.Unit
|
||||
{
|
||||
LauncherUnit (Bureau bureau, RunQueue runQueue) {
|
||||
super("LauncherUnit for " + bureau + ": " + StringUtil.toString(bureau.launcherEntry));
|
||||
_bureau = bureau;
|
||||
_runQueue = runQueue;
|
||||
}
|
||||
|
||||
@Override public boolean invoke () {
|
||||
try {
|
||||
_bureau.launch();
|
||||
} catch (Exception e) {
|
||||
_error = e;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult () {
|
||||
if (_error == null) {
|
||||
// bureau launched ok, but it may still not connect. wait for timeout
|
||||
int timeout = _bureau.launcherEntry.timeout;
|
||||
if (timeout != 0) {
|
||||
new Interval(_runQueue) {
|
||||
@Override public void expired () {
|
||||
launchTimeoutExpired(_bureau);
|
||||
}
|
||||
}.schedule(timeout);
|
||||
}
|
||||
_bureau.launched = true;
|
||||
_bureau.launcherEntry = null;
|
||||
log.info("Bureau launch requested", "bureau", _bureau);
|
||||
|
||||
} else {
|
||||
handleLaunchError(_bureau, _error, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected Bureau _bureau;
|
||||
protected Exception _error;
|
||||
protected RunQueue _runQueue;
|
||||
}
|
||||
|
||||
protected static class LauncherEntry
|
||||
{
|
||||
public Launcher launcher;
|
||||
public int timeout;
|
||||
|
||||
public LauncherEntry (Launcher launcher, int timeout) {
|
||||
this.launcher = launcher;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
return StringUtil.fieldsToString(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected enum AgentState
|
||||
{
|
||||
// Not yet stated, waiting for bureau to ack
|
||||
PENDING,
|
||||
// Bureau acked, agent told to start
|
||||
STARTED,
|
||||
// Agent ack'ed, now live and hosting, ready to tell other clients
|
||||
RUNNING,
|
||||
// Agent destruction requested, waiting for acknowledge (after which the agent is removed
|
||||
// from the Bureau, so has no state)
|
||||
DESTROYED,
|
||||
// Edge case: destroy request prior to RUNNING
|
||||
STILL_BORN
|
||||
}
|
||||
|
||||
/** Models the results of searching for an agent. */
|
||||
protected static class FoundAgent
|
||||
{
|
||||
FoundAgent (Bureau bureau, AgentObject agent, AgentState state) {
|
||||
this.bureau = bureau;
|
||||
this.agent = agent;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
// Bureau containing the agent
|
||||
Bureau bureau;
|
||||
|
||||
// The object
|
||||
AgentObject agent;
|
||||
|
||||
// The state of the agent
|
||||
AgentState state;
|
||||
}
|
||||
|
||||
/** Models a bureau, including the process handle, all running agents and their states. */
|
||||
protected static class Bureau
|
||||
{
|
||||
// non-null once the bureau is scheduled but not yet kicked off
|
||||
LauncherEntry launcherEntry;
|
||||
|
||||
// non-null once the bureau is kicked off
|
||||
boolean launched;
|
||||
|
||||
// The token given to this bureau for authentication
|
||||
String token;
|
||||
|
||||
// The bureau's key in the map of bureaus. All requests for this bureau
|
||||
// with this id should be associated with one instance
|
||||
String bureauId;
|
||||
|
||||
// The client object of the bureau that has opened a dobj connection to
|
||||
// the registry
|
||||
ClientObject clientObj;
|
||||
|
||||
// The client session
|
||||
PresentsSession client;
|
||||
|
||||
// The states of the various agents allocated to this bureau
|
||||
Map<AgentObject, AgentState> agentStates = Maps.newHashMap();
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("[Bureau id=").append(bureauId).append(", client=");
|
||||
if (clientObj == null) {
|
||||
builder.append("null");
|
||||
} else {
|
||||
builder.append(clientObj.getOid());
|
||||
}
|
||||
builder.append(", launcherEntry=").append(launcherEntry);
|
||||
builder.append(", launched=").append(launched);
|
||||
builder.append(", totalAgents=").append(agentStates.size());
|
||||
agentSummary(builder.append(", ")).append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
boolean ready () {
|
||||
return clientObj != null;
|
||||
}
|
||||
|
||||
StringBuilder agentSummary (StringBuilder str) {
|
||||
int[] counts = new int[AgentState.values().length];
|
||||
for (Map.Entry<AgentObject, AgentState> me : agentStates.entrySet()) {
|
||||
counts[me.getValue().ordinal()]++;
|
||||
}
|
||||
for (AgentState state : AgentState.values()) {
|
||||
if (state.ordinal() > 0) {
|
||||
str.append(", ");
|
||||
}
|
||||
str.append(counts[state.ordinal()]).append(" ").append(state.name());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void summarize () {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("Bureau ").append(bureauId).append(" [");
|
||||
agentSummary(str).append("]");
|
||||
log.info(str.toString());
|
||||
}
|
||||
|
||||
void launch () throws IOException {
|
||||
launcherEntry.launcher.launchBureau(bureauId, token);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class AgentData
|
||||
{
|
||||
Exception launchError;
|
||||
}
|
||||
|
||||
protected Map<String, LauncherEntry> _launchers = Maps.newHashMap();
|
||||
protected Map<String, Bureau> _bureaus = Maps.newHashMap();
|
||||
|
||||
@Inject protected RootDObjectManager _omgr;
|
||||
@Inject protected @MainInvoker Invoker _invoker;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.server;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationSender;
|
||||
|
||||
import com.threerings.bureau.client.BureauDecoder;
|
||||
import com.threerings.bureau.client.BureauReceiver;
|
||||
|
||||
/**
|
||||
* Used to issue notifications to a {@link BureauReceiver} instance on a
|
||||
* client.
|
||||
*/
|
||||
public class BureauSender extends InvocationSender
|
||||
{
|
||||
/**
|
||||
* Issues a notification that will result in a call to {@link
|
||||
* BureauReceiver#createAgent} on a client.
|
||||
*/
|
||||
public static void createAgent (
|
||||
ClientObject target, int arg1)
|
||||
{
|
||||
sendNotification(
|
||||
target, BureauDecoder.RECEIVER_CODE, BureauDecoder.CREATE_AGENT,
|
||||
new Object[] { Integer.valueOf(arg1) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a notification that will result in a call to {@link
|
||||
* BureauReceiver#destroyAgent} on a client.
|
||||
*/
|
||||
public static void destroyAgent (
|
||||
ClientObject target, int arg1)
|
||||
{
|
||||
sendNotification(
|
||||
target, BureauDecoder.RECEIVER_CODE, BureauDecoder.DESTROY_AGENT,
|
||||
new Object[] { Integer.valueOf(arg1) });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.server;
|
||||
|
||||
import com.threerings.presents.server.PresentsSession;
|
||||
|
||||
public class BureauSession extends PresentsSession
|
||||
{
|
||||
@Override // from PresentsSession
|
||||
protected void sessionConnectionClosed ()
|
||||
{
|
||||
super.sessionConnectionClosed();
|
||||
|
||||
// end our session when the connection is closed
|
||||
endSession();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.util;
|
||||
|
||||
import com.threerings.presents.util.PresentsContext;
|
||||
|
||||
import com.threerings.bureau.client.BureauDirector;
|
||||
|
||||
/**
|
||||
* Defines the objects held on a bureau client. This includes usual set of objects found on a
|
||||
* standard presents client.
|
||||
*/
|
||||
public interface BureauContext extends PresentsContext
|
||||
{
|
||||
/**
|
||||
* Access the director object.
|
||||
*/
|
||||
BureauDirector getBureauDirector ();
|
||||
|
||||
/**
|
||||
* Access the bureau id.
|
||||
*/
|
||||
String getBureauId ();
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.bureau.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
import com.samskivert.util.Logger;
|
||||
|
||||
import static com.threerings.bureau.Log.log;
|
||||
|
||||
/**
|
||||
* Captures the output of a bureau and redirects it into a single logger instance using a thread
|
||||
* name equal to the bureau id. The {@link Logger} instance is the one for this class. The intent
|
||||
* is that log4j will be configured to use %t (thread name) to embed the bureau id.
|
||||
*/
|
||||
public class BureauLogRedirector
|
||||
{
|
||||
/**
|
||||
* Creates a new redirector with no size limit.
|
||||
* @param bureauId the id of the bureau being redirected - this will become the thread name
|
||||
* @param input the stream that is the output of the bureau process
|
||||
*/
|
||||
public BureauLogRedirector (String bureauId, InputStream input)
|
||||
{
|
||||
this(bureauId, input, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new redirector.
|
||||
* @param bureauId the id of the bureau being redirected - this will become the thread name
|
||||
* @param input the stream that is the output of the bureau process
|
||||
* @param limit approximate limit for the total characters written to the logger
|
||||
*/
|
||||
public BureauLogRedirector (String bureauId, InputStream input, int limit)
|
||||
{
|
||||
_bureauId = bureauId;
|
||||
_reader = new BufferedReader(new InputStreamReader(input));
|
||||
_limit = limit;
|
||||
Thread thread = new Thread(bureauId) {
|
||||
@Override public void run () {
|
||||
copyLoop();
|
||||
}};
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bureau id this was created with.
|
||||
*/
|
||||
public String getBureauId ()
|
||||
{
|
||||
return _bureauId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of characters written to the log.
|
||||
*/
|
||||
public int getWritten ()
|
||||
{
|
||||
return _written;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the character limit associated with the log.
|
||||
*/
|
||||
public int getLimit ()
|
||||
{
|
||||
return _limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the redirector's truncation status and allows additional output up to the given
|
||||
* character limit.
|
||||
*/
|
||||
public synchronized void reset (int limit)
|
||||
{
|
||||
_written = 0;
|
||||
_truncated = false;
|
||||
_limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if this redirector has stopped copying lines due to the size limit being exceeded.
|
||||
*/
|
||||
public boolean isTruncated ()
|
||||
{
|
||||
return _truncated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the redirector is still active. Normally this indicates that the launched
|
||||
* process is still running.
|
||||
*/
|
||||
public boolean isRunning ()
|
||||
{
|
||||
return _reader != null;
|
||||
}
|
||||
|
||||
protected void copyLoop ()
|
||||
{
|
||||
String line;
|
||||
try {
|
||||
while ((line = _reader.readLine()) != null) {
|
||||
int length = line.length();
|
||||
boolean showTrunc = false;
|
||||
|
||||
synchronized (this) {
|
||||
if (_truncated) {
|
||||
line = null;
|
||||
} else if (_limit > 0 && _written + length > _limit) {
|
||||
_truncated = true;
|
||||
showTrunc = true;
|
||||
line = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (line != null) {
|
||||
_target.info(line); // this should get prefixed by the thread name
|
||||
_written += length;
|
||||
|
||||
} else if (showTrunc) {
|
||||
DateFormat format =
|
||||
DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL);
|
||||
_target.info(
|
||||
format.format(new Date()) +
|
||||
": Size limit reached, suppressing further output");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to read bureau output", "bureauId", _bureauId, e);
|
||||
} finally {
|
||||
StreamUtil.close(_reader);
|
||||
_reader = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected String _bureauId;
|
||||
protected BufferedReader _reader;
|
||||
protected int _limit;
|
||||
protected int _written;
|
||||
protected boolean _truncated;
|
||||
|
||||
protected static Logger _target = Logger.getLogger(BureauLogRedirector.class);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd;
|
||||
|
||||
import com.samskivert.util.Logger;
|
||||
|
||||
/**
|
||||
* Contains a reference to the log object used by the Crowd services.
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
/** We dispatch our log messages through this logger. */
|
||||
public static Logger log = Logger.getLogger("com.threerings.crowd");
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChatChannel;
|
||||
|
||||
/**
|
||||
* Provides a way for clients to speak on chat channels.
|
||||
*/
|
||||
public interface ChannelSpeakService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Requests to speak the supplied message on the specified channel.
|
||||
*/
|
||||
public void speak (Client client, ChatChannel channel, String message, byte mode);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.client;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChatMessage;
|
||||
|
||||
/**
|
||||
* A chat display provides a means by which chat messages can be
|
||||
* displayed. The chat display will be notified when chat messages of
|
||||
* various sorts have been received by the client.
|
||||
*/
|
||||
public interface ChatDisplay
|
||||
{
|
||||
/**
|
||||
* Called to clear the chat display.
|
||||
*/
|
||||
void clear ();
|
||||
|
||||
/**
|
||||
* Called to display a chat message.
|
||||
*
|
||||
* @param alreadyDisplayed true if a previous chat display in the list has
|
||||
* already displayed this message, false otherwise.
|
||||
*
|
||||
* @return true if the message was displayed, false if not.
|
||||
*/
|
||||
boolean displayMessage (ChatMessage msg, boolean alreadyDisplayed);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.client;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Filters messages chat messages to or from the server.
|
||||
*/
|
||||
public interface ChatFilter
|
||||
{
|
||||
/**
|
||||
* Filter a chat message.
|
||||
* @param msg the message text to be filtered.
|
||||
* @param otherUser an optional argument that represents the target or the speaker, depending
|
||||
* on 'outgoing', and can be considered in filtering if it is provided.
|
||||
* @param outgoing true if the message is going out to the server.
|
||||
*
|
||||
* @return the filtered message, or null to block it completely.
|
||||
*/
|
||||
String filter (String msg, Name otherUser, boolean outgoing);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.client;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
/**
|
||||
* The chat services provide a mechanism by which the client can broadcast chat messages to all
|
||||
* clients that are subscribed to a particular place object or directly to a particular client.
|
||||
* These services should not be used directly, but instead should be accessed via the
|
||||
* {@link ChatDirector}.
|
||||
*/
|
||||
public interface ChatService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Used to communicate the response to a {@link ChatService#tell} request.
|
||||
*/
|
||||
public static interface TellListener extends InvocationListener
|
||||
{
|
||||
/**
|
||||
* Communicates the response to a {@link ChatService#tell} request.
|
||||
*
|
||||
* @param idleTime the number of ms the tellee has been idle or 0L if they are not idle.
|
||||
* @param awayMessage the away message configured by the told player or null if they have
|
||||
* no away message.
|
||||
*/
|
||||
void tellSucceeded (long idleTime, String awayMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that a tell message be delivered to the user with username equal to
|
||||
* <code>target</code>.
|
||||
*
|
||||
* @param client a connected, operational client instance.
|
||||
* @param target the username of the user to which the tell message should be delivered.
|
||||
* @param message the contents of the message.
|
||||
* @param listener the reference that will receive the tell response.
|
||||
*/
|
||||
void tell (Client client, Name target, String message, TellListener listener);
|
||||
|
||||
/**
|
||||
* Requests that a message be broadcast to all users in the system.
|
||||
*
|
||||
* @param client a connected, operational client instance.
|
||||
* @param message the contents of the message.
|
||||
* @param listener the reference that will receive a failure response.
|
||||
*/
|
||||
void broadcast (Client client, String message, InvocationListener listener);
|
||||
|
||||
/**
|
||||
* Sets this client's away message. If the message is null or the empty string, the away
|
||||
* message will be cleared.
|
||||
*/
|
||||
void away (Client client, String message);
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.client;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.util.RandomUtil;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import static com.threerings.crowd.Log.log;
|
||||
|
||||
/**
|
||||
* A chat filter that can filter out curse words from user chat.
|
||||
*/
|
||||
public abstract class CurseFilter implements ChatFilter
|
||||
{
|
||||
/** Indicates how messages should be handled. */
|
||||
public enum Mode { DROP, COMIC, VERNACULAR, UNFILTERED; }
|
||||
|
||||
/**
|
||||
* Creates a curse filter. The curse words should be a string in the following format:
|
||||
*
|
||||
* <pre>
|
||||
* *penis*=John_Thomas shit*=barnacle muff=britches
|
||||
* </pre>
|
||||
*
|
||||
* The key/value pairs are separated by spaces, * matches word characters and the value after
|
||||
* the = is the string into which to convert the text when converting to the vernacular.
|
||||
* Underscores in the target string will be turned into spaces.
|
||||
*
|
||||
* <p> And stopWords should be in the following format:
|
||||
*
|
||||
* <pre>
|
||||
* *faggot* rape rapes raped raping
|
||||
* </pre>
|
||||
*
|
||||
* Words are separated by spaces and * matches any other word characters.
|
||||
*/
|
||||
public CurseFilter (String curseWords, String stopWords)
|
||||
{
|
||||
configureCurseWords(curseWords);
|
||||
configureStopWords(stopWords);
|
||||
}
|
||||
|
||||
/**
|
||||
* The client will need to provide a way to look up our current chat filter mode.
|
||||
*/
|
||||
public abstract Mode getFilterMode ();
|
||||
|
||||
// from interface ChatFilter
|
||||
public String filter (String msg, Name otherUser, boolean outgoing)
|
||||
{
|
||||
// first, check against the drop-always list
|
||||
_stopMatcher.reset(msg);
|
||||
if (_stopMatcher.find()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// then see what kind of curse filtering the user has configured
|
||||
Mode level = getFilterMode();
|
||||
if (level == Mode.UNFILTERED) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
StringBuffer inbuf = new StringBuffer(msg);
|
||||
StringBuffer outbuf = new StringBuffer(msg.length());
|
||||
for (int ii=0, nn=_matchers.length; ii < nn; ii++) {
|
||||
Matcher m = _matchers[ii];
|
||||
m.reset(inbuf);
|
||||
while (m.find()) {
|
||||
switch (level) {
|
||||
case DROP:
|
||||
return null;
|
||||
|
||||
case COMIC:
|
||||
m.appendReplacement(outbuf,
|
||||
_replacements[ii].replace(" ", comicChars(_comicLength[ii])));
|
||||
break;
|
||||
|
||||
case VERNACULAR:
|
||||
String vernacular = _vernacular[ii];
|
||||
if (Character.isUpperCase(m.group(2).codePointAt(0))) {
|
||||
int firstCharLen = Character.charCount(vernacular.codePointAt(0));
|
||||
vernacular = vernacular.substring(0, firstCharLen).toUpperCase() +
|
||||
vernacular.substring(firstCharLen);
|
||||
}
|
||||
m.appendReplacement(outbuf, _replacements[ii].replace(" ", vernacular));
|
||||
break;
|
||||
|
||||
case UNFILTERED:
|
||||
// We returned the msg unadulterated above in this case, so it should be
|
||||
// impossible to wind up here, but let's enumerate it so we can let the compiler
|
||||
// scream about missing enum values in a switch
|
||||
log.warning("Omg? We're trying to filter chat even though we're unfiltered?");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
if (outbuf.length() == 0) {
|
||||
// optimization: if we didn't find a match, jump to the next
|
||||
// pattern without doing any StringBuilder jimmying
|
||||
continue;
|
||||
}
|
||||
m.appendTail(outbuf);
|
||||
|
||||
// swap the buffers around and clear the output
|
||||
StringBuffer temp = inbuf;
|
||||
inbuf = outbuf;
|
||||
outbuf = temp;
|
||||
outbuf.setLength(0);
|
||||
}
|
||||
|
||||
return inbuf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the curse word portion of our filtering.
|
||||
*/
|
||||
protected void configureCurseWords (String curseWords)
|
||||
{
|
||||
StringTokenizer st = new StringTokenizer(curseWords);
|
||||
int numWords = st.countTokens();
|
||||
|
||||
_matchers = new Matcher[numWords];
|
||||
_replacements = new String[numWords];
|
||||
_vernacular = new String[numWords];
|
||||
_comicLength = new int[numWords];
|
||||
|
||||
for (int ii=0; ii < numWords; ii++) {
|
||||
String mapping = st.nextToken();
|
||||
StringTokenizer st2 = new StringTokenizer(mapping, "=");
|
||||
if (st2.countTokens() != 2) {
|
||||
log.warning("Something looks wrong in the x.cursewords properties (" +
|
||||
mapping + "), skipping.");
|
||||
continue;
|
||||
}
|
||||
String curse = st2.nextToken();
|
||||
|
||||
String s = "";
|
||||
String p = "";
|
||||
if (curse.startsWith("*")) {
|
||||
curse = curse.substring(1);
|
||||
p += "([a-zA-Z]*)";
|
||||
s += "$1";
|
||||
} else {
|
||||
p += "()";
|
||||
}
|
||||
s += " ";
|
||||
p += " ";
|
||||
if (curse.endsWith("*")) {
|
||||
curse = curse.substring(0, curse.length() - 1);
|
||||
p += "([a-zA-Z]*)";
|
||||
s += "$3";
|
||||
}
|
||||
|
||||
String pattern = "\\b" + p.replace(" ", "(" + curse + ")") + "\\b";
|
||||
Pattern pat = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
||||
_matchers[ii] = pat.matcher("");
|
||||
_replacements[ii] = s;
|
||||
_vernacular[ii] = st2.nextToken().replace('_', ' ');
|
||||
_comicLength[ii] = curse.codePointCount(0, curse.length());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the words that will stop.
|
||||
*/
|
||||
protected void configureStopWords (String stopWords)
|
||||
{
|
||||
StringTokenizer st = new StringTokenizer(stopWords);
|
||||
|
||||
String pattern = "";
|
||||
while (st.hasMoreTokens()) {
|
||||
if ("".equals(pattern)) {
|
||||
pattern += "(";
|
||||
} else {
|
||||
pattern += "|";
|
||||
}
|
||||
pattern += getStopWordRegexp(st.nextToken());
|
||||
}
|
||||
pattern += ")";
|
||||
|
||||
setStopPattern(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets our stop word matcher to one for the given regular expression.
|
||||
*/
|
||||
protected void setStopPattern (String pattern)
|
||||
{
|
||||
_stopMatcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a naughty word into a regular expression to catch it.
|
||||
*/
|
||||
protected String getStopWordRegexp (String word)
|
||||
{
|
||||
return "\\b" + word.replace("*", "[A-Za-z]*") + "\\b";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a comicy replacement of the specified length.
|
||||
*/
|
||||
protected String comicChars (int length)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int ii=0; ii < length; ii++) {
|
||||
buf.append(RandomUtil.pickRandom(COMIC_CHARS));
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** A matcher that will always cause a message to be dropped if it matches. */
|
||||
protected Matcher _stopMatcher;
|
||||
|
||||
/** Matchers for each curseword. */
|
||||
protected Matcher[] _matchers;
|
||||
|
||||
/** Length of comic-y replacements for each curseword. */
|
||||
protected int[] _comicLength;
|
||||
|
||||
/** Replacements. */
|
||||
protected String[] _replacements;
|
||||
|
||||
/** Replacements for each curseword "in the vernacular". */
|
||||
protected String[] _vernacular;
|
||||
|
||||
/** Comic replacement characters. */
|
||||
protected static final String[] COMIC_CHARS = { "!", "@", "#", "%", "&", "*" };
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChatMessage;
|
||||
|
||||
/**
|
||||
* Stores chat history.
|
||||
*/
|
||||
public class HistoryList extends ArrayList<ChatMessage>
|
||||
implements ChatDisplay
|
||||
{
|
||||
/** An interface for chat history observers. */
|
||||
public interface Observer {
|
||||
/** Called when messages have been added or removed from the chat history.
|
||||
* @param adjustment the number of messages that have been added (+) or removed (-). */
|
||||
void historyUpdated (int adjustment);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public boolean displayMessage (ChatMessage msg, boolean alreadyDisplayed)
|
||||
{
|
||||
// see if we're full, and if so, clear out a bunch of old stuff
|
||||
int adjusted;
|
||||
if (size() == MAX_HISTORY) {
|
||||
removeRange(0, PRUNE_HISTORY);
|
||||
adjusted = PRUNE_HISTORY;
|
||||
|
||||
} else {
|
||||
adjusted = 0;
|
||||
}
|
||||
|
||||
// add the message to the history
|
||||
add(msg);
|
||||
|
||||
// notify observers that something changed
|
||||
notify(adjusted);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear ()
|
||||
{
|
||||
// see how many entries we're clearing out..
|
||||
int adjusted = size();
|
||||
super.clear();
|
||||
|
||||
// and notify the chat displays of that fact
|
||||
notify(adjusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an {@link Observer} that wants to know about changes to the history.
|
||||
*/
|
||||
public void addObserver (Observer obs)
|
||||
{
|
||||
_obs.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link Observer} from hearing about changes to the history.
|
||||
*/
|
||||
public void removeObserver (Observer obs)
|
||||
{
|
||||
_obs.remove(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listening {@link Observer}s that there has been a change to this history.
|
||||
*/
|
||||
protected void notify (int adjustment)
|
||||
{
|
||||
_historyUpdatedOp.setAdjustment(adjustment);
|
||||
_obs.apply(_historyUpdatedOp);
|
||||
}
|
||||
|
||||
protected static class HistoryUpdatedOp
|
||||
implements ObserverList.ObserverOp<Observer>
|
||||
{
|
||||
public void setAdjustment (int adjustment) {
|
||||
_adjustment = adjustment;
|
||||
}
|
||||
|
||||
public boolean apply (Observer obs) {
|
||||
obs.historyUpdated(_adjustment);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected int _adjustment;
|
||||
}
|
||||
|
||||
/** A list of {@link Observer}s interested in history changes. */
|
||||
protected ObserverList<Observer> _obs = ObserverList.newFastUnsafe();
|
||||
|
||||
/** An operation used to notify observers of history updates. */
|
||||
protected HistoryList.HistoryUpdatedOp _historyUpdatedOp = new HistoryUpdatedOp();
|
||||
|
||||
/** The maximum number of history entries we'll keep. */
|
||||
protected static final int MAX_HISTORY = 2000;
|
||||
|
||||
/** The number of history entries we'll prune when we hit the max. */
|
||||
protected static final int PRUNE_HISTORY = 200;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.client;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
import com.threerings.util.MessageBundle;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.client.BasicDirector;
|
||||
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
/**
|
||||
* Manages the mutelist.
|
||||
*
|
||||
* TODO: This class right now is pretty much just a placeholder.
|
||||
*/
|
||||
public class MuteDirector extends BasicDirector
|
||||
implements ChatFilter
|
||||
{
|
||||
/**
|
||||
* An interface that can be registered with the MuteDirector to
|
||||
* receive notifications to the mutelist.
|
||||
*/
|
||||
public static interface MuteObserver
|
||||
{
|
||||
/**
|
||||
* The specified player was added or removed from the mutelist.
|
||||
*/
|
||||
void muteChanged (Name playername, boolean nowMuted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be instantiated after the ChatDirector.
|
||||
*/
|
||||
public MuteDirector (CrowdContext ctx)
|
||||
{
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the mute director with the specified list of initial mutees.
|
||||
*/
|
||||
public MuteDirector (CrowdContext ctx, Name[] list)
|
||||
{
|
||||
this(ctx);
|
||||
|
||||
Collections.addAll(_mutelist, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to shut down the mute director.
|
||||
*/
|
||||
public void shutdown ()
|
||||
{
|
||||
if (_chatdir != null) {
|
||||
_chatdir.removeChatFilter(this);
|
||||
_chatdir = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the required ChatDirector.
|
||||
*/
|
||||
public void setChatDirector (ChatDirector chatdir)
|
||||
{
|
||||
if (_chatdir == null) {
|
||||
_chatdir = chatdir;
|
||||
_chatdir.addChatFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified mutelist observer.
|
||||
*/
|
||||
public void addMuteObserver (MuteObserver obs)
|
||||
{
|
||||
_observers.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified mutelist observer.
|
||||
*/
|
||||
public void removeMuteObserver (MuteObserver obs)
|
||||
{
|
||||
_observers.remove(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the specified user is muted.
|
||||
*/
|
||||
public boolean isMuted (Name username)
|
||||
{
|
||||
return _mutelist.contains(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mute or unmute the specified user.
|
||||
*/
|
||||
public void setMuted (Name username, boolean mute)
|
||||
{
|
||||
boolean changed = mute ? _mutelist.add(username) : _mutelist.remove(username);
|
||||
String feedback;
|
||||
if (mute) {
|
||||
feedback = "m.muted";
|
||||
} else {
|
||||
feedback = changed ? "m.unmuted" : "m.notmuted";
|
||||
}
|
||||
|
||||
// always give some feedback to the user
|
||||
_chatdir.displayFeedback(null, MessageBundle.tcompose(feedback, username));
|
||||
|
||||
// if the mutelist actually changed, notify observers
|
||||
if (changed) {
|
||||
notifyObservers(username, mute);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list of the currently muted players.
|
||||
*
|
||||
* This list may be out of date immediately upon returning from this method.
|
||||
*/
|
||||
public Name[] getMuted ()
|
||||
{
|
||||
return _mutelist.toArray(new Name[_mutelist.size()]);
|
||||
}
|
||||
|
||||
// documentation inherited from interface ChatFilter
|
||||
public String filter (String msg, Name otherUser, boolean outgoing)
|
||||
{
|
||||
// we are only concerned with filtering things going to or coming
|
||||
// from muted users
|
||||
if ((otherUser != null) && isMuted(otherUser)) {
|
||||
// if it was outgoing, explain the dropped message, otherwise
|
||||
// silently drop
|
||||
if (outgoing) {
|
||||
_chatdir.displayFeedback(null, "m.no_tell_mute");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify our observers of a change in the mutelist.
|
||||
*/
|
||||
protected void notifyObservers (final Name username, final boolean muted)
|
||||
{
|
||||
_observers.apply(new ObserverList.ObserverOp<MuteObserver>() {
|
||||
public boolean apply (MuteObserver observer) {
|
||||
observer.muteChanged(username, muted);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** The chat director that we're working hard for. */
|
||||
protected ChatDirector _chatdir;
|
||||
|
||||
/** The mutelist. */
|
||||
protected HashSet<Name> _mutelist = Sets.newHashSet();
|
||||
|
||||
/** List of mutelist observers. */
|
||||
protected ObserverList<MuteObserver> _observers =
|
||||
new ObserverList<MuteObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
/**
|
||||
* Provides a means by which "speaking" can be allowed among subscribers
|
||||
* of a particular distributed object.
|
||||
*/
|
||||
public interface SpeakService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Issues a request to speak "on" the distributed object via which
|
||||
* this speak service was provided.
|
||||
*
|
||||
* @param message the message to be spoken.
|
||||
* @param mode the "mode" of the message. This is an opaque value that
|
||||
* will be passed back down via the {@link ChatDirector} to the {@link
|
||||
* ChatDisplay} implementations which can interpret it in an
|
||||
* application specific manner. It's useful for differentiating
|
||||
* between regular speech, emotes, etc.
|
||||
*/
|
||||
void speak (Client client, String message, byte mode);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChannelSpeakService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link ChannelSpeakService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChannelSpeakService.java.")
|
||||
public class ChannelSpeakMarshaller extends InvocationMarshaller
|
||||
implements ChannelSpeakService
|
||||
{
|
||||
/** The method id used to dispatch {@link #speak} requests. */
|
||||
public static final int SPEAK = 1;
|
||||
|
||||
// from interface ChannelSpeakService
|
||||
public void speak (Client arg1, ChatChannel arg2, String arg3, byte arg4)
|
||||
{
|
||||
sendRequest(arg1, SPEAK, new Object[] {
|
||||
arg2, arg3, Byte.valueOf(arg4)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import com.threerings.io.SimpleStreamableObject;
|
||||
|
||||
import com.threerings.presents.dobj.DSet;
|
||||
|
||||
/**
|
||||
* Represents a chat channel.
|
||||
*/
|
||||
public abstract class ChatChannel extends SimpleStreamableObject
|
||||
implements Comparable<ChatChannel>, DSet.Entry
|
||||
{
|
||||
// from interface Comparable<ChatChannel>
|
||||
public abstract int compareTo (ChatChannel other);
|
||||
|
||||
/**
|
||||
* Converts this channel into a unique name that can be used as the name of the distributed
|
||||
* lock used when resolving the channel.
|
||||
*/
|
||||
public abstract String getLockName ();
|
||||
|
||||
// from interface DSet.Entry
|
||||
public Comparable<?> getKey ()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object other)
|
||||
{
|
||||
return compareTo((ChatChannel)other) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract int hashCode ();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import com.threerings.presents.data.InvocationCodes;
|
||||
import com.threerings.presents.data.Permission;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatDirector;
|
||||
import com.threerings.crowd.chat.client.SpeakService;
|
||||
|
||||
/**
|
||||
* Contains codes used by the chat invocation services.
|
||||
*/
|
||||
public interface ChatCodes extends InvocationCodes
|
||||
{
|
||||
/** A return value used by the ChatDirector and possibly other entities to indicate successful
|
||||
* processing of chat. */
|
||||
public static final String SUCCESS = "success";
|
||||
|
||||
/** The message identifier for a chat notification message. */
|
||||
public static final String CHAT_NOTIFICATION = "crowd.chat";
|
||||
|
||||
/** The message identifier for a chat channel notification message. */
|
||||
public static final String CHAT_CHANNEL_NOTIFICATION = "crowd.chat.channel";
|
||||
|
||||
/** The access control identifier for normal chat privileges. */
|
||||
public static final Permission CHAT_ACCESS = new Permission();
|
||||
|
||||
/** The access control identifier for broadcast chat privileges. */
|
||||
public static final Permission BROADCAST_ACCESS = new Permission();
|
||||
|
||||
/** The configuration key for idle time. */
|
||||
public static final String IDLE_TIME_KEY = "narya.chat.idle_time";
|
||||
|
||||
/** The default time after which a player is assumed idle. */
|
||||
public static final long DEFAULT_IDLE_TIME = 3 * 60 * 1000L;
|
||||
|
||||
/** The chat localtype code for chat messages delivered on the place object currently occupied
|
||||
* by the client. This is the only type of chat message that will be delivered unless the chat
|
||||
* director is explicitly provided with other chat message sources via {@link
|
||||
* ChatDirector#addAuxiliarySource}. */
|
||||
public static final String PLACE_CHAT_TYPE = "placeChat";
|
||||
|
||||
/** The chat localtype for messages received on the user object. */
|
||||
public static final String USER_CHAT_TYPE = "userChat";
|
||||
|
||||
/** The default mode used by {@link SpeakService#speak} requests. */
|
||||
public static final byte DEFAULT_MODE = 0;
|
||||
|
||||
/** A {@link SpeakService#speak} mode to indicate that the user is thinking what they're
|
||||
* saying, or is it that they're saying what they're thinking? */
|
||||
public static final byte THINK_MODE = 1;
|
||||
|
||||
/** A {@link SpeakService#speak} mode to indicate that a speak is actually an emote. */
|
||||
public static final byte EMOTE_MODE = 2;
|
||||
|
||||
/** A {@link SpeakService#speak} mode to indicate that a speak is actually a shout. */
|
||||
public static final byte SHOUT_MODE = 3;
|
||||
|
||||
/** A {@link SpeakService#speak} mode to indicate that a speak is actually a server-wide
|
||||
* broadcast. */
|
||||
public static final byte BROADCAST_MODE = 4;
|
||||
|
||||
/** The last chat mode defined in the interface. */
|
||||
public static final byte LAST_MODE = BROADCAST_MODE;
|
||||
|
||||
/** String translations for the various chat modes. */
|
||||
public static final String[] XLATE_MODES = {
|
||||
"default", "think", "emote", "shout", "broadcast"
|
||||
};
|
||||
|
||||
/** An error code delivered when the user targeted for a tell notification is not online. */
|
||||
public static final String USER_NOT_ONLINE = "m.user_not_online";
|
||||
|
||||
/** An error code delivered when the user targeted for a tell notification is disconnected. */
|
||||
public static final String USER_DISCONNECTED = "m.user_disconnected";
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
import com.threerings.presents.dobj.InvocationResponseEvent;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link ChatService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChatService.java.")
|
||||
public class ChatMarshaller extends InvocationMarshaller
|
||||
implements ChatService
|
||||
{
|
||||
/**
|
||||
* Marshalls results to implementations of {@link ChatService.TellListener}.
|
||||
*/
|
||||
public static class TellMarshaller extends ListenerMarshaller
|
||||
implements TellListener
|
||||
{
|
||||
/** The method id used to dispatch {@link #tellSucceeded}
|
||||
* responses. */
|
||||
public static final int TELL_SUCCEEDED = 1;
|
||||
|
||||
// from interface TellMarshaller
|
||||
public void tellSucceeded (long arg1, String arg2)
|
||||
{
|
||||
_invId = null;
|
||||
omgr.postEvent(new InvocationResponseEvent(
|
||||
callerOid, requestId, TELL_SUCCEEDED,
|
||||
new Object[] { Long.valueOf(arg1), arg2 }, transport));
|
||||
}
|
||||
|
||||
@Override // from InvocationMarshaller
|
||||
public void dispatchResponse (int methodId, Object[] args)
|
||||
{
|
||||
switch (methodId) {
|
||||
case TELL_SUCCEEDED:
|
||||
((TellListener)listener).tellSucceeded(
|
||||
((Long)args[0]).longValue(), (String)args[1]);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchResponse(methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #away} requests. */
|
||||
public static final int AWAY = 1;
|
||||
|
||||
// from interface ChatService
|
||||
public void away (Client arg1, String arg2)
|
||||
{
|
||||
sendRequest(arg1, AWAY, new Object[] {
|
||||
arg2
|
||||
});
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #broadcast} requests. */
|
||||
public static final int BROADCAST = 2;
|
||||
|
||||
// from interface ChatService
|
||||
public void broadcast (Client arg1, String arg2, InvocationService.InvocationListener arg3)
|
||||
{
|
||||
ListenerMarshaller listener3 = new ListenerMarshaller();
|
||||
listener3.listener = arg3;
|
||||
sendRequest(arg1, BROADCAST, new Object[] {
|
||||
arg2, listener3
|
||||
});
|
||||
}
|
||||
|
||||
/** The method id used to dispatch {@link #tell} requests. */
|
||||
public static final int TELL = 3;
|
||||
|
||||
// from interface ChatService
|
||||
public void tell (Client arg1, Name arg2, String arg3, ChatService.TellListener arg4)
|
||||
{
|
||||
ChatMarshaller.TellMarshaller listener4 = new ChatMarshaller.TellMarshaller();
|
||||
listener4.listener = arg4;
|
||||
sendRequest(arg1, TELL, new Object[] {
|
||||
arg2, arg3, listener4
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.io.Streamable;
|
||||
|
||||
import com.threerings.util.ActionScript;
|
||||
|
||||
/**
|
||||
* The abstract base class of all the client-side ChatMessage objects.
|
||||
*/
|
||||
public abstract class ChatMessage
|
||||
implements Streamable
|
||||
{
|
||||
/** The actual text of the message. */
|
||||
public String message;
|
||||
|
||||
/** The bundle to use when translating this message. */
|
||||
public String bundle;
|
||||
|
||||
/** The client side 'localtype' of this chat, set to the type registered with an auxiliary
|
||||
* source in the ChatDirector. */
|
||||
public transient String localtype;
|
||||
|
||||
/** The client time that this message was created. */
|
||||
@ActionScript(type="int")
|
||||
public transient long timestamp;
|
||||
|
||||
/**
|
||||
* For all your unserialization needs.
|
||||
*/
|
||||
public ChatMessage ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a ChatMessage.
|
||||
*/
|
||||
public ChatMessage (String message, String bundle)
|
||||
{
|
||||
this.message = message;
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Once this message reaches the client, the information contained within is changed around a
|
||||
* bit.
|
||||
*/
|
||||
public void setClientInfo (String msg, String ltype)
|
||||
{
|
||||
message = msg;
|
||||
localtype = ltype;
|
||||
bundle = null;
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate message format for this message.
|
||||
*/
|
||||
public String getFormat ()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
return StringUtil.shortClassName(this) + StringUtil.fieldsToString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import com.threerings.crowd.chat.server.SpeakUtil;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Marks a {@link Name} as disinterested in chat history such that {@link SpeakUtil} will keep no
|
||||
* messages sent to it.
|
||||
*/
|
||||
public interface KeepNoHistory
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.SpeakService;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.data.InvocationMarshaller;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the {@link SpeakService} interface
|
||||
* that marshalls the arguments and delivers the request to the provider
|
||||
* on the server. Also provides an implementation of the response listener
|
||||
* interfaces that marshall the response arguments and deliver them back
|
||||
* to the requesting client.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from SpeakService.java.")
|
||||
public class SpeakMarshaller extends InvocationMarshaller
|
||||
implements SpeakService
|
||||
{
|
||||
/** The method id used to dispatch {@link #speak} requests. */
|
||||
public static final int SPEAK = 1;
|
||||
|
||||
// from interface SpeakService
|
||||
public void speak (Client arg1, String arg2, byte arg3)
|
||||
{
|
||||
sendRequest(arg1, SPEAK, new Object[] {
|
||||
arg2, Byte.valueOf(arg3)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Provides a mechanism by which the speak service can identify chat listeners so as to maintain a
|
||||
* recent history of all chat traffic on the server.
|
||||
*/
|
||||
public interface SpeakObject
|
||||
{
|
||||
/** Used in conjunction with {@link SpeakObject#applyToListeners}. */
|
||||
public static interface ListenerOp
|
||||
{
|
||||
/** Call this method if you only have access to body oids. */
|
||||
void apply (int bodyOid);
|
||||
|
||||
/** Call this method if you can provide usernames directly. */
|
||||
void apply (Name username);
|
||||
}
|
||||
|
||||
/**
|
||||
* The speak service will call this every time a chat message is delivered on this speak object
|
||||
* to note the listeners that received the message.
|
||||
*/
|
||||
void applyToListeners (ListenerOp op);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
/**
|
||||
* A ChatMessage that represents a message that came from the server and did not result from direct
|
||||
* user action.
|
||||
*/
|
||||
public class SystemMessage extends ChatMessage
|
||||
{
|
||||
/** Attention level constant to indicate that this message is merely providing the user with
|
||||
* information. */
|
||||
public static final byte INFO = 0;
|
||||
|
||||
/** Attention level constant to indicate that this message is the result of a user action. */
|
||||
public static final byte FEEDBACK = 1;
|
||||
|
||||
/** Attention level constant to indicate that some action is required. */
|
||||
public static final byte ATTENTION = 2;
|
||||
|
||||
/** The attention level of this message. */
|
||||
public byte attentionLevel;
|
||||
|
||||
// documentation inherited
|
||||
public SystemMessage ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a SystemMessage.
|
||||
*/
|
||||
public SystemMessage (String message, String bundle, byte attentionLevel)
|
||||
{
|
||||
super(message, bundle);
|
||||
this.attentionLevel = attentionLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* A feedback message to indicate that a tell succeeded.
|
||||
*/
|
||||
public class TellFeedbackMessage extends UserMessage
|
||||
{
|
||||
/**
|
||||
* A tell feedback message is only composed on the client.
|
||||
*/
|
||||
public TellFeedbackMessage (Name target, String message, boolean failure)
|
||||
{
|
||||
super(target, null, message, ChatCodes.DEFAULT_MODE);
|
||||
_failure = failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is a failure feedback, false if it is successful tell feedback.
|
||||
*/
|
||||
public boolean isFailure ()
|
||||
{
|
||||
return _failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat ()
|
||||
{
|
||||
return _failure ? null : "m.told_format";
|
||||
}
|
||||
|
||||
protected boolean _failure;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* A ChatMessage representing a message that came from another user.
|
||||
*/
|
||||
public class UserMessage extends ChatMessage
|
||||
{
|
||||
/** The user that the message came from. */
|
||||
public Name speaker;
|
||||
|
||||
/** The mode of the message. @see ChatCodes.DEFAULT_MODE */
|
||||
public byte mode;
|
||||
|
||||
/**
|
||||
* For unserialization.
|
||||
*/
|
||||
public UserMessage ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a user message.
|
||||
*/
|
||||
public UserMessage (Name speaker, String bundle, String message, byte mode)
|
||||
{
|
||||
super(message, bundle);
|
||||
this.speaker = speaker;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a user message for a player originated tell (which has no bundle and is in the
|
||||
* default mode).
|
||||
*/
|
||||
public UserMessage (Name speaker, String message)
|
||||
{
|
||||
super(message, null);
|
||||
this.speaker = speaker;
|
||||
this.mode = ChatCodes.DEFAULT_MODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name to display for the speaker. Some types of messages may wish to not use the
|
||||
* canonical name for the speaker and should thus override this function.
|
||||
*/
|
||||
public Name getSpeakerDisplayName ()
|
||||
{
|
||||
return speaker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormat ()
|
||||
{
|
||||
switch (mode) {
|
||||
case ChatCodes.THINK_MODE: return "m.think_format";
|
||||
case ChatCodes.EMOTE_MODE: return "m.emote_format";
|
||||
case ChatCodes.SHOUT_MODE: return "m.shout_format";
|
||||
case ChatCodes.BROADCAST_MODE: return "m.broadcast_format";
|
||||
default: // fall through
|
||||
}
|
||||
|
||||
if (ChatCodes.USER_CHAT_TYPE.equals(localtype)) {
|
||||
return "m.tell_format";
|
||||
}
|
||||
return "m.speak_format";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.data;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* A system message triggered by the activity of another user. If the user is muted we can suppress
|
||||
* this message, unlike a normal system message.
|
||||
*/
|
||||
public class UserSystemMessage extends SystemMessage
|
||||
{
|
||||
/** The "speaker" of this message, the user that triggered that this message be sent to us. */
|
||||
public Name speaker;
|
||||
|
||||
/** Suitable for unserialization. */
|
||||
public UserSystemMessage ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a INFO-level UserSystemMessage.
|
||||
*/
|
||||
public UserSystemMessage (Name sender, String message, String bundle)
|
||||
{
|
||||
this(sender, message, bundle, INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a UserSystemMessage.
|
||||
*/
|
||||
public UserSystemMessage (Name sender, String message, String bundle, byte attentionLevel)
|
||||
{
|
||||
super(message, bundle, attentionLevel);
|
||||
this.speaker = sender;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChannelSpeakMarshaller;
|
||||
import com.threerings.crowd.chat.data.ChatChannel;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link ChannelSpeakProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChannelSpeakService.java.")
|
||||
public class ChannelSpeakDispatcher extends InvocationDispatcher<ChannelSpeakMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public ChannelSpeakDispatcher (ChannelSpeakProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelSpeakMarshaller createMarshaller ()
|
||||
{
|
||||
return new ChannelSpeakMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case ChannelSpeakMarshaller.SPEAK:
|
||||
((ChannelSpeakProvider)provider).speak(
|
||||
source, (ChatChannel)args[0], (String)args[1], ((Byte)args[2]).byteValue()
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChannelSpeakService;
|
||||
import com.threerings.crowd.chat.data.ChatChannel;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
/**
|
||||
* Defines the server-side of the {@link ChannelSpeakService}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChannelSpeakService.java.")
|
||||
public interface ChannelSpeakProvider extends InvocationProvider
|
||||
{
|
||||
/**
|
||||
* Handles a {@link ChannelSpeakService#speak} request.
|
||||
*/
|
||||
void speak (ClientObject caller, ChatChannel arg1, String arg2, byte arg3);
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import com.samskivert.util.ArrayIntSet;
|
||||
import com.samskivert.util.ResultListener;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.annotation.AnyThread;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
import com.threerings.presents.server.PresentsDObjectMgr;
|
||||
|
||||
import com.threerings.presents.peer.data.ClientInfo;
|
||||
import com.threerings.presents.peer.data.NodeObject;
|
||||
import com.threerings.presents.peer.server.PeerManager;
|
||||
import com.threerings.presents.peer.server.PeerManager.NodeRequest;
|
||||
import com.threerings.presents.peer.server.NodeRequestsListener;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChatChannel;
|
||||
import com.threerings.crowd.chat.data.ChatCodes;
|
||||
import com.threerings.crowd.chat.data.UserMessage;
|
||||
import com.threerings.crowd.chat.server.SpeakUtil.ChatHistoryEntry;
|
||||
import com.threerings.crowd.data.BodyObject;
|
||||
import com.threerings.crowd.data.CrowdCodes;
|
||||
import com.threerings.crowd.peer.data.CrowdClientInfo;
|
||||
import com.threerings.crowd.peer.data.CrowdNodeObject;
|
||||
import com.threerings.crowd.peer.server.CrowdPeerManager;
|
||||
|
||||
import static com.threerings.crowd.Log.log;
|
||||
|
||||
/**
|
||||
* Handles chat channel services.
|
||||
*/
|
||||
public abstract class ChatChannelManager
|
||||
implements ChannelSpeakProvider
|
||||
{
|
||||
/**
|
||||
* Value asynchronously returned by {@link #collectChatHistory} after polling all peer nodes.
|
||||
*/
|
||||
public static class ChatHistoryResult
|
||||
{
|
||||
/** The set of nodes that either did not reply within the timeout, or had a failure. */
|
||||
public Set<String> failedNodes;
|
||||
|
||||
/** The things in the user's chat history, aggregated from all nodes and sorted by
|
||||
* timestamp. */
|
||||
public List<ChatHistoryEntry> history;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a body becomes a member of a channel, this method should be called so that any server
|
||||
* that happens to be hosting that channel can be told that the body in question is now a
|
||||
* participant.
|
||||
*/
|
||||
@AnyThread
|
||||
public void bodyAddedToChannel (ChatChannel channel, int bodyId)
|
||||
{
|
||||
_peerMan.invokeNodeAction(new ParticipantChanged(channel, bodyId, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* When a body loses channel membership, this method should be called so that any server that
|
||||
* happens to be hosting that channel can be told that the body in question is now a
|
||||
* participant.
|
||||
*/
|
||||
@AnyThread
|
||||
public void bodyRemovedFromChannel (ChatChannel channel, int bodyId)
|
||||
{
|
||||
_peerMan.invokeNodeAction(new ParticipantChanged(channel, bodyId, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all chat messages heard by the given user on all peers.
|
||||
*/
|
||||
@AnyThread
|
||||
public void collectChatHistory (Name user, final ResultListener<ChatHistoryResult> lner)
|
||||
{
|
||||
NodeRequestsListener<List<ChatHistoryEntry>> listener =
|
||||
new NodeRequestsListener<List<ChatHistoryEntry>>() {
|
||||
public void requestsProcessed (NodeRequestsResult<List<ChatHistoryEntry>> rRes) {
|
||||
ChatHistoryResult chRes = new ChatHistoryResult();
|
||||
chRes.failedNodes = rRes.getNodeErrors().keySet();
|
||||
chRes.history = Lists.newArrayList(
|
||||
Iterables.concat(rRes.getNodeResults().values()));
|
||||
Collections.sort(chRes.history, SORT_BY_TIMESTAMP);
|
||||
lner.requestCompleted(chRes);
|
||||
}
|
||||
public void requestFailed (String cause) {
|
||||
lner.requestFailed(new InvocationException(cause));
|
||||
}
|
||||
};
|
||||
_peerMan.invokeNodeRequest(new ChatCollectionRequest(user), listener);
|
||||
}
|
||||
|
||||
// from interface ChannelSpeakProvider
|
||||
public void speak (ClientObject caller, final ChatChannel channel, String message, byte mode)
|
||||
{
|
||||
final UserMessage umsg = new UserMessage(
|
||||
((BodyObject)caller).getVisibleName(), null, message, mode);
|
||||
|
||||
// if we're hosting this channel, dispatch it directly
|
||||
if (_channels.containsKey(channel)) {
|
||||
dispatchSpeak(channel, umsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're resolving this channel, queue up our message for momentary deliver
|
||||
List<UserMessage> msgs = _resolving.get(channel);
|
||||
if (msgs != null) {
|
||||
msgs.add(umsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// forward the speak request to the server that hosts the channel in question
|
||||
_peerMan.invokeNodeAction(new ForwardChannelSpeak(channel, umsg), new Runnable() {
|
||||
public void run () {
|
||||
_resolving.put(channel, Lists.newArrayList(umsg));
|
||||
resolveAndDispatch(channel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates our singleton manager and registers our invocation service.
|
||||
*/
|
||||
@Inject protected ChatChannelManager (PresentsDObjectMgr omgr, InvocationManager invmgr)
|
||||
{
|
||||
invmgr.registerDispatcher(new ChannelSpeakDispatcher(this), CrowdCodes.CROWD_GROUP);
|
||||
|
||||
// create and start our idle channel closer; this will run as long as omgr is alive
|
||||
omgr.newInterval(new Runnable() {
|
||||
public void run () {
|
||||
closeIdleChannels();
|
||||
}
|
||||
}).schedule(IDLE_CHANNEL_CHECK_PERIOD, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the channel specified in the supplied action and then dispatches it.
|
||||
*/
|
||||
protected void resolveAndDispatch (final ChatChannel channel)
|
||||
{
|
||||
NodeObject.Lock lock = new NodeObject.Lock("ChatChannel", channel.getLockName());
|
||||
_peerMan.performWithLock(lock, new PeerManager.LockedOperation() {
|
||||
public void run () {
|
||||
((CrowdNodeObject)_peerMan.getNodeObject()).addToHostedChannels(channel);
|
||||
finishResolveAndDispatch(channel);
|
||||
}
|
||||
public void fail (String peerName) {
|
||||
List<UserMessage> msgs = _resolving.remove(channel);
|
||||
if (peerName == null) {
|
||||
log.warning("Failed to resolve chat channel due to lock failure",
|
||||
"channel", channel);
|
||||
} else {
|
||||
// some other peer resolved this channel first, so forward any queued messages
|
||||
// directly to that node
|
||||
for (UserMessage msg : msgs) {
|
||||
_peerMan.invokeNodeAction(peerName, new ForwardChannelSpeak(channel, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the participant set for the specified chat channel and dispatches all pending
|
||||
* messages to the channel. End users of the chat channel system should override this method
|
||||
* and do what is necessary to resolve the channel's participant set and call {@link
|
||||
* #resolutionComplete} or {@link #resolutionFailed}.
|
||||
*/
|
||||
protected void finishResolveAndDispatch (ChatChannel channel)
|
||||
{
|
||||
resolutionComplete(channel, new ArrayIntSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called when a channel's participant set has been resolved.
|
||||
*/
|
||||
protected void resolutionComplete (ChatChannel channel, Set<Integer> parts)
|
||||
{
|
||||
// map the participants of our now resolved channel
|
||||
ChannelInfo info = new ChannelInfo();
|
||||
info.channel = channel;
|
||||
info.participants = parts;
|
||||
_channels.put(channel, info);
|
||||
|
||||
// dispatch any pending messages now that we know where they go
|
||||
for (UserMessage msg : _resolving.remove(channel)) {
|
||||
dispatchSpeak(channel, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called if channel resolution fails.
|
||||
*/
|
||||
protected void resolutionFailed (ChatChannel channel, Exception cause)
|
||||
{
|
||||
log.warning("Failed to resolve chat channel", "channel", channel, cause);
|
||||
|
||||
// alas, we just drop all pending messages because we're hosed
|
||||
_resolving.remove(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that we dispatch the supplied message to all participants of the specified chat
|
||||
* channel. The speaker will be validated prior to dispatching the message as the originating
|
||||
* server does not have the information it needs to validate the speaker and must leave that to
|
||||
* us, the channel hosting server.
|
||||
*/
|
||||
protected void dispatchSpeak (ChatChannel channel, UserMessage message)
|
||||
{
|
||||
final ChannelInfo info = _channels.get(channel);
|
||||
if (info == null) {
|
||||
// TODO: maybe we should just reresolve the channel...
|
||||
log.warning("Requested to dispatch speak on unhosted channel", "channel", channel,
|
||||
"msg", message);
|
||||
return;
|
||||
}
|
||||
|
||||
// validate the speaker
|
||||
if (!info.participants.contains(getBodyId(message.speaker))) {
|
||||
log.warning("Dropping channel chat message from non-speaker", "channel", channel,
|
||||
"message", message);
|
||||
return;
|
||||
}
|
||||
|
||||
// note that we're dispatching a message on this channel
|
||||
info.lastMessage = System.currentTimeMillis();
|
||||
|
||||
// generate a mapping from node name to an array of body ids for the participants that are
|
||||
// currently on the node in question
|
||||
final Map<String,int[]> partMap = Maps.newHashMap();
|
||||
for (NodeObject nodeobj : _peerMan.getNodeObjects()) {
|
||||
ArrayIntSet nodeBodyIds = new ArrayIntSet();
|
||||
for (ClientInfo clinfo : nodeobj.clients) {
|
||||
int bodyId = getBodyId(((CrowdClientInfo)clinfo).visibleName);
|
||||
if (info.participants.contains(bodyId)) {
|
||||
nodeBodyIds.add(bodyId);
|
||||
}
|
||||
}
|
||||
partMap.put(nodeobj.nodeName, nodeBodyIds.toIntArray());
|
||||
}
|
||||
|
||||
for (Map.Entry<String,int[]> entry : partMap.entrySet()) {
|
||||
_peerMan.invokeNodeAction(
|
||||
entry.getKey(), new DispatchChannelSpeak(channel, message, entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers the supplied chat channel message to the specified bodies.
|
||||
*/
|
||||
protected void deliverSpeak (ChatChannel channel, UserMessage message, int[] bodyIds)
|
||||
{
|
||||
channel = intern(channel);
|
||||
for (int bodyId : bodyIds) {
|
||||
BodyObject bobj = getBodyObject(bodyId);
|
||||
if (bobj != null && shouldDeliverSpeak(channel, message, bobj)) {
|
||||
SpeakUtil.recordToChatHistory(channel, message, bobj.getVisibleName());
|
||||
bobj.postMessage(ChatCodes.CHAT_CHANNEL_NOTIFICATION, channel, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called periodically to check for and close any channels that have been idle too long.
|
||||
*/
|
||||
protected void closeIdleChannels ()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator<Map.Entry<ChatChannel, ChannelInfo>> iter = _channels.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<ChatChannel, ChannelInfo> entry = iter.next();
|
||||
if (now - entry.getValue().lastMessage > IDLE_CHANNEL_CLOSE_TIME) {
|
||||
((CrowdNodeObject)_peerMan.getNodeObject()).removeFromHostedChannels(
|
||||
entry.getKey());
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ratifies the delivery of the supplied chat channel message to the specified body. Derived
|
||||
* classes can override this method to implement channel disabling, mute lists or any other
|
||||
* suppression they might need.
|
||||
*/
|
||||
protected boolean shouldDeliverSpeak (ChatChannel channel, UserMessage message, BodyObject body)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a widely referenced instance equivalent to the given channel, if one is available.
|
||||
* This reduces memory usage since clients send new channel instances with each message.
|
||||
*/
|
||||
protected ChatChannel intern (ChatChannel channel)
|
||||
{
|
||||
ChannelInfo chinfo = _channels.get(channel);
|
||||
if (chinfo != null) {
|
||||
return chinfo.channel;
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a speaker's visible name into a unique integer id. This is not the oid for this
|
||||
* speaker but rather a persistent integer identifier that can be passed between servers and
|
||||
* used to look up the speaker on the target server via a call to {@link #getBodyObject}. We
|
||||
* use this rather than names to avoid having to send (large) {@link Name} objects for every
|
||||
* channel participant to each individual peer that will be forwarding messages.
|
||||
*/
|
||||
protected abstract int getBodyId (Name speaker);
|
||||
|
||||
/**
|
||||
* Locates a body object from the given unique id. May return null.
|
||||
*/
|
||||
protected abstract BodyObject getBodyObject (int bodyId);
|
||||
|
||||
/** Forwards a channel speak request from the server hosting the message originator to the
|
||||
* server that is hosting the channel. */
|
||||
protected abstract static class ChannelAction extends PeerManager.NodeAction
|
||||
{
|
||||
public ChannelAction (ChatChannel channel) {
|
||||
_channel = channel;
|
||||
}
|
||||
public ChannelAction () {
|
||||
}
|
||||
@Override public boolean isApplicable (NodeObject nodeobj) {
|
||||
return ((CrowdNodeObject)nodeobj).hostedChannels.contains(_channel);
|
||||
}
|
||||
protected ChatChannel _channel;
|
||||
@Inject protected transient ChatChannelManager _channelMan;
|
||||
}
|
||||
|
||||
/** Informs the server hosting a channel that a body has been added to or removed from the
|
||||
* channel's participants set. */
|
||||
protected static class ParticipantChanged extends ChannelAction
|
||||
{
|
||||
public ParticipantChanged (ChatChannel channel, int bodyId, boolean added) {
|
||||
super(channel);
|
||||
_bodyId = bodyId;
|
||||
_added = added;
|
||||
}
|
||||
public ParticipantChanged () {
|
||||
}
|
||||
@Override protected void execute () {
|
||||
ChannelInfo info = _channelMan._channels.get(_channel);
|
||||
if (info != null) {
|
||||
if (_added) {
|
||||
info.participants.add(_bodyId);
|
||||
} else {
|
||||
info.participants.remove(_bodyId);
|
||||
}
|
||||
} else if (_channelMan._resolving.containsKey(_channel)) {
|
||||
log.warning("Oh for fuck's sake, distributed systems are complicated",
|
||||
"channel", _channel);
|
||||
}
|
||||
}
|
||||
protected int _bodyId;
|
||||
protected boolean _added;
|
||||
}
|
||||
|
||||
protected static class ChatCollectionRequest extends NodeRequest
|
||||
{
|
||||
public ChatCollectionRequest (Name user)
|
||||
{
|
||||
_user = user;
|
||||
}
|
||||
|
||||
public ChatCollectionRequest ()
|
||||
{
|
||||
}
|
||||
|
||||
@Override public boolean isApplicable (NodeObject nodeobj)
|
||||
{
|
||||
// poll all nodes
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override protected void execute (InvocationService.ResultListener listener)
|
||||
{
|
||||
// find all the UserMessages for the given user and send them back
|
||||
listener.requestProcessed(Lists.newArrayList(Iterables.filter(
|
||||
SpeakUtil.getChatHistory(_user), IS_USER_MESSAGE)));
|
||||
}
|
||||
|
||||
protected Name _user;
|
||||
}
|
||||
|
||||
protected static final Predicate<ChatHistoryEntry> IS_USER_MESSAGE =
|
||||
new Predicate<ChatHistoryEntry>() {
|
||||
public boolean apply (ChatHistoryEntry entry) {
|
||||
return entry.message instanceof UserMessage;
|
||||
}
|
||||
};
|
||||
|
||||
protected static final Comparator<ChatHistoryEntry> SORT_BY_TIMESTAMP =
|
||||
new Comparator<ChatHistoryEntry>() {
|
||||
public int compare (ChatHistoryEntry e1, ChatHistoryEntry e2) {
|
||||
return Longs.compare(e1.message.timestamp, e2.message.timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
/** Forwards a channel speak request from the server hosting the message originator to the
|
||||
* server that is hosting the channel. */
|
||||
protected static class ForwardChannelSpeak extends ChannelAction
|
||||
{
|
||||
public ForwardChannelSpeak (ChatChannel channel, UserMessage message) {
|
||||
super(channel);
|
||||
_message = message;
|
||||
}
|
||||
public ForwardChannelSpeak () {
|
||||
}
|
||||
@Override protected void execute () {
|
||||
_channelMan.dispatchSpeak(_channel, _message);
|
||||
}
|
||||
protected UserMessage _message;
|
||||
}
|
||||
|
||||
/** Forwards a chat channel message to the server to which some subset of the channel
|
||||
* participants are connected so that it can dispatch the message on their body objects. */
|
||||
protected static class DispatchChannelSpeak extends ForwardChannelSpeak
|
||||
{
|
||||
public DispatchChannelSpeak (ChatChannel channel, UserMessage message, int[] bodyIds) {
|
||||
super(channel, message);
|
||||
_bodyIds = bodyIds;
|
||||
}
|
||||
public DispatchChannelSpeak () {
|
||||
}
|
||||
@Override public boolean isApplicable (NodeObject nodeobj) {
|
||||
return true; // not used
|
||||
}
|
||||
@Override protected void execute () {
|
||||
_channelMan.deliverSpeak(_channel, _message, _bodyIds);
|
||||
}
|
||||
protected int[] _bodyIds;
|
||||
}
|
||||
|
||||
/** Contains metadata for a particular channel. */
|
||||
protected static class ChannelInfo
|
||||
{
|
||||
/** The channel this info is for. */
|
||||
public ChatChannel channel;
|
||||
|
||||
/** The body ids of the participants of this channel. */
|
||||
public Set<Integer> participants;
|
||||
|
||||
/** The time at which a message was last dispatched on this channel. */
|
||||
public long lastMessage;
|
||||
}
|
||||
|
||||
/** Contains pending messages for all channels currently being resolved. */
|
||||
protected Map<ChatChannel,List<UserMessage>> _resolving = Maps.newHashMap();
|
||||
|
||||
/** A map of resolved channels to metadata records. */
|
||||
protected Map<ChatChannel,ChannelInfo> _channels = Maps.newHashMap();
|
||||
|
||||
/** Provides peer services. */
|
||||
@Inject protected CrowdPeerManager _peerMan;
|
||||
|
||||
/** The period on which we check for idle channels. */
|
||||
protected static final long IDLE_CHANNEL_CHECK_PERIOD = 5 * 1000L;
|
||||
|
||||
/** The amount of idle time (in milliseconds) after which we close a channel. */
|
||||
protected static final long IDLE_CHANNEL_CLOSE_TIME = 5 * 60 * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatService;
|
||||
import com.threerings.crowd.chat.data.ChatMarshaller;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.util.Name;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link ChatProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from ChatService.java.")
|
||||
public class ChatDispatcher extends InvocationDispatcher<ChatMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public ChatDispatcher (ChatProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatMarshaller createMarshaller ()
|
||||
{
|
||||
return new ChatMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case ChatMarshaller.AWAY:
|
||||
((ChatProvider)provider).away(
|
||||
source, (String)args[0]
|
||||
);
|
||||
return;
|
||||
|
||||
case ChatMarshaller.BROADCAST:
|
||||
((ChatProvider)provider).broadcast(
|
||||
source, (String)args[0], (InvocationService.InvocationListener)args[1]
|
||||
);
|
||||
return;
|
||||
|
||||
case ChatMarshaller.TELL:
|
||||
((ChatProvider)provider).tell(
|
||||
source, (Name)args[0], (String)args[1], (ChatService.TellListener)args[2]
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.util.MessageBundle;
|
||||
import com.threerings.util.Name;
|
||||
import com.threerings.util.TimeUtil;
|
||||
|
||||
import com.threerings.presents.client.InvocationService.InvocationListener;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
import com.threerings.crowd.chat.client.ChatService.TellListener;
|
||||
import com.threerings.crowd.chat.client.ChatService;
|
||||
import com.threerings.crowd.chat.data.ChatCodes;
|
||||
import com.threerings.crowd.chat.data.SystemMessage;
|
||||
import com.threerings.crowd.chat.data.UserMessage;
|
||||
import com.threerings.crowd.data.BodyObject;
|
||||
import com.threerings.crowd.data.CrowdCodes;
|
||||
import com.threerings.crowd.data.OccupantInfo;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.crowd.server.BodyLocal;
|
||||
import com.threerings.crowd.server.BodyLocator;
|
||||
import com.threerings.crowd.server.PlaceRegistry;
|
||||
|
||||
/**
|
||||
* The chat provider handles the server side of the chat-related invocation services.
|
||||
*/
|
||||
@Singleton
|
||||
public class ChatProvider
|
||||
implements InvocationProvider
|
||||
{
|
||||
/** Interface to allow an auto response to a tell message. */
|
||||
public static interface TellAutoResponder
|
||||
{
|
||||
/**
|
||||
* Called following the delivery of <code>message</code> from <code>teller</code> to
|
||||
* <code>tellee</code>.
|
||||
*/
|
||||
void sentTell (BodyObject teller, BodyObject tellee, String message);
|
||||
}
|
||||
|
||||
/** Used to forward certain types of chat messages between servers in a multi-server setup. */
|
||||
public static interface ChatForwarder
|
||||
{
|
||||
/**
|
||||
* Requests that the supplied tell message be delivered to the appropriate destination.
|
||||
*
|
||||
* @return true if the tell was delivered, false otherwise.
|
||||
*/
|
||||
boolean forwardTell (UserMessage message, Name target, TellListener listener);
|
||||
|
||||
/**
|
||||
* Requests that the supplied broadcast message be delivered on other servers.
|
||||
*/
|
||||
void forwardBroadcast (Name from, byte levelOrMode, String bundle, String msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and registers this chat provider.
|
||||
*/
|
||||
@Inject public ChatProvider (InvocationManager invmgr)
|
||||
{
|
||||
// register a chat provider with the invocation manager
|
||||
invmgr.registerDispatcher(new ChatDispatcher(this), CrowdCodes.CROWD_GROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an object to which all broadcasts should be sent, rather than iterating over the place
|
||||
* objects and sending to each of them.
|
||||
*
|
||||
* @param object an object to send all broadcasts, or null to send to each place object
|
||||
* instead.
|
||||
*/
|
||||
public void setAlternateBroadcastObject (DObject object)
|
||||
{
|
||||
_broadcastObject = object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the auto tell responder for the chat provider. Only one auto responder is allowed.
|
||||
* <em>Note:</em> this only works for same-server tells. If the tell is forwarded to another
|
||||
* server, no auto-response opportunity is provided (because we never have both body objects in
|
||||
* the same place).
|
||||
*/
|
||||
public void setTellAutoResponder (TellAutoResponder autoRespond)
|
||||
{
|
||||
_autoRespond = autoRespond;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the chat forwarder. This is used by the Crowd peer services to forward messages
|
||||
* between servers in a multi-server cluster.
|
||||
*/
|
||||
public void setChatForwarder (ChatForwarder forwarder)
|
||||
{
|
||||
_chatForwarder = forwarder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a {@link ChatService#tell} request.
|
||||
*/
|
||||
public void tell (ClientObject caller, Name target, String message, TellListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
// ensure that the caller has normal chat privileges
|
||||
InvocationException.requireAccess(caller, ChatCodes.CHAT_ACCESS);
|
||||
|
||||
// deliver the tell message to the target
|
||||
BodyObject source = (BodyObject)caller;
|
||||
deliverTell(createTellMessage(source, message), target, listener);
|
||||
|
||||
// inform the auto-responder if needed
|
||||
BodyObject targobj;
|
||||
if (_autoRespond != null && (targobj = _locator.lookupBody(target)) != null) {
|
||||
_autoRespond.sentTell(source, targobj, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a {@link ChatService#broadcast} request.
|
||||
*/
|
||||
public void broadcast (ClientObject caller, String message, InvocationListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
// make sure the requesting user has broadcast privileges
|
||||
InvocationException.requireAccess(caller, ChatCodes.BROADCAST_ACCESS);
|
||||
BodyObject body = (BodyObject)caller;
|
||||
broadcast(body.getVisibleName(), null, message, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a {@link ChatService#away} request.
|
||||
*/
|
||||
public void away (ClientObject caller, String message)
|
||||
{
|
||||
BodyObject body = (BodyObject)caller;
|
||||
// we modify this field via an invocation service request because a body object is not
|
||||
// modifiable by the client
|
||||
body.setAwayMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the specified message to all place objects in the system.
|
||||
*
|
||||
* @param from the user the broadcast is from, or null to send the message as a system message.
|
||||
* @param bundle the bundle, or null if the message needs no translation.
|
||||
* @param msg the content of the message to broadcast.
|
||||
* @param attention if true, the message is sent as ATTENTION level, otherwise as INFO. Ignored
|
||||
* if from is non-null.
|
||||
* @param forward if true, forward this broadcast on to any registered chat forwarder, if
|
||||
* false, deliver it only locally on this server.
|
||||
*/
|
||||
public void broadcast (Name from, String bundle, String msg, boolean attention, boolean forward)
|
||||
{
|
||||
byte levelOrMode = (from != null) ? ChatCodes.BROADCAST_MODE
|
||||
: (attention ? SystemMessage.ATTENTION : SystemMessage.INFO);
|
||||
broadcast(from, levelOrMode, bundle, msg, forward);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast with support for a customizable level or mode.
|
||||
* @param levelOrMode if from is null, it's an attentionLevel, else it's a mode code.
|
||||
*/
|
||||
public void broadcast (Name from, byte levelOrMode, String bundle, String msg, boolean forward)
|
||||
{
|
||||
if (_broadcastObject != null) {
|
||||
broadcastTo(_broadcastObject, from, levelOrMode, bundle, msg);
|
||||
|
||||
} else {
|
||||
for (Iterator<PlaceObject> iter = _plreg.enumeratePlaces(); iter.hasNext(); ) {
|
||||
PlaceObject plobj = iter.next();
|
||||
if (plobj.shouldBroadcast()) {
|
||||
broadcastTo(plobj, from, levelOrMode, bundle, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (forward && _chatForwarder != null) {
|
||||
_chatForwarder.forwardBroadcast(from, levelOrMode, bundle, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers a tell message to the specified target and notifies the supplied listener of the
|
||||
* result. It is assumed that the teller has already been permissions checked.
|
||||
*/
|
||||
public void deliverTell (UserMessage message, Name target, TellListener listener)
|
||||
throws InvocationException
|
||||
{
|
||||
// make sure the target user is online
|
||||
BodyObject tobj = _locator.lookupBody(target);
|
||||
if (tobj == null) {
|
||||
// if we have a forwarder configured, try forwarding the tell
|
||||
if (_chatForwarder != null && _chatForwarder.forwardTell(message, target, listener)) {
|
||||
return;
|
||||
}
|
||||
throw new InvocationException(ChatCodes.USER_NOT_ONLINE);
|
||||
}
|
||||
|
||||
if (tobj.status == OccupantInfo.DISCONNECTED) {
|
||||
String errmsg = MessageBundle.compose(
|
||||
ChatCodes.USER_DISCONNECTED, TimeUtil.getTimeOrderString(
|
||||
System.currentTimeMillis() - tobj.getLocal(BodyLocal.class).statusTime,
|
||||
TimeUtil.SECOND));
|
||||
throw new InvocationException(errmsg);
|
||||
}
|
||||
|
||||
// deliver a tell notification to the target player
|
||||
deliverTell(tobj, message);
|
||||
|
||||
// let the teller know it went ok
|
||||
long idle = 0L;
|
||||
if (tobj.status == OccupantInfo.IDLE) {
|
||||
idle = System.currentTimeMillis() - tobj.getLocal(BodyLocal.class).statusTime;
|
||||
}
|
||||
String awayMessage = null;
|
||||
if (!StringUtil.isBlank(tobj.awayMessage)) {
|
||||
awayMessage = tobj.awayMessage;
|
||||
}
|
||||
listener.tellSucceeded(idle, awayMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers a tell notification to the specified target player. It is assumed that the message
|
||||
* is coming from some server entity and need not be permissions checked or notified of the
|
||||
* result.
|
||||
*/
|
||||
public void deliverTell (BodyObject target, UserMessage message)
|
||||
{
|
||||
SpeakUtil.sendMessage(target, message);
|
||||
|
||||
// note that the teller "heard" what they said
|
||||
SpeakUtil.noteMessage(message.speaker, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to create a {@link UserMessage} for the supplied sender.
|
||||
*/
|
||||
protected UserMessage createTellMessage (BodyObject source, String message)
|
||||
{
|
||||
return new UserMessage(source.getVisibleName(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct a broadcast to the specified object.
|
||||
*/
|
||||
protected void broadcastTo (
|
||||
DObject object, Name from, byte levelOrMode, String bundle, String msg)
|
||||
{
|
||||
if (from == null) {
|
||||
SpeakUtil.sendSystem(object, bundle, msg, levelOrMode /* level */);
|
||||
|
||||
} else {
|
||||
SpeakUtil.sendSpeak(object, from, bundle, msg, levelOrMode /* mode */);
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides access to place managers. */
|
||||
@Inject protected PlaceRegistry _plreg;
|
||||
|
||||
/** Used to look up body objects by name. */
|
||||
@Inject protected BodyLocator _locator;
|
||||
|
||||
/** Generates auto-responses to tells. May be null. */
|
||||
protected TellAutoResponder _autoRespond;
|
||||
|
||||
/** Forwards chat between servers. May be null. */
|
||||
protected ChatForwarder _chatForwarder;
|
||||
|
||||
/** An alternative object to which broadcasts should be sent. */
|
||||
protected DObject _broadcastObject;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.data.SpeakMarshaller;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationDispatcher;
|
||||
import com.threerings.presents.server.InvocationException;
|
||||
|
||||
/**
|
||||
* Dispatches requests to the {@link SpeakProvider}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from SpeakService.java.")
|
||||
public class SpeakDispatcher extends InvocationDispatcher<SpeakMarshaller>
|
||||
{
|
||||
/**
|
||||
* Creates a dispatcher that may be registered to dispatch invocation
|
||||
* service requests for the specified provider.
|
||||
*/
|
||||
public SpeakDispatcher (SpeakProvider provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeakMarshaller createMarshaller ()
|
||||
{
|
||||
return new SpeakMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchRequest (
|
||||
ClientObject source, int methodId, Object[] args)
|
||||
throws InvocationException
|
||||
{
|
||||
switch (methodId) {
|
||||
case SpeakMarshaller.SPEAK:
|
||||
((SpeakProvider)provider).speak(
|
||||
source, (String)args[0], ((Byte)args[1]).byteValue()
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchRequest(source, methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import com.threerings.util.MessageManager;
|
||||
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.server.InvocationManager;
|
||||
|
||||
import com.threerings.crowd.chat.client.SpeakService;
|
||||
import com.threerings.crowd.chat.data.ChatCodes;
|
||||
import com.threerings.crowd.data.BodyObject;
|
||||
|
||||
import static com.threerings.crowd.Log.log;
|
||||
|
||||
/**
|
||||
* Wires up the {@link SpeakService} to a particular distributed object. A server entity can make
|
||||
* "speech" available among the subscribers of a particular distributed object by constructing a
|
||||
* speak handler and registering it with the {@link InvocationManager}, then placing the resulting
|
||||
* marshaller into the distributed object in question so that subscribers to that object can use it
|
||||
* to generate "speak" requests on that object.
|
||||
*/
|
||||
public class SpeakHandler
|
||||
implements SpeakProvider
|
||||
{
|
||||
/**
|
||||
* Used to prevent abitrary users from issuing speak requests.
|
||||
*/
|
||||
public static interface SpeakerValidator
|
||||
{
|
||||
/**
|
||||
* Should return true if the supplied speaker is allowed to speak via the speak provider
|
||||
* with which this validator was registered.
|
||||
*/
|
||||
boolean isValidSpeaker (DObject speakObj, ClientObject speaker, byte mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a handler that will provide speech on the supplied distributed object.
|
||||
*
|
||||
* @param speakObj the object for which speech requests will be processed.
|
||||
* @param validator an optional validator that can be used to prevent arbitrary users from
|
||||
* using the speech services on this object.
|
||||
*/
|
||||
public SpeakHandler (DObject speakObj, SpeakerValidator validator)
|
||||
{
|
||||
_speakObj = speakObj;
|
||||
_validator = validator;
|
||||
}
|
||||
|
||||
// from interface SpeakProvider
|
||||
public void speak (ClientObject caller, String message, byte mode)
|
||||
{
|
||||
// ensure that the caller has normal chat privileges
|
||||
BodyObject source = (BodyObject)caller;
|
||||
String errmsg = source.checkAccess(ChatCodes.CHAT_ACCESS, null);
|
||||
if (errmsg != null) {
|
||||
// we normally don't listen for responses to speak messages so we can't just throw an
|
||||
// InvocationException we have to specifically communicate the error to the user
|
||||
SpeakUtil.sendFeedback(source, MessageManager.GLOBAL_BUNDLE, errmsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: broadcast should be handled more like a system message rather than as a mode for a
|
||||
// user message so that we don't have to do this validation here. Or not.
|
||||
|
||||
// ensure that the speaker is valid
|
||||
if ((mode == ChatCodes.BROADCAST_MODE) ||
|
||||
(_validator != null && !_validator.isValidSpeaker(_speakObj, caller, mode))) {
|
||||
log.warning("Refusing invalid speak request", "caller", caller.who(),
|
||||
"speakObj", _speakObj.which(), "message", message, "mode", mode);
|
||||
|
||||
} else {
|
||||
// issue the speak message on our speak object
|
||||
SpeakUtil.sendSpeak(_speakObj, source.getVisibleName(), null, message, mode);
|
||||
}
|
||||
}
|
||||
|
||||
/** Our speech object. */
|
||||
protected DObject _speakObj;
|
||||
|
||||
/** The entity that will validate our speakers. */
|
||||
protected SpeakerValidator _validator;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.threerings.crowd.chat.client.SpeakService;
|
||||
import com.threerings.presents.data.ClientObject;
|
||||
import com.threerings.presents.server.InvocationProvider;
|
||||
|
||||
/**
|
||||
* Defines the server-side of the {@link SpeakService}.
|
||||
*/
|
||||
@Generated(value={"com.threerings.presents.tools.GenServiceTask"},
|
||||
comments="Derived from SpeakService.java.")
|
||||
public interface SpeakProvider extends InvocationProvider
|
||||
{
|
||||
/**
|
||||
* Handles a {@link SpeakService#speak} request.
|
||||
*/
|
||||
void speak (ClientObject caller, String arg1, byte arg2);
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.chat.server;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.dobj.DObject;
|
||||
import com.threerings.presents.dobj.RootDObjectManager;
|
||||
|
||||
import com.threerings.crowd.chat.data.ChatChannel;
|
||||
import com.threerings.crowd.chat.data.ChatCodes;
|
||||
import com.threerings.crowd.chat.data.ChatMessage;
|
||||
import com.threerings.crowd.chat.data.KeepNoHistory;
|
||||
import com.threerings.crowd.chat.data.SpeakObject;
|
||||
import com.threerings.crowd.chat.data.SystemMessage;
|
||||
import com.threerings.crowd.chat.data.UserMessage;
|
||||
import com.threerings.crowd.data.BodyObject;
|
||||
import com.threerings.io.Streamable;
|
||||
|
||||
import static com.threerings.crowd.Log.log;
|
||||
|
||||
/**
|
||||
* Provides the back-end of the chat speaking facilities.
|
||||
*/
|
||||
public class SpeakUtil
|
||||
{
|
||||
/**
|
||||
* An interface used to notify external systems whenever a chat message is spoken by one user
|
||||
* and heard by another.
|
||||
*/
|
||||
public static interface MessageObserver
|
||||
{
|
||||
/**
|
||||
* Called for each player that hears a particular chat message.
|
||||
*/
|
||||
void messageDelivered (Name hearer, UserMessage message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recorded parcel of chat for historical purposes, maintained by
|
||||
* {@link #recordToChatHistory(ChatChannel, UserMessage, Name...)},
|
||||
* {@link #getChatHistory(Name)}, and {@link #clearHistory(Name)}.
|
||||
*/
|
||||
public static class ChatHistoryEntry
|
||||
implements Streamable
|
||||
{
|
||||
/** The channel on which the message was sent, of null if the channel manager was not
|
||||
* used. */
|
||||
public ChatChannel channel;
|
||||
|
||||
/** The message sent. */
|
||||
public ChatMessage message;
|
||||
|
||||
/** For deserialization. */
|
||||
public ChatHistoryEntry ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new history entry.
|
||||
*/
|
||||
public ChatHistoryEntry (ChatChannel channel, ChatMessage message)
|
||||
{
|
||||
this.channel = channel;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link MessageObserver} to be notified whenever a user-originated chat message
|
||||
* is heard by another user.
|
||||
*/
|
||||
public static void registerMessageObserver (MessageObserver obs)
|
||||
{
|
||||
_messageObs.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a registration made previously with {@link #registerMessageObserver}.
|
||||
*/
|
||||
public static void removeMessageObserver (MessageObserver obs)
|
||||
{
|
||||
_messageObs.remove(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a speak notification to the specified place object originating with the specified
|
||||
* speaker (the speaker optionally being a server entity that wishes to fake a "speak" message)
|
||||
* and with the supplied message content.
|
||||
*
|
||||
* @param speakObj the object on which to generate the speak message.
|
||||
* @param speaker the username of the user that generated the message (or some special speaker
|
||||
* name for server messages).
|
||||
* @param bundle null when the message originates from a real human, the bundle identifier that
|
||||
* will be used by the client to translate the message text when the message originates from a
|
||||
* server entity "faking" a chat message.
|
||||
* @param message the text of the speak message.
|
||||
*/
|
||||
public static void sendSpeak (DObject speakObj, Name speaker, String bundle, String message)
|
||||
{
|
||||
sendSpeak(speakObj, speaker, bundle, message, ChatCodes.DEFAULT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a speak notification to the specified place object originating with the specified
|
||||
* speaker (the speaker optionally being a server entity that wishes to fake a "speak" message)
|
||||
* and with the supplied message content.
|
||||
*
|
||||
* @param speakObj the object on which to generate the speak message.
|
||||
* @param speaker the username of the user that generated the message (or some special speaker
|
||||
* name for server messages).
|
||||
* @param bundle null when the message originates from a real human, the bundle identifier that
|
||||
* will be used by the client to translate the message text when the message originates from a
|
||||
* server entity "faking" a chat message.
|
||||
* @param message the text of the speak message.
|
||||
* @param mode the mode of the message, see {@link ChatCodes#DEFAULT_MODE}.
|
||||
*/
|
||||
public static void sendSpeak (DObject speakObj, Name speaker, String bundle, String message,
|
||||
byte mode)
|
||||
{
|
||||
sendMessage(speakObj, new UserMessage(speaker, bundle, message, mode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system INFO message notification to the specified object with the supplied message
|
||||
* content. A system message is one that will be rendered where the speak messages are
|
||||
* rendered, but in a way that makes it clear that it is a message from the server.
|
||||
*
|
||||
* Info messages are sent when something happens that was neither directly triggered by the
|
||||
* user, nor requires direct action.
|
||||
*
|
||||
* @param speakObj the object on which to deliver the message.
|
||||
* @param bundle the name of the localization bundle that should be used to translate this
|
||||
* system message prior to displaying it to the client.
|
||||
* @param message the text of the message.
|
||||
*/
|
||||
public static void sendInfo (DObject speakObj, String bundle, String message)
|
||||
{
|
||||
sendSystem(speakObj, bundle, message, SystemMessage.INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system FEEDBACK message notification to the specified object with the supplied
|
||||
* message content. A system message is one that will be rendered where the speak messages are
|
||||
* rendered, but in a way that makes it clear that it is a message from the server.
|
||||
*
|
||||
* Feedback messages are sent in direct response to a user action, usually to indicate success
|
||||
* or failure of the user's action.
|
||||
*
|
||||
* @param speakObj the object on which to deliver the message.
|
||||
* @param bundle the name of the localization bundle that should be used to translate this
|
||||
* system message prior to displaying it to the client.
|
||||
* @param message the text of the message.
|
||||
*/
|
||||
public static void sendFeedback (DObject speakObj, String bundle, String message)
|
||||
{
|
||||
sendSystem(speakObj, bundle, message, SystemMessage.FEEDBACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system ATTENTION message notification to the specified object with the supplied
|
||||
* message content. A system message is one that will be rendered where the speak messages are
|
||||
* rendered, but in a way that makes it clear that it is a message from the server.
|
||||
*
|
||||
* Attention messages are sent when something requires user action that did not result from
|
||||
* direct action by the user.
|
||||
*
|
||||
* @param speakObj the object on which to deliver the message.
|
||||
* @param bundle the name of the localization bundle that should be used to translate this
|
||||
* system message prior to displaying it to the client.
|
||||
* @param message the text of the message.
|
||||
*/
|
||||
public static void sendAttention (DObject speakObj, String bundle, String message)
|
||||
{
|
||||
sendSystem(speakObj, bundle, message, SystemMessage.ATTENTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified message on the specified object.
|
||||
*/
|
||||
public static void sendMessage (DObject speakObj, ChatMessage msg)
|
||||
{
|
||||
if (speakObj == null) {
|
||||
log.warning("Dropping speak message, no speak obj '" + msg + "'.", new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
// post the message to the relevant object
|
||||
speakObj.postMessage(ChatCodes.CHAT_NOTIFICATION, new Object[] { msg });
|
||||
|
||||
// if this is a user message; add it to the heard history of all users that can "hear" it
|
||||
if (!(msg instanceof UserMessage)) {
|
||||
return;
|
||||
|
||||
} else if (speakObj instanceof SpeakObject) {
|
||||
_messageMapper.omgr = (RootDObjectManager)speakObj.getManager();
|
||||
_messageMapper.message = (UserMessage)msg;
|
||||
((SpeakObject)speakObj).applyToListeners(_messageMapper);
|
||||
_messageMapper.omgr = null;
|
||||
_messageMapper.message = null;
|
||||
|
||||
} else {
|
||||
log.info("Unable to note listeners", "dclass", speakObj.getClass(), "msg", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of {@link ChatMessage} objects to which this user has been privy in the
|
||||
* recent past. If the given name implements {@link KeepNoHistory}, null is returned.
|
||||
*/
|
||||
public static List<ChatHistoryEntry> getChatHistory (Name username)
|
||||
{
|
||||
List<ChatHistoryEntry> history = getHistoryList(username);
|
||||
if (history != null) {
|
||||
pruneHistory(System.currentTimeMillis(), history);
|
||||
}
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to clear the chat history for the specified user.
|
||||
*/
|
||||
public static void clearHistory (Name username)
|
||||
{
|
||||
// Log.info("Clearing history for " + username + ".");
|
||||
_histories.remove(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the specified channel and message to the specified users' chat histories. If {@link
|
||||
* ChatMessage#timestamp} is not already filled in, it will be.
|
||||
*/
|
||||
public static void recordToChatHistory (
|
||||
ChatChannel channel, UserMessage msg, Name... usernames)
|
||||
{
|
||||
// fill in the message's time stamp if necessary
|
||||
if (msg.timestamp == 0L) {
|
||||
msg.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
for (Name username : usernames) {
|
||||
// add the message to this user's chat history
|
||||
List<ChatHistoryEntry> history = getHistoryList(username);
|
||||
if (history == null) {
|
||||
continue;
|
||||
}
|
||||
history.add(new ChatHistoryEntry(channel, msg));
|
||||
|
||||
// if the history is big enough, potentially prune it (we always prune when asked for
|
||||
// the history, so this is just to balance memory usage with CPU expense)
|
||||
if (history.size() > 15) {
|
||||
pruneHistory(msg.timestamp, history);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notes that the specified user was privy to the specified message. If {@link
|
||||
* ChatMessage#timestamp} is not already filled in, it will be.
|
||||
*/
|
||||
protected static void noteMessage (Name username, UserMessage msg)
|
||||
{
|
||||
// fill in the message's time stamp if necessary
|
||||
if (msg.timestamp == 0L) {
|
||||
msg.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
recordToChatHistory(null, msg, username);
|
||||
// Log.info("Noted that " + username + " heard " + msg + ".");
|
||||
|
||||
// notify any message observers
|
||||
_messageOp.init(username, msg);
|
||||
_messageObs.apply(_messageOp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified system message on the specified dobj.
|
||||
*/
|
||||
protected static void sendSystem (DObject speakObj, String bundle, String message, byte level)
|
||||
{
|
||||
sendMessage(speakObj, new SystemMessage(message, bundle, level));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this user's chat history, creating one if necessary. If the given name implements
|
||||
* {@link KeepNoHistory}, null is returned.
|
||||
*/
|
||||
protected static List<ChatHistoryEntry> getHistoryList (Name username)
|
||||
{
|
||||
if (username instanceof KeepNoHistory) {
|
||||
return null;
|
||||
}
|
||||
List<ChatHistoryEntry> history = _histories.get(username);
|
||||
if (history == null) {
|
||||
_histories.put(username, history = Lists.newArrayList());
|
||||
}
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prunes all messages from this history which are expired.
|
||||
*/
|
||||
protected static void pruneHistory (long now, List<ChatHistoryEntry> history)
|
||||
{
|
||||
int prunepos = 0;
|
||||
for (int ll = history.size(); prunepos < ll; prunepos++) {
|
||||
ChatHistoryEntry entry = history.get(prunepos);
|
||||
if (now - entry.message.timestamp < HISTORY_EXPIRATION) {
|
||||
break; // stop when we get to the first valid message
|
||||
}
|
||||
}
|
||||
history.subList(0, prunepos).clear();
|
||||
}
|
||||
|
||||
/** Used to note the recipients of a chat message. */
|
||||
protected static class MessageMapper implements SpeakObject.ListenerOp
|
||||
{
|
||||
public RootDObjectManager omgr;
|
||||
public UserMessage message;
|
||||
|
||||
public void apply (int bodyOid) {
|
||||
DObject dobj = omgr.getObject(bodyOid);
|
||||
if (dobj != null && dobj instanceof BodyObject) {
|
||||
noteMessage(((BodyObject)dobj).getVisibleName(), message);
|
||||
}
|
||||
}
|
||||
|
||||
public void apply (Name username) {
|
||||
noteMessage(username, message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to notify our {@link MessageObserver}s. */
|
||||
protected static class MessageObserverOp
|
||||
implements ObserverList.ObserverOp<MessageObserver>
|
||||
{
|
||||
public void init (Name hearer, UserMessage message) {
|
||||
_hearer = hearer;
|
||||
_message = message;
|
||||
}
|
||||
|
||||
public boolean apply (MessageObserver observer) {
|
||||
observer.messageDelivered(_hearer, _message);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Name _hearer;
|
||||
protected UserMessage _message;
|
||||
}
|
||||
|
||||
/** Recent chat history for the server. */
|
||||
protected static Map<Name, List<ChatHistoryEntry>> _histories = Maps.newHashMap();
|
||||
|
||||
/** Used to note the recipients of a chat message. */
|
||||
protected static MessageMapper _messageMapper = new MessageMapper();
|
||||
|
||||
/** A list of {@link MessageObserver}s. */
|
||||
protected static ObserverList<MessageObserver> _messageObs = ObserverList.newFastUnsafe();
|
||||
|
||||
/** Used to notify our {@link MessageObserver}s. */
|
||||
protected static MessageObserverOp _messageOp = new MessageObserverOp();
|
||||
|
||||
/** The amount of time before chat history becomes... history. */
|
||||
protected static final long HISTORY_EXPIRATION = 5L * 60L * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
/**
|
||||
* The client side of the body-related invocation services.
|
||||
*/
|
||||
public interface BodyService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Requests to set the idle state of the client to the specified
|
||||
* value.
|
||||
*/
|
||||
void setIdle (Client client, boolean idle);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
|
||||
/**
|
||||
* The location adapter makes life easier for a class that really only
|
||||
* cares about one or two of the location observer callbacks and doesn't
|
||||
* want to provide empty implementations of the others. One can either
|
||||
* extend location adapter, or create an anonymous instance that overrides
|
||||
* the desired callback(s). Note that the location adapter defaults to
|
||||
* ratifying any location change.
|
||||
*
|
||||
* @see LocationObserver
|
||||
*/
|
||||
public class LocationAdapter implements LocationObserver
|
||||
{
|
||||
// documentation inherited
|
||||
public boolean locationMayChange (int placeId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void locationDidChange (PlaceObject place)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void locationChangeFailed (int placeId, String reason)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.threerings.presents.client.InvocationDecoder;
|
||||
|
||||
/**
|
||||
* Dispatches calls to a {@link LocationReceiver} instance.
|
||||
*/
|
||||
public class LocationDecoder extends InvocationDecoder
|
||||
{
|
||||
/** The generated hash code used to identify this receiver class. */
|
||||
public static final String RECEIVER_CODE = "58f2830e027f4f3377e100ef12332497";
|
||||
|
||||
/** The method id used to dispatch {@link LocationReceiver#forcedMove}
|
||||
* notifications. */
|
||||
public static final int FORCED_MOVE = 1;
|
||||
|
||||
/**
|
||||
* Creates a decoder that may be registered to dispatch invocation
|
||||
* service notifications to the specified receiver.
|
||||
*/
|
||||
public LocationDecoder (LocationReceiver receiver)
|
||||
{
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReceiverCode ()
|
||||
{
|
||||
return RECEIVER_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchNotification (int methodId, Object[] args)
|
||||
{
|
||||
switch (methodId) {
|
||||
case FORCED_MOVE:
|
||||
((LocationReceiver)receiver).forcedMove(
|
||||
((Integer)args[0]).intValue()
|
||||
);
|
||||
return;
|
||||
|
||||
default:
|
||||
super.dispatchNotification(methodId, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,628 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
import com.samskivert.util.ResultListener;
|
||||
import com.samskivert.util.ObserverList.ObserverOp;
|
||||
|
||||
import com.threerings.presents.client.BasicDirector;
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.dobj.ObjectAccessException;
|
||||
import com.threerings.presents.dobj.Subscriber;
|
||||
import com.threerings.presents.util.SafeSubscriber;
|
||||
|
||||
import com.threerings.crowd.data.BodyObject;
|
||||
import com.threerings.crowd.data.CrowdCodes;
|
||||
import com.threerings.crowd.data.LocationCodes;
|
||||
import com.threerings.crowd.data.PlaceConfig;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
import static com.threerings.crowd.Log.log;
|
||||
|
||||
/**
|
||||
* The location director provides a means by which entities on the client can request to move from
|
||||
* place to place and can be notified if other entities have caused the client to move to a new
|
||||
* place. It also provides a mechanism for ratifying a request to move to a new place before
|
||||
* actually issuing the request.
|
||||
*/
|
||||
public class LocationDirector extends BasicDirector
|
||||
implements LocationCodes, LocationReceiver
|
||||
{
|
||||
/**
|
||||
* Used to recover from a moveTo request that was accepted but resulted in a failed attempt to
|
||||
* fetch the place object to which we were moving.
|
||||
*/
|
||||
public static interface FailureHandler
|
||||
{
|
||||
/**
|
||||
* Should instruct the client to move to the last known working location (as well as clean
|
||||
* up after the failed moveTo request).
|
||||
*/
|
||||
void recoverFailedMove (int placeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a location director which will configure itself for operation using the supplied
|
||||
* context.
|
||||
*/
|
||||
public LocationDirector (CrowdContext ctx)
|
||||
{
|
||||
super(ctx);
|
||||
|
||||
// keep this around for later
|
||||
_ctx = ctx;
|
||||
|
||||
// register for location notifications
|
||||
_ctx.getClient().getInvocationDirector().registerReceiver(new LocationDecoder(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a location observer to the list. This observer will subsequently be notified of
|
||||
* potential, effected and failed location changes.
|
||||
*/
|
||||
public void addLocationObserver (LocationObserver observer)
|
||||
{
|
||||
_observers.add(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a location observer from the list.
|
||||
*/
|
||||
public void removeLocationObserver (LocationObserver observer)
|
||||
{
|
||||
_observers.remove(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the place object for the location we currently occupy or null if we're not currently
|
||||
* occupying any location.
|
||||
*/
|
||||
public PlaceObject getPlaceObject ()
|
||||
{
|
||||
return _plobj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a pending move request.
|
||||
*/
|
||||
public boolean movePending ()
|
||||
{
|
||||
return (_pendingPlaceId > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that this client be moved to the specified place. A request will be made and when
|
||||
* the response is received, the location observers will be notified of success or failure.
|
||||
*
|
||||
* @return true if the move to request was issued, false if it was rejected by a location
|
||||
* observer or because we have another request outstanding.
|
||||
*/
|
||||
public boolean moveTo (int placeId)
|
||||
{
|
||||
// make sure the placeId is valid
|
||||
if (placeId < 0) {
|
||||
log.warning("Refusing moveTo(): invalid placeId " + placeId + ".");
|
||||
return false;
|
||||
}
|
||||
|
||||
// first check to see if our observers are happy with this move request
|
||||
if (!mayMoveTo(placeId, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we need to call this both to mark that we're issuing a move request and to check to see
|
||||
// if the last issued request should be considered stale
|
||||
boolean refuse = checkRepeatMove();
|
||||
|
||||
// complain if we're over-writing a pending request
|
||||
if (_pendingPlaceId != -1) {
|
||||
// if the pending request has been outstanding more than a minute, go ahead and let
|
||||
// this new one through in an attempt to recover from dropped moveTo requests
|
||||
if (refuse) {
|
||||
log.warning("Refusing moveTo; We have a request outstanding",
|
||||
"ppid", _pendingPlaceId, "npid", placeId);
|
||||
return false;
|
||||
|
||||
} else {
|
||||
log.warning("Overriding stale moveTo request", "ppid", _pendingPlaceId,
|
||||
"npid", placeId);
|
||||
}
|
||||
}
|
||||
|
||||
// make a note of our pending place id
|
||||
_pendingPlaceId = placeId;
|
||||
|
||||
// issue a moveTo request
|
||||
log.info("Issuing moveTo(" + placeId + ").");
|
||||
_lservice.moveTo(_ctx.getClient(), placeId, new LocationService.MoveListener() {
|
||||
public void moveSucceeded (PlaceConfig config) {
|
||||
// handle the successful move
|
||||
didMoveTo(_pendingPlaceId, config);
|
||||
// and clear out the tracked pending oid
|
||||
_pendingPlaceId = -1;
|
||||
|
||||
handlePendingForcedMove();
|
||||
}
|
||||
public void requestFailed (String reason) {
|
||||
// clear out our pending request oid
|
||||
int placeId = _pendingPlaceId;
|
||||
_pendingPlaceId = -1;
|
||||
log.info("moveTo failed", "pid", placeId, "reason", reason);
|
||||
// let our observers know that something has gone horribly awry
|
||||
handleFailure(placeId, reason);
|
||||
handlePendingForcedMove();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests to move to the room that we last occupied, if such a room exists.
|
||||
*
|
||||
* @return true if we had a previous room and we requested to move to it, false if we had no
|
||||
* previous room.
|
||||
*/
|
||||
public boolean moveBack ()
|
||||
{
|
||||
if (_previousPlaceId == -1) {
|
||||
return false;
|
||||
|
||||
} else {
|
||||
moveTo(_previousPlaceId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a request to leave our current location.
|
||||
*
|
||||
* @return true if we were able to leave, false if we are in the middle of moving somewhere and
|
||||
* can't yet leave.
|
||||
*/
|
||||
public boolean leavePlace ()
|
||||
{
|
||||
if (_pendingPlaceId != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_lservice.leavePlace(_ctx.getClient());
|
||||
didLeavePlace();
|
||||
|
||||
// let our observers know that we're no longer in a location
|
||||
_observers.apply(_didChangeOp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be called by cooperating directors that need to coopt the moving process to extend
|
||||
* it in some way or other. In such situations, they should call this method before moving to a
|
||||
* new location to check to be sure that all of the registered location observers are amenable
|
||||
* to a location change.
|
||||
*
|
||||
* @param placeId the place oid of our tentative new location.
|
||||
*
|
||||
* @return true if everyone is happy with the move, false if it was vetoed by one of the
|
||||
* location observers.
|
||||
*/
|
||||
public boolean mayMoveTo (final int placeId, ResultListener<PlaceConfig> rl)
|
||||
{
|
||||
final boolean[] vetoed = new boolean[1];
|
||||
_observers.apply(new ObserverOp<LocationObserver>() {
|
||||
public boolean apply (LocationObserver obs) {
|
||||
vetoed[0] = (vetoed[0] || !obs.locationMayChange(placeId));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// if we're actually going somewhere, let the controller know that we might be leaving
|
||||
mayLeavePlace();
|
||||
|
||||
// if we have a result listener, let it know if we failed or keep it for later if we're
|
||||
// still going
|
||||
if (rl != null) {
|
||||
if (vetoed[0]) {
|
||||
rl.requestFailed(new MoveVetoedException());
|
||||
} else {
|
||||
_moveListener = rl;
|
||||
}
|
||||
}
|
||||
// and return the result
|
||||
return !vetoed[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to inform our controller that we may be leaving the current place.
|
||||
*/
|
||||
protected void mayLeavePlace ()
|
||||
{
|
||||
if (_controller != null) {
|
||||
try {
|
||||
_controller.mayLeavePlace(_plobj);
|
||||
} catch (Exception e) {
|
||||
log.warning("Place controller choked in mayLeavePlace", "plobj", _plobj, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be called by cooperating directors that need to coopt the moving process to extend
|
||||
* it in some way or other. In such situations, they will be responsible for receiving the
|
||||
* successful move response and they should let the location director know that the move has
|
||||
* been effected.
|
||||
*
|
||||
* @param placeId the place oid of our new location.
|
||||
* @param config the configuration information for the new place.
|
||||
*/
|
||||
public void didMoveTo (int placeId, PlaceConfig config)
|
||||
{
|
||||
if (_moveListener != null) {
|
||||
_moveListener.requestCompleted(config);
|
||||
_moveListener = null;
|
||||
}
|
||||
|
||||
// keep track of our previous place id
|
||||
_previousPlaceId = _placeId;
|
||||
|
||||
// clear out our last request time
|
||||
_lastRequestTime = 0;
|
||||
|
||||
// do some cleaning up in case we were previously in a place
|
||||
didLeavePlace();
|
||||
|
||||
// make a note that we're now mostly in the new location
|
||||
_placeId = placeId;
|
||||
|
||||
// start up a new place controller to manage the new place
|
||||
try {
|
||||
_controller = createController(config);
|
||||
if (_controller == null) {
|
||||
log.warning("Place config returned null controller", "config", config);
|
||||
return;
|
||||
}
|
||||
_controller.init(_ctx, config);
|
||||
|
||||
// subscribe to our new place object to complete the move
|
||||
_subber = new SafeSubscriber<PlaceObject>(_placeId, new Subscriber<PlaceObject>() {
|
||||
public void objectAvailable (PlaceObject object) {
|
||||
gotPlaceObject(object);
|
||||
}
|
||||
public void requestFailed (int oid, ObjectAccessException cause) {
|
||||
// aiya! we were unable to fetch our new place object; something is badly wrong
|
||||
log.warning("Aiya! Unable to fetch place object for new location", "plid", oid,
|
||||
"reason", cause);
|
||||
// clear out our half initialized place info
|
||||
int placeId = _placeId;
|
||||
_placeId = -1;
|
||||
// let the kids know shit be fucked
|
||||
handleFailure(placeId, "m.unable_to_fetch_place_object");
|
||||
}
|
||||
});
|
||||
_subber.subscribe(_ctx.getDObjectManager());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to create place controller", "config", config, e);
|
||||
handleFailure(_placeId, LocationCodes.E_INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we're leaving our current location. Informs the location's controller that we're
|
||||
* departing, unsubscribes from the location's place object, and clears out our internal place
|
||||
* information.
|
||||
*/
|
||||
public void didLeavePlace ()
|
||||
{
|
||||
// unsubscribe from our old place object
|
||||
if (_subber != null) {
|
||||
_subber.unsubscribe(_ctx.getDObjectManager());
|
||||
_subber = null;
|
||||
}
|
||||
|
||||
// let the old controller know that things are going away
|
||||
if (_plobj != null && _controller != null) {
|
||||
try {
|
||||
_controller.didLeavePlace(_plobj);
|
||||
} catch (Exception e) {
|
||||
log.warning("Place controller choked in didLeavePlace", "plobj", _plobj, e);
|
||||
}
|
||||
}
|
||||
|
||||
// and clear out other bits
|
||||
_plobj = null;
|
||||
_controller = null;
|
||||
_placeId = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be called by cooperating directors that need to coopt the moving process to extend
|
||||
* it in some way or other. If the coopted move request fails, this failure can be propagated
|
||||
* to the location observers if appropriate.
|
||||
*
|
||||
* @param placeId the place oid to which we failed to move.
|
||||
* @param reason the reason code given for failure.
|
||||
*/
|
||||
public void failedToMoveTo (int placeId, String reason)
|
||||
{
|
||||
if (_moveListener != null) {
|
||||
_moveListener.requestFailed(new MoveFailedException(reason));
|
||||
_moveListener = null;
|
||||
}
|
||||
|
||||
// clear out our last request time
|
||||
_lastRequestTime = 0;
|
||||
|
||||
// let our observers know what's up
|
||||
handleFailure(placeId, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to test and set a time stamp that we use to determine if a pending moveTo request is
|
||||
* stale.
|
||||
*/
|
||||
public boolean checkRepeatMove ()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - _lastRequestTime < STALE_REQUEST_DURATION) {
|
||||
return true;
|
||||
|
||||
} else {
|
||||
_lastRequestTime = now;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientDidLogon (Client client)
|
||||
{
|
||||
super.clientDidLogon(client);
|
||||
|
||||
// subscribe to our body object
|
||||
Subscriber<BodyObject> sub = new Subscriber<BodyObject>() {
|
||||
public void objectAvailable (BodyObject object) {
|
||||
gotBodyObject(object);
|
||||
}
|
||||
public void requestFailed (int oid, ObjectAccessException cause) {
|
||||
log.warning("Location director unable to fetch body object; all has gone " +
|
||||
"horribly wrong", "cause", cause);
|
||||
}
|
||||
};
|
||||
int cloid = client.getClientOid();
|
||||
client.getDObjectManager().subscribeToObject(cloid, sub);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientDidLogoff (Client client)
|
||||
{
|
||||
super.clientDidLogoff(client);
|
||||
|
||||
// clear ourselves out and inform observers of our departure
|
||||
mayLeavePlace();
|
||||
didLeavePlace();
|
||||
|
||||
// let our observers know that we're no longer in a location
|
||||
_observers.apply(_didChangeOp);
|
||||
|
||||
// clear out everything else (it's possible that we were logged off in the middle of a
|
||||
// change location request)
|
||||
_pendingPlaceId = -1;
|
||||
_pendingForcedMoves.clear();
|
||||
_previousPlaceId = -1;
|
||||
_lastRequestTime = 0L;
|
||||
_lservice = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerServices (Client client)
|
||||
{
|
||||
client.addServiceGroup(CrowdCodes.CROWD_GROUP);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fetchServices (Client client)
|
||||
{
|
||||
// obtain our service handle
|
||||
_lservice = client.requireService(LocationService.class);
|
||||
}
|
||||
|
||||
protected void gotPlaceObject (PlaceObject object)
|
||||
{
|
||||
// yay, we have our new place object
|
||||
_plobj = object;
|
||||
|
||||
// fill in our manager caller
|
||||
_plobj.initManagerCaller(_ctx.getClient().getDObjectManager());
|
||||
|
||||
// let the place controller know that we're ready to roll
|
||||
if (_controller != null) {
|
||||
try {
|
||||
_controller.willEnterPlace(_plobj);
|
||||
} catch (Exception e) {
|
||||
log.warning("Controller choked in willEnterPlace", "place", _plobj, e);
|
||||
}
|
||||
}
|
||||
|
||||
// let our observers know that all is well on the western front
|
||||
_observers.apply(_didChangeOp);
|
||||
}
|
||||
|
||||
protected void gotBodyObject (BodyObject clobj)
|
||||
{
|
||||
// TODO? check to see if we are already in a location, in which case we'll want to be going
|
||||
// there straight away
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void forcedMove (final int placeId)
|
||||
{
|
||||
// if we're in the middle of a move, we can't abort it or we will screw everything up, so
|
||||
// just finish up what we're doing and assume that the repeated move request was the
|
||||
// spurious one as it would be in the case of lag causing rapid-fire repeat requests
|
||||
if (movePending()) {
|
||||
if (_pendingPlaceId == placeId) {
|
||||
log.info("Dropping forced move because we have a move pending",
|
||||
"pendId", _pendingPlaceId, "reqId", placeId);
|
||||
} else {
|
||||
log.info("Delaying forced move because we have a move pending",
|
||||
"pendId", _pendingPlaceId, "reqId", placeId);
|
||||
addPendingForcedMove(new Runnable() {
|
||||
public void run () {
|
||||
forcedMove(placeId);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Moving at request of server", "placeId", placeId);
|
||||
// clear out our old place information
|
||||
mayLeavePlace();
|
||||
didLeavePlace();
|
||||
// move to the new place
|
||||
moveTo(placeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the failure handler which will recover from place object fetching failures. In the
|
||||
* event that we are unable to fetch our place object after making a successful moveTo request,
|
||||
* we attempt to rectify the failure by moving back to the last known working location. Because
|
||||
* entites that cooperate with the location director may need to become involved in this
|
||||
* failure recovery, we provide this interface whereby they can interject themseves into the
|
||||
* failure recovery process and do their own failure recovery.
|
||||
*/
|
||||
public void setFailureHandler (FailureHandler handler)
|
||||
{
|
||||
if (_failureHandler != null) {
|
||||
log.warning("Requested to set failure handler, but we've already got one. The " +
|
||||
"conflicting entities will likely need to perform more sophisticated " +
|
||||
"coordination to deal with failures.",
|
||||
"old", _failureHandler, "new", handler);
|
||||
|
||||
} else {
|
||||
_failureHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleFailure (final int placeId, final String reason)
|
||||
{
|
||||
_observers.apply(new ObserverOp<LocationObserver>() {
|
||||
public boolean apply (LocationObserver obs) {
|
||||
obs.locationChangeFailed(placeId, reason);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// try to return to our previous location
|
||||
if (_failureHandler != null) {
|
||||
_failureHandler.recoverFailedMove(placeId);
|
||||
|
||||
} else if (_placeId <= 0) {
|
||||
// if we were previously somewhere (and that somewhere isn't where we just tried to
|
||||
// go), try going back to that happy place
|
||||
if (_previousPlaceId != -1 && _previousPlaceId != placeId) {
|
||||
moveTo(_previousPlaceId);
|
||||
}
|
||||
|
||||
} // else we're currently somewhere, so just stay there
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create our place controller using the supplied place configuration. This lives in
|
||||
* a separate method so that derived instances can do funny class loader business if necessary
|
||||
* to load the place controller using a sandboxed class loader.
|
||||
*/
|
||||
protected PlaceController createController (PlaceConfig config)
|
||||
{
|
||||
return config.createController();
|
||||
}
|
||||
|
||||
public void addPendingForcedMove (Runnable move)
|
||||
{
|
||||
_pendingForcedMoves.add(move);
|
||||
}
|
||||
|
||||
protected void handlePendingForcedMove ()
|
||||
{
|
||||
if (!_pendingForcedMoves.isEmpty()) {
|
||||
_ctx.getClient().getRunQueue().postRunnable(_pendingForcedMoves.remove(0));
|
||||
}
|
||||
}
|
||||
|
||||
/** The context through which we access needed services. */
|
||||
protected CrowdContext _ctx;
|
||||
|
||||
/** Provides access to location services. */
|
||||
protected LocationService _lservice;
|
||||
|
||||
/** Our location observer list. */
|
||||
protected ObserverList<LocationObserver> _observers = new ObserverList<LocationObserver>(
|
||||
ObserverList.SAFE_IN_ORDER_NOTIFY);
|
||||
|
||||
/** Used to subscribe to our place object. */
|
||||
protected SafeSubscriber<PlaceObject> _subber;
|
||||
|
||||
/** The oid of the place we currently occupy. */
|
||||
protected int _placeId = -1;
|
||||
|
||||
/** The place object that we currently occupy. */
|
||||
protected PlaceObject _plobj;
|
||||
|
||||
/** The place controller in effect for our current place. */
|
||||
protected PlaceController _controller;
|
||||
|
||||
/** The oid of the place for which we have an outstanding moveTo request, or -1 if we have no
|
||||
* outstanding request. */
|
||||
protected int _pendingPlaceId = -1;
|
||||
|
||||
/** The oid of the place we previously occupied. */
|
||||
protected int _previousPlaceId = -1;
|
||||
|
||||
/** The last time we requested a move to. */
|
||||
protected long _lastRequestTime;
|
||||
|
||||
/** The entity that deals when we fail to subscribe to a place object. */
|
||||
protected FailureHandler _failureHandler;
|
||||
|
||||
/** A listener that wants to know if we succeeded or how we failed to move. */
|
||||
protected ResultListener<PlaceConfig> _moveListener;
|
||||
|
||||
/** Forced move actions we should take once we complete the move we're in the middle of. */
|
||||
protected ArrayList<Runnable> _pendingForcedMoves = Lists.newArrayList();
|
||||
|
||||
/** The operation used to inform observers that the location changed. */
|
||||
protected ObserverOp<LocationObserver> _didChangeOp = new ObserverOp<LocationObserver>() {
|
||||
public boolean apply (LocationObserver obs) {
|
||||
obs.locationDidChange(_plobj);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** We require that a moveTo request be outstanding for one minute before it is declared to be
|
||||
* stale. */
|
||||
protected static final long STALE_REQUEST_DURATION = 60L * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
|
||||
/**
|
||||
* The location observer interface makes it possible for entities to be
|
||||
* notified when the client moves to a new location. It also provides a
|
||||
* means for an entity to participate in the ratification process of a new
|
||||
* location. Observers may opt to reject a request to change to a new
|
||||
* location, probably because something is going on in the previous
|
||||
* location that should not be abandoned.
|
||||
*
|
||||
* <p> Note that these location callbacks occur on the main thread and
|
||||
* should execute quickly and not block under any circumstance.
|
||||
*/
|
||||
public interface LocationObserver
|
||||
{
|
||||
/**
|
||||
* Called when someone has requested that we switch to a new location.
|
||||
* An observer may choose to veto the location change request for some
|
||||
* reason or other.
|
||||
*
|
||||
* @return true if it's OK for the location to change, false if the
|
||||
* change request should be aborted.
|
||||
*/
|
||||
boolean locationMayChange (int placeId);
|
||||
|
||||
/**
|
||||
* Called when we have switched to a new location.
|
||||
*
|
||||
* @param place the place object that represents the new location or
|
||||
* null if we have switched to no location.
|
||||
*/
|
||||
void locationDidChange (PlaceObject place);
|
||||
|
||||
/**
|
||||
* This is called on all location observers when a location change
|
||||
* request is rejected by the server or fails for some other reason.
|
||||
*
|
||||
* @param placeId the place id to which we attempted to relocate, but
|
||||
* failed.
|
||||
* @param reason the reason code that explains why the location change
|
||||
* request was rejected or otherwise failed.
|
||||
*/
|
||||
void locationChangeFailed (int placeId, String reason);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.threerings.presents.client.InvocationReceiver;
|
||||
|
||||
/**
|
||||
* Defines, for the location services, a set of notifications delivered
|
||||
* asynchronously by the server to the client.
|
||||
*/
|
||||
public interface LocationReceiver extends InvocationReceiver
|
||||
{
|
||||
/**
|
||||
* Used to communicate a required move notification to the client. The
|
||||
* server will have removed the client from their existing location
|
||||
* and the client is then responsible for generating a {@link
|
||||
* LocationService#moveTo} request to move to the new location.
|
||||
*/
|
||||
void forcedMove (int placeId);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.threerings.presents.client.Client;
|
||||
import com.threerings.presents.client.InvocationService;
|
||||
|
||||
import com.threerings.crowd.data.PlaceConfig;
|
||||
|
||||
/**
|
||||
* The location services provide a mechanism by which the client can request to move from place to
|
||||
* place in the server. These services should not be used directly, but instead should be accessed
|
||||
* via the {@link LocationDirector}.
|
||||
*/
|
||||
public interface LocationService extends InvocationService
|
||||
{
|
||||
/**
|
||||
* Used to communicate responses to {@link LocationService#moveTo} requests.
|
||||
*/
|
||||
public static interface MoveListener extends InvocationListener
|
||||
{
|
||||
/**
|
||||
* Called in response to a successful {@link LocationService#moveTo} request.
|
||||
*/
|
||||
void moveSucceeded (PlaceConfig config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that this client's body be moved to the specified location.
|
||||
*
|
||||
* @param client a reference to the client object that defines the context in which this
|
||||
* invocation service should be executed.
|
||||
* @param placeId the object id of the place object to which the body should be moved.
|
||||
* @param listener the listener that will be informed of success or failure.
|
||||
*/
|
||||
void moveTo (Client client, int placeId, MoveListener listener);
|
||||
|
||||
/**
|
||||
* Requests that we leave our current place and move to nowhere land.
|
||||
*/
|
||||
void leavePlace (Client client);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
/**
|
||||
* An exception that indicates that the server did not allow us to move.
|
||||
*/
|
||||
public class MoveFailedException extends Exception
|
||||
{
|
||||
public MoveFailedException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
/**
|
||||
* An exception that indicates that a LocationObserver vetoed our move request.
|
||||
*/
|
||||
public class MoveVetoedException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.threerings.crowd.data.OccupantInfo;
|
||||
|
||||
/**
|
||||
* The occupant adapter makes life easier for occupant observer classes
|
||||
* that only care about one or two of the occupant observer
|
||||
* callbacks. They can either extend occupant adapter or create an
|
||||
* anonymous class that extends it and overrides just the callbacks they
|
||||
* care about.
|
||||
*/
|
||||
public class OccupantAdapter implements OccupantObserver
|
||||
{
|
||||
// documentation inherited from interface
|
||||
public void occupantEntered (OccupantInfo info)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void occupantLeft (OccupantInfo info)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void occupantUpdated (OccupantInfo oinfo, OccupantInfo info)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.samskivert.util.ObserverList;
|
||||
|
||||
import com.threerings.util.Name;
|
||||
|
||||
import com.threerings.presents.client.BasicDirector;
|
||||
import com.threerings.presents.client.Client;
|
||||
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.crowd.data.OccupantInfo;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
/**
|
||||
* The occupant director listens for occupants of places to enter and
|
||||
* exit, and dispatches notices to interested parties about these events.
|
||||
*
|
||||
* <p> It will eventually provide a framework for keeping track of
|
||||
* occupant information in a network efficient manner. The idea being that
|
||||
* we want to store as little information about occupants as possible in
|
||||
* the place object (probably just body oid and username), but upon
|
||||
* entering a place, this will be all we know about the occupants. We then
|
||||
* dispatch a request to get information about all of the occupants in the
|
||||
* room (things like avatar information for a graphical display or perhaps
|
||||
* their ratings in the game that is associated with a place for a gaming
|
||||
* site) which we then pass on to the occupant observers when it becomes
|
||||
* available.
|
||||
*
|
||||
* <p> This information would be cached and we could return cached
|
||||
* information for occupants for which we have cached info. We will
|
||||
* probably want to still make a request for the occupant info so that we
|
||||
* can update non-static occupant data rather than permanently using
|
||||
* what's in the cache.
|
||||
*/
|
||||
public class OccupantDirector extends BasicDirector
|
||||
implements LocationObserver, SetListener<OccupantInfo>
|
||||
{
|
||||
/**
|
||||
* Constructs a new occupant director with the supplied context.
|
||||
*/
|
||||
public OccupantDirector (CrowdContext ctx)
|
||||
{
|
||||
super(ctx);
|
||||
|
||||
// register ourselves as a location observer
|
||||
ctx.getLocationDirector().addLocationObserver(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified occupant observer to the list.
|
||||
*/
|
||||
public void addOccupantObserver (OccupantObserver obs)
|
||||
{
|
||||
_observers.add(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified occupant observer from the list.
|
||||
*/
|
||||
public void removeOccupantObserver (OccupantObserver obs)
|
||||
{
|
||||
_observers.remove(obs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the occupant info for the user in question if it exists in
|
||||
* the currently occupied place. Returns null if no occupant info
|
||||
* exists for the specified body.
|
||||
*/
|
||||
public OccupantInfo getOccupantInfo (int bodyOid)
|
||||
{
|
||||
// make sure we're somewhere
|
||||
return (_place == null) ? null : _place.occupantInfo.get(Integer.valueOf(bodyOid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the occupant info for the user in question if it exists in
|
||||
* the currently occupied place. Returns null if no occupant info
|
||||
* exists with the specified username.
|
||||
*/
|
||||
public OccupantInfo getOccupantInfo (Name username)
|
||||
{
|
||||
return (_place == null) ? null : _place.getOccupantInfo(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientDidLogoff (Client client)
|
||||
{
|
||||
// clear things out
|
||||
if (_place != null) {
|
||||
_place.removeListener(this);
|
||||
_place = null;
|
||||
}
|
||||
}
|
||||
|
||||
// inherit documentation
|
||||
public boolean locationMayChange (int placeId)
|
||||
{
|
||||
// we've got no opinion
|
||||
return true;
|
||||
}
|
||||
|
||||
// inherit documentation
|
||||
public void locationDidChange (PlaceObject place)
|
||||
{
|
||||
// unlisten to the old place object if there was one
|
||||
if (_place != null) {
|
||||
_place.removeListener(this);
|
||||
}
|
||||
|
||||
// listen to the new one
|
||||
_place = place;
|
||||
if (_place != null) {
|
||||
_place.addListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
// inherit documentation
|
||||
public void locationChangeFailed (int placeId, String reason)
|
||||
{
|
||||
// nothing to do here either
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals with all of the processing when an occupant shows up.
|
||||
*/
|
||||
public void entryAdded (EntryAddedEvent<OccupantInfo> event)
|
||||
{
|
||||
// bail if this isn't for the OCCUPANT_INFO field
|
||||
if (!event.getName().equals(PlaceObject.OCCUPANT_INFO)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// now let the occupant observers know what's up
|
||||
final OccupantInfo info = event.getEntry();
|
||||
_observers.apply(new ObserverList.ObserverOp<OccupantObserver>() {
|
||||
public boolean apply (OccupantObserver observer) {
|
||||
observer.occupantEntered(info);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals with all of the processing when an occupant is updated.
|
||||
*/
|
||||
public void entryUpdated (EntryUpdatedEvent<OccupantInfo> event)
|
||||
{
|
||||
// bail if this isn't for the OCCUPANT_INFO field
|
||||
if (!event.getName().equals(PlaceObject.OCCUPANT_INFO)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// now let the occupant observers know what's up
|
||||
final OccupantInfo info = event.getEntry();
|
||||
final OccupantInfo oinfo = event.getOldEntry();
|
||||
_observers.apply(new ObserverList.ObserverOp<OccupantObserver>() {
|
||||
public boolean apply (OccupantObserver observer) {
|
||||
observer.occupantUpdated(oinfo, info);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deals with all of the processing when an occupant leaves.
|
||||
*/
|
||||
public void entryRemoved (EntryRemovedEvent<OccupantInfo> event)
|
||||
{
|
||||
// bail if this isn't for the OCCUPANT_INFO field
|
||||
if (!event.getName().equals(PlaceObject.OCCUPANT_INFO)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// let the occupant observers know what's up
|
||||
final OccupantInfo oinfo = event.getOldEntry();
|
||||
_observers.apply(new ObserverList.ObserverOp<OccupantObserver>() {
|
||||
public boolean apply (OccupantObserver observer) {
|
||||
observer.occupantLeft(oinfo);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** The occupant observers to keep abreast of occupant antics. */
|
||||
protected ObserverList<OccupantObserver> _observers = ObserverList.newSafeInOrder();
|
||||
|
||||
/** The user's current location. */
|
||||
protected PlaceObject _place;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import com.threerings.crowd.data.OccupantInfo;
|
||||
|
||||
/**
|
||||
* An entity that is interested in hearing about bodies that enter and leave a location (as well
|
||||
* as disconnect and reconnect) can implement this interface and register itself with the
|
||||
* {@link OccupantDirector}.
|
||||
*/
|
||||
public interface OccupantObserver
|
||||
{
|
||||
/**
|
||||
* Called when a body enters the place.
|
||||
*/
|
||||
void occupantEntered (OccupantInfo info);
|
||||
|
||||
/**
|
||||
* Called when a body leaves the place.
|
||||
*/
|
||||
void occupantLeft (OccupantInfo info);
|
||||
|
||||
/**
|
||||
* Called when an occupant is updated.
|
||||
*
|
||||
* @param oldinfo the occupant info prior to the update.
|
||||
* @param newinfo the newly update info record.
|
||||
*/
|
||||
void occupantUpdated (OccupantInfo oldinfo, OccupantInfo newinfo);
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.samskivert.swing.Controller;
|
||||
|
||||
import com.threerings.crowd.data.PlaceConfig;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
/**
|
||||
* Controls the user interface that is used to display a place. When the client moves to a new
|
||||
* place, the appropriate place controller is constructed and requested to create and display the
|
||||
* user interface for that place.
|
||||
*/
|
||||
public abstract class PlaceController extends Controller
|
||||
{
|
||||
/**
|
||||
* Used to call methods in delegates.
|
||||
*/
|
||||
public static abstract class DelegateOp
|
||||
{
|
||||
public DelegateOp (Class<? extends PlaceControllerDelegate> delegateClass) {
|
||||
_delegateClass = delegateClass;
|
||||
}
|
||||
|
||||
/** Applies an operation to the supplied delegate. */
|
||||
public abstract void apply (PlaceControllerDelegate delegate);
|
||||
|
||||
public boolean shouldApply (PlaceControllerDelegate delegate) {
|
||||
return _delegateClass.isInstance(delegate);
|
||||
}
|
||||
|
||||
protected Class<? extends PlaceControllerDelegate> _delegateClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this place controller with a reference to the context that they can use to
|
||||
* access client services and to the configuration record for this place. The controller
|
||||
* should create as much of its user interface that it can without having access to the place
|
||||
* object because this will be invoked in parallel with the fetching of the place object. When
|
||||
* the place object is obtained, the controller will be notified and it can then finish the
|
||||
* user interface configuration and put the user interface into operation.
|
||||
*
|
||||
* @param ctx the client context.
|
||||
* @param config the place configuration for this place.
|
||||
*/
|
||||
public void init (CrowdContext ctx, PlaceConfig config)
|
||||
{
|
||||
// keep these around
|
||||
_ctx = ctx;
|
||||
_config = config;
|
||||
|
||||
// create our user interface
|
||||
_view = createPlaceView(_ctx);
|
||||
|
||||
// initialize our delegates
|
||||
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
|
||||
@Override
|
||||
public void apply (PlaceControllerDelegate delegate) {
|
||||
delegate.init(_ctx, _config);
|
||||
}
|
||||
});
|
||||
|
||||
// let the derived classes do any initialization stuff
|
||||
didInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes can override this and perform any post-initialization processing they might
|
||||
* need. They should of course be sure to call <code>super.didInit()</code>.
|
||||
*/
|
||||
protected void didInit ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the place view associated with this controller. This is only valid
|
||||
* after a call has been made to {@link #init}.
|
||||
*/
|
||||
public PlaceView getPlaceView ()
|
||||
{
|
||||
return _view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PlaceConfig} associated with this place.
|
||||
*/
|
||||
public PlaceConfig getPlaceConfig ()
|
||||
{
|
||||
return _config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the user interface that will be used to display this place. The view instance
|
||||
* returned will later be configured with the place object, once it becomes available.
|
||||
*
|
||||
* @param ctx a reference to the {@link CrowdContext} associated with this controller.
|
||||
*/
|
||||
protected PlaceView createPlaceView (CrowdContext ctx)
|
||||
{
|
||||
return createPlaceView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obsolete but retained for runtime compatibility with the old and busted.
|
||||
*
|
||||
* @deprecated Use {@link #createPlaceView(CrowdContext)}.
|
||||
*/
|
||||
@Deprecated
|
||||
protected PlaceView createPlaceView ()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called by the location director once the place object has been fetched. The place
|
||||
* controller will dispatch the place object to the user interface hierarchy via
|
||||
* {@link PlaceViewUtil#dispatchWillEnterPlace}. Derived classes can override this and perform
|
||||
* any other starting up that they need to do
|
||||
*/
|
||||
public void willEnterPlace (final PlaceObject plobj)
|
||||
{
|
||||
// keep a handle on our place object
|
||||
_plobj = plobj;
|
||||
|
||||
if (_view != null) {
|
||||
// let the UI hierarchy know that we've got our place
|
||||
PlaceViewUtil.dispatchWillEnterPlace(_view, plobj);
|
||||
// and display the user interface
|
||||
_ctx.setPlaceView(_view);
|
||||
}
|
||||
|
||||
// let our delegates know what's up
|
||||
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
|
||||
@Override
|
||||
public void apply (PlaceControllerDelegate delegate) {
|
||||
delegate.willEnterPlace(plobj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a request is submitted to the server to leave the current place. As such,
|
||||
* this method may be called multiple times before {@link #didLeavePlace} is finally called.
|
||||
* The request to leave may be rejected, but if a place controller needs to flush any
|
||||
* information to the place manager before it leaves, it should so do here. This is the only
|
||||
* place in which the controller is guaranteed to be able to communicate to the place manager,
|
||||
* as by the time {@link #didLeavePlace} is called, the place manager may have already been
|
||||
* destroyed.
|
||||
*/
|
||||
public void mayLeavePlace (final PlaceObject plobj)
|
||||
{
|
||||
// let our delegates know what's up
|
||||
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
|
||||
@Override
|
||||
public void apply (PlaceControllerDelegate delegate) {
|
||||
delegate.mayLeavePlace(plobj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called by the location director when we are leaving this place and need to clean up
|
||||
* after ourselves and shutdown. Derived classes should override this method (being sure to
|
||||
* call <code>super.didLeavePlace</code>) and perform any necessary cleanup.
|
||||
*/
|
||||
public void didLeavePlace (final PlaceObject plobj)
|
||||
{
|
||||
// let our delegates know what's up
|
||||
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
|
||||
@Override
|
||||
public void apply (PlaceControllerDelegate delegate) {
|
||||
delegate.didLeavePlace(plobj);
|
||||
}
|
||||
});
|
||||
|
||||
// let the UI hierarchy know that we're outta here
|
||||
if (_view != null) {
|
||||
PlaceViewUtil.dispatchDidLeavePlace(_view, plobj);
|
||||
_ctx.clearPlaceView(_view);
|
||||
_view = null;
|
||||
}
|
||||
|
||||
_plobj = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles basic place controller action events. Derived classes should be sure to call
|
||||
* <code>super.handleAction</code> for events they don't specifically handle.
|
||||
*/
|
||||
@Override
|
||||
public boolean handleAction (final ActionEvent action)
|
||||
{
|
||||
final boolean[] handled = new boolean[1];
|
||||
|
||||
// let our delegates have a crack at the action
|
||||
applyToDelegates(new DelegateOp(PlaceControllerDelegate.class) {
|
||||
@Override
|
||||
public void apply (PlaceControllerDelegate delegate) {
|
||||
// we take advantage of short-circuiting here
|
||||
handled[0] = handled[0] || delegate.handleAction(action);
|
||||
}
|
||||
});
|
||||
|
||||
// if they didn't handle it, pass it off to the super class
|
||||
return handled[0] || super.handleAction(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the supplied delegate to the list for this controller.
|
||||
*/
|
||||
protected void addDelegate (PlaceControllerDelegate delegate)
|
||||
{
|
||||
if (_delegates == null) {
|
||||
_delegates = Lists.newArrayList();
|
||||
}
|
||||
_delegates.add(delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the supplied operation to the registered delegates.
|
||||
*/
|
||||
protected void applyToDelegates (DelegateOp op)
|
||||
{
|
||||
if (_delegates != null) {
|
||||
for (int ii = 0, ll = _delegates.size(); ii < ll; ii++) {
|
||||
PlaceControllerDelegate delegate = _delegates.get(ii);
|
||||
if (op.shouldApply(delegate)) {
|
||||
op.apply(delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A reference to the active client context. */
|
||||
protected CrowdContext _ctx;
|
||||
|
||||
/** A reference to our place configuration. */
|
||||
protected PlaceConfig _config;
|
||||
|
||||
/** A reference to the place object for which we're controlling a user
|
||||
* interface. */
|
||||
protected PlaceObject _plobj;
|
||||
|
||||
/** A reference to the root user interface component. */
|
||||
protected PlaceView _view;
|
||||
|
||||
/** A list of the delegates in use by this controller. */
|
||||
protected ArrayList<PlaceControllerDelegate> _delegates;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import com.threerings.crowd.data.PlaceConfig;
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
import com.threerings.crowd.util.CrowdContext;
|
||||
|
||||
/**
|
||||
* Provides an extensible mechanism for encapsulating delegated
|
||||
* functionality that works with the place services.
|
||||
*
|
||||
* <p> Thanks to Java's lack of multiple inheritance, it will likely
|
||||
* become necessary to factor certain services that might be used by a
|
||||
* variety of {@link PlaceController} derived classes into delegate
|
||||
* classes because they do not fit into the single inheritance hierarchy
|
||||
* that makes sense for a particular application. To facilitate this
|
||||
* process, this delegate class is provided which the standard place
|
||||
* controller can be made to call out to for all of the standard methods.
|
||||
*/
|
||||
public class PlaceControllerDelegate
|
||||
{
|
||||
/**
|
||||
* Constructs the delegate with the controller for which it is
|
||||
* delegating.
|
||||
*/
|
||||
public PlaceControllerDelegate (PlaceController controller)
|
||||
{
|
||||
_controller = controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to initialize the delegate.
|
||||
*/
|
||||
public void init (CrowdContext ctx, PlaceConfig config)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to let the delegate know that we're entering a place.
|
||||
*/
|
||||
public void willEnterPlace (PlaceObject plobj)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a request is submitted to the server to leave the
|
||||
* current place. The request to leave may be rejected, but if a place
|
||||
* controller needs to make a final communication to the place manager
|
||||
* before it leaves, it should so do here. This is the only place in
|
||||
* which the controller is guaranteed to be able to communicate to the
|
||||
* place manager, as by the time {@link #didLeavePlace} is called, the
|
||||
* place manager may have already been destroyed.
|
||||
*/
|
||||
public void mayLeavePlace (final PlaceObject plobj)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to let the delegate know that we've left the place.
|
||||
*/
|
||||
public void didLeavePlace (PlaceObject plobj)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to give the delegate a chance to handle controller actions
|
||||
* that weren't handled by the main controller.
|
||||
*/
|
||||
public boolean handleAction (ActionEvent action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/** A reference to the controller for which we are delegating. */
|
||||
protected PlaceController _controller;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.crowd.client;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import com.samskivert.swing.Controller;
|
||||
import com.samskivert.swing.ControllerProvider;
|
||||
|
||||
import com.threerings.crowd.data.PlaceObject;
|
||||
|
||||
/**
|
||||
* A useful base class for client interfaces which wish to make use of a
|
||||
* {@link JPanel} as their top-level {@link PlaceView}.
|
||||
*/
|
||||
public class PlacePanel extends JPanel
|
||||
implements ControllerProvider, PlaceView
|
||||
{
|
||||
/**
|
||||
* Constructs a place panel with the specified controller which will
|
||||
* be made availabel via the {@link ControllerProvider} interface.
|
||||
*/
|
||||
public PlacePanel (PlaceController controller)
|
||||
{
|
||||
_controller = controller;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Controller getController ()
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void willEnterPlace (PlaceObject plobj)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void didLeavePlace (PlaceObject plobj)
|
||||
{
|
||||
}
|
||||
|
||||
/** A reference to the controller with which we interoperate. */
|
||||
protected PlaceController _controller;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user