/******************************************************************************
 * 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.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext;
import org.eclipse.gemini.blueprint.context.DependencyAwareBeanFactoryPostProcessor;
import org.eclipse.gemini.blueprint.context.DependencyInitializationAwareBeanPostProcessor;
import org.eclipse.gemini.blueprint.context.OsgiBundleApplicationContextExecutor;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEventMulticaster;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEventMulticasterAdapter;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextClosedEvent;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextFailedEvent;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextRefreshedEvent;
import org.eclipse.gemini.blueprint.util.OsgiBundleUtils;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.eclipse.gemini.blueprint.util.internal.PrivilegedUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * OSGi-specific application context that delegates the execution of its life cycle methods to a different class. The
 * main reason behind this is to <em>break</em> the startup of the application context in steps that can be executed
 * asynchronously. <p/> <p/> <p/> The {@link #refresh()} and {@link #close()} methods delegate their execution to an
 * {@link OsgiBundleApplicationContextExecutor} class that chooses how to call the lifecycle methods. <p/> <p/> <p/> One
 * can still call the 'traditional' lifecycle methods through {@link #normalRefresh()} and {@link #normalClose()}.
 * 
 * @author Costin Leau
 * @see DelegatedExecutionOsgiBundleApplicationContext
 */
public abstract class AbstractDelegatedExecutionApplicationContext extends AbstractOsgiBundleApplicationContext
		implements DelegatedExecutionOsgiBundleApplicationContext {

	/**
	 * Executor that offers the traditional way of <code>refreshing</code>/ <code>closing</code> of an
	 * ApplicationContext (no conditions have to be met and the refresh happens in only one step).
	 * 
	 * @author Costin Leau
	 */
	private static class NoDependenciesWaitRefreshExecutor implements OsgiBundleApplicationContextExecutor {

		private final DelegatedExecutionOsgiBundleApplicationContext context;

		private NoDependenciesWaitRefreshExecutor(DelegatedExecutionOsgiBundleApplicationContext ctx) {
			context = ctx;
		}

		public void refresh() throws BeansException, IllegalStateException {
			context.normalRefresh();
		}

		public void close() {
			context.normalClose();
		}
	}

	/**
	 * BeanPostProcessor that logs an info message when a bean is created during BeanPostProcessor instantiation, i.e.
	 * when a bean is not eligible for getting processed by all BeanPostProcessors.
	 */
	private class BeanPostProcessorChecker implements BeanPostProcessor {

		private final ConfigurableListableBeanFactory beanFactory;

		private final int beanPostProcessorTargetCount;

		public BeanPostProcessorChecker(ConfigurableListableBeanFactory beanFactory, int beanPostProcessorTargetCount) {
			this.beanFactory = beanFactory;
			this.beanPostProcessorTargetCount = beanPostProcessorTargetCount;
		}

		public Object postProcessBeforeInitialization(Object bean, String beanName) {
			return bean;
		}

		public Object postProcessAfterInitialization(Object bean, String beanName) {
			if (!(bean instanceof BeanPostProcessor)
					&& this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
				if (logger.isInfoEnabled()) {
					logger.info("Bean '" + beanName + "' is not eligible for getting processed by all "
							+ "BeanPostProcessors (for example: not eligible for auto-proxying)");
				}
			}
			return bean;
		}
	}

	/**
	 * Default executor
	 */
	private OsgiBundleApplicationContextExecutor executor = new NoDependenciesWaitRefreshExecutor(this);

	/**
	 * monitor used during refresh/close
	 */
	private final Object startupShutdownMonitor = new Object();

	/**
	 * Delegated multicaster
	 */
	private OsgiBundleApplicationContextEventMulticaster delegatedMulticaster;

	private ContextClassLoaderProvider cclProvider;

	/**
	 * Constructs a new <code>AbstractDelegatedExecutionApplicationContext</code> instance.
	 */
	public AbstractDelegatedExecutionApplicationContext() {
		super();
	}

	/**
	 * Constructs a new <code>AbstractDelegatedExecutionApplicationContext</code> instance.
	 * 
	 * @param parent parent application context
	 */
	public AbstractDelegatedExecutionApplicationContext(ApplicationContext parent) {
		super(parent);
	}

	/**
	 * Delegate execution of refresh method to a third party. This allows breaking the refresh process into several
	 * small pieces providing continuation-like behaviour or completion of the refresh method on several threads, in a
	 * asynch manner. <p/> By default, the refresh method in executed in <em>one go</em> (normal behaviour). <p/>
	 * {@inheritDoc}
	 */
	public void refresh() throws BeansException, IllegalStateException {
		executor.refresh();
	}

	public void normalRefresh() {
		Assert.notNull(getBundleContext(), "bundle context should be set before refreshing the application context");

		try {
			PrivilegedUtils.executeWithCustomTCCL(contextClassLoaderProvider().getContextClassLoader(),
					new PrivilegedUtils.UnprivilegedExecution() {

						public Object run() {
							AbstractDelegatedExecutionApplicationContext.super.refresh();
							sendRefreshedEvent();
							return null;
						}
					});
		} catch (Throwable th) {
			if (logger.isDebugEnabled()) {
				logger.debug("Refresh error", th);
			}
			sendFailedEvent(th);
			// propagate exception to the caller
			// rethrow the problem w/o rewrapping
			if (th instanceof RuntimeException) {
				throw (RuntimeException) th;
			} else {
				throw (Error) th;
			}
		}
	}

	public void normalClose() {
		try {
			PrivilegedUtils.executeWithCustomTCCL(contextClassLoaderProvider().getContextClassLoader(),
					new PrivilegedUtils.UnprivilegedExecution() {

						public Object run() {
							AbstractDelegatedExecutionApplicationContext.super.doClose();
							sendClosedEvent();
							return null;
						}
					});
		} catch (Throwable th) {
			// send failure event
			sendClosedEvent(th);
			// rethrow the problem w/o rewrapping
			if (th instanceof RuntimeException) {
				throw (RuntimeException) th;
			} else {
				throw (Error) th;
			}
		}

	}

	// Adds behaviour for isAvailable flag.
	protected void doClose() {
		executor.close();
	}

	public void startRefresh() {

		try {
			PrivilegedUtils.executeWithCustomTCCL(contextClassLoaderProvider().getContextClassLoader(),
					new PrivilegedUtils.UnprivilegedExecution<Object>() {

						public Object run() {
							synchronized (startupShutdownMonitor) {

								if (ObjectUtils.isEmpty(getConfigLocations())) {
									setConfigLocations(getDefaultConfigLocations());
								}
								if (!OsgiBundleUtils.isBundleActive(getBundle())
										&& !OsgiBundleUtils.isBundleLazyActivated(getBundle())) {
									throw new ApplicationContextException(
											"Unable to refresh application context: bundle is neither active nor lazy-activated but "
													+ OsgiStringUtils.bundleStateAsString(getBundle()));
								}

								ConfigurableListableBeanFactory beanFactory = null;
								// Prepare this context for refreshing.
								prepareRefresh();

								// Tell the subclass to refresh the internal bean
								// factory.
								beanFactory = obtainFreshBeanFactory();

								// Prepare the bean factory for use in this context.
								prepareBeanFactory(beanFactory);

								try {
									// Allows post-processing of the bean factory in
									// context subclasses.
									postProcessBeanFactory(beanFactory);

									// Invoke factory processors registered as beans
									// in the context.
									invokeBeanFactoryPostProcessors(beanFactory);

									// Register bean processors that intercept bean
									// creation.
									registerBeanPostProcessors(beanFactory,
											DependencyInitializationAwareBeanPostProcessor.class, null, false);

									return null;
								} catch (BeansException ex) {
									// Destroy already created singletons to avoid
									// dangling resources.
									beanFactory.destroySingletons();
									cancelRefresh(ex);
									// propagate exception to the caller
									throw ex;
								}
							}
						}
					});
		} catch (Throwable th) {
			if (logger.isDebugEnabled()) {
				logger.debug("Pre refresh error", th);
			}
			// send failure event
			sendFailedEvent(th);
			// rethrow the problem w/o rewrapping
			if (th instanceof RuntimeException) {
				throw (RuntimeException) th;
			} else {
				throw (Error) th;
			}
		}
	}

	public void completeRefresh() {
		try {
			PrivilegedUtils.executeWithCustomTCCL(contextClassLoaderProvider().getContextClassLoader(),
					new PrivilegedUtils.UnprivilegedExecution<Object>() {

						public Object run() {

							synchronized (startupShutdownMonitor) {
								try {
									ConfigurableListableBeanFactory beanFactory = getBeanFactory();

									// Invoke factory processors registered as beans
									// in the context.
									invokeBeanFactoryPostProcessors(beanFactory,
											DependencyAwareBeanFactoryPostProcessor.class, null);

									// Register bean processors that intercept bean
									// creation.
									registerBeanPostProcessors(beanFactory);

									// Initialize message source for this context.
									initMessageSource();

									// Initialize event multicaster for this
									// context.
									initApplicationEventMulticaster();

									// Initialize other special beans in specific
									// context
									// subclasses.
									onRefresh();

									// Check for listener beans and register them.
									registerListeners();

									// Instantiate all remaining (non-lazy-init)
									// singletons.
									finishBeanFactoryInitialization(beanFactory);

									// Last step: publish corresponding event.
									finishRefresh();

									// everything went okay, post notification
									sendRefreshedEvent();
									return null;
								} catch (BeansException ex) {
									// Destroy already created singletons to avoid
									// dangling
									// resources.
									getBeanFactory().destroySingletons();
									cancelRefresh(ex);
									// propagate exception to the caller
									throw ex;
								}
							}
						}
					});
		} catch (Throwable th) {
			if (logger.isDebugEnabled()) {
				logger.debug("Post refresh error", th);
			}
			// post notification
			sendFailedEvent(th);
			// rethrow the problem w/o rewrapping
			if (th instanceof RuntimeException) {
				throw (RuntimeException) th;
			} else {
				throw (Error) th;
			}
		}
	}

	// customized to handle DependencyAwareBeanFactoryPostProcessor classes
	protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		invokeBeanFactoryPostProcessors(beanFactory, BeanFactoryPostProcessor.class,
				DependencyAwareBeanFactoryPostProcessor.class);
	}

	/**
	 * Instantiate and invoke all registered BeanFactoryPostProcessor beans, respecting explicit order if given. <p/>
	 * Must be called before singleton instantiation. Very similar to
	 * {@link AbstractApplicationContext#invokeBeanFactoryPostProcessors} but allowing exclusion of a certain type.
	 * 
	 * @param beanFactory
	 * @param type
	 * @param exclude
	 */
	private void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, Class<?> type,
			Class<?> exclude) {
		// Invoke factory processors registered with the context instance.
		for (Iterator<BeanFactoryPostProcessor> it = getBeanFactoryPostProcessors().iterator(); it.hasNext();) {
			BeanFactoryPostProcessor factoryProcessor = it.next();
			// check the exclude type
			if (type.isInstance(factoryProcessor) && (exclude == null || !exclude.isInstance(factoryProcessor))) {
				factoryProcessor.postProcessBeanFactory(beanFactory);
			}
		}

		// Do not initialize FactoryBeans here: We need to leave all regular
		// beans uninitialized to let the bean factory post-processors apply to
		// them!
		String[] postProcessorNames = beanFactory.getBeanNamesForType(type, true, false);

		// Separate between BeanFactoryPostProcessors that implement
		// PriorityOrdered,
		// Ordered, and the rest.
		List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
		List<String> orderedPostProcessorNames = new ArrayList<String>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
		for (int i = 0; i < postProcessorNames.length; i++) {
			// first check the excluded type
			if (exclude == null || !isTypeMatch(postProcessorNames[i], exclude)) {
				if (isTypeMatch(postProcessorNames[i], PriorityOrdered.class)) {
					priorityOrderedPostProcessors.add(beanFactory.getBean(postProcessorNames[i],
							BeanFactoryPostProcessor.class));
				} else if (isTypeMatch(postProcessorNames[i], Ordered.class)) {
					orderedPostProcessorNames.add(postProcessorNames[i]);
				} else {
					nonOrderedPostProcessorNames.add(postProcessorNames[i]);
				}
			}
		}

		// First, invoke the BeanFactoryPostProcessors that implement
		// PriorityOrdered.
		Collections.sort(priorityOrderedPostProcessors, new OrderComparator());
		invokeBeanFactoryPostProcessors(beanFactory, priorityOrderedPostProcessors);

		// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
		List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
		for (Iterator<String> it = orderedPostProcessorNames.iterator(); it.hasNext();) {
			String postProcessorName = it.next();
			orderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		Collections.sort(orderedPostProcessors, new OrderComparator());
		invokeBeanFactoryPostProcessors(beanFactory, orderedPostProcessors);

		// Finally, invoke all other BeanFactoryPostProcessors.
		List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
		for (Iterator<String> it = nonOrderedPostProcessorNames.iterator(); it.hasNext();) {
			String postProcessorName = it.next();
			nonOrderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class));
		}
		invokeBeanFactoryPostProcessors(beanFactory, nonOrderedPostProcessors);
	}

	/**
	 * Invoke given post processors.
	 * 
	 * @param beanFactory
	 * @param postProcessors
	 */
	private void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory,
			List<BeanFactoryPostProcessor> postProcessors) {

		for (BeanFactoryPostProcessor beanFactoryPostProcessor : postProcessors) {
			beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
		}
	}

	// customized to handle DependencyInitializationAwareBeanPostProcessor
	// classes
	protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		registerBeanPostProcessors(beanFactory, BeanPostProcessor.class,
				DependencyInitializationAwareBeanPostProcessor.class, true);
	}

	/**
	 * Instantiate and invoke all registered BeanPostProcessor beans, respecting explicit order if given. <p/> Must be
	 * called before any instantiation of application beans. Very similar to
	 * {@link AbstractApplicationContext#invokeBeanFactoryPostProcessors} but allowing exclusion of a certain type.
	 */
	protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, Class<?> type,
			Class<?> exclude, boolean check) {
		String[] postProcessorNames = beanFactory.getBeanNamesForType(type, true, false);

		if (check) {
			// Register BeanPostProcessorChecker that logs an info message when
			// a bean is created during BeanPostProcessor instantiation, i.e.
			// when
			// a bean is not eligible for getting processed by all
			// BeanPostProcessors.
			int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
			beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
		}

		// Separate between BeanPostProcessors that implement PriorityOrdered,
		// Ordered, and the rest.
		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
		List<String> orderedPostProcessorNames = new ArrayList<String>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
		for (int i = 0; i < postProcessorNames.length; i++) {
			// check exclude type first
			if (exclude == null || !isTypeMatch(postProcessorNames[i], exclude)) {
				if (isTypeMatch(postProcessorNames[i], PriorityOrdered.class)) {
					priorityOrderedPostProcessors.add(beanFactory.getBean(postProcessorNames[i],
							BeanPostProcessor.class));
				} else if (isTypeMatch(postProcessorNames[i], Ordered.class)) {
					orderedPostProcessorNames.add(postProcessorNames[i]);
				} else {
					nonOrderedPostProcessorNames.add(postProcessorNames[i]);
				}
			}
		}

		// First, register the BeanPostProcessors that implement
		// PriorityOrdered.
		Collections.sort(priorityOrderedPostProcessors, new OrderComparator());
		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

		// Next, register the BeanPostProcessors that implement Ordered.
		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>();

		for (String postProcessorName : orderedPostProcessorNames) {
			orderedPostProcessors.add(getBean(postProcessorName, BeanPostProcessor.class));
		}

		Collections.sort(orderedPostProcessors, new OrderComparator());
		registerBeanPostProcessors(beanFactory, orderedPostProcessors);

		// Finally, register all other BeanPostProcessors.
		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
		for (String postProcessorName : nonOrderedPostProcessorNames) {
			nonOrderedPostProcessors.add(getBean(postProcessorName, BeanPostProcessor.class));
		}
		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
	}

	/**
	 * Register the given BeanPostProcessor beans.
	 */
	private void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory,
			List<BeanPostProcessor> postProcessors) {

		for (BeanPostProcessor postProcessor : postProcessors) {
			beanFactory.addBeanPostProcessor(postProcessor);
		}
	}

	public void setExecutor(OsgiBundleApplicationContextExecutor executor) {
		this.executor = executor;
	}

	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException, BeansException {
	}

	public void setDelegatedEventMulticaster(OsgiBundleApplicationContextEventMulticaster multicaster) {
		this.delegatedMulticaster = multicaster;
	}

	/**
	 * Sets the OSGi multicaster by using a Spring {@link ApplicationEventMulticaster}. This method is added as a
	 * covenience.
	 * 
	 * @param multicaster Spring multi-caster used for propagating OSGi specific events
	 * @see OsgiBundleApplicationContextEventMulticasterAdapter
	 */
	public void setDelegatedEventMulticaster(ApplicationEventMulticaster multicaster) {
		this.delegatedMulticaster = new OsgiBundleApplicationContextEventMulticasterAdapter(multicaster);
	}

	public OsgiBundleApplicationContextEventMulticaster getDelegatedEventMulticaster() {
		return this.delegatedMulticaster;
	}

	private void sendFailedEvent(Throwable cause) {
		if (delegatedMulticaster != null)
			delegatedMulticaster.multicastEvent(new OsgiBundleContextFailedEvent(this, this.getBundle(), cause));
	}

	private void sendRefreshedEvent() {
		if (delegatedMulticaster != null)
			delegatedMulticaster.multicastEvent(new OsgiBundleContextRefreshedEvent(this, this.getBundle()));
	}

	private void sendClosedEvent() {
		if (delegatedMulticaster != null)
			delegatedMulticaster.multicastEvent(new OsgiBundleContextClosedEvent(this, this.getBundle()));
	}

	private void sendClosedEvent(Throwable cause) {
		if (delegatedMulticaster != null)
			delegatedMulticaster.multicastEvent(new OsgiBundleContextClosedEvent(this, this.getBundle(), cause));
	}

	/**
	 * private method used for doing lazy-init-if-not-set for cclProvider
	 */
	private ContextClassLoaderProvider contextClassLoaderProvider() {
		if (cclProvider == null) {
			DefaultContextClassLoaderProvider defaultProvider = new DefaultContextClassLoaderProvider();
			defaultProvider.setBeanClassLoader(getClassLoader());
			cclProvider = defaultProvider;
		}
		return cclProvider;
	}

	/**
	 * Sets the {@link ContextClassLoaderProvider} used by this OSGi application context instance. By default,
	 * {@link DefaultContextClassLoaderProvider} is used.
	 * 
	 * @param contextClassLoaderProvider context class loader provider to use
	 * @see ContextClassLoaderProvider
	 * @see DefaultContextClassLoaderProvider
	 */
	public void setContextClassLoaderProvider(ContextClassLoaderProvider contextClassLoaderProvider) {
		this.cclProvider = contextClassLoaderProvider;
	}
}