Fix FELIX-4231 Provide service binding interceptors
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1523996 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/components/FooConsumer.java b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/components/FooConsumer.java
index 349cb3c..11ce322 100644
--- a/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/components/FooConsumer.java
+++ b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/components/FooConsumer.java
@@ -21,8 +21,10 @@
import org.apache.felix.ipojo.annotations.*;
import org.apache.felix.ipojo.runtime.core.test.services.CheckService;
+import org.apache.felix.ipojo.runtime.core.test.services.Enhanced;
import org.apache.felix.ipojo.runtime.core.test.services.FooService;
+import java.util.Dictionary;
import java.util.Map;
import java.util.Properties;
@@ -33,21 +35,24 @@
@Provides
public class FooConsumer implements CheckService {
- @Requires(id= "foo", policy = "dynamic-priority")
+ @Requires(id= "foo", policy = "dynamic-priority", proxy = false)
private FooService foo;
private Map<String, Object> props;
@Override
public boolean check() {
- return foo != null;
+ return foo.foo();
}
@Override
- public Properties getProps() {
+ public Dictionary getProps() {
Properties properties = new Properties();
properties.put("props", props);
properties.put("grade", foo.getGrade());
+ if (foo instanceof Enhanced) {
+ properties.put("enhanced", ((Enhanced) foo).enhance());
+ }
return properties;
}
diff --git a/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/interceptors/EnhancingBindingInterceptor.java b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/interceptors/EnhancingBindingInterceptor.java
new file mode 100644
index 0000000..69a5382
--- /dev/null
+++ b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/interceptors/EnhancingBindingInterceptor.java
@@ -0,0 +1,77 @@
+/*
+ * 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.ipojo.runtime.core.test.interceptors;
+
+import org.apache.felix.ipojo.annotations.Component;
+import org.apache.felix.ipojo.annotations.Provides;
+import org.apache.felix.ipojo.annotations.ServiceProperty;
+import org.apache.felix.ipojo.dependency.interceptors.DefaultDependencyInterceptor;
+import org.apache.felix.ipojo.dependency.interceptors.ServiceBindingInterceptor;
+import org.apache.felix.ipojo.runtime.core.test.services.Enhanced;
+import org.apache.felix.ipojo.util.DependencyModel;
+import org.osgi.framework.ServiceReference;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * A binding interceptor enhancing the service object.
+ */
+@Component(immediate = true)
+@Provides
+public class EnhancingBindingInterceptor extends DefaultDependencyInterceptor implements ServiceBindingInterceptor {
+
+ @ServiceProperty
+ private String target;
+
+ private HashMap<ServiceReference, Object> deps = new HashMap<ServiceReference, Object>();
+
+ @Override
+ public <S> S getService(DependencyModel dependency, ServiceReference<S> reference, S service) {
+ S proxy = (S) Proxy.newProxyInstance(this.getClass().getClassLoader(),
+ new Class[]{dependency.getSpecification(), Enhanced.class}, new Interceptor(service));
+ deps.put(reference, proxy);
+ return proxy;
+ }
+
+ @Override
+ public <S> void ungetService(DependencyModel dependency, ServiceReference<S> reference) {
+ deps.remove(reference);
+ }
+
+ private class Interceptor implements InvocationHandler {
+
+ private final Object service;
+
+ public Interceptor(Object service) {
+ this.service = service;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getName().equals("enhance")) {
+ return "yo!";
+ }
+ return method.invoke(service, args);
+ }
+ }
+}
diff --git a/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/interceptors/ProxyBindingInterceptor.java b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/interceptors/ProxyBindingInterceptor.java
new file mode 100644
index 0000000..79a623d
--- /dev/null
+++ b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/interceptors/ProxyBindingInterceptor.java
@@ -0,0 +1,100 @@
+/*
+ * 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.ipojo.runtime.core.test.interceptors;
+
+import org.apache.felix.ipojo.annotations.Component;
+import org.apache.felix.ipojo.annotations.Provides;
+import org.apache.felix.ipojo.annotations.ServiceProperty;
+import org.apache.felix.ipojo.dependency.interceptors.DefaultDependencyInterceptor;
+import org.apache.felix.ipojo.dependency.interceptors.ServiceBindingInterceptor;
+import org.apache.felix.ipojo.runtime.core.test.services.CheckService;
+import org.apache.felix.ipojo.util.DependencyModel;
+import org.osgi.framework.ServiceReference;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.*;
+
+/**
+ * A binding interceptor generating a proxy to monitor the invocations.
+ */
+@Component
+@Provides
+public class ProxyBindingInterceptor extends DefaultDependencyInterceptor implements ServiceBindingInterceptor, CheckService {
+
+ @ServiceProperty
+ private String target;
+
+ private HashMap<ServiceReference, Object> deps = new HashMap<ServiceReference, Object>();
+ private Dictionary data = new Hashtable();
+
+
+ private void increment(String key) {
+ if (data.get(key) == null) {
+ data.put(key, 1);
+ } else {
+ data.put(key, (Integer) data.get(key) + 1);
+ }
+ }
+
+ @Override
+ public <S> S getService(DependencyModel dependency, ServiceReference<S> reference, S service) {
+ S proxy = (S) Proxy.newProxyInstance(this.getClass().getClassLoader(),
+ new Class[]{dependency.getSpecification()}, new Interceptor(service));
+ deps.put(reference, proxy);
+ increment("bound");
+ return proxy;
+ }
+
+ @Override
+ public <S> void ungetService(DependencyModel dependency, ServiceReference<S> reference) {
+ deps.remove(reference);
+ increment("unbound");
+ }
+
+ @Override
+ public boolean check() {
+ return true;
+ }
+
+ @Override
+ public Dictionary getProps() {
+ return data;
+ }
+
+ private class Interceptor implements InvocationHandler {
+
+ private final Object service;
+
+ public Interceptor(Object service) {
+ this.service = service;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ increment(method.getName());
+
+ if (method.getName().equals("toString")) {
+ return this.toString();
+ }
+ return method.invoke(service, args);
+ }
+ }
+}
diff --git a/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/services/CheckService.java b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/services/CheckService.java
index 610c62d..2ed95ff 100644
--- a/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/services/CheckService.java
+++ b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/services/CheckService.java
@@ -19,7 +19,7 @@
package org.apache.felix.ipojo.runtime.core.test.services;
-import java.util.Properties;
+import java.util.Dictionary;
public interface CheckService {
@@ -27,6 +27,6 @@
public boolean check();
- public Properties getProps();
+ public Dictionary getProps();
}
diff --git a/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/services/Enhanced.java b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/services/Enhanced.java
new file mode 100644
index 0000000..32333c4
--- /dev/null
+++ b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/main/java/org/apache/felix/ipojo/runtime/core/test/services/Enhanced.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ipojo.runtime.core.test.services;
+
+/**
+ * An interface added on the fly to the service object.
+ */
+public interface Enhanced {
+
+ public String enhance();
+}
diff --git a/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/test/java/org/apache/felix/ipojo/runtime/core/test/dependencies/TestBindingInterceptors.java b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/test/java/org/apache/felix/ipojo/runtime/core/test/dependencies/TestBindingInterceptors.java
new file mode 100644
index 0000000..b20ad3e
--- /dev/null
+++ b/ipojo/runtime/core-it/ipojo-core-service-dependency-interceptor-test/src/test/java/org/apache/felix/ipojo/runtime/core/test/dependencies/TestBindingInterceptors.java
@@ -0,0 +1,177 @@
+/*
+ * 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.ipojo.runtime.core.test.dependencies;
+
+import org.apache.felix.ipojo.ComponentInstance;
+import org.apache.felix.ipojo.runtime.core.test.services.CheckService;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+
+import java.util.Properties;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * Checks binding interceptors.
+ */
+public class TestBindingInterceptors extends Common {
+
+ private ComponentInstance provider;
+
+ @Before
+ public void setUp() {
+ provider = ipojoHelper.createComponentInstance("org.apache.felix.ipojo.runtime.core.test" +
+ ".components.FooProvider");
+ }
+
+ @Test
+ public void testProxyBindingInterceptorBeforeInstanceCreation() {
+ // Create the interceptor
+ Properties configuration = new Properties();
+ configuration.put("target", "(dependency.id=foo)");
+ ipojoHelper.createComponentInstance("org.apache.felix.ipojo.runtime.core.test.interceptors" +
+ ".ProxyBindingInterceptor", configuration);
+
+ // Create the FooConsumer
+ ComponentInstance instance = ipojoHelper.createComponentInstance("org.apache.felix.ipojo.runtime.core.test" +
+ ".components" +
+ ".FooConsumer");
+
+ ServiceReference ref = osgiHelper.waitForService(CheckService.class.getName(),
+ "(instance.name=" + instance.getInstanceName() + ")",
+ 1000, true);
+ CheckService check = (CheckService) osgiHelper.getServiceObject(ref);
+
+ assertThat(check.check());
+
+ // Extract monitored data
+ CheckService checkService = osgiHelper.getServiceObject(CheckService.class,
+ "(factory.name=org.apache.felix.ipojo.runtime.core.test.interceptors.ProxyBindingInterceptor)");
+
+ assertThat(checkService).isNotNull();
+ assertThat(checkService.getProps().get("bound")).isEqualTo(1);
+ assertThat(checkService.getProps().get("foo")).isEqualTo(1);
+
+ provider.dispose();
+
+ assertThat(checkService.getProps().get("bound")).isEqualTo(1);
+ assertThat(checkService.getProps().get("unbound")).isEqualTo(1);
+ }
+
+ /**
+ * Same as previous but the interceptor arrives after the instance.
+ */
+ @Test
+ public void testProxyBindingInterceptorAfterInstanceCreation() {
+ // Create the FooConsumer
+ ComponentInstance instance = ipojoHelper.createComponentInstance("org.apache.felix.ipojo.runtime.core.test" +
+ ".components" +
+ ".FooConsumer");
+
+ ServiceReference ref = osgiHelper.waitForService(CheckService.class.getName(),
+ "(instance.name=" + instance.getInstanceName() + ")",
+ 1000, true);
+ CheckService check = (CheckService) osgiHelper.getServiceObject(ref);
+
+ assertThat(check.check());
+
+ // Create the interceptor
+ Properties configuration = new Properties();
+ configuration.put("target", "(dependency.id=foo)");
+ ipojoHelper.createComponentInstance("org.apache.felix.ipojo.runtime.core.test.interceptors" +
+ ".ProxyBindingInterceptor", configuration);
+
+ // Extract monitored data
+ CheckService checkService = osgiHelper.getServiceObject(CheckService.class,
+ "(factory.name=org.apache.felix.ipojo.runtime.core.test.interceptors.ProxyBindingInterceptor)");
+
+ // Nothing was intercepted.
+ assertThat(checkService).isNotNull();
+ assertThat(checkService.getProps().get("bound")).isNull();
+ assertThat(checkService.getProps().get("foo")).isNull();
+
+ // Force rebinding.
+ provider.stop();
+ provider.start();
+
+ check.check();
+
+ // Things should have been intercepted
+ assertThat(checkService).isNotNull();
+ assertThat(checkService.getProps().get("bound")).isEqualTo(1);
+ assertThat(checkService.getProps().get("foo")).isEqualTo(1);
+
+ provider.dispose();
+
+ // Two unget calls, as we intercepted the first one (provider.stop()).
+ assertThat(checkService.getProps().get("unbound")).isEqualTo(2);
+ }
+
+ /**
+ * Checks that two interceptors are called sequentially.
+ */
+ @Test
+ public void testWithTwoInterceptors() {
+ // First, only one interceptor.
+
+ // Create the interceptor
+ Properties configuration = new Properties();
+ configuration.put("target", "(dependency.id=foo)");
+ ipojoHelper.createComponentInstance("org.apache.felix.ipojo.runtime.core.test.interceptors" +
+ ".ProxyBindingInterceptor", configuration);
+
+
+ // Create the FooConsumer
+ ComponentInstance instance = ipojoHelper.createComponentInstance("org.apache.felix.ipojo.runtime.core.test" +
+ ".components" +
+ ".FooConsumer");
+
+ ServiceReference ref = osgiHelper.waitForService(CheckService.class.getName(),
+ "(instance.name=" + instance.getInstanceName() + ")",
+ 1000, true);
+ CheckService check = (CheckService) osgiHelper.getServiceObject(ref);
+
+ assertThat(check.check());
+
+ // Create the second interceptor, but it's too late to modify the first binding.
+ configuration = new Properties();
+ configuration.put("target", "(dependency.id=foo)");
+ ipojoHelper.createComponentInstance("org.apache.felix.ipojo.runtime.core.test.interceptors" +
+ ".EnhancingBindingInterceptor", configuration);
+
+ assertThat(check.getProps().get("enhanced")).isNull();
+
+ // Extract monitored data
+ CheckService checkService = osgiHelper.getServiceObject(CheckService.class,
+ "(factory.name=org.apache.felix.ipojo.runtime.core.test.interceptors.ProxyBindingInterceptor)");
+
+ assertThat(checkService).isNotNull();
+ assertThat(checkService.getProps().get("bound")).isEqualTo(1);
+ assertThat(checkService.getProps().get("foo")).isEqualTo(1);
+
+ // Force re-binding.
+ provider.stop();
+ provider.start();
+
+ assertThat(check.getProps().get("enhanced")).isNotNull();
+ }
+
+}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/impl/ServiceReferenceManager.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/impl/ServiceReferenceManager.java
index 21f9f22..96e5ed7 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/impl/ServiceReferenceManager.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/impl/ServiceReferenceManager.java
@@ -20,6 +20,7 @@
package org.apache.felix.ipojo.dependency.impl;
import org.apache.felix.ipojo.Factory;
+import org.apache.felix.ipojo.dependency.interceptors.ServiceBindingInterceptor;
import org.apache.felix.ipojo.dependency.interceptors.ServiceRankingInterceptor;
import org.apache.felix.ipojo.dependency.interceptors.ServiceTrackingInterceptor;
import org.apache.felix.ipojo.dependency.interceptors.TransformedServiceReference;
@@ -71,10 +72,20 @@
*/
private ServiceRankingInterceptor m_rankingInterceptor;
/**
- * Service interceptor tracker.
+ * Service Ranking Interceptor trackers.
*/
private Tracker m_rankingInterceptorTracker;
+
+ /**
+ * Service Tracking Interceptor trackers.
+ */
private Tracker m_trackingInterceptorTracker;
+
+ /**
+ * Service Binding Interceptor trackers.
+ */
+ private Tracker m_bindingInterceptorTracker;
+
/**
* The set of tracking interceptors.
* TODO this set should be sorted according to the OSGi ranking policy.
@@ -84,6 +95,13 @@
LinkedList<ServiceTrackingInterceptor>();
/**
+ * The set of binding interceptors.
+ * TODO this set should be sorted according to the OSGi ranking policy.
+ */
+ private LinkedList<ServiceBindingInterceptor> m_bindingInterceptors = new
+ LinkedList<ServiceBindingInterceptor>();
+
+ /**
* Creates the service reference manager.
*
* @param dep the dependency
@@ -191,6 +209,42 @@
}
});
m_rankingInterceptorTracker.open();
+
+ m_bindingInterceptorTracker = new Tracker(m_dependency.getBundleContext(),
+ ServiceBindingInterceptor.class.getName(),
+ new TrackerCustomizer() {
+
+ public boolean addingService(ServiceReference reference) {
+ return DependencyProperties.match(reference, m_dependency);
+ }
+
+ public void addedService(ServiceReference reference) {
+ ServiceBindingInterceptor interceptor = (ServiceBindingInterceptor) m_bindingInterceptorTracker
+ .getService(reference);
+ if (interceptor != null) {
+ addBindingInterceptor(interceptor);
+ } else {
+ m_dependency.getComponentInstance().getFactory().getLogger().log(Log.ERROR,
+ "Cannot retrieve the interceptor object from service reference " + reference
+ .getProperty(Constants.SERVICE_ID) + " - " + reference.getProperty
+ (Factory.INSTANCE_NAME_PROPERTY));
+ }
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ // Not supported.
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ if (service != null && service instanceof ServiceBindingInterceptor &&
+ m_bindingInterceptors.contains(service)
+ ) {
+ removeBindingInterceptor((ServiceBindingInterceptor) service);
+ }
+ }
+ }
+ );
+ m_bindingInterceptorTracker.open();
}
private void addTrackingInterceptor(ServiceTrackingInterceptor interceptor) {
@@ -220,6 +274,53 @@
m_dependency.onChange(changeset);
}
+ private void addBindingInterceptor(ServiceBindingInterceptor interceptor) {
+ // A new interceptor arrives, open it.
+ // Binding interceptor cannot modify existing bindings.
+ try {
+ m_dependency.acquireWriteLockIfNotHeld();
+ m_bindingInterceptors.add(interceptor);
+ interceptor.open(m_dependency);
+ } finally {
+ m_dependency.releaseWriteLockIfHeld();
+ }
+ }
+
+ private void removeBindingInterceptor(ServiceBindingInterceptor interceptor) {
+ try {
+ m_dependency.acquireWriteLockIfNotHeld();
+ m_bindingInterceptors.remove(interceptor);
+ interceptor.close(m_dependency);
+ } finally {
+ m_dependency.releaseWriteLockIfHeld();
+ }
+ }
+
+ public Object weavingServiceBinding(DependencyModel.ServiceBindingHolder sbh) {
+ Object svc = sbh.service;
+ try {
+ m_dependency.acquireReadLockIfNotHeld();
+ for (ServiceBindingInterceptor interceptor : m_bindingInterceptors) {
+ // Interceptor are not allowed to return null.
+ svc = interceptor.getService(m_dependency, sbh.reference, svc);
+ }
+ } finally {
+ m_dependency.releaseReadLockIfHeld();
+ }
+ return svc;
+ }
+
+ public void unweavingServiceBinding(DependencyModel.ServiceBindingHolder sbh) {
+ try {
+ m_dependency.acquireReadLockIfNotHeld();
+ for (ServiceBindingInterceptor interceptor : m_bindingInterceptors) {
+ interceptor.ungetService(m_dependency, sbh.reference);
+ }
+ } finally {
+ m_dependency.releaseReadLockIfHeld();
+ }
+ }
+
private ChangeSet computeChangesInMatchingServices() {
if (m_dependency.getTracker() == null || m_dependency.getTracker().getServiceReferences() == null) {
// Tracker closed, no problem
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceBindingInterceptor.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceBindingInterceptor.java
new file mode 100644
index 0000000..af7c049
--- /dev/null
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceBindingInterceptor.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ipojo.dependency.interceptors;
+
+import org.apache.felix.ipojo.util.DependencyModel;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * A service to modify / monitor the service bindings.
+ * This service is notified every time the dependency is weaving a new service bindings on un-weaves an existing one.
+ *
+ * Several binding interceptors can be plugged to the same service dependency. In this case,
+ * a chain is created where all interceptor are called in a sequence.
+ *
+ * A binding interceptor cannot modify the existing bindings.
+ *
+ * Obviously an interceptor can be plugged to several dependencies.
+ *
+ * @since 1.10.2
+ */
+public interface ServiceBindingInterceptor extends DependencyInterceptor {
+
+ /**
+ * Notification method when a dependency is weaving a new service binding.
+ * The interceptor can modify the service object. It must <strong>never</strong> return a {@code null} object,
+ * but the receive service object if it does not want to do anything with the service object.
+ *
+ * When the interceptor <em>modifies</em> the service object, the returned object <strong>must</strong> be
+ * compatible with the dependency specification.
+ *
+ * The received service object may already have been <em>wrapped</em> by binding interceptors called before the
+ * current one.
+ *
+ * @param dependency the dependency
+ * @param reference the service reference bound
+ * @param service the service object
+ * @param <S> the service specification
+ * @return the service object to be injected within the component. Must never be {@code null}.
+ */
+ public <S> S getService(DependencyModel dependency, ServiceReference<S> reference, S service);
+
+ /**
+ * Notification method when a dependency is un-weaving a service binding.
+ * The interceptor must released all objects related to this service binding.
+ *
+ * @param dependency the dependency
+ * @param reference the unbound service reference
+ * @param <S> the service specification
+ */
+ public <S> void ungetService(DependencyModel dependency, ServiceReference<S> reference);
+
+}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceRankingInterceptor.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceRankingInterceptor.java
index 2cf1689..e349c1b 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceRankingInterceptor.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceRankingInterceptor.java
@@ -31,6 +31,8 @@
*
* This interceptors is called to compute the selected set of services from the matching set,
* i.e. the set of services that matching the filter (actually accepted by the tracking interceptors).
+ *
+ * @since 1.10.1
*/
public interface ServiceRankingInterceptor extends DependencyInterceptor {
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceTrackingInterceptor.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceTrackingInterceptor.java
index 1885478..5a7a316 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceTrackingInterceptor.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/dependency/interceptors/ServiceTrackingInterceptor.java
@@ -31,7 +31,10 @@
* a chain is created where all interceptor can influence the next one. If the dependency has a filter,
* a tracking interceptor using this filter is the last interceptor of the chain.
*
- * Obviously an interceptor can be plugged to several interceptors.
+ * Obviously an interceptor can be plugged to several dependencies. Conversely, several tracking interceptor can be
+ * plugged to one dependency.
+ *
+ * @since 1.10.1
*/
public interface ServiceTrackingInterceptor extends DependencyInterceptor {
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/Dependency.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/Dependency.java
index 53188d2..570471a 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/Dependency.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/Dependency.java
@@ -697,7 +697,7 @@
} else {
// Use a reflective construction to avoid class cast exception. This method allows setting the component type.
Object[] objs = (Object[]) Array.newInstance(getSpecification(), refs.length);
- for (int i = 0; refs != null && i < refs.length; i++) {
+ for (int i = 0; i < refs.length; i++) {
ServiceReference ref = refs[i];
objs[i] = getService(ref);
}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyHandlerDescription.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyHandlerDescription.java
index 720d7f1..b5cf947 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyHandlerDescription.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/handlers/dependency/DependencyHandlerDescription.java
@@ -18,9 +18,6 @@
*/
package org.apache.felix.ipojo.handlers.dependency;
-import java.util.Iterator;
-import java.util.List;
-
import org.apache.felix.ipojo.Factory;
import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.metadata.Attribute;
@@ -29,6 +26,8 @@
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
+import java.util.List;
+
/**
* Dependency Handler Description.
*
@@ -40,26 +39,6 @@
* Dependencies managed by the dependency handler.
*/
private DependencyDescription[] m_dependencies = new DependencyDescription[0];
-
- // TODO Define the DependencyStateListener Interface (in ipojo utils)
-
- // TODO Add the list of listener.
-
- // TODO Add register listener method.
-
- // TODO Add unregister listener method.
-
- // TODO Implement the validate method.
-
- // TODO Implement the invalidate method.
-
- // TODO Implement the onServiceArrival method.
-
- // TODO Implement the onServiceDeparture method.
-
- // TODO Implement the onServiceBound method.
-
- // TODO Implement the onServiceUnbound method.
/**
* Creates the Dependency Handler description.
@@ -71,7 +50,6 @@
m_dependencies = new DependencyDescription[deps.length];
for (int i = 0; i < m_dependencies.length; i++) {
m_dependencies[i] = new DependencyDescription(deps[i]);
- //TODO Register callback there on the dependency model.
}
}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java
index 0ff5614..1040787 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java
@@ -128,7 +128,7 @@
* This map stores service object, and so is able to handle
* iPOJO custom policies.
*/
- private Map<ServiceReference, Object> m_serviceObjects = new HashMap<ServiceReference, Object>();
+ private Map<ServiceReference, ServiceBindingHolder> m_serviceObjects = new HashMap<ServiceReference, ServiceBindingHolder>();
/**
* The current list of bound services.
*/
@@ -269,15 +269,16 @@
* The method is called while holding the exclusive lock.
*/
private void ungetAllServices() {
- for (Map.Entry<ServiceReference, Object> entry : m_serviceObjects.entrySet()) {
+ for (Map.Entry<ServiceReference, ServiceBindingHolder> entry : m_serviceObjects.entrySet()) {
ServiceReference ref = entry.getKey();
- Object svc = entry.getValue();
+ ServiceBindingHolder sbh = entry.getValue();
if (m_tracker != null) {
m_tracker.ungetService(ref);
}
- if (svc instanceof IPOJOServiceFactory) {
- ((IPOJOServiceFactory) svc).ungetService(m_instance, svc);
+ if (sbh.factory != null) {
+ sbh.factory.ungetService(m_instance, sbh.service);
}
+ m_serviceReferenceManager.unweavingServiceBinding(sbh);
}
m_serviceObjects.clear();
}
@@ -790,12 +791,12 @@
for (ServiceReference ref : arrivals) {
onServiceArrival(ref);
// Notify service binding to listeners
- notifyListeners(DependencyEventType.BINDING, ref, m_serviceObjects.get(ref));
+ notifyListeners(DependencyEventType.BINDING, ref, m_serviceObjects.get(ref).service);
}
for (ServiceReference ref : departures) {
onServiceDeparture(ref);
// Notify service unbinding to listeners
- notifyListeners(DependencyEventType.UNBINDING, ref, m_serviceObjects.get(ref));
+ notifyListeners(DependencyEventType.UNBINDING, ref, m_serviceObjects.get(ref).service);
}
} finally {
releaseReadLockIfHeld();
@@ -912,6 +913,12 @@
return null;
}
+ // If we already have the service object, just return it.
+ if (m_serviceObjects.containsKey(ref)) {
+ return m_serviceObjects.get(ref).service;
+ }
+
+ ServiceBindingHolder holder = null;
Object svc = m_tracker.getService(ref);
IPOJOServiceFactory factory = null;
@@ -919,20 +926,23 @@
factory = (IPOJOServiceFactory) svc;
svc = factory.getService(m_instance);
}
+ holder = new ServiceBindingHolder(ref, factory, svc);
+
+ svc = m_serviceReferenceManager.weavingServiceBinding(holder);
if (store) {
try {
acquireWriteLockIfNotHeld();
- if (factory != null) {
- m_serviceObjects.put(ref, factory);
+ // The service object may have been modified by the interceptor, update the holder
+ if (svc != holder.service) {
+ m_serviceObjects.put(ref, new ServiceBindingHolder(ref, factory, svc));
} else {
- m_serviceObjects.put(ref, svc);
+ m_serviceObjects.put(ref, holder);
}
} finally {
releaseWriteLockIfHeld();
}
}
-
return svc;
}
@@ -943,18 +953,21 @@
*/
public void ungetService(ServiceReference ref) {
m_tracker.ungetService(ref);
- Object obj;
+ ServiceBindingHolder sbh;
try {
acquireWriteLockIfNotHeld();
- obj = m_serviceObjects.remove(ref);
+ sbh = m_serviceObjects.remove(ref);
} finally {
releaseWriteLockIfHeld();
}
// Call the callback outside the lock.
- if (obj != null && obj instanceof IPOJOServiceFactory) {
- ((IPOJOServiceFactory) obj).ungetService(m_instance, obj);
+ if (sbh != null && sbh.factory != null) {
+ sbh.factory.ungetService(m_instance, sbh.service);
}
+
+ m_serviceReferenceManager.unweavingServiceBinding(sbh);
+
}
public ContextSourceManager getContextSourceManager() {
@@ -988,11 +1001,11 @@
m_state = BROKEN;
// We are going to call callbacks, releasing the lock.
- Object svc = m_serviceObjects.get(ref);
+ ServiceBindingHolder sbh = m_serviceObjects.get(ref);
releaseWriteLockIfHeld();
// Notify listeners
- notifyListeners(DependencyEventType.UNBINDING, ref, svc);
+ notifyListeners(DependencyEventType.UNBINDING, ref, sbh.service);
notifyListeners(DependencyEventType.DEPARTURE, ref, null);
invalidate(); // This will invalidate the instance.
@@ -1072,7 +1085,7 @@
}
// Before leaving the protected region, copy used services.
- Map<ServiceReference, Object> services = new HashMap<ServiceReference, Object>(m_serviceObjects);
+ Map<ServiceReference, ServiceBindingHolder> services = new HashMap<ServiceReference, ServiceBindingHolder>(m_serviceObjects);
// Leaving the locked region to invoke callbacks
releaseWriteLockIfHeld();
@@ -1080,14 +1093,24 @@
for (ServiceReference ref : departures) {
onServiceDeparture(ref);
// Notify service unbinding to listeners
- Object svc = services.get(ref);
- notifyListeners(DependencyEventType.UNBINDING, ref, svc);
+ final ServiceBindingHolder sbh = services.get(ref);
+ if (sbh != null) {
+ notifyListeners(DependencyEventType.UNBINDING, ref, sbh.service);
+ } else {
+ notifyListeners(DependencyEventType.UNBINDING, ref, null);
+ }
+ // Unget the service reference.
+ ungetService(ref);
}
for (ServiceReference ref : arrivals) {
onServiceArrival(ref);
// Notify service binding to listeners
- Object svc = services.get(ref);
- notifyListeners(DependencyEventType.BINDING, ref, svc);
+ final ServiceBindingHolder sbh = services.get(ref);
+ if (sbh != null) {
+ notifyListeners(DependencyEventType.BINDING, ref, sbh.service);
+ } else {
+ notifyListeners(DependencyEventType.BINDING, ref, null);
+ }
}
// Do we have a modified service ?
if (set.modified != null && m_boundServices.contains(set.modified)) {
@@ -1229,4 +1252,26 @@
m_listeners.clear();
}
}
+
+ /**
+ * Service binding structure.
+ */
+ public class ServiceBindingHolder {
+
+ public final Object service;
+ public final IPOJOServiceFactory factory;
+ public final ServiceReference reference;
+
+ private ServiceBindingHolder(ServiceReference reference, IPOJOServiceFactory factory, Object service) {
+ this.service = service;
+ this.factory = factory;
+ this.reference = reference;
+ }
+
+ private ServiceBindingHolder(ServiceReference reference, Object service) {
+ this.service = service;
+ this.factory = null;
+ this.reference = reference;
+ }
+ }
}