added support for temporal service dependency annotations

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@902651 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/api/TemporalServiceDependency.java b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/api/TemporalServiceDependency.java
new file mode 100644
index 0000000..9d75d2b
--- /dev/null
+++ b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/api/TemporalServiceDependency.java
@@ -0,0 +1,91 @@
+package org.apache.felix.dm.annotation.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a method or a field for injecting a Temporal Service Dependency.
+ * A Temporal Service dependency can block the caller thread between service updates. Only
+ * useful for required stateless dependencies that can be replaced transparently.
+ * A Dynamic Proxy is used to wrap the actual service dependency. When the dependency goes 
+ * away, an attempt is made to replace it with another one which satisfies the service dependency 
+ * criteria. If no service replacement is available, then any method invocation (through the 
+ * dynamic proxy) will block during a configurable timeout. On timeout, an unchecked 
+ * <code>IllegalStateException</code> exception is raised (but the service is not deactivated).<p>
+ * 
+ * <b>Only supports required dependencies</b>
+ *
+ * <p> Sample Code:<p>
+ * <blockquote>
+ * 
+ * <pre>
+ * class MyServer implements Runnable {
+ *   @TemporalServiceDependency(timeout=15000)
+ *   MyDependency _dependency;.
+ *   
+ *   @Start
+ *   void start() {
+ *     (new Thread(this)).start();
+ *   }
+ *   
+ *   public void run() {
+ *     try {
+ *       _dependency.doWork();
+ *     } catch (IllegalStateException e) {
+ *       t.printStackTrace();
+ *     }
+ *   }   
+ * </pre>
+ * 
+ * </blockquote>
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target( { ElementType.METHOD, ElementType.FIELD })
+public @interface TemporalServiceDependency
+{
+    /**
+     * Sets the timeout for this temporal dependency. Specifying a timeout value of zero means that there is no timeout period,
+     * and an invocation on a missing service will fail immediately.
+     * 
+     * @param timeout the dependency timeout value greater or equals to 0
+     * @throws IllegalArgumentException if the timeout is negative
+     * @return this temporal dependency
+     */
+    long timeout() default 30000L;
+
+    /**
+     * Returns the Service dependency type (by default, the type is method parameter type).
+     * @return the Service dependency type.
+     */
+    Class<?> service() default Object.class;
+
+    /**
+     * Returns the Service dependency OSGi filter.
+     * @return The Service dependency filter.
+     */
+    String filter() default "";
+
+    /**
+     * Returns the class for the default implementation, if the dependency is not available.
+     * @return The default class used when the dependency is not available.
+     */
+    Class<?> defaultImpl() default Object.class;
+
+    /**
+     * Returns the callback method to be invoked when the service is available. This attribute is only meaningful when 
+     * the annotation is applied on a class field.
+     */
+    String added() default "";
+
+    /**
+     * Returns the callback method to be invoked when the service properties have changed.
+     */
+    String changed() default "";
+
+    /**
+     * Returns the callback method to invoke when the service is lost.
+     */
+    String removed() default "";
+}
diff --git a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
index 6455c85..02e3f4f 100644
--- a/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
+++ b/dependencymanager/annotation/src/main/java/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
@@ -37,6 +37,7 @@
 import org.apache.felix.dm.annotation.api.ServiceDependency;
 import org.apache.felix.dm.annotation.api.Start;
 import org.apache.felix.dm.annotation.api.Stop;
+import org.apache.felix.dm.annotation.api.TemporalServiceDependency;
 
 import aQute.lib.osgi.Annotation;
 import aQute.lib.osgi.ClassDataCollector;
@@ -61,6 +62,8 @@
         + ServiceDependency.class.getName().replace('.', '/') + ";";
     private final static String A_CONFIGURATION_DEPENDENCY = "L"
         + ConfigurationDependency.class.getName().replace('.', '/') + ";";
+    private final static String A_TEMPORAL_SERVICE_DEPENDENCY = "L"
+        + TemporalServiceDependency.class.getName().replace('.', '/') + ";";
 
     private Reporter m_reporter;
     private String m_className;
@@ -88,6 +91,7 @@
     enum EntryTypes {
         Service,
         ServiceDependency,
+        TemporalServiceDependency,
         ConfigurationDependency,
     };
     
@@ -113,7 +117,8 @@
         autoConfig,
         pid,
         propagate,
-        updated;
+        updated,
+        timeout
     };
     
     /**
@@ -122,7 +127,7 @@
      */
     private class Info
     {
-        /** The component descriptor entry type (either Service, ServiceDependency, or ConfigurationDependency) */
+        /** The component descriptor entry type: either Service, (Temporal)ServiceDependency, or ConfigurationDependency */
         EntryTypes m_entry;
         
         /** The component descriptor entry parameters (init/start/stop ...) */
@@ -350,11 +355,15 @@
         }
         else if (annotation.getName().equals(A_SERVICE_DEP))
         {
-            parseServiceDependencyAnnotation(annotation);
+            parseServiceDependencyAnnotation(annotation, false);
         }
         else if (annotation.getName().equals(A_CONFIGURATION_DEPENDENCY))
         {
             parseConfigurationDependencyAnnotation(annotation);
+        } 
+        else if (annotation.getName().equals(A_TEMPORAL_SERVICE_DEPENDENCY))
+        {
+            parseServiceDependencyAnnotation(annotation, true);
         }
     }
 
