blob: f387635460c042095a746f4f26aa1770afeb931a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.catalina.tribes.group;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.apache.catalina.tribes.Channel;
import org.apache.catalina.tribes.ChannelException;
import org.apache.catalina.tribes.ChannelListener;
import org.apache.catalina.tribes.Member;
import org.apache.catalina.tribes.util.UUIDGenerator;
/**
* A channel to handle RPC messaging
* @author Filip Hanik
*/
public class RpcChannel implements ChannelListener{
protected static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(RpcChannel.class);
public static final int FIRST_REPLY = 1;
public static final int MAJORITY_REPLY = 2;
public static final int ALL_REPLY = 3;
public static final int NO_REPLY = 4;
private Channel channel;
private RpcCallback callback;
private byte[] rpcId;
private HashMap responseMap = new HashMap();
/**
* Create an RPC channel. You can have several RPC channels attached to a group
* all separated out by the uniqueness
* @param rpcId - the unique Id for this RPC group
* @param channel Channel
* @param callback RpcCallback
*/
public RpcChannel(byte[] rpcId, Channel channel, RpcCallback callback) {
this.channel = channel;
this.callback = callback;
this.rpcId = rpcId;
channel.addChannelListener(this);
}
/**
* Send a message and wait for the response.
* @param destination Member[] - the destination for the message, and the members you request a reply from
* @param message Serializable - the message you are sending out
* @param options int - FIRST_REPLY, MAJORITY_REPLY or ALL_REPLY
* @param timeout long - timeout in milliseconds, if no reply is received within this time null is returned
* @return Response[] - an array of response objects.
* @throws ChannelException
*/
public Response[] send(Member[] destination,
Serializable message,
int rpcOptions,
int channelOptions,
long timeout) throws ChannelException {
if ( destination==null || destination.length == 0 ) return new Response[0];
//avoid dead lock
channelOptions = channelOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
RpcCollectorKey key = new RpcCollectorKey(UUIDGenerator.randomUUID(false));
RpcCollector collector = new RpcCollector(key,rpcOptions,destination.length,timeout);
try {
synchronized (collector) {
if ( rpcOptions != NO_REPLY ) responseMap.put(key, collector);
RpcMessage rmsg = new RpcMessage(rpcId, key.id, message);
channel.send(destination, rmsg, channelOptions);
if ( rpcOptions != NO_REPLY ) collector.wait(timeout);
}
} catch ( InterruptedException ix ) {
Thread.currentThread().interrupted();
//throw new ChannelException(ix);
}finally {
responseMap.remove(key);
}
return collector.getResponses();
}
public void messageReceived(Serializable msg, Member sender) {
RpcMessage rmsg = (RpcMessage)msg;
RpcCollectorKey key = new RpcCollectorKey(rmsg.uuid);
if ( rmsg.reply ) {
RpcCollector collector = (RpcCollector)responseMap.get(key);
if (collector == null) {
callback.leftOver(rmsg.message, sender);
} else {
synchronized (collector) {
//make sure it hasn't been removed
if ( responseMap.containsKey(key) ) {
if ( (rmsg instanceof RpcMessage.NoRpcChannelReply) )
collector.destcnt--;
else
collector.addResponse(rmsg.message, sender);
if (collector.isComplete()) collector.notifyAll();
} else {
if (! (rmsg instanceof RpcMessage.NoRpcChannelReply) )
callback.leftOver(rmsg.message, sender);
}
}//synchronized
}//end if
} else{
Serializable reply = callback.replyRequest(rmsg.message,sender);
rmsg.reply = true;
rmsg.message = reply;
try {
channel.send(new Member[] {sender}, rmsg,0);
}catch ( Exception x ) {
log.error("Unable to send back reply in RpcChannel.",x);
}
}//end if
}
public void breakdown() {
channel.removeChannelListener(this);
}
public void finalize() {
breakdown();
}
public boolean accept(Serializable msg, Member sender) {
if ( msg instanceof RpcMessage ) {
RpcMessage rmsg = (RpcMessage)msg;
return Arrays.equals(rmsg.rpcId,rpcId);
}else return false;
}
public Channel getChannel() {
return channel;
}
public RpcCallback getCallback() {
return callback;
}
public byte[] getRpcId() {
return rpcId;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public void setCallback(RpcCallback callback) {
this.callback = callback;
}
public void setRpcId(byte[] rpcId) {
this.rpcId = rpcId;
}
/**
*
* Class that holds all response.
* @author not attributable
* @version 1.0
*/
public static class RpcCollector {
public ArrayList responses = new ArrayList();
public RpcCollectorKey key;
public int options;
public int destcnt;
public long timeout;
public RpcCollector(RpcCollectorKey key, int options, int destcnt, long timeout) {
this.key = key;
this.options = options;
this.destcnt = destcnt;
this.timeout = timeout;
}
public void addResponse(Serializable message, Member sender){
Response resp = new Response(sender,message);
responses.add(resp);
}
public boolean isComplete() {
if ( destcnt <= 0 ) return true;
switch (options) {
case ALL_REPLY:
return destcnt == responses.size();
case MAJORITY_REPLY:
{
float perc = ((float)responses.size()) / ((float)destcnt);
return perc >= 0.50f;
}
case FIRST_REPLY:
return responses.size()>0;
default:
return false;
}
}
public int hashCode() {
return key.hashCode();
}
public boolean equals(Object o) {
if ( o instanceof RpcCollector ) {
RpcCollector r = (RpcCollector)o;
return r.key.equals(this.key);
} else return false;
}
public Response[] getResponses() {
return (Response[])responses.toArray(new Response[responses.size()]);
}
}
public static class RpcCollectorKey {
byte[] id;
public RpcCollectorKey(byte[] id) {
this.id = id;
}
public int hashCode() {
return id[0]+id[1]+id[2]+id[3];
}
public boolean equals(Object o) {
if ( o instanceof RpcCollectorKey ) {
RpcCollectorKey r = (RpcCollectorKey)o;
return Arrays.equals(id,r.id);
} else return false;
}
}
protected static String bToS(byte[] data) {
StringBuffer buf = new StringBuffer(4*16);
buf.append("{");
for (int i=0; data!=null && i<data.length; i++ ) buf.append(String.valueOf(data[i])).append(" ");
buf.append("}");
return buf.toString();
}
}