/*-
* Copyright (c) 2012-2016 Diamond Light Source Ltd.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package uk.ac.diamond.scisoft.analysis.rpc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.eclipse.dawnsci.analysis.api.rpc.AnalysisRpcException;
import org.eclipse.dawnsci.analysis.api.rpc.IAnalysisRpcClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.diamond.scisoft.analysis.rpc.flattening.IRootFlattener;
import uk.ac.diamond.scisoft.analysis.rpc.internal.AnalysisRpcTypeFactoryImpl;
/**
* Client class for AnalysisRpc.
* <p>
* Generally it is expected that a provider of a service will write a wrapper
* class that delegates to {@link AnalysisRpcClient} and provides a "nice"
* interface which is strongly typed.
* <p>
*
* @see AnalysisRpcBasicTest See the Ananlysis Rpc Basic Test for an example of
* use
*/
public class AnalysisRpcClient implements IAnalysisRpcClient {
private static final Logger logger = LoggerFactory
.getLogger(AnalysisRpcClient.class);
private XmlRpcClient client;
private IRootFlattener flattener = FlatteningService.getFlattener();
private final int port;
/**
* Create a new AnalysisRpc client that connects to a server on the given
* port
*
* @param port
* to connect to
*/
public AnalysisRpcClient(int port) {
this.port = port;
try {
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setServerURL(new URL("http://127.0.0.1:" + port + "/RPC2"));
client = new XmlRpcClient();
client.setConfig(config);
client.setTypeFactory(new AnalysisRpcTypeFactoryImpl(client));
} catch (MalformedURLException e) {
// This is a programming error
logger.error(
"Failed to create AnalysisRPCClient due to MalformedURLException",
e);
}
}
private Object request_common(String destination, Object[] args,
boolean debug, boolean suspend) throws AnalysisRpcException {
try {
if (args == null) {
// No arguments, convert null to empty array
args = new Object[0];
}
Object[] flatargs = (Object[]) flattener.flatten(args);
final Object flatret;
if (debug) {
flatret = client.execute("Analysis.handler_debug",
new Object[] { destination, flatargs, suspend });
} else {
flatret = client.execute("Analysis.handler", new Object[] {
destination, flatargs });
}
Object unflatret = flattener.unflatten(flatret);
if (unflatret instanceof Exception) {
throw new AnalysisRpcException((Exception) unflatret);
}
return unflatret;
} catch (XmlRpcException e) {
throw new AnalysisRpcException(e);
} catch (UnsupportedOperationException e) {
throw new AnalysisRpcException(e);
}
}
@Override
public Object request(String destination, Object[] args)
throws AnalysisRpcException {
return request_common(destination, args, false, false);
}
@Override
public Object request_debug(String destination, Object[] args,
boolean suspend) throws AnalysisRpcException {
return request_common(destination, args, true, suspend);
}
@Override
public boolean isAlive() {
try {
client.execute("Analysis.is_alive", new Object[0]);
return true;
} catch (XmlRpcException e) {
return false;
}
}
@Override
public void setPyDevSetTraceParams(Map<String, Object> options)
throws AnalysisRpcException {
try {
client.execute("Analysis.set_pydev_settrace_params",
new Object[] { options });
} catch (XmlRpcException e) {
throw new AnalysisRpcException(
"Failed to set_pydev_settrace_params", e);
}
}
@Override
public void setPyDevSetTracePort(int port) throws AnalysisRpcException {
Map<String, Object> options = new HashMap<String, Object>();
options.put("port", port);
setPyDevSetTraceParams(options);
}
@Override
public void waitUntilAlive() throws AnalysisRpcException {
String timeoutSystemProp = System.getProperty(
"uk.ac.diamond.scisoft.analysis.xmlrpc.client.timeout", "5000");
final long ms = Integer.parseInt(timeoutSystemProp);
waitUntilAlive(ms);
}
@Override
public void waitUntilAlive(long milliseconds) throws AnalysisRpcException {
long stop = System.currentTimeMillis() + milliseconds;
while (System.currentTimeMillis() < stop) {
if (isAlive())
return;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
throw new AnalysisRpcException(
"Timeout waiting for other end to be alive");
}
@Override
public int getPort() {
return port;
}
@Override
public Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
boolean debug) {
return Proxy.newProxyInstance(loader, interfaces,
new ClientProxy(debug));
}
@Override
public Object newProxyInstance(Class<?>[] interfaces, boolean debug) {
return newProxyInstance(interfaces[0].getClassLoader(), interfaces,
debug);
}
@Override
public <T> T newProxyInstance(Class<T> single_interface, boolean debug) {
@SuppressWarnings("unchecked")
T result = (T) newProxyInstance(new Class<?>[] { single_interface },
debug);
return result;
}
@Override
public <T> T newProxyInstance(Class<T> single_interface) {
@SuppressWarnings("unchecked")
T result = (T) newProxyInstance(new Class<?>[] { single_interface },
false);
return result;
}
private class ClientProxy implements InvocationHandler {
private boolean debug;
public ClientProxy(boolean debug) {
this.debug = debug;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (!hasThrowsAnalysisRpcException(method.getExceptionTypes())) {
throw new RuntimeException(
"Invoked methods on AnalysisRpcClient must be declared to throw AnalysisRpcException");
}
final String methodName = method.getName();
if (debug) {
return request_debug(methodName, args, false);
} else {
return request(methodName, args);
}
}
private boolean hasThrowsAnalysisRpcException(Class<?>[] exceptionTypes) {
for (Class<?> exceptionType : exceptionTypes) {
if (exceptionType.isAssignableFrom(AnalysisRpcException.class)) {
return true;
}
}
return false;
}
}
}