// ======================================================================== // Copyright 2008 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //======================================================================== package org.mortbay.cometd; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.cometd.Bayeux; import org.cometd.Channel; import org.cometd.Client; import org.cometd.Listener; import org.cometd.Message; import org.cometd.MessageListener; import org.mortbay.component.LifeCycle; import org.mortbay.log.Log; import org.mortbay.thread.QueuedThreadPool; import org.mortbay.thread.ThreadPool; /* ------------------------------------------------------------ */ /** * Abstract Bayeux Service class. This is a base class to assist with the * creation of server side @ link Bayeux} clients that provide services to * remote Bayeux clients. The class provides a Bayeux {@link Client} and * {@link Listener} together with convenience methods to map subscriptions to * methods on the derived class and to send responses to those methods. * *
* If a {@link #set_threadPool(ThreadPool)} is set, then messages are handled in * their own threads. This is desirable if the handling of a message can take * considerable time and it is desired not to hold up the delivering thread * (typically a HTTP request handling thread). * *
* If the BayeuxService is constructed asynchronously (the default), then * messages are delivered unsynchronized and multiple simultaneous calls to * handling methods may occur. * *
* If the BayeuxService is constructed as a synchronous service, then message
* delivery is synchronized on the internal {@link Client} instances used and
* only a single call will be made to the handler method (unless a thread pool
* is used).
*
* @see MessageListener
* @author gregw
*
*/
public abstract class BayeuxService
{
private String _name;
private Bayeux _bayeux;
private Client _client;
private Map
* Typically a service will subscribe to a channel in the "/service/**"
* space which is not a broadcast channel. Messages published to these
* channels are only delivered to server side clients like this service.
*
*
* Any object returned by a mapped subscription method is delivered to the
* calling client and not broadcast. If the method returns void or null,
* then no response is sent. A mapped subscription method may also call
* {@link #send(Client, String, Object, String)} to deliver a response
* message(s) to different clients and/or channels. It may also publish
* methods via the normal {@link Bayeux} API.
*
*
*
* @param channelId
* The channel to subscribe to
* @param methodName
* The name of the method on this object to call when messages
* are recieved.
*/
protected void subscribe(String channelId, String methodName)
{
Method method=null;
Class> c=this.getClass();
while(c != null && c != Object.class)
{
Method[] methods=c.getDeclaredMethods();
for (int i=methods.length; i-- > 0;)
{
if (methodName.equals(methods[i].getName()))
{
if (method != null)
throw new IllegalArgumentException("Multiple methods called '" + methodName + "'");
method=methods[i];
}
}
c=c.getSuperclass();
}
if (method == null)
throw new NoSuchMethodError(methodName);
int params=method.getParameterTypes().length;
if (params < 2 || params > 4)
throw new IllegalArgumentException("Method '" + methodName + "' does not have 2or3 parameters");
if (!Client.class.isAssignableFrom(method.getParameterTypes()[0]))
throw new IllegalArgumentException("Method '" + methodName + "' does not have Client as first parameter");
Channel channel=_bayeux.getChannel(channelId,true);
if (((ChannelImpl)channel).getChannelId().isWild())
{
final Method m=method;
Client wild_client=_bayeux.newClient(_name + "-wild");
wild_client.addListener(_listener instanceof MessageListener.Asynchronous?new AsyncWildListen(wild_client,m):new SyncWildListen(wild_client,m));
channel.subscribe(wild_client);
}
else
{
_methods.put(channelId,method);
channel.subscribe(_client);
}
}
/* ------------------------------------------------------------ */
/**
* Send data to a individual client. The data passed is sent to the client
* as the "data" member of a message with the given channel and id. The
* message is not published on the channel and is thus not broadcast to all
* channel subscribers. However to the target client, the message appears as
* if it was broadcast.
*
* Typcially this method is only required if a service method sends
* response(s) to channels other than the subscribed channel. If the
* response is to be sent to the subscribed channel, then the data can
* simply be returned from the subscription method.
*
* @param toClient
* The target client
* @param onChannel
* The channel the message is for
* @param data
* The data of the message
* @param id
* The id of the message (or null for a random id).
*/
protected void send(Client toClient, String onChannel, Object data, String id)
{
toClient.deliver(getClient(),onChannel,data,id);
}
/* ------------------------------------------------------------ */
/**
* Handle Exception. This method is called when a mapped subscription method
* throws and exception while handling a message.
*
* @param fromClient
* @param toClient
* @param msg
* @param th
*/
protected void exception(Client fromClient, Client toClient, Map
*
myMethod(Client fromClient,Object data)
myMethod(Client fromClient,Object data,String id)
myMethod(Client fromClient,String channel,Object data,String id)
*