Fix the issue Felix-860.
Replace dynamic proxies by proxies generated with ASM.
Moreover, non proxied dependencies use a ThreadLocal mechanism to guaranty the consistency of injected services (as for regular service dependency).
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@735632 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/handler/temporal/LICENSE.asm b/ipojo/handler/temporal/LICENSE.asm
new file mode 100644
index 0000000..9496b17
--- /dev/null
+++ b/ipojo/handler/temporal/LICENSE.asm
@@ -0,0 +1,28 @@
+
+ ASM: a very small and fast Java bytecode manipulation framework
+ Copyright (c) 2000-2005 INRIA, France Telecom
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/ipojo/handler/temporal/NOTICE b/ipojo/handler/temporal/NOTICE
index fbaec6c..59c055e 100644
--- a/ipojo/handler/temporal/NOTICE
+++ b/ipojo/handler/temporal/NOTICE
@@ -7,6 +7,10 @@
The Apache Software Foundation (http://www.apache.org/).
Licensed under the Apache License 2.0.
+This product includes software developed at
+Copyright (c) 2000-2005 INRIA, France Telecom
+Licensed under BSD License.
+
II. Used Software
This product uses software developed at
@@ -16,3 +20,5 @@
III. License Summary
- Apache License 2.0
+- BSD Licence
+
diff --git a/ipojo/handler/temporal/pom.xml b/ipojo/handler/temporal/pom.xml
index dfb467e..21c8c93 100644
--- a/ipojo/handler/temporal/pom.xml
+++ b/ipojo/handler/temporal/pom.xml
@@ -41,6 +41,17 @@
<artifactId>org.apache.felix.ipojo</artifactId>
<version>1.1.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>asm</groupId>
+ <artifactId>asm-all</artifactId>
+ <version>3.0</version>
+ <exclusions>
+ <exclusion>
+ <groupId>asm</groupId>
+ <artifactId>asm-tree</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
</dependencies>
<build>
<plugins>
@@ -51,10 +62,13 @@
<extensions>true</extensions>
<configuration>
<instructions>
- <Private-Package> org.apache.felix.ipojo.handler.temporal
+ <Private-Package>
+ org.apache.felix.ipojo.handler.temporal,
+ org.objectweb.asm
</Private-Package>
<Bundle-Name>${pom.name}</Bundle-Name>
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+ <Import-Package>!org.objectweb.asm.tree, *</Import-Package>
<Bundle-Vendor> The Apache Software Foundation </Bundle-Vendor>
<Bundle-Description> iPOJO Temporal Dependency Handler
</Bundle-Description>
@@ -62,7 +76,9 @@
http://felix.apache.org/site/temporal-service-dependency.html
</Bundle-DocURL>
<Include-Resource> META-INF/LICENCE=LICENSE,
- META-INF/NOTICE=NOTICE </Include-Resource>
+ META-INF/LICENCE.asm=LICENSE.asm,
+ META-INF/NOTICE=NOTICE
+ </Include-Resource>
</instructions>
</configuration>
</plugin>
diff --git a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ProxyGenerator.java b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ProxyGenerator.java
new file mode 100644
index 0000000..90013a3
--- /dev/null
+++ b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ProxyGenerator.java
@@ -0,0 +1,183 @@
+/*
+ * 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.handler.temporal;
+
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * Generates proxy class delegating operation invocations thanks to a
+ * a temporal dependency.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ProxyGenerator implements Opcodes {
+
+ /**
+ * The temporal dependency name.
+ */
+ private static final String DEPENDENCY = "m_dependency";
+
+ /**
+ * The Temporal Dependency descriptor.
+ */
+ private static final String DEPENDENCY_DESC = Type.getDescriptor(TemporalDependency.class);
+
+ /**
+ * Temporal dependency internal class name.
+ */
+ private static final String TEMPORAL_DEPENDENCY = "org/apache/felix/ipojo/handler/temporal/TemporalDependency";
+
+ /**
+ * Gets the internal names of the given class objects.
+ * @param classes the classes
+ * @return the array containing internal names of the given class array.
+ */
+ private static String[] getInternalClassNames(Class[] classes) {
+ final String[] names = new String[classes.length];
+ for (int i = 0; i < names.length; i++) {
+ names[i] = Type.getInternalName(classes[i]);
+ }
+ return names;
+ }
+
+ /**
+ * Generates a proxy class.
+ * @param spec the proxied service specification
+ * @return the byte[] for the generated proxy class.
+ */
+ public static byte[] dumpProxy(Class spec) {
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ String internalClassName = Type.getInternalName(spec); // Specification class internal name.
+ String[] itfs = new String[] {internalClassName}; // Implemented interface.
+ String className = internalClassName + "$$Proxy"; // Unique name.
+ Method[] methods = spec.getMethods(); // Method to delegate
+
+ cw.visit(Opcodes.V1_3, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, className, null, "java/lang/Object", itfs);
+ addDependencyField(cw);
+ generateConstructor(cw, className);
+
+ // For each method, create the delegator code.
+ for (int i = 0; i < methods.length; i++) {
+ if ((methods[i].getModifiers() & (Modifier.STATIC | Modifier.FINAL)) == 0) {
+ generateDelegator(cw, methods[i], className, internalClassName);
+ }
+ }
+
+ cw.visitEnd();
+
+ return cw.toByteArray();
+
+ }
+
+ /**
+ * Generates a delegated method.
+ * @param cw the class writer
+ * @param method the method object to delegate
+ * @param className the generated class name
+ * @param itfName the internal specification class name
+ */
+ private static void generateDelegator(ClassWriter cw, Method method,
+ String className, String itfName) {
+ String methodName = method.getName();
+ String desc = Type.getMethodDescriptor(method);
+ String[] exceptions = getInternalClassNames(method.getExceptionTypes());
+ int modifiers = method.getModifiers()
+ & ~(Modifier.ABSTRACT | Modifier.NATIVE | Modifier.SYNCHRONIZED);
+ Type[] types = Type.getArgumentTypes(method);
+
+ int freeRoom = 1;
+ for (int t = 0; t < types.length; t++) {
+ freeRoom = freeRoom + types[t].getSize();
+ }
+
+ MethodVisitor mv = cw.visitMethod(modifiers, methodName, desc, null,
+ exceptions);
+ mv.visitCode();
+
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, className, DEPENDENCY, DEPENDENCY_DESC); // The temporal dependency is on the stack.
+ mv.visitMethodInsn(INVOKEVIRTUAL, TEMPORAL_DEPENDENCY, "getService", // Call getService
+ "()Ljava/lang/Object;"); // The service object is on the stack.
+ int varSvc = freeRoom;
+ freeRoom = freeRoom + 1; // Object Reference.
+ mv.visitVarInsn(ASTORE, varSvc); // Store the service object.
+
+ // Invoke the method on the service object.
+ mv.visitVarInsn(ALOAD, varSvc);
+ // Push argument on the stack.
+ int i = 1; // Arguments. (non static method)
+ for (int t = 0; t < types.length; t++) {
+ mv.visitVarInsn(types[t].getOpcode(ILOAD), i);
+ i = i + types[t].getSize();
+ }
+ // Invocation
+ mv.visitMethodInsn(INVOKEINTERFACE, itfName, methodName, desc);
+
+ // Return the result
+ Type returnType = Type.getReturnType(desc);
+ if (returnType.getSort() != Type.VOID) {
+ mv.visitInsn(returnType.getOpcode(IRETURN));
+ } else {
+ mv.visitInsn(RETURN);
+ }
+
+ // End of the method.
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ /**
+ * Generates the constructors. The constructor receives a temporal dependency
+ * and set the {@link ProxyGenerator#DEPENDENCY} field.
+ * @param cw the class writer
+ * @param className the generated class name.
+ */
+ private static void generateConstructor(ClassWriter cw, String className) {
+ MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", '(' + DEPENDENCY_DESC + ")V", null, null);
+ mv.visitCode();
+
+ mv.visitVarInsn(ALOAD, 0); // Load this
+ mv.visitInsn(DUP); // Dup
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); // Call super
+ mv.visitVarInsn(ALOAD, 1); // Load the argument
+ mv.visitFieldInsn(PUTFIELD, className, DEPENDENCY, DEPENDENCY_DESC); // Assign the dependency field
+ mv.visitInsn(RETURN); // Return void
+
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ /**
+ * Adds the temporal dependency field {@link ProxyGenerator#DEPENDENCY}.
+ * @param cw the class writer
+ */
+ private static void addDependencyField(ClassWriter cw) {
+ cw.visitField(Opcodes.ACC_FINAL, DEPENDENCY, DEPENDENCY_DESC, null, null);
+ cw.visitEnd();
+ }
+
+
+
+}
diff --git a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ServiceProxy.java b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ServiceProxy.java
deleted file mode 100644
index 69aa1a4..0000000
--- a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ServiceProxy.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.handler.temporal;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-
-import org.osgi.framework.ServiceReference;
-
-/**
- * Wrapper in front of a temporal service dependencies.
- * This proxy can be used by collaborators. Service lookup
- * and on timeout policies are executed when a method is invoked.
- * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
- */
-public class ServiceProxy implements InvocationHandler {
-
- /**
- * The wrapped temporal dependency.
- */
- private TemporalDependency m_dependency;
-
- /**
- * Creates a Service Proxy.
- * @param dep the wrapped temporal dependency
- */
- public ServiceProxy(TemporalDependency dep) {
- m_dependency = dep;
- }
-
- /**
- * Intercept a method invocation.
- * This method looks for a service provider, wait for timeout and
- * depending on the lookup result either call the method
- * on a service object or executed the on timeout policy.
- * In this latter case, this methods can throw a {@link RuntimeException},
- * throws a {@link NullPointerException} (null policy) or invokes the
- * method on a nullable/default-implementation object.
- * @param proxy the proxy on which the method is invoked
- * @param method the invoked method
- * @param args the arguments
- * @return the invocation result.
- * @throws Exception an exception occurs either during the invocation or
- * is the result to the on timeout policy.
- * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
- */
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Exception {
- ServiceReference ref = m_dependency.getServiceReference();
- if (ref != null) {
- // Immediate return.
- return invokeOperation(ref, method, args); // Invoke the method on the service object.
- } else {
- // Begin to wait ...
- long enter = System.currentTimeMillis();
- boolean exhausted = false;
- synchronized (this) {
- while (m_dependency.getServiceReference() == null && !exhausted) {
- try {
- wait(1);
- } catch (InterruptedException e) {
- // We was interrupted ....
- } finally {
- long end = System.currentTimeMillis();
- exhausted = (end - enter) > m_dependency.getTimeout();
- }
- }
- }
- // Check
- if (exhausted) {
- Object oto = m_dependency.onTimeout(); // Throws the RuntimeException
- if (oto == null) { // If null, return null
- throw new NullPointerException("No service object available"); // throws an NPE in this case.
- } else {
- // invoke the method on the returned object.
- return invokeOperation(oto, method, args);
- }
- } else {
- ref = m_dependency.getServiceReference();
- return invokeOperation(ref, method, args); // Invoke the method on the service object.
- }
- }
- }
-
- /**
- * Helper method invoking the given method, with the given
- * argument on the given object.
- * @param svc the service object
- * @param method the method object
- * @param args the arguments
- * @return the invocation result
- * @throws Exception occurs when an issue happen during method invocation.
- */
- private Object invokeOperation(Object svc, Method method, Object[] args)
- throws Exception {
- return method.invoke(svc, args);
- }
-
- /**
- * Helper method invoking the given method, with the given
- * argument on the given service reference. This methods gets
- * the service object and then invokes the method.
- * @param ref the service reference
- * @param method the method object
- * @param args the arguments
- * @return the invocation result
- * @throws Exception occurs when an issue happen during method invocation.
- */
- private Object invokeOperation(ServiceReference ref, Method method, Object[] args)
- throws Exception {
- Object svc = m_dependency.getService(ref);
- return method.invoke(svc, args);
- }
-
-}
diff --git a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ServiceUsage.java b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ServiceUsage.java
new file mode 100644
index 0000000..70184c9
--- /dev/null
+++ b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/ServiceUsage.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.handler.temporal;
+
+
+/**
+ * Object managing thread local copy of required services.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceUsage extends ThreadLocal {
+
+ /**
+ * Structure contained in the Thread Local.
+ */
+ public static class Usage {
+
+ /**
+ * Stack Size.
+ */
+ int m_stack = 0;
+ /**
+ * Object to inject.
+ */
+ Object m_object;
+
+ /**
+ * Increment the stack level.
+ */
+ public void inc() {
+ m_stack++;
+ }
+
+ /**
+ * Decrement the stack level.
+ * @return true if the stack is 0 after the decrement.
+ */
+ public boolean dec() {
+ m_stack--;
+ return m_stack == 0;
+ }
+
+ /**
+ * Clear the service object array.
+ */
+ public void clear() {
+ m_object = null;
+ }
+
+ }
+
+ /**
+ * Initialize the cached object.
+ * @return an empty Usage object.
+ * @see java.lang.ThreadLocal#initialValue()
+ */
+ public Object initialValue() {
+ return new Usage();
+ }
+
+}
diff --git a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalDependency.java b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalDependency.java
index f0f270b..0689505 100644
--- a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalDependency.java
+++ b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalDependency.java
@@ -19,14 +19,18 @@
package org.apache.felix.ipojo.handler.temporal;
import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.felix.ipojo.FieldInterceptor;
+import org.apache.felix.ipojo.MethodInterceptor;
import org.apache.felix.ipojo.Nullable;
import org.apache.felix.ipojo.PrimitiveHandler;
+import org.apache.felix.ipojo.handler.temporal.ServiceUsage.Usage;
import org.apache.felix.ipojo.handlers.dependency.NullableObject;
import org.apache.felix.ipojo.util.DependencyModel;
import org.osgi.framework.BundleContext;
@@ -40,7 +44,7 @@
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class TemporalDependency extends DependencyModel implements
- FieldInterceptor {
+ FieldInterceptor, MethodInterceptor {
/**
* The timeout.
@@ -81,6 +85,18 @@
* Enables the proxy mode.
*/
private boolean m_proxy;
+
+ /**
+ * Service Usage (Thread Local).
+ */
+ private ServiceUsage m_usage;
+
+ /**
+ * The proxy object.
+ * This field is used for scalar proxied temporal dependency.
+ */
+ private Object m_proxyObject;
+
/**
* Creates a temporal dependency.
@@ -106,6 +122,12 @@
m_handler = handler;
m_collection = collection;
m_proxy = proxy;
+ if (! proxy) { // No proxy => initialize the Thread local.
+ m_usage = new ServiceUsage();
+ } else if (proxy && ! agg) { // Scalar proxy => Create the proxy.
+ ProxyFactory proxyFactory = new ProxyFactory(this.getSpecification().getClassLoader(), this.getClass().getClassLoader());
+ m_proxyObject = proxyFactory.getProxy(getSpecification(), this);
+ }
}
/**
@@ -123,23 +145,20 @@
/**
* A provider arrives.
- * @param arg0 service reference of the new provider.
+ * @param ref service reference of the new provider.
* @see org.apache.felix.ipojo.util.DependencyModel#onServiceArrival(org.osgi.framework.ServiceReference)
*/
- public void onServiceArrival(ServiceReference arg0) {
+ public synchronized void onServiceArrival(ServiceReference ref) {
// Notify if a thread is waiting.
- synchronized (this) {
- notifyAll();
- }
+ notifyAll();
}
/**
- * A provider leaves. Nothing to do.
+ * A provider leaves.
* @param arg0 leaving service references.
* @see org.apache.felix.ipojo.util.DependencyModel#onServiceDeparture(org.osgi.framework.ServiceReference)
*/
- public synchronized void onServiceDeparture(ServiceReference arg0) {
- }
+ public void onServiceDeparture(ServiceReference arg0) { }
/**
* The code require a value of the monitored field. If providers are
@@ -153,12 +172,20 @@
* @see org.apache.felix.ipojo.FieldInterceptor#onGet(java.lang.Object, java.lang.String, java.lang.Object)
*/
public synchronized Object onGet(Object arg0, String arg1, Object arg2) {
+ // Check if the Thread local as a value
+ if (! m_proxy) {
+ Usage usage = (Usage) m_usage.get();
+ if (usage.m_stack > 0) {
+ return usage.m_object;
+ }
+ }
+
ServiceReference[] refs = getServiceReferences();
if (refs != null) {
// Immediate return.
return getServiceObjects(refs);
} else {
- // Begin to wait ...
+ // Begin to wait ...
long enter = System.currentTimeMillis();
boolean exhausted = false;
synchronized (this) {
@@ -184,6 +211,65 @@
}
/**
+ * A POJO method will be invoked.
+ * @param pojo : Pojo object
+ * @param method : called method
+ * @param args : arguments
+ * @see org.apache.felix.ipojo.MethodInterceptor#onEntry(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
+ */
+ public void onEntry(Object pojo, Method method, Object[] args) {
+ if (m_usage != null) {
+ Usage usage = (Usage) m_usage.get();
+ if (usage.m_stack > 0) {
+ usage.inc();
+ m_usage.set(usage); // Set the Thread local as value has been modified
+ }
+ }
+ }
+
+ /**
+ * A POJO method has thrown an error.
+ * This method does nothing and wait for the finally.
+ * @param pojo : POJO object.
+ * @param method : Method object.
+ * @param throwable : thrown error
+ * @see org.apache.felix.ipojo.MethodInterceptor#onError(java.lang.Object, java.lang.reflect.Method, java.lang.Throwable)
+ */
+ public void onError(Object pojo, Method method, Throwable throwable) {
+ // Nothing to do : wait onFinally
+ }
+
+ /**
+ * A POJO method has returned.
+ * @param pojo : POJO object.
+ * @param method : Method object.
+ * @param returnedObj : returned object (null for void method)
+ * @see org.apache.felix.ipojo.MethodInterceptor#onExit(java.lang.Object, java.lang.reflect.Method, java.lang.Object)
+ */
+ public void onExit(Object pojo, Method method, Object returnedObj) {
+ // Nothing to do : wait onFinally
+ }
+
+ /**
+ * A POJO method is finished.
+ * @param pojo : POJO object.
+ * @param method : Method object.
+ * @see org.apache.felix.ipojo.MethodInterceptor#onFinally(java.lang.Object, java.lang.reflect.Method)
+ */
+ public void onFinally(Object pojo, Method method) {
+ if (m_usage != null) {
+ Usage usage = (Usage) m_usage.get();
+ if (usage.m_stack > 0) {
+ if (usage.dec()) {
+ // Exit the method flow => Release all objects
+ usage.clear();
+ m_usage.set(usage); // Set the Thread local as value has been modified
+ }
+ }
+ }
+ }
+
+ /**
* Creates and returns object to inject in the dependency.
* This method handles aggregate, collection and proxy cases.
* @param refs the available service references
@@ -192,36 +278,81 @@
*/
private Object getServiceObjects(ServiceReference [] refs) {
if (m_proxy) {
- if (isAggregate()) { // Necessary a collection
+ if (m_proxyObject == null) { // Not aggregate.
return new ServiceCollection(this);
} else {
- return Proxy.newProxyInstance(m_handler
- .getInstanceManager().getClazz().getClassLoader(),
- new Class[] { getSpecification() },
- new ServiceProxy(this)); // NOPMD
+ return m_proxyObject;
}
} else {
- if (isAggregate()) {
- if (m_collection) {
- Collection svc = new ArrayList(refs.length); // Use an array list as collection implementation.
- for (int i = 0; i < refs.length; i++) {
- svc.add(getService(refs[i]));
+ // Initialize the thread local object is not already touched.
+ Usage usage = (Usage) m_usage.get();
+ if (usage.m_stack == 0) { // uninitialized usage.
+ if (isAggregate()) {
+ if (m_collection) {
+ Collection svc = new ArrayList(refs.length); // Use an array list as collection implementation.
+ for (int i = 0; i < refs.length; i++) {
+ svc.add(getService(refs[i]));
+ }
+ usage.m_object = svc;
+ } else {
+ Object[] svc = (Object[]) Array.newInstance(getSpecification(),
+ refs.length);
+ for (int i = 0; i < svc.length; i++) {
+ svc[i] = getService(refs[i]);
+ }
+ usage.m_object = svc;
}
- return svc;
} else {
- Object[] svc = (Object[]) Array.newInstance(getSpecification(),
- refs.length);
- for (int i = 0; i < svc.length; i++) {
- svc[i] = getService(refs[i]);
- }
- return svc;
+ usage.m_object = getService(refs[0]);
}
- } else {
- return getService(refs[0]);
+ usage.inc(); // Start the caching, so set the stack level to 1
+ m_usage.set(usage);
}
+ return usage.m_object;
}
}
+
+ /**
+ * Called by the proxy to get a service object to delegate a method.
+ * This methods manages the waited time and on timeout policies.
+ * @return a service object or a nullable/default-implmentation object.
+ */
+ public Object getService() {
+ ServiceReference ref = getServiceReference();
+ if (ref != null) {
+ return getService(ref); // Return immediately the service object.
+ } else {
+ // Begin to wait ...
+ long enter = System.currentTimeMillis();
+ boolean exhausted = false;
+ synchronized (this) {
+ while (ref == null && !exhausted) {
+ try {
+ wait(1);
+ } catch (InterruptedException e) {
+ // We was interrupted ....
+ } finally {
+ long end = System.currentTimeMillis();
+ exhausted = (end - enter) > m_timeout;
+ ref = getServiceReference();
+ }
+ }
+ }
+ // Check
+ if (exhausted) {
+ Object obj = onTimeout(); // Throw the Runtime Exception
+ if (obj == null) {
+ throw new NullPointerException("No service available"); // NPE if null.
+ } else {
+ return obj; // Return a nullable or DI
+ }
+ } else {
+ // If not exhausted, ref is not null.
+ return getService(ref);
+ }
+ }
+ }
/**
* Start method. Initializes the nullable object.
@@ -316,6 +447,7 @@
public void stop() {
super.stop();
m_nullableObject = null;
+ m_proxyObject = null;
}
/**
@@ -325,8 +457,7 @@
* @param arg2 received value
* @see org.apache.felix.ipojo.FieldInterceptor#onSet(java.lang.Object, java.lang.String, java.lang.Object)
*/
- public void onSet(Object arg0, String arg1, Object arg2) {
- }
+ public void onSet(Object arg0, String arg1, Object arg2) { }
/**
* Implements the timeout policy according to the specified configuration.
@@ -351,6 +482,77 @@
return m_timeout;
}
+ /**
+ * Creates proxy object for proxied scalar dependencies.
+ */
+ private class ProxyFactory extends ClassLoader {
+
+ /**
+ * Instance classloader, used to load specification and dependent classes.
+ */
+ private ClassLoader m_instanceCL;
+
+ /**
+ * Handler classloader, used to load the temporal dependency class.
+ */
+ private ClassLoader m_handlerCL;
+
+ /**
+ * Creates the proxy classloader.
+ * @param parent1 the instance classloader.
+ * @param parent2 the handler classloader.
+ */
+ public ProxyFactory(ClassLoader parent1, ClassLoader parent2) {
+ this.m_instanceCL = parent1;
+ this.m_handlerCL = parent2;
+ }
+
+ /**
+ * Loads a proxy class generated for the given (interface) class.
+ * @param clazz the service specification to proxy
+ * @return the Class object of the proxy.
+ */
+ protected Class getProxyClass(Class clazz) {
+ byte[] clz = ProxyGenerator.dumpProxy(clazz); // Generate the proxy.
+ return defineClass(clazz.getName() + "$$Proxy", clz, 0, clz.length);
+ }
+
+ /**
+ * Create a proxy object for the given specification. The proxy
+ * uses the given temporal dependency to get the service object.
+ * @param spec the service specification (interface)
+ * @param dep the temporal dependency used to get the service
+ * @return the proxy object.
+ */
+ public Object getProxy(Class spec, TemporalDependency dep) {
+ try {
+ Class clazz = getProxyClass(getSpecification());
+ Constructor constructor = clazz.getConstructor(new Class[] {dep.getClass()}); // The proxy constructor
+ return constructor.newInstance(new Object[] {dep});
+ } catch (Throwable e) {
+ m_handler.error("Cannot create the proxy object", e);
+ m_handler.getInstanceManager().stop();
+ return null;
+ }
+ }
+
+ /**
+ * Loads the given class.
+ * This class use the classloader of the specification class
+ * or the handler class loader.
+ * @param name the class name
+ * @return the class object
+ * @throws ClassNotFoundException if the class is not found by the two classloaders.
+ * @see java.lang.ClassLoader#loadClass(java.lang.String)
+ */
+ public Class loadClass(String name) throws ClassNotFoundException {
+ try {
+ return m_instanceCL.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ return m_handlerCL.loadClass(name);
+ }
+ }
+ }
}
diff --git a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalHandler.java b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalHandler.java
index a65c616..c980101 100644
--- a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalHandler.java
+++ b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalHandler.java
@@ -27,6 +27,7 @@
import org.apache.felix.ipojo.PrimitiveHandler;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.parser.FieldMetadata;
+import org.apache.felix.ipojo.parser.MethodMetadata;
import org.apache.felix.ipojo.parser.PojoMetadata;
import org.apache.felix.ipojo.util.DependencyModel;
import org.apache.felix.ipojo.util.DependencyStateListener;
@@ -190,6 +191,13 @@
TemporalDependency dep = new TemporalDependency(specification, agg, collection, proxy, filter, getInstanceManager().getContext(), timeout, policy, di, this);
m_dependencies.add(dep);
+ if (! proxy) { // Register method interceptor only if are not a proxy
+ MethodMetadata[] methods = manipulation.getMethods();
+ for (int k = 0; k < methods.length; k++) {
+ getInstanceManager().register(methods[k], dep);
+ }
+ }
+
getInstanceManager().register(fieldmeta, dep);
}
}
diff --git a/ipojo/pom.xml b/ipojo/pom.xml
index 254e46c..45e20e8 100644
--- a/ipojo/pom.xml
+++ b/ipojo/pom.xml
@@ -43,7 +43,7 @@
<module>handler/temporal</module>
<module>handler/eventadmin</module>
<module>handler/whiteboard</module>
- <module>junit4osgi</module>
+
</modules>
diff --git a/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/DelayTest.java b/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/DelayTest.java
index 6adb197..bbd6a5c 100644
--- a/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/DelayTest.java
+++ b/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/DelayTest.java
@@ -44,16 +44,18 @@
// Stop the provider.
provider.stop();
+ assertNull("No FooService", Utils.getServiceReference(context, FooService.class.getName(), null));
ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
assertNotNull("Check cs availability - 2", ref_cs);
long begin = System.currentTimeMillis();
DelayedProvider dp = new DelayedProvider(provider, 200);
dp.start();
cs = (CheckService) context.getService(ref_cs);
+
assertTrue("Check invocation - 2", cs.check());
long end = System.currentTimeMillis();
- assertTrue("Assert delay", (end - begin) >= 200);
+ assertTrue("Assert delay (" + (end - begin) + ")", (end - begin) >= 200);
ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
assertNotNull("Check cs availability - 3", ref_cs);
@@ -80,7 +82,7 @@
CheckService cs = (CheckService) context.getService(ref_cs);
assertTrue("Check invocation", cs.check());
-
+
// Stop the provider.
provider.stop();
ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
@@ -105,6 +107,7 @@
under.dispose();
}
+
public void testTimeout() {
String prov = "provider";
ComponentInstance provider = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
@@ -142,6 +145,7 @@
fail("Timeout expected");
}
+
public void testTimeoutWithProxy() {
String prov = "provider";
ComponentInstance provider = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
@@ -178,7 +182,8 @@
fail("Timeout expected");
}
-
+
+
public void testDelayTimeout() {
String prov = "provider";
ComponentInstance provider = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
@@ -217,7 +222,7 @@
under.stop();
under.dispose();
}
-
+
public void testDelayTimeoutWithProxy() {
String prov = "provider";
ComponentInstance provider = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
@@ -256,7 +261,7 @@
under.stop();
under.dispose();
}
-
+
public void testSetTimeout() {
String prov = "provider";
ComponentInstance provider = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
@@ -293,7 +298,8 @@
fail("Timeout expected");
}
-
+
+
public void testSetTimeoutWithProxy() {
String prov = "provider";
ComponentInstance provider = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
@@ -330,7 +336,7 @@
fail("Timeout expected");
}
-
+
public void testDelayOnMultipleDependency() {
String prov = "provider";
ComponentInstance provider1 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
@@ -379,7 +385,8 @@
under.stop();
under.dispose();
}
-
+
+
public void testDelayOnCollectionDependency() {
String prov = "provider";
ComponentInstance provider1 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
@@ -428,7 +435,8 @@
under.stop();
under.dispose();
}
-
+
+
public void testDelayOnProxiedCollectionDependency() {
String prov = "provider";
ComponentInstance provider1 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);