blob: 202ce1660f7055990e2d8f5e93b3335f6adc5eb8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 Composent, Inc. and others. 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
*
* Contributors:
* Composent, Inc. - initial API and implementation
******************************************************************************/
package org.eclipse.ecf.remoteservice;
import java.lang.reflect.*;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.ecf.core.jobs.JobsExecutor;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.internal.remoteservice.Activator;
import org.eclipse.equinox.concurrent.future.IFuture;
import org.eclipse.equinox.concurrent.future.IProgressRunnable;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceException;
/**
* Abstract remote service implementation. Clients may subclass to avoid re-implementing
* methods from IRemoteService.
*
* @since 4.1
*/
public abstract class AbstractRemoteService implements IRemoteService, InvocationHandler {
protected static final Object[] EMPTY_ARGS = new Object[0];
protected abstract String[] getInterfaceClassNames();
protected abstract IRemoteServiceID getRemoteServiceID();
protected abstract IRemoteServiceReference getRemoteServiceReference();
protected Class loadInterfaceClass(String className) throws ClassNotFoundException {
return loadInterfaceClass(this.getClass().getClassLoader(), className);
}
/**
* @since 6.0
*/
protected Class loadInterfaceClass(ClassLoader cl, String className) throws ClassNotFoundException {
return Class.forName(className, true, cl);
}
protected IRemoteService getRemoteService() {
return this;
}
protected long getDefaultTimeout() {
return IRemoteCall.DEFAULT_TIMEOUT;
}
public IFuture callAsync(final IRemoteCall call) {
JobsExecutor executor = new JobsExecutor("callAsynch " + call.getMethod()); //$NON-NLS-1$
return executor.execute(new IProgressRunnable() {
public Object run(IProgressMonitor monitor) throws Exception {
return callSync(call);
}
}, null);
}
@SuppressWarnings("unchecked")
public Object getProxy() throws ECFException {
List classes = new ArrayList();
ClassLoader cl = this.getClass().getClassLoader();
try {
// Get clazz from reference
final String[] clazzes = getInterfaceClassNames();
for (int i = 0; i < clazzes.length; i++)
classes.add(loadInterfaceClass(cl, clazzes[i]));
} catch (final Exception e) {
ECFException except = new ECFException("Failed to create proxy", e); //$NON-NLS-1$
logWarning("Exception in remote service getProxy", except); //$NON-NLS-1$
throw except;
} catch (final NoClassDefFoundError e) {
ECFException except = new ECFException("Failed to load proxy interface class", e); //$NON-NLS-1$
logWarning("Could not load class for getProxy", except); //$NON-NLS-1$
throw except;
}
return getProxy(cl, (Class[]) classes.toArray(new Class[classes.size()]));
}
/**
* @since 6.0
*/
protected void addRemoteServiceProxyToProxy(List classes) {
IRemoteServiceReference rsReference = getRemoteServiceReference();
// add IRemoteServiceProxy interface to set of interfaces supported by this proxy
if (rsReference != null && rsReference.getProperty(Constants.SERVICE_PREVENT_RSPROXY) == null)
classes.add(IRemoteServiceProxy.class);
}
/**
* @since 6.0
*/
@SuppressWarnings("unchecked")
public Object getProxy(ClassLoader cl, Class[] interfaces) throws ECFException {
// Add async classes
// for all interfaces, add async classes
List classes = new ArrayList();
for (int i = 0; i < interfaces.length; i++) {
// add interface to classes
classes.add(interfaces[i]);
Class asyncClass = findAsyncRemoteServiceProxyClass(cl, interfaces[i]);
if (asyncClass != null)
classes.add(asyncClass);
}
// Add IRemoteServiceProxy to classes, if not restricted via service properties
addRemoteServiceProxyToProxy(classes);
// create and return proxy
try {
return createProxy(cl, (Class[]) classes.toArray(new Class[classes.size()]));
} catch (final Exception e) {
ECFException except = new ECFException("Failed to create proxy", e); //$NON-NLS-1$
logWarning("Exception in remote service getProxy", except); //$NON-NLS-1$
throw except;
} catch (final NoClassDefFoundError e) {
ECFException except = new ECFException("Failed to load proxy interface class", e); //$NON-NLS-1$
logWarning("Could not load class for getProxy", except); //$NON-NLS-1$
throw except;
}
}
class ProxyClassLoader extends ClassLoader {
private ClassLoader cl;
public ProxyClassLoader(ClassLoader cl) {
this.cl = cl;
}
public Class loadClass(String name) throws ClassNotFoundException {
try {
return cl.loadClass(name);
} catch (ClassNotFoundException e) {
// If the classloader passed in upon construction cannot
// find the class, then use this bundle's classloader to
// try to load the class
Activator a = Activator.getDefault();
if (a == null)
throw e;
BundleContext context = a.getContext();
if (context == null)
throw e;
return context.getBundle().loadClass(name);
}
}
}
/**
* @since 6.0
*/
protected Object createProxy(ClassLoader cl, Class[] classes) {
return Proxy.newProxyInstance(new ProxyClassLoader(cl), classes, this);
}
protected Object createProxy(Class[] classes) {
return createProxy(this.getClass().getClassLoader(), classes);
}
/**
* @since 3.3
*/
protected Class findAsyncRemoteServiceProxyClass(Class c) {
String proxyClassName = convertInterfaceNameToAsyncInterfaceName(c.getName());
try {
return Class.forName(proxyClassName);
} catch (ClassNotFoundException e) {
logInfo("No async remote service interface found with name=" + proxyClassName + " for proxy service class=" + c.getName(), e); //$NON-NLS-1$ //$NON-NLS-2$
return null;
} catch (NoClassDefFoundError e) {
logWarning("Async remote service interface with name=" + proxyClassName + " could not be loaded for proxy service class=" + c.getName(), e); //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
}
/**
* @since 6.0
*/
protected Class findAsyncRemoteServiceProxyClass(ClassLoader cl, Class c) {
String proxyClassName = convertInterfaceNameToAsyncInterfaceName(c.getName());
try {
return Class.forName(proxyClassName, true, cl);
} catch (ClassNotFoundException e) {
//logInfo("No async remote service interface found with name=" + proxyClassName + " for remote service class=" + c.getName(), e); //$NON-NLS-1$ //$NON-NLS-2$
return null;
} catch (NoClassDefFoundError e) {
logWarning("Async remote service interface with name=" + proxyClassName + " could not be loaded for proxy service class=" + c.getName(), e); //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
}
protected String convertInterfaceNameToAsyncInterfaceName(String interfaceName) {
if (interfaceName == null)
return null;
String asyncProxyName = (String) getRemoteServiceReference().getProperty(Constants.SERVICE_ASYNC_RSPROXY_CLASS_ + interfaceName);
if (asyncProxyName != null)
return asyncProxyName;
// If a value has been specified by the ServiceProperty
return interfaceName + IAsyncRemoteServiceProxy.ASYNC_INTERFACE_SUFFIX;
}
protected Object[] getCallParametersForProxyInvoke(String callMethod, Method proxyMethod, Object[] args) {
return args == null ? EMPTY_ARGS : args;
}
protected long getCallTimeoutForProxyInvoke(String callMethod, Method proxyMethod, Object[] args) {
return IRemoteCall.DEFAULT_TIMEOUT;
}
protected String getCallMethodNameForProxyInvoke(Method method, Object[] args) {
return method.getName();
}
protected Object invokeObject(Object proxy, final Method method, final Object[] args) throws Throwable {
if (method.getName().equals("toString")) { //$NON-NLS-1$
final String[] clazzes = getInterfaceClassNames();
String proxyClass = (clazzes.length == 1) ? clazzes[0] : Arrays.asList(clazzes).toString();
return proxyClass + ".proxy@" + getRemoteServiceID(); //$NON-NLS-1$
} else if (method.getName().equals("hashCode")) { //$NON-NLS-1$
return new Integer(hashCode());
} else if (method.getName().equals("equals")) { //$NON-NLS-1$
if (args == null || args.length == 0)
return Boolean.FALSE;
try {
return new Boolean(Proxy.getInvocationHandler(args[0]).equals(this));
} catch (IllegalArgumentException e) {
return Boolean.FALSE;
}
// This handles the use of IRemoteServiceProxy.getRemoteService method
} else if (method.getName().equals("getRemoteService")) { //$NON-NLS-1$
return getRemoteService();
} else if (method.getName().equals("getRemoteServiceReference")) { //$NON-NLS-1$
return getRemoteServiceReference();
}
return null;
}
protected Object invokeSync(IRemoteCall call) throws ECFException {
return callSync(call);
}
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
// methods declared by Object
try {
// If the method is from Class Object, or from IRemoteServiceProxy
// then return result
Object resultObject = invokeObject(proxy, method, args);
if (resultObject != null)
return resultObject;
// If the method's class is a subclass of IAsyncRemoteServiceProxy, then we assume
// that the methods are intended to be invoked asynchronously
if (Arrays.asList(method.getDeclaringClass().getInterfaces()).contains(IAsyncRemoteServiceProxy.class))
return invokeAsync(method, args);
// else call synchronously/block and return result
final String callMethod = getCallMethodNameForProxyInvoke(method, args);
final Object[] callParameters = getCallParametersForProxyInvoke(callMethod, method, args);
final long callTimeout = getCallTimeoutForProxyInvoke(callMethod, method, args);
final IRemoteCall remoteCall = new IRemoteCall() {
public String getMethod() {
return callMethod;
}
public Object[] getParameters() {
return callParameters;
}
public long getTimeout() {
return callTimeout;
}
};
return invokeSync(remoteCall);
} catch (Throwable t) {
if (t instanceof ServiceException)
throw t;
// rethrow as service exception
throw new ServiceException("Service exception on remote service proxy rsid=" + getRemoteServiceID(), ServiceException.REMOTE, t); //$NON-NLS-1$
}
}
/**
* @since 3.3
*/
protected class AsyncArgs {
private IRemoteCallListener listener;
private Object[] args;
public AsyncArgs(IRemoteCallListener listener, Object[] originalArgs) {
this.listener = listener;
if (this.listener != null) {
int asynchArgsLength = originalArgs.length - 1;
this.args = new Object[asynchArgsLength];
System.arraycopy(originalArgs, 0, args, 0, asynchArgsLength);
} else
this.args = originalArgs;
}
public IRemoteCallListener getListener() {
return listener;
}
public Object[] getArgs() {
return args;
}
}
/**
* @since 3.3
*/
protected Object invokeAsync(final Method method, final Object[] args) throws Throwable {
final String invokeMethodName = getAsyncInvokeMethodName(method);
final AsyncArgs asyncArgs = getAsyncArgs(method, args);
IRemoteCallListener listener = asyncArgs.getListener();
IRemoteCall call = new IRemoteCall() {
public String getMethod() {
return invokeMethodName;
}
public Object[] getParameters() {
return asyncArgs.getArgs();
}
public long getTimeout() {
return DEFAULT_TIMEOUT;
}
};
if (listener == null)
return callAsync(call);
callAsync(call, listener);
return null;
}
/**
* @since 3.3
*/
protected AsyncArgs getAsyncArgs(Method method, Object[] args) {
IRemoteCallListener listener = null;
Class returnType = method.getReturnType();
// If the return type is declared to be *anything* except an IFuture, then
// we are expecting the last argument to be an IRemoteCallListener
if (!returnType.equals(IFuture.class)) {
// If the provided args do *not* include an IRemoteCallListener then we have a problem
if (args == null || args.length == 0)
throw new IllegalArgumentException("Async calls must include a IRemoteCallListener instance as the last argument"); //$NON-NLS-1$
// Get the last arg
Object lastArg = args[args.length - 1];
// If it's an IRemoteCallListener implementer directly, then just cast and return
if (lastArg instanceof IRemoteCallListener) {
listener = (IRemoteCallListener) lastArg;
}
// If it's an implementation of IAsyncCallback, then create a new listener based upon
// callback and return
if (lastArg instanceof IAsyncCallback) {
listener = new CallbackRemoteCallListener((IAsyncCallback) lastArg);
}
// If the last are is not an instance of IRemoteCallListener then there is a problem
if (listener == null)
throw new IllegalArgumentException("Last argument must be an instance of IRemoteCallListener"); //$NON-NLS-1$
}
return new AsyncArgs(listener, args);
}
/**
* @since 3.3
*/
protected String getAsyncInvokeMethodName(Method method) {
String methodName = method.getName();
return methodName.endsWith(IAsyncRemoteServiceProxy.ASYNC_METHOD_SUFFIX) ? methodName.substring(0, methodName.length() - IAsyncRemoteServiceProxy.ASYNC_METHOD_SUFFIX.length()) : methodName;
}
private void logInfo(String message, Throwable e) {
Activator a = Activator.getDefault();
if (a != null)
a.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, message, e));
}
protected void logWarning(String string, Throwable e) {
Activator a = Activator.getDefault();
if (a != null)
a.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, string, e));
}
}