/******************************************************************************
 * 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.service.exporter.support.internal.support;

import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;

import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.service.util.internal.aop.ProxyUtils;
import org.eclipse.gemini.blueprint.service.util.internal.aop.ServiceTCCLInterceptor;
import org.eclipse.gemini.blueprint.util.DebugUtils;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;

/**
 * ServiceFactory used for publishing the service beans. Acts as a a wrapper around special beans (such as
 * ServiceFactory). Additionally, used to create TCCL managing proxies.
 * 
 * @author Costin Leau
 */
public class PublishingServiceFactory implements ServiceFactory {

	/** logger */
	private static final Log log = LogFactory.getLog(PublishingServiceFactory.class);

	/** proxy cache in case the given bean has a non-singleton scope */
	private final Map<Object, WeakReference<Object>> proxyCache;

	private final LazyTargetResolver targetResolver;
	private final Class<?>[] classes;
	private final boolean createTCCLProxy;
	private final ClassLoader classLoader;
	private final ClassLoader aopClassLoader;
	private final BundleContext bundleContext;
	private final Object lock = new Object();

	/**
	 * Constructs a new <code>PublishingServiceFactory</code> instance. Since its an internal class, this constructor
	 * accepts a number of parameters to sacrifice readability for thread-safety.
	 * 
	 * @param classes
	 * @param target
	 * @param beanFactory
	 * @param targetBeanName
	 * @param createTCCLProxy
	 * @param classLoader
	 * @param aopClassLoader
	 * @param bundleContext
	 */
	public PublishingServiceFactory(LazyTargetResolver targetResolver, Class<?>[] classes, boolean createTCCLProxy,
			ClassLoader classLoader, ClassLoader aopClassLoader, BundleContext bundleContext) {
		super();

		this.targetResolver = targetResolver;
		this.classes = classes;

		this.createTCCLProxy = createTCCLProxy;
		this.classLoader = classLoader;
		this.aopClassLoader = aopClassLoader;
		this.bundleContext = bundleContext;

		proxyCache = (createTCCLProxy ? new WeakHashMap<Object, WeakReference<Object>>(4) : null);
	}

	public Object getService(Bundle bundle, ServiceRegistration serviceRegistration) {
		if (log.isTraceEnabled()) {
			log.trace("Get service called by bundle " + OsgiStringUtils.nullSafeName(bundle) + " on registration "
					+ OsgiStringUtils.nullSafeToString(serviceRegistration.getReference()));
		}

		targetResolver.activate();
		Object bn = targetResolver.getBean();

		// handle SF beans
		if (bn instanceof ServiceFactory) {
			bn = ((ServiceFactory) bn).getService(bundle, serviceRegistration);
		}

		if (createTCCLProxy) {
			// check proxy cache
			synchronized (proxyCache) {
				WeakReference<Object> value = proxyCache.get(bn);
				Object proxy = null;
				if (value != null) {
					proxy = value.get();
				}
				if (proxy == null) {
					proxy = createCLLProxy(bn);
					proxyCache.put(bn, new WeakReference<Object>(proxy));
				}
				bn = proxy;
			}
		}

		return bn;
	}

	/**
	 * Proxy the target object with an interceptor that manages the context classloader. This should be applied only if
	 * such management is needed. Additionally, this method uses a cache to prevent multiple proxies to be created for
	 * the same object.
	 * 
	 * @param target
	 * @return
	 */
	private Object createCLLProxy(final Object target) {
		try {
			return ProxyUtils.createProxy(classes, target, aopClassLoader, bundleContext,
					new Advice[] { new ServiceTCCLInterceptor(classLoader) });
		} catch (Throwable th) {
			log.error("Cannot create TCCL managed proxy; falling back to the naked object", th);
			if (th instanceof NoClassDefFoundError) {
				NoClassDefFoundError ncdfe = (NoClassDefFoundError) th;
				if (log.isWarnEnabled()) {
					DebugUtils.debugClassLoadingThrowable(ncdfe, bundleContext.getBundle(), classes);
				}
				throw ncdfe;
			}
		}

		return target;
	}

	public void ungetService(Bundle bundle, ServiceRegistration serviceRegistration, Object service) {

		if (log.isTraceEnabled()) {
			log.trace("Unget service called by bundle " + OsgiStringUtils.nullSafeName(bundle) + " on registration "
					+ OsgiStringUtils.nullSafeToString(serviceRegistration.getReference()));
		}

		Class<?> type = targetResolver.getType();
		// handle SF beans
		if (ServiceFactory.class.isAssignableFrom(type)) {
			ServiceFactory sf = (ServiceFactory) targetResolver.getBean();
			sf.ungetService(bundle, serviceRegistration, service);
		}

		if (createTCCLProxy) {
			synchronized (proxyCache) {
				// trigger purging of unused entries
				proxyCache.size();
			}
		}
	}
}