@@ -390,12 +399,12 @@
     }
 
     /**
-     * Parses a ServiceDependency Annotation.
+     * Parses a ServiceDependency or a TemporalServiceDependency Annotation.
      * @param annotation the ServiceDependency Annotation.
      */
-    private void parseServiceDependencyAnnotation(Annotation annotation)
+    private void parseServiceDependencyAnnotation(Annotation annotation, boolean temporal)
     {
-        Info info = new Info(EntryTypes.ServiceDependency);
+        Info info = new Info(temporal ? EntryTypes.TemporalServiceDependency : EntryTypes.ServiceDependency);
         m_infos.add(info);
 
         // service attribute
@@ -434,8 +443,10 @@
         // defaultImpl attribute
         info.addClassParam(annotation, Params.defaultImpl, null);
 
-        // required attribute
-        info.addParam(annotation, Params.required, null);
+        // required attribute (not valid if parsing a temporal service dependency)
+        if (! temporal) {
+            info.addParam(annotation, Params.required, null);
+        }
 
         // added callback
         info.addParam(annotation, Params.added, (!m_isField) ? m_method : null);
@@ -445,6 +456,11 @@
 
         // removed callback
         info.addParam(annotation, Params.removed, null);
+        
+        // timeout attribute (only valid if parsing a temporal service dependency)
+        if (temporal) {
+            info.addParam(annotation, Params.timeout, null);
+        }
     }
 
     /**
diff --git a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/ComponentManager.java b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/ComponentManager.java
index 5806201..cda68ea 100644
--- a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/ComponentManager.java
+++ b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/ComponentManager.java
@@ -31,6 +31,7 @@
 import org.apache.felix.dm.dependencies.ConfigurationDependency;
 import org.apache.felix.dm.dependencies.Dependency;
 import org.apache.felix.dm.dependencies.ServiceDependency;
+import org.apache.felix.dm.dependencies.TemporalServiceDependency;
 import org.apache.felix.dm.service.Service;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -152,7 +153,12 @@
 
                     case ServiceDependency:
                         checkServiceParsed(service);
-                        service.add(createServiceDependency(b, dm, parser));
+                        service.add(createServiceDependency(b, dm, parser, false));
+                        break;
+
+                    case TemporalServiceDependency:
+                        checkServiceParsed(service);
+                        service.add(createServiceDependency(b, dm, parser, true));
                         break;
 
                     case ConfigurationDependency:
@@ -283,13 +289,15 @@
      * @param b
      * @param dm
      * @param parser
+     * @param temporal true if this dependency is a temporal one, false if not.
      * @return
      * @throws ClassNotFoundException
      */
     private ServiceDependency createServiceDependency(Bundle b, DependencyManager dm,
-        DescriptorParser parser) throws ClassNotFoundException
+        DescriptorParser parser, boolean temporal) throws ClassNotFoundException
     {
-        ServiceDependency sd = dm.createServiceDependency();
+        ServiceDependency sd = temporal ? dm.createTemporalServiceDependency()
+            : dm.createServiceDependency();
 
         // Set service with eventual service filter
         String service = parser.getString(DescriptorParam.service);
@@ -305,9 +313,16 @@
             sd.setDefaultImplementation(defaultServiceImplClass);
         }
 
