/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.communication.core.factory; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.log.LogService; import org.eclipse.equinox.log.Logger; import org.eclipse.riena.communication.core.IRemoteServiceReference; import org.eclipse.riena.communication.core.IRemoteServiceRegistration; import org.eclipse.riena.communication.core.IRemoteServiceRegistry; import org.eclipse.riena.communication.core.RemoteFailure; import org.eclipse.riena.communication.core.RemoteServiceDescription; import org.eclipse.riena.core.Log4r; import org.eclipse.riena.core.RienaStatus; import org.eclipse.riena.core.util.Iter; import org.eclipse.riena.core.util.Orderer; import org.eclipse.riena.core.wire.InjectExtension; import org.eclipse.riena.core.wire.InjectService; import org.eclipse.riena.internal.communication.core.Activator; import org.eclipse.riena.internal.communication.core.factory.CallHooksProxy; import org.eclipse.riena.internal.communication.core.factory.ICallInterceptorExtension; import org.eclipse.riena.internal.communication.core.factory.IRemoteServiceFactoryExtension; /** * The IRemoteServiceFactory creates a {@link IRemoteServiceReference} for given * service end point description. The IRemoteServiceReference holds a * serviceInstance reference instance to the service end point. The * RemoteService Factory can register an IRemoteServiceReference into the * {@link IRemoteServiceRegistry}. To create a {@link IRemoteServiceReference} * call {@link #createProxy(..)}. To create and register a "remote" OSGi Service * in one step call {@link #createAndRegisterProxy(..)} * <p> * The RemoteServiceFactory does not create a IRemoteServiceReference itself. * This is delegated to protocol specific implementations of * {@code IRemoteServiceFactory}. These implementations are defined with the * extension point "remoteservicefactory". * <p> * This RemoteServiceFactory does nothing if no protocol specific * IRemoteServiceFactory is available. * <p> * <b>NOTE</b><br> * The Riena communication bundle content includes generic class loading and * object instantiation or delegates this behavior to other Riena communication * bundles. Riena supports Eclipse-BuddyPolicy concept. * * @noinstantiate * * @see <a * href="http://wiki.eclipse.org/Riena_Getting_started_remoteservices">Riena * Wiki</a> */ public class RemoteServiceFactory { private IRemoteServiceRegistry registry; private HashMap<String, IRemoteServiceFactory> remoteServiceFactoryImplementations = null; private ICallInterceptorExtension[] callInterceptorExtensions; private Map<Class<?>, List<ICallInterceptorExtension>> callInterceptorsRaw; private Map<Class<?>, Iterable<Class<?>>> callInterceptors; private boolean callInterceptorsReified; private static final Logger LOGGER = Log4r.getLogger(Activator.getDefault(), RemoteServiceFactory.class); /** * This class should only be used via the {@code Companion}. * <p> * Creates a RemoteServiceFactory instance with the default bundle context. * Prerequisite: application bundle should registered as * Eclipse-RegisterBuddy. Sample Manifest.mf of foo.myapplication.api: * * Eclipse-RegisterBuddy: org.eclipse.riena.communication.core * */ protected RemoteServiceFactory() { // prevent direct usage - almost! Sub classing should be allowed } @InjectService(useRanking = true) public void bind(final IRemoteServiceRegistry registryParm) { registry = registryParm; } public void unbind(final IRemoteServiceRegistry registryParm) { if (registry == registryParm) { registry = null; } } /** * @since 4.0 */ @InjectExtension public void update(final IRemoteServiceFactoryExtension[] factories) { remoteServiceFactoryImplementations = new HashMap<String, IRemoteServiceFactory>(); for (final IRemoteServiceFactoryExtension factory : factories) { remoteServiceFactoryImplementations.put(factory.getProtocol(), factory.createRemoteServiceFactory()); } } /** * Creates and registers a protocol specific remote service reference and * registers the reference into the {@link IRemoteServiceRegistry}. A * registered reference becomes automatically registered as "remote" OSGi * Service within the local OSGi container. Answers the registration object * for the reference. If no protocol specific {@link IRemoteServiceFactory} * OSGI Service available answers <code>null</code>.<br> * <p> * * @param interfaceClass * the interface of the OSGi Service * @param url * the URL of the remote service location * @param protocol * the used protocol * @param context * the context in which the proxy is registered * @return the registration object or <code>null</code> */ public IRemoteServiceRegistration createAndRegisterProxy(final Class<?> interfaceClass, final String url, final String protocol, final BundleContext context) { final RemoteServiceDescription rsd = createDescription(interfaceClass, url, protocol, context.getBundle()); return createAndRegisterProxy(rsd, context); } /** * Creates and registers a protocol specific remote service reference and * registers the reference into the {@link IRemoteServiceRegistry}. A * registered reference becomes automatically registered as "remote" OSGi * Service within the local OSGi container. Answers the registration object * for the reference. If no protocol specific {@link IRemoteServiceFactory} * OSGI Service available answers <code>null</code>.<br> * <p> * The hostId identifies who is responsible for this remote service * registration * * @param rsDesc * the remote service description with all the metadata about the * remote service * @param context * the context in which the proxy is registered * @return the registration object or <code>null</code> */ public IRemoteServiceRegistration createAndRegisterProxy(final RemoteServiceDescription rsDesc, final BundleContext context) { // create serviceInstance first IRemoteServiceReference rsRef = createProxy(rsDesc); if (rsRef == null) { rsRef = createLazyProxy(rsDesc); } if (rsRef == null) { LOGGER.log(LogService.LOG_ERROR, "could not create serviceInstance (neither serviceInstance nor lazy serviceInstance) for " //$NON-NLS-1$ + rsDesc); return null; } // register directly if (registry != null) { final IRemoteServiceRegistration reg = registry.registerService(rsRef, context); return reg; } return null; } /** * Creates a protocol specific serviceInstance reference * (IRemoteServiceReference) with given end point parameters. Answers the * IRemoteServiceReference. If no protocol specific * {@link IRemoteServiceFactory} OSGI Service available answers * <code>null</code>. * * @param interfaceClass * @param url * @param protocol * @return the serviceInstance references or <code>null</code> * @since 3.0 */ public IRemoteServiceReference createProxy(final Class<?> interfaceClass, final String url, final String protocol, final BundleContext context) { return createProxy(createDescription(interfaceClass, url, protocol, context.getBundle())); } private RemoteServiceDescription createDescription(final Class<?> interfaceClass, final String url, final String protocol, final Bundle bundle) { return new RemoteServiceDescription(interfaceClass, url, protocol, bundle); } /** * Creates a protocol specific IRemoteServcieReference for the given end * point description. Answers the IRemoteServiceReference. If no end point * specific {@link IRemoteServiceFactory} OSGI Service available answers * <code>null</code>. * * @param rsd * @return the serviceInstance references or <code>null</code> */ public IRemoteServiceReference createProxy(final RemoteServiceDescription rsd) { if (!RienaStatus.isActive()) { LOGGER.log(LogService.LOG_WARNING, "riena.core is not started. This may probably not work."); //$NON-NLS-1$ } if (rsd.getProtocol() == null) { return null; } // find a factory for this specific protocol final IRemoteServiceFactory factory = remoteServiceFactoryImplementations.get(rsd.getProtocol()); // could not get instance for existing reference if (factory == null) { LOGGER.log(LogService.LOG_WARNING, "no IRemoteServiceFactory extension available protocol [" //$NON-NLS-1$ + rsd.getProtocol() + "] id [" + rsd.getServiceInterfaceClassName() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ return null; } LOGGER.log(LogService.LOG_INFO, "found protocol [" + rsd.getProtocol() + "] " + factory); //$NON-NLS-1$ //$NON-NLS-2$ // ask factory to create a serviceInstance for me, and intercept the // calls with a CallHooksProxy instance final IRemoteServiceReference rsr = factory.createProxy(rsd); final CallHooksProxy callHooksProxy = new CallHooksProxy(rsr.getServiceInstance()); callHooksProxy.setRemoteServiceDescription(rsd); callHooksProxy.setMessageContextAccessor(factory.getMessageContextAccessor()); final Object serviceProxy = Proxy.newProxyInstance(rsd.getServiceInterfaceClass().getClassLoader(), new Class[] { rsd.getServiceInterfaceClass() }, callHooksProxy); rsr.setServiceInstance(createInterceptorChain(rsd.getServiceInterfaceClass(), serviceProxy)); return rsr; } private Object createInterceptorChain(final Class<?> serviceInterface, final Object serviceProxy) { Object delegate = serviceProxy; for (final Class<?> interceptorClass : getCallInterceptors(serviceInterface)) { delegate = createInterceptor(serviceInterface, interceptorClass, delegate); } return delegate; } private Object createInterceptor(final Class<?> serviceInterface, final Class<?> interceptorClass, final Object delegate) { try { if (!serviceInterface.isAssignableFrom(interceptorClass)) { throw new RemoteFailure("Could not create call-interceptor class " + interceptorClass.getName() //$NON-NLS-1$ + " because it does not implement " + serviceInterface.getName()); //$NON-NLS-1$ } final Constructor<?> constructor = interceptorClass.getConstructor(serviceInterface); final Object interceptor = constructor.newInstance(delegate); return interceptor; } catch (final NoSuchMethodException e) { throw new RemoteFailure("Could not create call-interceptor class " + interceptorClass.getName() //$NON-NLS-1$ + " because it does not have a public constructor with a single " + serviceInterface.getName() //$NON-NLS-1$ + " parameter.", e); //$NON-NLS-1$ } catch (final Exception e) { throw new RemoteFailure("Could not create call-interceptor class " + interceptorClass.getName(), e); //$NON-NLS-1$ } } // Note: This method might get called recursively. This can happen due to the class loading when loading the interceptor class. // This can than activate another bundle which also registers a remote service and thus enters this method again. private synchronized Iterable<Class<?>> getCallInterceptors(final Class<?> serviceInterface) { if (!callInterceptorsReified) { if (callInterceptorsRaw == null) { // create a new map and fill it only if necessary, i.e. when extension points have changed (see methods comment) callInterceptorsRaw = new HashMap<Class<?>, List<ICallInterceptorExtension>>(); for (final ICallInterceptorExtension callInterceptorExtension : callInterceptorExtensions) { List<ICallInterceptorExtension> list = callInterceptorsRaw.get(callInterceptorExtension .getServiceInterface()); if (list == null) { list = new ArrayList<ICallInterceptorExtension>(); callInterceptorsRaw.put(callInterceptorExtension.getServiceInterface(), list); } list.add(callInterceptorExtension); } } if (callInterceptors == null) { // create a new map only if necessary, i.e. when extension points have changed (see methods comment) callInterceptors = new HashMap<Class<?>, Iterable<Class<?>>>(); } for (final Entry<Class<?>, List<ICallInterceptorExtension>> entry : callInterceptorsRaw.entrySet()) { if (!callInterceptors.containsKey(entry.getKey())) { final Orderer<Class<?>> orderer = new Orderer<Class<?>>(); for (final ICallInterceptorExtension extension : entry.getValue()) { orderer.add(extension.getCallInterceptorClass(), extension.getName(), extension.getPreInterceptors(), extension.getPostInterceptors()); // extension.getCallInterceptorClass() might cause a recursion! } callInterceptors.put(entry.getKey(), Iter.ableReverse(orderer.getOrderedObjects())); } } callInterceptorsReified = true; } return Iter.able(callInterceptors.get(serviceInterface)); } /** * @since 4.0 */ @InjectExtension public synchronized void update(final ICallInterceptorExtension[] callInterceptorExtensions) { this.callInterceptorExtensions = callInterceptorExtensions; callInterceptors = null; callInterceptorsRaw = null; callInterceptorsReified = false; } private IRemoteServiceReference createLazyProxy(final RemoteServiceDescription rsd) { final Class<?> serviceClass = rsd.getServiceInterfaceClass(); if (serviceClass == null) { LOGGER.log(LogService.LOG_ERROR, "Could not load service interface class '" //$NON-NLS-1$ + rsd.getServiceInterfaceClassName() + "'."); //$NON-NLS-1$ return null; } final LazyProxyHandler lazyProxyHandler = new LazyProxyHandler(rsd); final Object serviceInstance = Proxy.newProxyInstance(rsd.getServiceClassLoader(), new Class[] { serviceClass }, lazyProxyHandler); final LazyRemoteServiceReference ref = new LazyRemoteServiceReference(serviceInstance, rsd.getServiceInterfaceClassName(), rsd); lazyProxyHandler.setLazyRemoteServiceReference(ref); return ref; } /** * Load class types for the given intefaceClassName. * * @param interfaceClassName * @return the class type for the given class Name * * @throws ClassNotFoundException */ public Class<?> loadClass(final String interfaceClassName) throws ClassNotFoundException { return getClass().getClassLoader().loadClass(interfaceClassName); } private class LazyProxyHandler implements InvocationHandler { private InvocationHandler delegateHandler; private final RemoteServiceDescription rsd; private LazyRemoteServiceReference lazyRemoteServiceReference; protected LazyProxyHandler(final RemoteServiceDescription rsd) { super(); this.rsd = rsd; } public void setLazyRemoteServiceReference(final LazyRemoteServiceReference ref) { this.lazyRemoteServiceReference = ref; } public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (delegateHandler == null) { final IRemoteServiceReference ref = createProxy(rsd); if (ref == null) { throw new RuntimeException("LazyProxy: missing IRemoteServiceFactory to create proxy for " //$NON-NLS-1$ + "protocol=" + rsd.getProtocol() + " url=" + rsd.getURL() + " interface=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + rsd.getServiceInterfaceClassName()); } final Object proxyInstance = ref.getServiceInstance(); delegateHandler = Proxy.getInvocationHandler(proxyInstance); lazyRemoteServiceReference.setDelegateRef(ref); } return delegateHandler.invoke(proxy, method, args); } } private static class LazyRemoteServiceReference implements IRemoteServiceReference { private final Object serviceInstance; private final String serviceClass; private IRemoteServiceReference delegateReference; private BundleContext tempBundleContext; private final RemoteServiceDescription rsd; private ServiceRegistration serviceRegistration; protected LazyRemoteServiceReference(final Object serviceInstance, final String serviceClass, final RemoteServiceDescription rsd) { super(); this.serviceInstance = serviceInstance; this.serviceClass = serviceClass; this.rsd = rsd; } private void setDelegateRef(final IRemoteServiceReference delegateRef) { this.delegateReference = delegateRef; if (tempBundleContext != null) { delegateRef.setContext(tempBundleContext); } } public void dispose() { if (delegateReference != null) { delegateReference.dispose(); } } @Override public boolean equals(final Object obj) { if (delegateReference != null) { return delegateReference.equals(obj); } return false; } public RemoteServiceDescription getDescription() { if (delegateReference == null) { return rsd; } return delegateReference.getDescription(); } public BundleContext getContext() { if (delegateReference == null) { return tempBundleContext; } return delegateReference.getContext(); } public Object getServiceInstance() { if (delegateReference == null) { return serviceInstance; } return delegateReference.getServiceInstance(); } public String getServiceInterfaceClassName() { if (delegateReference == null) { return serviceClass; } return delegateReference.getServiceInterfaceClassName(); } public ServiceRegistration getServiceRegistration() { return serviceRegistration; } public String getURL() { if (delegateReference != null) { return delegateReference.getURL(); } return null; } @Override public int hashCode() { if (delegateReference != null) { return delegateReference.hashCode(); } return this.getClass().hashCode(); } public void setContext(final BundleContext context) { if (delegateReference == null) { tempBundleContext = context; } else { delegateReference.setContext(context); } } public void setServiceInstance(final Object serviceInstance) { if (delegateReference != null) { delegateReference.setServiceInstance(serviceInstance); } else { throw new RuntimeException( "trying to set serviceInstance for lazyRemoteServiceReference with no delegate"); //$NON-NLS-1$ } } public void setServiceRegistration(final ServiceRegistration serviceRegistration) { this.serviceRegistration = serviceRegistration; } @Override public String toString() { if (delegateReference != null) { return delegateReference.toString(); } String symbolicName = "no context"; //$NON-NLS-1$ if (tempBundleContext != null) { symbolicName = tempBundleContext.getBundle().getSymbolicName(); } return "(lazyreference) context for bundle=" + symbolicName + ", end point=(" + getDescription() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } }