/*
* Copyright 2009 Thomas Bocek
*
* 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 net.tomp2p.rpc;
import java.io.IOException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.tomp2p.connection2.ChannelCreator;
import net.tomp2p.connection2.ConnectionBean;
import net.tomp2p.connection2.PeerBean;
import net.tomp2p.connection2.RequestHandler;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Buffer;
import net.tomp2p.message.Message2;
import net.tomp2p.message.Message2.Type;
import net.tomp2p.p2p.builder.SendDirectBuilder;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DirectDataRPC extends DispatchHandler {
private static final Logger LOG = LoggerFactory.getLogger(DirectDataRPC.class);
public static final byte DIRECT_DATA_COMMAND = 7;
private volatile RawDataReply rawDataReply;
private volatile ObjectDataReply objectDataReply;
public DirectDataRPC(PeerBean peerBean, ConnectionBean connectionBean) {
super(peerBean, connectionBean, DIRECT_DATA_COMMAND);
}
/**
* Send data directly to a peer. Make sure you have set up a reply handler. This is an RPC.
*
* @param remotePeer
* The remote peer to store the data
* @param buffer
* The data to send to the remote peer
* @param raw
* Set to true if a the byte array is expected or if it should be converted to an object
* @param channelCreator
* The channel creator
* @param idleTCPMillis
* Set the timeout when a connection is considered inactive (idle)
* @param forceUDP
* Set to true if the communication should be UDP, default is TCP
* @return FutureResponse that stores which content keys have been stored.
*/
public RequestHandler<FutureResponse> sendInternal(final PeerAddress remotePeer,
final SendDirectBuilderI sendDirectBuilder) {
final Message2 message = createMessage(remotePeer, DIRECT_DATA_COMMAND,
sendDirectBuilder.isRaw() ? Type.REQUEST_1 : Type.REQUEST_2);
final FutureResponse futureResponse = new FutureResponse(message,
sendDirectBuilder.progressListener());
if (sendDirectBuilder.isSignMessage()) {
message.setPublicKeyAndSign(peerBean().getKeyPair());
}
message.streaming(sendDirectBuilder.streaming());
if (sendDirectBuilder.isRaw()) {
message.setBuffer(sendDirectBuilder.getBuffer());
} else {
byte[] me;
try {
me = Utils.encodeJavaObject(sendDirectBuilder.getObject());
message.setBuffer(new Buffer(Unpooled.wrappedBuffer(me)));
} catch (IOException e) {
futureResponse.setFailed("cannot convert object", e);
}
}
return new RequestHandler<FutureResponse>(futureResponse, peerBean(), connectionBean(),
sendDirectBuilder);
}
public FutureResponse send(final PeerAddress remotePeer, final SendDirectBuilderI sendDirectBuilder,
final ChannelCreator channelCreator) {
final RequestHandler<FutureResponse> requestHandler = sendInternal(remotePeer, sendDirectBuilder);
if (!sendDirectBuilder.isForceUDP()) {
return requestHandler.sendTCP(channelCreator);
} else {
return requestHandler.sendUDP(channelCreator);
}
}
public void setReply(final RawDataReply rawDataReply) {
this.rawDataReply = rawDataReply;
}
public void setReply(ObjectDataReply objectDataReply) {
this.objectDataReply = objectDataReply;
}
public boolean hasRawDataReply() {
return rawDataReply != null;
}
public boolean hasObjectDataReply() {
return objectDataReply != null;
}
@Override
public Message2 handleResponse(final Message2 message, final boolean sign) throws Exception {
if (!((message.getType() == Type.REQUEST_1 || message.getType() == Type.REQUEST_2) && message
.getCommand() == DIRECT_DATA_COMMAND)) {
throw new IllegalArgumentException("Message content is wrong");
}
final Message2 responseMessage = createResponseMessage(message, Type.OK);
if (sign) {
responseMessage.setPublicKeyAndSign(peerBean().getKeyPair());
}
final RawDataReply rawDataReply2 = rawDataReply;
final ObjectDataReply objectDataReply2 = objectDataReply;
if (message.getType() == Type.REQUEST_1 && rawDataReply2 == null) {
responseMessage.setType(Type.NOT_FOUND);
} else if (message.getType() == Type.REQUEST_2 && objectDataReply2 == null) {
responseMessage.setType(Type.NOT_FOUND);
} else {
final Buffer requestBuffer = message.getBuffer(0);
// the user can reply with null, indicating not found. Or
// returning the request buffer, which means nothing is
// returned. Or an exception can be thrown
if (message.getType() == Type.REQUEST_1) {
LOG.debug("handling requet1");
final Buffer replyBuffer = rawDataReply2.reply(message.getSender(), requestBuffer,
message.isDone());
if (replyBuffer == null && message.isDone()) {
responseMessage.setType(Type.NOT_FOUND);
} else if (replyBuffer != requestBuffer) {
// can be partial as well
if (!replyBuffer.isComplete()) {
responseMessage.setStreaming();
}
responseMessage.setBuffer(replyBuffer);
}
} else { // no streaming here when we deal with objects
Object obj = Utils.decodeJavaObject(requestBuffer.buffer());
LOG.debug("handling {}", obj);
Object reply = objectDataReply2.reply(message.getSender(), obj);
if (reply == null) {
responseMessage.setType(Type.NOT_FOUND);
} else if (reply == obj) {
responseMessage.setType(Type.OK);
} else {
byte[] me = Utils.encodeJavaObject(reply);
responseMessage.setBuffer(new Buffer(Unpooled.wrappedBuffer(me)));
}
}
}
return responseMessage;
}
}