/******************************************************************************
 * 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;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessController;
import java.security.PrivilegedAction;

import org.eclipse.gemini.blueprint.util.OsgiFilterUtils;
import org.eclipse.gemini.blueprint.util.OsgiServiceReferenceUtils;
import org.eclipse.gemini.blueprint.util.internal.ClassUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

/**
 * Utility class for easy, but reliable, tracking of OSGi services. It does service tracking internally but wraps the
 * logic into a proxy. Note that this class uses raw JDK proxies and thus is usable only with interfaces. This allows to
 * create simple proxies with minimal class dependencies.
 * 
 * <p/> This class can be seen as a much shorter, less featured version of
 * {@link org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceProxyFactoryBean} . It is intended for the
 * bootstrap areas of the project where no classloading or listeners are required.
 * 
 * @author Costin Leau
 * 
 */
abstract class TrackingUtil {

	/**
	 * JDK Proxy invocation handler that delegates all calls to services found in the OSGi space at the time of the
	 * call, falling back to a given object.
	 * 
	 * @author Costin Leau
	 */
	private static class OsgiServiceHandler implements InvocationHandler {

		private final Object fallbackObject;
		private final BundleContext context;
		private final String filterClassName;
		private final String filter;
		private final boolean securityOn;

		/**
		 * flag used to bypass the OSGi space if the context becomes unavailable
		 */
		private volatile boolean bundleContextInvalidated = false;

		public OsgiServiceHandler(Object fallbackObject, BundleContext bundleContext, String filterClass, String filter) {
			this.fallbackObject = fallbackObject;
			this.context = bundleContext;
			this.filterClassName = filterClass;
			this.filter = filter;
			this.securityOn = (System.getSecurityManager() != null);
		}

		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			// fast dispatch
			if (method.getName().equals("equals")) {
				// Only consider equal when proxies are identical.
				return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
			} else if (method.getName().equals("hashCode")) {
				// Use hashCode of Session proxy.
				return new Integer(System.identityHashCode(proxy));
			}

			Object target = null;

			if (!bundleContextInvalidated) {
				try {
					if (securityOn) {
						target = AccessController.doPrivileged(new PrivilegedAction<Object>() {
							public Object run() {
								return getTarget(context, filter);
							}
						});
					} else {
						target = getTarget(context, filter);
					}
				} catch (IllegalStateException ise) {
					// context has been invalidated
					bundleContextInvalidated = true;
				}
			}

			if (target == null) {
				target = fallbackObject;
			}

			// re-route call to the target
			try {
				Object result = method.invoke(target, args);
				return result;
			} catch (InvocationTargetException ex) {
				throw ex.getTargetException();
			}
		}

		private Object getTarget(BundleContext context, String filter) {
			ServiceReference ref = OsgiServiceReferenceUtils.getServiceReference(context, filterClassName, filter);
			return (ref != null ? context.getService(ref) : null);
		}
	}

	/**
	 * Returns a proxy that on each call seeks the relevant OSGi service and delegates the method invocation to it. In
	 * case no service is found, the fallback object is used.
	 * 
	 * <p/> Since JDK proxies are used to create services only interfaces are used.
	 * 
	 * @param classes array of classes used during proxy weaving
	 * @param filter OSGi filter (can be null)
	 * @param classLoader class loader to use - normally classes.getClassLoader()
	 * @param context bundle context used for searching the services
	 * @param fallbackObject object to fall back onto if no OSGi service is found.
	 * @return the proxy doing the lookup on each method invocation
	 */
	static Object getService(Class<?>[] classes, String filter, ClassLoader classLoader, BundleContext context,
			Object fallbackObject) {
		// mold the proxy
		String flt = OsgiFilterUtils.unifyFilter(classes, filter);

		return Proxy.newProxyInstance(classLoader, classes, new OsgiServiceHandler(fallbackObject, context, ClassUtils
				.getParticularClass(classes).getName(), flt));
	}
}