/* * 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.*; 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; import org.osgi.framework.Filter; import org.osgi.framework.ServiceReference; /** * Temporal dependency. A temporal dependency waits (block) for the availability * of the service. If no provider arrives in the specified among of time, a * runtime exception is thrown. * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class TemporalDependency extends DependencyModel implements FieldInterceptor, MethodInterceptor { /** * The timeout. */ private long m_timeout; /** * The default implementation. */ private String m_di; /** * The {@link Nullable} object or Default-Implementation instance if used. */ private Object m_nullableObject; /** * The handler managing this dependency. */ private PrimitiveHandler m_handler; /** * The timeout policy. Null injects null, {@link Nullable} injects a nullable object or * an array with a nullable object, Default-Implementation injects an object * created from the specified injected implementation or an array with it * Empty array inject an empty array (must be an aggregate dependency) No * policy (0) throw a runtime exception when the timeout occurs * */ private int m_policy; /** * The dependency is injected as a collection. * The field must be of the {@link Collection} type */ private boolean m_collection; /** * 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. * @param spec the service specification * @param agg is the dependency aggregate ? * @param collection the dependency field is a collection * @param proxy enable the proxy-mode * @param filter the LDAP filter * @param context service context * @param timeout timeout * @param handler Handler managing this dependency * @param defaultImpl class used as default-implementation * @param policy onTimeout policy */ public TemporalDependency(Class spec, boolean agg, boolean collection, boolean proxy, Filter filter, BundleContext context, long timeout, int policy, String defaultImpl, TemporalHandler handler) { super(spec, agg, true, filter, null, DependencyModel.DYNAMIC_BINDING_POLICY, context, handler, handler.getInstanceManager()); m_di = defaultImpl; m_policy = policy; m_timeout = timeout; 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.getClass().getClassLoader()); m_proxyObject = proxyFactory.getProxy(getSpecification(), this); } } /** * The dependency has been reconfigured. * @param arg0 new service references * @param arg1 old service references * @see org.apache.felix.ipojo.util.DependencyModel#onDependencyReconfiguration(org.osgi.framework.ServiceReference[], * org.osgi.framework.ServiceReference[]) */ public void onDependencyReconfiguration(ServiceReference[] arg0, ServiceReference[] arg1) { throw new UnsupportedOperationException( "Reconfiguration not yet supported"); } /** * A provider arrives. * @param ref service reference of the new provider. * @see org.apache.felix.ipojo.util.DependencyModel#onServiceArrival(org.osgi.framework.ServiceReference) */ public synchronized void onServiceArrival(ServiceReference ref) { // Notify if a thread is waiting. notifyAll(); } /** * A provider leaves. * @param arg0 leaving service references. * @see org.apache.felix.ipojo.util.DependencyModel#onServiceDeparture(org.osgi.framework.ServiceReference) */ public void onServiceDeparture(ServiceReference arg0) { } /** * A provider is modified. * @param arg0 leaving service references. * @see org.apache.felix.ipojo.util.DependencyModel#onServiceDeparture(org.osgi.framework.ServiceReference) */ public void onServiceModification(ServiceReference arg0) { } /** * The code require a value of the monitored field. If providers are * available, the method return service object(s) immediately. Else, the * thread is blocked until an arrival. If no provider arrives during the * among of time specified, the method throws a Runtime Exception. * @param arg0 POJO instance asking for the service * @param arg1 field name * @param arg2 previous value * @return the object to inject. * @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 ... long enter = System.currentTimeMillis(); boolean exhausted = false; synchronized (this) { while (getServiceReference() == null && !exhausted) { try { wait(1); } catch (InterruptedException e) { // We was interrupted .... } finally { long end = System.currentTimeMillis(); exhausted = (end - enter) > m_timeout; } } } // Check if (exhausted) { return onTimeout(); } else { refs = getServiceReferences(); return getServiceObjects(refs); } } } /** * 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.Member, java.lang.Object[]) */ public void onEntry(Object pojo, Member 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.Member, java.lang.Throwable) */ public void onError(Object pojo, Member method, Throwable throwable) { // Nothing to do : wait onFinally } /** * A POJO method has returned. * @param pojo : POJO object. * @param member : Method object. * @param returnedObj : returned object (null for void method) * @see org.apache.felix.ipojo.MethodInterceptor#onExit(java.lang.Object, java.lang.reflect.Member, java.lang.Object) */ public void onExit(Object pojo, Member member, 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.Member) */ public void onFinally(Object pojo, Member 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 * @return the object to inject. Can be a 'simple' object, a proxy, * a collection or an array. */ private Object getServiceObjects(ServiceReference [] refs) { if (m_proxy) { if (m_proxyObject == null) { // Not aggregate. return new ServiceCollection(this); } else { return m_proxyObject; } } else { // 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; } } else { usage.m_object = 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 RuntimeException("No service available"); // Runtime Exception to be consistent with iPOJO Core. } 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. * @see org.apache.felix.ipojo.util.DependencyModel#start() */ public void start() { super.start(); switch (m_policy) { case TemporalHandler.NULL: m_nullableObject = null; break; case TemporalHandler.NULLABLE: // To load the proxy we use the POJO class loader. Indeed, this // classloader imports iPOJO (so can access to Nullable) and has // access to the service specification. try { m_nullableObject = Proxy.newProxyInstance(m_handler .getInstanceManager().getClazz().getClassLoader(), new Class[] { getSpecification(), Nullable.class }, new NullableObject()); // NOPMD if (isAggregate()) { if (m_collection) { List list = new ArrayList(1); list.add(m_nullableObject); m_nullableObject = list; } else { Object[] array = (Object[]) Array.newInstance( getSpecification(), 1); array[0] = m_nullableObject; m_nullableObject = array; } } } catch (NoClassDefFoundError e) { // A NoClassDefFoundError is thrown if the specification // uses a // class not accessible by the actual instance. // It generally comes from a missing import. throw new IllegalStateException( "Cannot create the Nullable object, a referenced class cannot be loaded: " + e.getMessage()); } break; case TemporalHandler.DEFAULT_IMPLEMENTATION: // Create the default-implementation object. try { Class clazz = m_handler.getInstanceManager().getContext() .getBundle().loadClass(m_di); m_nullableObject = clazz.newInstance(); } catch (IllegalAccessException e) { throw new IllegalStateException( "Cannot load the default-implementation " + m_di + " : " + e.getMessage()); } catch (InstantiationException e) { throw new IllegalStateException( "Cannot load the default-implementation " + m_di + " : " + e.getMessage()); } catch (ClassNotFoundException e) { throw new IllegalStateException( "Cannot load the default-implementation " + m_di + " : " + e.getMessage()); } if (isAggregate()) { if (m_collection) { List list = new ArrayList(1); list.add(m_nullableObject); m_nullableObject = list; } else { Object[] array = (Object[]) Array.newInstance( getSpecification(), 1); array[0] = m_nullableObject; m_nullableObject = array; } } break; case TemporalHandler.EMPTY: if (! m_collection) { m_nullableObject = Array.newInstance(getSpecification(), 0); } else { // Empty collection m_nullableObject = new ArrayList(0); } break; default: // Cannot occurs break; } } /** * Stop method. Just releases the reference on the nullable object. * @see org.apache.felix.ipojo.util.DependencyModel#stop() */ public void stop() { super.stop(); m_nullableObject = null; m_proxyObject = null; } /** * The monitored field receives a value. Nothing to do. * @param arg0 POJO setting the value. * @param arg1 field name * @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) { } /** * Implements the timeout policy according to the specified configuration. * @return the object to return when the timeout occurs. */ Object onTimeout() { switch (m_policy) { case TemporalHandler.NULL: case TemporalHandler.NULLABLE: case TemporalHandler.DEFAULT_IMPLEMENTATION: case TemporalHandler.EMPTY: return m_nullableObject; default: // Throws a runtime exception throw new RuntimeException("Service " + getSpecification().getName() + " unavailable : timeout"); } } long getTimeout() { return m_timeout; } /** * Creates proxy object for proxied scalar dependencies. */ private class ProxyFactory extends ClassLoader { /** * Handler classloader, used to load the temporal dependency class. */ private ClassLoader m_handlerCL; /** * Creates the proxy classloader. * @param parent the handler classloader. */ public ProxyFactory(ClassLoader parent) { this.m_handlerCL = parent; } /** * 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_handler.getInstanceManager().getContext().getBundle().loadClass(name); } catch (ClassNotFoundException e) { return m_handlerCL.loadClass(name); } } } }