All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.athaydes.protobuf.tcp.internal.ProtobufInvocationHandler Maven / Gradle / Ivy
package com.athaydes.protobuf.tcp.internal;
import com.athaydes.protobuf.tcp.api.Api;
import com.athaydes.protobuf.tcp.api.Api.MethodInvocation;
import com.athaydes.protobuf.tcp.api.CommunicationException;
import com.athaydes.protobuf.tcp.api.RemoteException;
import com.google.protobuf.Any;
import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import com.google.protobuf.BytesValue;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
import com.google.protobuf.Message;
import com.google.protobuf.StringValue;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.stream.Collectors.toList;
/**
* TCP Protobuf-based {@link InvocationHandler}.
*
* This class can be used to create a {@link java.lang.reflect.Proxy} for a service
* whose implementation is provided by a remote service.
*/
public class ProtobufInvocationHandler implements InvocationHandler, AutoCloseable {
private static final Map, Function> packFunctions;
static {
Map, Function> packFunctions_ = new HashMap<>(9);
packFunctions_.put(String.class, object ->
Any.pack(StringValue.newBuilder().setValue((String) object).build()));
packFunctions_.put(Character.class, object ->
Any.pack(StringValue.newBuilder().setValue(object.toString()).build()));
packFunctions_.put(Boolean.class, object ->
Any.pack(BoolValue.newBuilder().setValue((boolean) object).build()));
packFunctions_.put(Integer.class, object ->
Any.pack(Int32Value.newBuilder().setValue((int) object).build()));
packFunctions_.put(Short.class, object ->
Any.pack(Int32Value.newBuilder().setValue((short) object).build()));
packFunctions_.put(Long.class, object ->
Any.pack(Int64Value.newBuilder().setValue((long) object).build()));
packFunctions_.put(Float.class, object ->
Any.pack(FloatValue.newBuilder().setValue((float) object).build()));
packFunctions_.put(Double.class, object ->
Any.pack(DoubleValue.newBuilder().setValue((double) object).build()));
packFunctions_.put(Byte.class, object ->
Any.pack(BytesValue.newBuilder().setValue(ByteString.copyFrom(new byte[]{(byte) object})).build()));
packFunctions = Collections.unmodifiableMap(packFunctions_);
}
private static final Logger log = LoggerFactory.getLogger(ProtobufInvocationHandler.class);
private static final Method closeMethod;
static {
try {
closeMethod = Closeable.class.getMethod("close");
} catch (NoSuchMethodException e) {
throw new ExceptionInInitializerError("Closeable interface does not have a close() method");
}
}
private final URI address;
private final AtomicReference socketRef = new AtomicReference<>();
private final boolean forwardCloseMethodCall;
public ProtobufInvocationHandler(URI address) {
this(address, false);
}
public ProtobufInvocationHandler(URI address, boolean forwardCloseMethodCall) {
this.address = address;
this.forwardCloseMethodCall = forwardCloseMethodCall;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws RuntimeException {
if (closeMethod.equals(method)) {
return handleCloseMethod();
} else if (method.getDeclaringClass().equals(Object.class)) {
return invokeLocalObjectMethod(proxy, method, args);
} else {
return callRemoteMethod(method, args);
}
}
private Object invokeLocalObjectMethod(Object proxy, Method method, Object[] args) {
log.info("Invoking local method {}", method);
// implement only the Object methods that are overridable
switch (method.getName()) {
case "toString":
return "RemoteService{address=" + address + ",class=" + proxy.getClass().getName() + "}";
case "equals":
return args[0] == proxy;
case "hashCode":
return proxy.getClass().hashCode() + address.hashCode();
default:
throw new RuntimeException(new NoSuchMethodException(method.getName()));
}
}
private Object callRemoteMethod(Method method, Object[] args) {
log.debug("Calling remote method '{}'", method.getName());
MethodInvocation invocation = MethodInvocation.newBuilder()
.setMethodName(method.getName())
.addAllArgs(Arrays.stream(args == null ? new Object[]{} : args)
.map(ProtobufInvocationHandler::packedMessage)
.peek(msg -> {
if (msg == null) {
throw new NullPointerException("Remote method invocation cannot accept null argument");
}
}).collect(toList())).build();
Api.Result result = null;
int retries = 1;
while (retries >= 0) {
Socket socket = null;
try {
socket = createOrReuseSocket();
log.debug("Connected to server {}, sending message with length: {}",
address, invocation.getSerializedSize());
OutputStream out = socket.getOutputStream();
invocation.writeDelimitedTo(out);
out.flush();
log.debug("Waiting for server response");
result = Api.Result.parseDelimitedFrom(socket.getInputStream());
if (result == null) {
log.debug("Received EOF, resetting the socket");
socketRef.set(null);
} else {
break;
}
} catch (IOException e) {
log.debug("Problem connecting to server [retries={}]: {}", retries, e.toString());
socketRef.set(null);
Optional.ofNullable(socket).ifPresent(Utils::closeQuietly);
if (retries <= 0) {
throw new CommunicationException(e);
}
} finally {
retries--;
}
}
log.debug("Received result: {}", result);
if (result == null) {
return null;
} else switch (result.getResultCase()) {
case SUCCESSRESULT:
if (method.getReturnType().equals(void.class)) {
return null;
}
try {
return MethodInvocationResolver.convert(result.getSuccessResult(), method.getReturnType());
} catch (IOException e) {
throw new CommunicationException(e);
}
case EXCEPTION:
throw new RemoteException(result.getException().getType(), result.getException().getMessage());
default:
return null;
}
}
private Object handleCloseMethod() {
if (forwardCloseMethodCall) try {
callRemoteMethod(closeMethod, new Object[]{});
} catch (CommunicationException e) {
// swallow CommunicationException
log.debug("Ignoring CommunicationException when closing client: {}", e.toString());
}
log.debug("Closing client");
close();
return null;
}
private Socket createOrReuseSocket() throws IOException {
Socket oldSocket = socketRef.get();
if (oldSocket != null) {
log.debug("Reusing socket for {}", address);
return oldSocket;
} else {
log.debug("Creating new socket for {}", address);
Socket newSocket = new Socket(address.getHost(), address.getPort());
socketRef.set(newSocket);
return newSocket;
}
}
/**
* Converts an object to a Protobuf message wrapped into {@link Any}.
*
* @param object to convert
* @return converted object if possible. If object is null, null is returned.
* @throws IllegalArgumentException if a conversion is not possible.
*/
static Any packedMessage(Object object) {
if (object == null) {
return null;
}
if (object instanceof Message) {
return Any.pack((Message) object);
}
Function packFun = packFunctions.get(object.getClass());
if (packFun != null) {
return packFun.apply(object);
}
throw new IllegalArgumentException("Cannot pack " + object.getClass() + " into protobuff message");
}
@Override
public void close() {
Optional.ofNullable(socketRef.getAndSet(null)).ifPresent(Utils::closeQuietly);
}
}