FELIX-1546 added a missing file, and an integration test that validates the working of the temporal dependency

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@883477 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/TemporalServiceDependency.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/TemporalServiceDependency.java
new file mode 100644
index 0000000..d842503
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/TemporalServiceDependency.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless
+ * required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/**
+* A Temporal Service dependency that 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 ServiceUnavailable 
+* exception is raised (but the service is not deactivated).<p>
+* 
+* When an OSGi update takes place, we use the following locking strategy: A Read/Write lock is used
+* to synchronize the updating thread with respect to the other threads which invoke the backed service. 
+* The Updating thread uses an exclusive Write lock and service invokers uses a Read lock. This model
+* allows multiple threads to invoke the backed service concurrently, but the updating thread won't
+* update the dependency if it's currently in use.<p>
+* 
+* <b>This class only supports required dependencies, and temporal dependencies must be accessed outside
+* the Activator (OSGi) thread, because method invocations may block the caller thread when dependencies
+* are not satisfied.
+* </b>
+*
+* <p> Sample Code:<p>
+* <blockquote>
+* 
+* <pre>
+* import org.apache.felix.dependencymanager.*;
+* 
+* public class Activator extends DependencyActivatorBase {
+*   public void init(BundleContext ctx, DependencyManager dm) throws Exception {
+*     dm.add(createService()
+*            .setImplementation(MyServer.class)
+*        .add(createTemporalServiceDependency()
+*            .setService(MyDependency.class)
+*            .setTimeout(15000)));
+*   }
+* 
+*   public void destroy(BundleContext ctx, DependencyManager dm) throws Exception {
+*   }
+* }
+* 
+* class MyServer implements Runnable {
+*   MyDependency _dependency; // Auto-Injected by reflection.
+*   void start() {
+*     (new Thread(this)).start();
+*   }
+*   
+*   public void run() {
+*     try {
+*       _dependency.doWork();
+*     } catch (ServiceUnavailableException e) {
+*       t.printStackTrace();
+*     }
+*   }   
+* </pre>
+* 
+* </blockquote>
+*/
+public class TemporalServiceDependency extends ServiceDependency implements InvocationHandler {
+    // Max millis to wait for service availability.
+    private long m_timeout = 30000;
+
+    /**
+     * Creates a new Temporal Service Dependency.
+     * 
+     * @param context The bundle context of the bundle which is instantiating this dependency object
+     * @param logger the logger our Internal logger for logging events.
+     * @see DependencyActivatorBase#createTemporalServiceDependency()
+     */
+    public TemporalServiceDependency(BundleContext context, Logger logger) {
+        super(context, logger);
+        super.setRequired(true);
+    }
+
+    /**
+     * 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
+     */
+    public TemporalServiceDependency setTimeout(long timeout) {
+        if (timeout < 0) {
+            throw new IllegalArgumentException("Invalid timeout value: " + timeout);
+        }
+        m_timeout = timeout;
+        return this;
+    }
+
+    /**
+     * The ServiceTracker calls us here in order to inform about a service arrival.
+     */
+    public synchronized void addedService(ServiceReference ref, Object service) {
+        if (makeAvailable()) {
+            // So far, our dependency was not satisfied: wrap the service behind our proxy.
+            m_serviceInstance = Proxy.newProxyInstance(m_trackedServiceName.getClassLoader(), new Class[] { m_trackedServiceName }, this);
+            m_service.dependencyAvailable(this); // will invoke "added" callbacks, if any.
+        }
+        else {
+            notifyAll();
+        }
+    }
+
+    /**
+     * The ServiceTracker calls us here when a tracked service properties are modified.
+     */
+    public void modifiedService(ServiceReference ref, Object service) {
+        // We don't care.
+    }
+
+    /**
+     * The ServiceTracker calls us here when a tracked service is lost.
+     */
+    public synchronized void removedService(ServiceReference ref, Object service) {
+        // Unget what we got in addingService (see ServiceTracker 701.4.1)
+        m_context.ungetService(ref);
+    }
+
+    /**
+     * @returns our dependency instance. Unlike in ServiceDependency, we always returns our proxy.
+     */
+    public synchronized Object getService() {
+        return m_serviceInstance;
+    }
+
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        Object service = m_tracker.getService();
+        if (service == null) {
+            synchronized (this) {
+                long start = System.currentTimeMillis();
+                long waitTime = m_timeout;
+                while (service == null) {
+                    if (waitTime <= 0) {
+                        throw new IllegalStateException("Service unavailable: " + m_trackedServiceName.getName());
+                    }
+                    try {
+                        wait(waitTime);
+                    }
+                    catch (InterruptedException e) {
+                        throw new IllegalStateException("Service unavailable: " + m_trackedServiceName.getName());
+                    }
+                    waitTime = m_timeout - (System.currentTimeMillis() - start);
+                    service = m_tracker.getService();
+                }
+            }
+        }
+        method.setAccessible(true);
+        return method.invoke(service, args);
+    }
+}
diff --git a/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/TemporalServiceDependencyTest.java b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/TemporalServiceDependencyTest.java
new file mode 100644
index 0000000..88db3cc
--- /dev/null
+++ b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/TemporalServiceDependencyTest.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager.test;
+
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Logger;
+import org.apache.felix.dependencymanager.Service;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.BundleContext;
+
+@RunWith(JUnit4TestRunner.class)
+public class TemporalServiceDependencyTest {
+    @Configuration
+    public static Option[] configuration() {
+        return options(
+            provision(
+                mavenBundle().groupId("org.apache.felix").artifactId("org.osgi.compendium").versionAsInProject(),
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.dependencymanager").versionAsInProject()
+            )
+        );
+    }    
+    
+    @Test
+    public void testServiceConsumptionAndIntermittentAvailability(BundleContext context) {
+        DependencyManager m = new DependencyManager(context, new Logger(context));
+        // helper class that ensures certain steps get executed in sequence
+        Ensure e = new Ensure();
+        // create a service provider and consumer
+        Service sp = m.createService().setImplementation(new TemporalServiceProvider(e)).setInterface(TemporalServiceInterface.class.getName(), null);
+        Service sp2 = m.createService().setImplementation(new TemporalServiceProvider2(e)).setInterface(TemporalServiceInterface.class.getName(), null);
+        Service sc = m.createService().setImplementation(new TemporalServiceConsumer(e)).add(m.createTemporalServiceDependency().setService(TemporalServiceInterface.class).setRequired(true));
+        // add the service consumer
+        m.add(sc);
+        // now add the first provider
+        m.add(sp);
+        e.waitForStep(2, 1000);
+        // and remove it again (this should not affect the consumer yet)
+        m.remove(sp);
+        // now add the second provider
+        m.add(sp2);
+        e.step(3);
+        e.waitForStep(4, 1000);
+        // and remove it again
+        m.remove(sp2);
+        // finally remove the consumer
+        m.remove(sc);
+        // ensure we executed all steps inside the component instance
+        e.step(6);
+    }
+}
+
+interface TemporalServiceInterface {
+    public void invoke();
+}
+
+class TemporalServiceProvider implements TemporalServiceInterface {
+    private final Ensure m_ensure;
+    public TemporalServiceProvider(Ensure e) {
+        m_ensure = e;
+    }
+    public void invoke() {
+        m_ensure.step(2);
+    }
+}
+
+class TemporalServiceProvider2 implements TemporalServiceInterface {
+    private final Ensure m_ensure;
+    public TemporalServiceProvider2(Ensure e) {
+        m_ensure = e;
+    }
+    public void invoke() {
+        m_ensure.step(4);
+    }
+}
+
+class TemporalServiceConsumer implements Runnable {
+    private volatile TemporalServiceInterface m_service;
+    private final Ensure m_ensure;
+
+    public TemporalServiceConsumer(Ensure e) {
+        m_ensure = e;
+    }
+    
+    public void start() {
+        m_ensure.step(1);
+        Thread t = new Thread(this);
+        t.start();
+    }
+    
+    public void run() {
+        m_service.invoke();
+        m_ensure.waitForStep(3, 1000);
+        m_service.invoke();
+    }
+    
+    public void stop() {
+        m_ensure.step(5);
+    }
+}