-        // Set required flag
-        String required = parser.getString(DescriptorParam.required, "true");
-        sd.setRequired("true".equals(required));
+        // Set required flag (always true for a temporal dependency)
+        if (temporal)
+        {
+            sd.setRequired(true);
+        }
+        else
+        {
+            String required = parser.getString(DescriptorParam.required, "true");
+            sd.setRequired("true".equals(required));
+        }
 
         // Set bind/unbind/rebind
         String added = parser.getString(DescriptorParam.added, null);
@@ -321,6 +336,16 @@
         {
             sd.setAutoConfig(autoConfigField);
         }
+
+        // Set the timeout value for a temporal service dependency
+        if (temporal)
+        {
+            String timeout = parser.getString(DescriptorParam.timeout, null);
+            if (timeout != null)
+            {
+                ((TemporalServiceDependency) sd).setTimeout(Long.parseLong(timeout));
+            }
+        }
         return sd;
     }
 
@@ -336,14 +361,16 @@
     {
         ConfigurationDependency cd = dm.createConfigurationDependency();
         String pid = parser.getString(DescriptorParam.pid);
-        if (pid == null) {
-            throw new IllegalArgumentException("pid attribute not provided in ConfigurationDependency declaration");
+        if (pid == null)
+        {
+            throw new IllegalArgumentException(
+                "pid attribute not provided in ConfigurationDependency declaration");
         }
         cd.setPid(pid);
-        
+
         String propagate = parser.getString(DescriptorParam.propagate, "false");
         cd.setPropagate("true".equals(propagate));
-        
+
         String callback = parser.getString(DescriptorParam.updated, "updated");
         cd.setCallback(callback);
         return cd;
diff --git a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/DescriptorEntry.java b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/DescriptorEntry.java
index 226799f..56e8f39 100644
--- a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/DescriptorEntry.java
+++ b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/DescriptorEntry.java
@@ -26,5 +26,6 @@
 {
     Service,
     ServiceDependency,
+    TemporalServiceDependency,
     ConfigurationDependency
 }
diff --git a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/DescriptorParam.java b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/DescriptorParam.java
index 9ba1af2..5fd0dc9 100644
--- a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/DescriptorParam.java
+++ b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/DescriptorParam.java
@@ -87,7 +87,10 @@
     propagate,
 
     /* ConfigurationDependency attribute for the updated callback method (the parsed value is a String */
-    updated;
+    updated,
+    
+    /* TemporalServiceDependency attribute for the timeout (the parsed value is a String) */
+    timeout;
     
     /**
      * Indicates if a given attribute is a Service attribute.
@@ -108,6 +111,15 @@
     }
 
     /**
+     * Indicates if a given attribute is a TemporalServiceDependency attribute.
+     * @param attr a Descriptor attribute
+     * @return true if the descriptor is a Temporal Service attribute, false if not
+     */
+    public static boolean isTemporalServiceDepependencyAttribute(DescriptorParam attr) {
+        return serviceDependencyAttribute.contains(attr) || attr == timeout;
+    }
+
+    /**
      * Indicates if a given attribute is a ServiceDependency attribute.
      * @param attr a Descriptor attribute
      * @return true if the descriptor is a Service attribute, false if not