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

import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
import org.eclipse.gemini.blueprint.extender.internal.util.BundleUtils;
import org.eclipse.gemini.blueprint.util.OsgiBundleUtils;
import org.eclipse.gemini.blueprint.util.OsgiServiceUtils;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.springframework.util.Assert;
import org.xml.sax.EntityResolver;

/**
 * Support class that deals with namespace parsers discovered inside Spring bundles.
 * 
 * @author Costin Leau
 * 
 */
public class NamespaceManager implements InitializingBean, DisposableBean {

	private static final Log log = LogFactory.getLog(NamespaceManager.class);

	/** The set of all namespace plugins known to the extender */
	private NamespacePlugins namespacePlugins;

	/**
	 * ServiceRegistration object returned by OSGi when registering the NamespacePlugins instance as a service
	 */
	private ServiceRegistration nsResolverRegistration, enResolverRegistration = null;

	/** OSGi Environment */
	private final BundleContext context;

	private final String extenderInfo;

	private static final String META_INF = "META-INF/";

	private static final String SPRING_HANDLERS = "spring.handlers";

	private static final String SPRING_SCHEMAS = "spring.schemas";

	/**
	 * Constructs a new <code>NamespaceManager</code> instance.
	 * 
	 * @param context containing bundle context
	 */
	public NamespaceManager(BundleContext context) {
		this.context = context;

		extenderInfo =
				context.getBundle().getSymbolicName() + "|" + OsgiBundleUtils.getBundleVersion(context.getBundle());

		// detect package admin
		this.namespacePlugins = new NamespacePlugins();
	}

	/**
	 * Registers the namespace plugin handler if this bundle defines handler mapping or schema mapping resources.
	 * 
	 * <p/> This method considers only the bundle space and not the class space.
	 * 
	 * @param bundle target bundle
	 * @param isLazyBundle indicator if the bundle analyzed is lazily activated
	 */
	public void maybeAddNamespaceHandlerFor(Bundle bundle, boolean isLazyBundle) {
		// Ignore system bundle
		if (OsgiBundleUtils.isSystemBundle(bundle)) {
			return;
		}

		// Ignore non-wired Spring DM bundles
		if ("org.eclipse.gemini.blueprint.core".equals(bundle.getSymbolicName())
				&& !bundle.equals(BundleUtils.getDMCoreBundle(context))) {
			return;
		}

		boolean debug = log.isDebugEnabled();
		boolean trace = log.isTraceEnabled();
		// FIXME: Blueprint uber bundle temporary hack
		// since embedded libraries are not discovered by findEntries and inlining them doesn't work
		// (due to resource classes such as namespace handler definitions)
		// we use getResource

		boolean hasHandlers = false, hasSchemas = false;

		if (trace) {
			log.trace("Inspecting bundle " + bundle + " for Spring namespaces");
		}
		// extender/RFC 124 bundle
		if (context.getBundle().equals(bundle)) {

			try {
				Enumeration<?> handlers = bundle.getResources(META_INF + SPRING_HANDLERS);
				Enumeration<?> schemas = bundle.getResources(META_INF + SPRING_SCHEMAS);

				hasHandlers = handlers != null;
				hasSchemas = schemas != null;

				if (hasHandlers && debug) {
					log.debug("Found namespace handlers: " + Collections.list(schemas));
				}
			} catch (IOException ioe) {
				log.warn("Cannot discover own namespaces", ioe);
			}
		} else {
			hasHandlers = bundle.findEntries(META_INF, SPRING_HANDLERS, false) != null;
			hasSchemas = bundle.findEntries(META_INF, SPRING_SCHEMAS, false) != null;
		}

		// if the bundle defines handlers
		if (hasHandlers) {

			if (trace)
				log.trace("Bundle " + bundle + " provides Spring namespace handlers...");

			if (isLazyBundle) {
				this.namespacePlugins.addPlugin(bundle, isLazyBundle, true);
			} else {
				// check type compatibility between the bundle's and spring-extender's spring version
				if (hasCompatibleNamespaceType(bundle)) {
					this.namespacePlugins.addPlugin(bundle, isLazyBundle, false);
				} else {
					if (debug)
						log.debug("Bundle [" + OsgiStringUtils.nullSafeNameAndSymName(bundle)
								+ "] declares namespace handlers but is not compatible with extender [" + extenderInfo
								+ "]; ignoring...");
				}
			}
		} else {
			// bundle declares only schemas, add it though the handlers might not be compatible...
			if (hasSchemas) {
				this.namespacePlugins.addPlugin(bundle, isLazyBundle, false);
				if (trace)
					log.trace("Bundle " + bundle + " provides Spring schemas...");
			}
		}
	}

	private boolean hasCompatibleNamespaceType(Bundle bundle) {
		return namespacePlugins.isTypeCompatible(bundle);
	}

	/**
	 * Removes the target bundle from the set of those known to provide handler or schema mappings.
	 * 
	 * @param bundle handler bundle
	 */
	public void maybeRemoveNameSpaceHandlerFor(Bundle bundle) {
		Assert.notNull(bundle);
		boolean removed = this.namespacePlugins.removePlugin(bundle);
		if (removed && log.isDebugEnabled()) {
			log.debug("Removed namespace handler resolver for " + OsgiStringUtils.nullSafeNameAndSymName(bundle));
		}
	}

	/**
	 * Registers the NamespacePlugins instance as an Osgi Resolver service
	 */
	private void registerResolverServices() {
		if (log.isDebugEnabled()) {
			log.debug("Registering Spring NamespaceHandlerResolver and EntityResolver...");
		}

		Bundle bnd = BundleUtils.getDMCoreBundle(context);
		Properties props = null;
		if (bnd != null) {
			props = new Properties();
			props.setProperty(BundleUtils.DM_CORE_ID, "" + bnd.getBundleId());
			props.setProperty(BundleUtils.DM_CORE_TS, "" + bnd.getLastModified());
		}
		nsResolverRegistration =
				context.registerService(new String[] { NamespaceHandlerResolver.class.getName() },
						this.namespacePlugins, props);

		enResolverRegistration =
				context.registerService(new String[] { EntityResolver.class.getName() }, this.namespacePlugins, props);

	}

	/**
	 * Unregisters the NamespaceHandler and EntityResolver service
	 */
	private void unregisterResolverService() {

		boolean result = OsgiServiceUtils.unregisterService(nsResolverRegistration);
		result = result || OsgiServiceUtils.unregisterService(enResolverRegistration);

		if (result) {
			if (log.isDebugEnabled())
				log.debug("Unregistering Spring NamespaceHandler and EntityResolver service");
		}

		this.nsResolverRegistration = null;
		this.enResolverRegistration = null;
	}

	public NamespacePlugins getNamespacePlugins() {
		return namespacePlugins;
	}

	//
	// Lifecycle methods
	//

	public void afterPropertiesSet() {
		registerResolverServices();
	}

	public void destroy() {
		unregisterResolverService();
		this.namespacePlugins.destroy();
		this.namespacePlugins = null;
	}
}