/******************************************************************************
 * 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.importer.support;

import java.lang.reflect.Modifier;
import java.util.LinkedHashSet;
import java.util.Set;

import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.context.support.internal.classloader.ClassLoaderFactory;
import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceInvoker;
import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceStaticInterceptor;
import org.eclipse.gemini.blueprint.service.util.internal.aop.ServiceTCCLInterceptor;
import org.eclipse.gemini.blueprint.util.OsgiServiceReferenceUtils;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.eclipse.gemini.blueprint.util.internal.ClassUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.springframework.util.ObjectUtils;

/**
 * @author Costin Leau
 * 
 */
class StaticServiceProxyCreator extends AbstractServiceProxyCreator {

	private static final Log log = LogFactory.getLog(StaticServiceProxyCreator.class);
	/** greedy proxying mechanism */
	private final boolean greedyProxying;
	/** should greedy proxying consider just interfaces ? */
	private final boolean interfacesOnlyProxying;
	/** use SpringDM or Blueprint exceptions ? */
	private final boolean useBlueprintExceptions;

	/**
	 * Constructs a new <code>StaticServiceProxyCreator</code> instance.
	 * 
	 * @param classes
	 * @param aopClassLoader
	 * @param bundleContext
	 * @param iccl
	 * @param greedyProxying
	 */
	StaticServiceProxyCreator(Class<?>[] classes, ClassLoader aopClassLoader, ClassLoader bundleClassLoader,
			BundleContext bundleContext, ImportContextClassLoaderEnum iccl, boolean greedyProxying,
			boolean useBlueprintExceptions) {
		super(classes, aopClassLoader, bundleClassLoader, bundleContext, iccl);
		this.greedyProxying = greedyProxying;
		this.useBlueprintExceptions = useBlueprintExceptions;

		boolean onlyInterfaces = true;

		// see if cglib is used or not
		for (int i = 0; i < classes.length; i++) {
			if (!classes[i].isInterface())
				onlyInterfaces = false;
		}

		interfacesOnlyProxying = onlyInterfaces;

		String msg = (interfacesOnlyProxying ? "NOT" : "");

		if (log.isDebugEnabled())
			log.debug("Greedy proxying will " + msg + " consider exposed classes");
	}

	ServiceInvoker createDispatcherInterceptor(ServiceReference reference) {
		ServiceStaticInterceptor interceptor = new ServiceStaticInterceptor(bundleContext, reference);
		interceptor.setUseBlueprintExceptions(useBlueprintExceptions);
		return interceptor;
	}

	Advice createServiceProviderTCCLAdvice(ServiceReference reference) {
		Bundle bundle = reference.getBundle();
		// if reference is dead already, it's impossible to provide the service
		// class loader
		if (bundle == null)
			return null;

		return new ServiceTCCLInterceptor(ClassLoaderFactory.getBundleClassLoaderFor(bundle));
	}

	/**
	 * Apply 'greedy' proxying by discovering the exposed classes.
	 * 
	 * @param ref
	 * @return
	 */
	Class<?>[] discoverProxyClasses(ServiceReference ref) {
		boolean trace = log.isTraceEnabled();

		if (trace)
			log.trace("Generating greedy proxy for service " + OsgiStringUtils.nullSafeToString(ref));

		String[] classNames = OsgiServiceReferenceUtils.getServiceObjectClasses(ref);

		if (trace)
			log.trace("Discovered raw classes " + ObjectUtils.nullSafeToString(classNames));

		// try to get as many classes as possible
		Class<?>[] classes = ClassUtils.loadClassesIfPossible(classNames, classLoader);

		if (trace)
			log.trace("Visible classes are " + ObjectUtils.nullSafeToString(classes));

		// exclude final classes
		classes = ClassUtils.excludeClassesWithModifier(classes, Modifier.FINAL);

		if (trace)
			log.trace("Filtering out final classes; left out with " + ObjectUtils.nullSafeToString(classes));

		// remove classes if needed
		if (interfacesOnlyProxying) {
			Set<Class<?>> clazzes = new LinkedHashSet<Class<?>>(classes.length);
			for (int classIndex = 0; classIndex < classes.length; classIndex++) {
				Class<?> clazz = classes[classIndex];
				if (clazz.isInterface())
					clazzes.add(clazz);
			}
			if (trace)
				log.trace("Filtering out concrete classes; left out with " + clazzes);

			classes = (Class[]) clazzes.toArray(new Class[clazzes.size()]);
		}

		// remove class duplicates/parents
		classes = ClassUtils.removeParents(classes);

		if (trace)
			log.trace("Filtering out parent classes; left out with " + classes);

		return classes;
	}

	Class<?>[] getInterfaces(ServiceReference reference) {
		if (greedyProxying) {
			Class<?>[] clazzes = discoverProxyClasses(reference);
			if (log.isTraceEnabled())
				log.trace("generating 'greedy' service proxy using classes " + ObjectUtils.nullSafeToString(clazzes)
						+ " over " + ObjectUtils.nullSafeToString(this.classes));
			return clazzes;
		}
		// no greedy proxy, return just the configured classes
		return classes;
	}
}