summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Liebig2008-02-11 09:06:34 (EST)
committerStefan Liebig2008-02-11 09:06:34 (EST)
commitbdfb8c4b0ee270b9caac72641373721e130797dc (patch)
tree39006cd4d7d9bf168bf1c7ccf78ae7c74c8784fe
parent926d7a78498d98668fb75caea163c8e4b8214ffd (diff)
downloadorg.eclipse.riena-bdfb8c4b0ee270b9caac72641373721e130797dc.zip
org.eclipse.riena-bdfb8c4b0ee270b9caac72641373721e130797dc.tar.gz
org.eclipse.riena-bdfb8c4b0ee270b9caac72641373721e130797dc.tar.bz2
new service injector
-rw-r--r--org.eclipse.riena.core/src/org/eclipse/riena/core/service/Injector.java299
-rw-r--r--org.eclipse.riena.core/src/org/eclipse/riena/core/service/ServiceId.java136
2 files changed, 435 insertions, 0 deletions
diff --git a/org.eclipse.riena.core/src/org/eclipse/riena/core/service/Injector.java b/org.eclipse.riena.core/src/org/eclipse/riena/core/service/Injector.java
new file mode 100644
index 0000000..e450e07
--- /dev/null
+++ b/org.eclipse.riena.core/src/org/eclipse/riena/core/service/Injector.java
@@ -0,0 +1,299 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 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.core.service;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.riena.core.util.PropertiesUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The is the service injector. See {@link ServiceId} for explanation and usage.
+ */
+public class Injector {
+
+ /**
+ * Default ´bind´ method name.
+ */
+ public static final String DEFAULT_BIND_METHOD_NAME = "bind";
+
+ /**
+ * Default ´unbind´ method name.
+ */
+ public static final String DEFAULT_UNBIND_METHOD_NAME = "unbind";
+
+ private ServiceId serviceId;
+ private Object target;
+ private String bindMethodName = null;
+ private String unbindMethodName = null;
+ private boolean started = false;
+ private BundleContext context = null;
+ private List<ServiceReference> trackedServiceRefs = null;
+ private ServiceListener serviceListner;
+
+ private RuntimeException lastError;
+
+ /**
+ * Constructor for the <code>injectInto()</code> of <code>ServiceId</code>.
+ *
+ * @param serviceId
+ * @param target
+ */
+ Injector(ServiceId serviceId, Object target) {
+ this.serviceId = serviceId;
+ this.target = target;
+ }
+
+ /**
+ * Start the binding/un-binding/tracking for the target.
+ *
+ * @throws IllegalStateException
+ * if injector has already been started.
+ * @throws IllegalArgumentException
+ * if the bind/un-bind methods are wrong
+ * @param context
+ * @return this injector
+ */
+ public Injector start(BundleContext context) {
+ if (started)
+ throw new IllegalStateException("Injector already started!");
+ started = true;
+ lastError = null;
+ this.context = context;
+ if (bindMethodName == null)
+ bindMethodName = DEFAULT_BIND_METHOD_NAME;
+ assertMethod("Bind method", bindMethodName);
+ if (unbindMethodName == null)
+ unbindMethodName = DEFAULT_UNBIND_METHOD_NAME;
+ assertMethod("Unbind method", unbindMethodName);
+ serviceListner = new InjectorServiceListener();
+ trackedServiceRefs = new ArrayList<ServiceReference>(1);
+ start();
+ if (lastError != null)
+ throw lastError;
+ return this;
+ }
+
+ /**
+ * Stops tracking the specified service
+ */
+ public void stop() {
+ if (!started)
+ return;
+ context.removeServiceListener(serviceListner);
+
+ // copy list to array so that I iterate through array and still
+ // remove entries from List concurrently
+ ServiceReference[] serviceRefs;
+ synchronized (trackedServiceRefs) {
+ serviceRefs = trackedServiceRefs.toArray(new ServiceReference[trackedServiceRefs.size()]);
+ }
+ for (ServiceReference serviceRef : serviceRefs)
+ unbind(serviceRef);
+ started = false;
+ }
+
+ /**
+ * Specify the bind method. If not specified
+ * {@link #DEFAULT_BIND_METHOD_NAME} will be used.
+ *
+ * @throws IllegalStateException
+ * if the the injector had already been started
+ * @param bindMethodName
+ * @return this injector
+ */
+ public Injector bind(String bindMethodName) {
+ if (started)
+ throw new IllegalStateException("Injector already started!");
+ this.bindMethodName = bindMethodName;
+ return this;
+ }
+
+ /**
+ * Specify the un-bind method. If not specified
+ * {@link #DEFAULT_UNBIND_METHOD_NAME} will be used.
+ *
+ * @throws IllegalStateException
+ * if the the injector had already been started
+ * @param unbindMethodName
+ * @return this injector
+ */
+ public synchronized Injector unbind(String unbindMethodName) {
+ if (started)
+ throw new IllegalStateException("Injector already started!");
+ this.unbindMethodName = unbindMethodName;
+ return this;
+ }
+
+ public Throwable getLastError() {
+ return lastError;
+ }
+
+ private void assertMethod(String message, String methodName) {
+ Method[] methods = target.getClass().getMethods();
+ for (Method method : methods)
+ if (method.getName().equals(methodName))
+ return;
+
+ throw new IllegalArgumentException(message + " '" + methodName + "' does not exist in target class '"
+ + target.getClass().getName());
+ }
+
+ /**
+ * Starts to track the specified OSGi Service
+ */
+ private void start() {
+ try {
+ ServiceReference[] serviceRefs = null;
+ // try to find the service initially
+ if (serviceId.usesRanking()) {
+ ServiceReference serviceRef = context.getServiceReference(serviceId.getServiceId());
+ if (serviceRef != null)
+ serviceRefs = new ServiceReference[] { serviceRef };
+ } else
+ serviceRefs = context.getServiceReferences(serviceId.getServiceId(), serviceId.getFilter());
+
+ // add an service listener for register or unregister
+ // register the service listener before we go through the reference
+ // list since its very more likely that no service is registered
+ // between getServiceReferences and addServiceListener
+ context.addServiceListener(serviceListner, serviceId.getFilter());
+ // then go through the list of references
+ if (serviceRefs != null)
+ for (ServiceReference serviceRef : serviceRefs)
+ bind(serviceRef);
+ } catch (InvalidSyntaxException e) {
+ e.printStackTrace();
+ }
+ started = true;
+ }
+
+ protected void handleEvent(ServiceEvent event) {
+ switch (event.getType()) {
+ case ServiceEvent.REGISTERED:
+ bind(event.getServiceReference());
+ break;
+ case ServiceEvent.UNREGISTERING:
+ unbind(event.getServiceReference());
+ break;
+ }
+ }
+
+ private void bind(ServiceReference serviceRef) {
+ synchronized (trackedServiceRefs) {
+ if (trackedServiceRefs.contains(serviceRef))
+ return;
+
+ Object service = context.getService(serviceRef);
+ if (service != null) {
+ Method method = findMatchingMethod(target.getClass(), bindMethodName, service.getClass());
+ invoke(method, service);
+ }
+ trackedServiceRefs.add(serviceRef);
+ }
+ }
+
+ private void unbind(ServiceReference serviceRef) {
+ synchronized (trackedServiceRefs) {
+ if (!trackedServiceRefs.contains(serviceRef))
+ return;
+
+ Object service = context.getService(serviceRef);
+ if (service != null) {
+ Method method = findMatchingMethod(target.getClass(), unbindMethodName, service.getClass());
+ invoke(method, service);
+ }
+ trackedServiceRefs.remove(serviceRef);
+ context.ungetService(serviceRef);
+ }
+ }
+
+ /**
+ * @param method
+ * @param service
+ */
+ private void invoke(Method method, Object service) {
+ if (method == null) {
+ lastError = new IllegalArgumentException("Bind/Unbind method '" + method
+ + "' does not exist in target class '" + target.getClass().getName());
+ // TODO logging
+ return;
+ }
+ try {
+ method.invoke(target, service);
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static Method findMatchingMethod(Class<?> targetType, String methodName, Class<?> parameterType) {
+ assert targetType != null;
+ assert methodName != null;
+ assert parameterType != null;
+
+ List<Method> targetedMethods = new ArrayList<Method>(1);
+ Method[] methods = targetType.getMethods();
+ for (Method method : methods) {
+ if (!method.getName().equals(methodName))
+ continue;
+
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ if (parameterTypes.length != 1)
+ continue;
+
+ if (parameterTypes[0].isAssignableFrom(parameterType))
+ targetedMethods.add(method);
+ }
+
+ if (targetedMethods.size() == 1)
+ return targetedMethods.get(0);
+
+ if (targetedMethods.isEmpty())
+ return null;
+
+ // find most specific method
+ Class<?> superType;
+ for (Method method : targetedMethods)
+ while ((superType = parameterType.getSuperclass()) != null)
+ if (!method.getParameterTypes()[0].isAssignableFrom(superType))
+ return method;
+
+ return targetedMethods.get(0);
+ }
+
+ class InjectorServiceListener implements ServiceListener {
+ public void serviceChanged(ServiceEvent event) {
+ if (serviceId != null) {
+ Object property = event.getServiceReference().getProperty("objectClass");
+ String objectClass = PropertiesUtils.accessProperty(property, null);
+ // if serviceId set than only handle this event if id is equal.
+ if (objectClass.equals(serviceId.getServiceId()))
+ handleEvent(event);
+ } else
+ // if no serviceId set handle any event
+ handleEvent(event);
+ }
+ }
+
+}
diff --git a/org.eclipse.riena.core/src/org/eclipse/riena/core/service/ServiceId.java b/org.eclipse.riena.core/src/org/eclipse/riena/core/service/ServiceId.java
new file mode 100644
index 0000000..ff60161
--- /dev/null
+++ b/org.eclipse.riena.core/src/org/eclipse/riena/core/service/ServiceId.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 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.core.service;
+
+/**
+ * ServiceId and Injector simplify finding of OSGi Services and injects them
+ * into a target object. To do so the Injector contains a service tracker
+ * listening to appearing and disappearing services and injects them into the
+ * target. A target object defines named and typed bind and unbind methods. The
+ * Injector calls the bind method when the specified service was registered or
+ * modified. Injector calls the unbind method when the specified service becomes
+ * unregistered.
+ * <p>
+ * The service injector tracks the specified OSGi Service after calls
+ * {@link #start()} and stop tracks after calls {@link #stop()}.
+ * <p>
+ * The ServiceId and Injector are implemented as a ´fluent interface´ allowing
+ * constructs like:
+ * <ol>
+ * <li>new ServiceId("id1").injectInto(target).start(context)</li>
+ * <li>new
+ * ServiceId("id2").useFilter(filter).injectInto(target).bind("register").unbind("unregister").start(context)</li>
+ * <li>new
+ * ServiceId("id3").useRanking().injectInto(target).bind("register").unbind("unregister").start(context)</li>
+ * <li>..</li>
+ * </ol>
+ * <p>
+ * This fluent interface make a few presumptions (defaults) that makes writing
+ * service injectors short and expressive , e.g. number one the list, means use
+ * no service ranking, no filter, and the bind method name is "bind" and the
+ * un-bind method name is "unbind".
+ * <p>
+ * A service or services may be injected into the target by either specifying
+ * <code>useRanking</code> or by specifying a filter expression with
+ * <code>userFilter("filter")</code>.
+ * <p>
+ * The bind and un-bind method that get called by the injector can be
+ * polymorphic, i.e. the target can have multiple bind/un-bind methods with the
+ * same name but with different signatures. The injector takes responsibility
+ * for choosing the appropriate methods.
+ */
+public class ServiceId {
+
+ /**
+ * Default service ranking for all riena services.
+ */
+ public final static Integer DEFAULT_RANKING = -100;
+
+ private String serviceId;
+ private boolean ranking;
+ private String filter;
+
+ public ServiceId(String serviceId) {
+ this.serviceId = serviceId;
+ }
+
+ /**
+ * Use filtering {@link org.osgi.framework.Filter} for picking the tracked
+ * service(s) to inject.
+ *
+ * @throws IllegalStateException
+ * if a filter has already been set or if ranking is activated
+ * @return this service id
+ */
+ public ServiceId useFilter(String filter) {
+ assertState();
+ this.filter = filter;
+ return this;
+ }
+
+ /**
+ * Use service ranking {@link org.osgi.framework.Constants#SERVICE_RANKING}
+ * picking the tracked service to inject.
+ *
+ * @throws IllegalStateException
+ * if ranking is already activated or a filter has been set
+ * @return this service id
+ */
+ public ServiceId useRanking() {
+ assertState();
+ ranking = true;
+ return this;
+ }
+
+ /**
+ * Inject this service id into the specified target.
+ *
+ * @param target
+ * @throws IllegalArgumentException
+ * on target == null
+ * @return the injector responsible for tracking this service id
+ */
+ public Injector injectInto(Object target) {
+ if (target == null)
+ throw new IllegalArgumentException("target may not be null.");
+
+ return new Injector(this, target);
+ }
+
+ private void assertState() {
+ if (filter != null)
+ throw new IllegalStateException("Filter is already set!");
+ if (ranking)
+ throw new IllegalStateException("Ranking is already set!");
+ }
+
+ /**
+ * @return
+ */
+ String getServiceId() {
+ return serviceId;
+ }
+
+ /**
+ * @return the useRanking
+ */
+ boolean usesRanking() {
+ return ranking;
+ }
+
+ /**
+ * @return the filter
+ */
+ String getFilter() {
+ return filter;
+ }
+
+}