/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.context.support.internal.classloader;

import java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.WeakHashMap;

import org.springframework.aop.framework.ProxyFactory;

/**
 * Default implementation for {@link InternalAopClassLoaderFactory}. Uses an internal {@link WeakHashMap} to cache aop
 * class loaders to prevent duplicated copies.
 * 
 * @author Costin Leau
 */
class CachingAopClassLoaderFactory implements InternalAopClassLoaderFactory {

	private static final String CGLIB_CLASS = "net.sf.cglib.proxy.Enhancer";
	/** CGLIB class (if it's present) */
	private final Class<?> cglibClass;

	/** class loader -> aop class loader cache */
	private final Map<ClassLoader, WeakReference<ChainedClassLoader>> cache =
			new WeakHashMap<ClassLoader, WeakReference<ChainedClassLoader>>();

	CachingAopClassLoaderFactory() {
		// load CGLIB through Spring-AOP
		ClassLoader springAopClassLoader = ProxyFactory.class.getClassLoader();
		Class<?> clazz = null;
		try {
			clazz = springAopClassLoader.loadClass(CGLIB_CLASS);
		} catch (ClassNotFoundException cnfe) {
			// assume cglib is not present
		}
		cglibClass = clazz;
	}

	public ChainedClassLoader createClassLoader(final ClassLoader classLoader) {
		// search key (should be fast as the default classloader (BundleDelegatingClassLoader) has identity
		// equality/hashcode)
		synchronized (cache) {
			ChainedClassLoader aopClassLoader = null;
			WeakReference<ChainedClassLoader> loaderReference = cache.get(classLoader);

			if (loaderReference != null) {
				aopClassLoader = loaderReference.get();
			}

			// no associated class loader found, create one and put it in the cache
			if (aopClassLoader == null) {
				if (System.getSecurityManager() != null) {
					aopClassLoader = AccessController.doPrivileged(new PrivilegedAction<ChainedClassLoader>() {
						public ChainedClassLoader run() {
							return doCreateClassLoader(classLoader);
						}
					});
				} else {
					aopClassLoader = doCreateClassLoader(classLoader);
				}
				// save the class loader as a weak reference (since it refers to the given class loader)
				cache.put(classLoader, new WeakReference<ChainedClassLoader>(aopClassLoader));
			}
			return aopClassLoader;
		}
	}

	private ChainedClassLoader doCreateClassLoader(ClassLoader classLoader) {
		// use the given class loader, spring-aop, cglib (if available) and then spring-dm core class loader (for its
		// infrastructure interfaces)
		if (cglibClass != null) {
			return new ChainedClassLoader(new ClassLoader[] { classLoader, ProxyFactory.class.getClassLoader(),
					cglibClass.getClassLoader(), CachingAopClassLoaderFactory.class.getClassLoader() });
		} else {
			return new ChainedClassLoader(new ClassLoader[] { classLoader, ProxyFactory.class.getClassLoader(),
					CachingAopClassLoaderFactory.class.getClassLoader() });
		}
	}
}