/* * 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.aries.async.impl; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import org.osgi.framework.Bundle; import org.osgi.framework.ServiceReference; import org.osgi.framework.wiring.BundleWiring; import org.osgi.service.async.Async; import org.osgi.service.log.LogService; import org.osgi.util.promise.Promise; import org.osgi.util.tracker.ServiceTracker; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Factory; public class AsyncService implements Async { private static final class CGLibAwareClassLoader extends ClassLoader { private final ClassLoader serviceTypeLoader; private CGLibAwareClassLoader(Bundle registeringBundle) { this.serviceTypeLoader = registeringBundle.adapt(BundleWiring.class).getClassLoader(); } private CGLibAwareClassLoader(ClassLoader loader) { this.serviceTypeLoader = loader; } @Override protected Class<?> findClass(String var0) throws ClassNotFoundException { if(var0.startsWith("net.sf.cglib")) { return AsyncService.class.getClassLoader().loadClass(var0); } else { return serviceTypeLoader.loadClass(var0); } } } /** * It is important to use both weak keys *and* values in this map. The * key must be weakly held because it is typically a type from another * bundle, and would represent a classloader leak if held after that * bundle was uninstalled. The value must be weak because it either * extends or implements the type that is the key, and so holds a strong * reference to the key, which again would cause a leak. * * This cache may drop the value type if no mediators are held, however in * this situation we can simply create a new value without risking exploding * the heap. */ private final WeakHashMap<Class<?>, WeakReference<Class<?>>> proxyLoaderCache = new WeakHashMap<Class<?>, WeakReference<Class<?>>>(); private final Bundle clientBundle; private final ConcurrentMap<Thread, MethodCall> invocations = new ConcurrentHashMap<Thread, MethodCall>(); private final ExecutorService executor; private final ScheduledExecutorService ses; private final ServiceTracker<LogService, LogService> logServiceTracker; public AsyncService(Bundle clientBundle, ExecutorService executor, ScheduledExecutorService ses, ServiceTracker<LogService, LogService> logServiceTracker) { super(); this.clientBundle = clientBundle; this.executor = executor; this.ses = ses; this.logServiceTracker = logServiceTracker; } void clear() { proxyLoaderCache.clear(); } public <T> T mediate(final T service, final Class<T> iface) { return AccessController.doPrivileged(new PrivilegedAction<T>() { public T run() { return privMediate(service, iface); } }); } @SuppressWarnings("unchecked") private <T> T privMediate(T service, Class<T> iface) { TrackingInvocationHandler handler = new TrackingInvocationHandler(this, clientBundle, logServiceTracker, service); synchronized(proxyLoaderCache) { T toReturn = cachedMediate(iface, handler); if(toReturn != null) { return toReturn; } else if(iface.isInterface()) { toReturn = (T) Proxy.newProxyInstance( new ClassLoader(service.getClass().getClassLoader()){}, new Class[] {iface}, handler); } else { toReturn = (T) proxyClass(iface, handler, new CGLibAwareClassLoader(service.getClass().getClassLoader())); } proxyLoaderCache.put(iface, new WeakReference<Class<?>>(toReturn.getClass())); return toReturn; } } @SuppressWarnings("unchecked") private <T> T cachedMediate(Class<T> iface, TrackingInvocationHandler handler) { WeakReference<Class<?>> weakReference = proxyLoaderCache.get(iface); Class<?> cached = weakReference == null ? null : weakReference.get(); if(cached != null) { if(iface.isInterface()) { try { return (T) cached.getConstructor(InvocationHandler.class) .newInstance(handler); } catch (Exception e) { throw new IllegalArgumentException("Unable to mediate interface: " + iface, e); } } else { try { T t = (T) cached.getConstructor().newInstance(); ((Factory)t).setCallbacks(new Callback[] {handler}); return t; } catch (Exception e) { throw new IllegalArgumentException("Unable to mediate class: " + iface, e); } } } return null; } public <T> T mediate(final ServiceReference<? extends T> ref, final Class<T> iface) { return AccessController.doPrivileged(new PrivilegedAction<T>() { public T run() { return privMediate(ref, iface); } }); } @SuppressWarnings("unchecked") private <T> T privMediate(ServiceReference<? extends T> ref, Class<T> iface) { TrackingInvocationHandler handler = new TrackingInvocationHandler(this, clientBundle, logServiceTracker, ref); synchronized(proxyLoaderCache) { T toReturn = cachedMediate(iface, handler); if(toReturn != null) { return toReturn; } else if(iface.isInterface()) { toReturn = (T) Proxy.newProxyInstance( new ClassLoader(iface.getClassLoader()){}, new Class[] {iface}, handler); } else { toReturn = (T) proxyClass(iface, handler, new CGLibAwareClassLoader(iface.getClassLoader())); } proxyLoaderCache.put(iface, new WeakReference<Class<?>>(toReturn.getClass())); return toReturn; } } private Object proxyClass(Class<?> mostSpecificClass, TrackingInvocationHandler handler, ClassLoader classLoader) { acceptClassType(mostSpecificClass); Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(classLoader); enhancer.setSuperclass(mostSpecificClass); enhancer.setCallback(handler); return enhancer.create(); } private void acceptClassType(Class<?> type) { if(Modifier.isFinal(type.getModifiers())) { throw new IllegalArgumentException("The type " + type.getName() + " is final"); } try { type.getConstructor(); } catch (NoSuchMethodException nsme) { throw new IllegalArgumentException("The type " + type.getName() + " has no zero-argument constructor", nsme); } Class<?> toCheck = type; while(toCheck != Object.class) { for(Method m : toCheck.getDeclaredMethods()) { if(Modifier.isFinal(m.getModifiers())) { throw new IllegalArgumentException("The type hierarchy for " + type.getName() + " has a final method " + m.getName() + " defined on " + toCheck.getName()); } } toCheck = toCheck.getSuperclass(); } } public <T> Promise<T> call(T call) throws IllegalStateException { MethodCall currentInvocation = consumeCurrentInvocation(); if(currentInvocation == null) throw new IllegalStateException("Incorrect API usage - this thread has no pending method calls"); return currentInvocation.invokeAsynchronously(clientBundle, executor, ses); } public Promise<?> call() throws IllegalStateException { return call(null); } public Promise<Void> execute() throws IllegalStateException { MethodCall currentInvocation = consumeCurrentInvocation(); if(currentInvocation == null) throw new IllegalStateException("Incorrect API usage - this thread has no pending method calls"); return currentInvocation.fireAndForget(clientBundle, executor, ses); } void registerInvocation(MethodCall invocation) { if(invocations.putIfAbsent(Thread.currentThread(), invocation) != null) { invocations.remove(Thread.currentThread()); throw new IllegalStateException("Incorrect API usage - this thread already has a pending method call"); } } MethodCall consumeCurrentInvocation() { return invocations.remove(Thread.currentThread()); } }