content: /*
   * 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());
  	}
  
  }
  