/*******************************************************************************
 * Copyright (c) 2005, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.metatype.impl;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Hashtable;
import javax.xml.parsers.*;
import org.eclipse.equinox.metatype.EquinoxMetaTypeInformation;
import org.eclipse.equinox.metatype.EquinoxMetaTypeService;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.*;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
import org.xml.sax.SAXException;

/**
 * Implementation of MetaTypeService
 */
public class MetaTypeServiceImpl implements EquinoxMetaTypeService, SynchronousBundleListener {

	SAXParserFactory _parserFactory;
	private Hashtable<Long, EquinoxMetaTypeInformation> _mtps = new Hashtable<Long, EquinoxMetaTypeInformation>(7);

	private final LogTracker logger;
	private final ServiceTracker<Object, Object> metaTypeProviderTracker;

	/**
	 * Constructor of class MetaTypeServiceImpl.
	 */
	public MetaTypeServiceImpl(SAXParserFactory parserFactory, LogTracker logger, ServiceTracker<Object, Object> metaTypeProviderTracker) {
		this._parserFactory = parserFactory;
		this.logger = logger;
		this.metaTypeProviderTracker = metaTypeProviderTracker;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.osgi.service.metatype.MetaTypeService#getMetaTypeInformation(org.osgi.framework.Bundle)
	 */
	public EquinoxMetaTypeInformation getMetaTypeInformation(Bundle bundle) {
		return getMetaTypeProvider(bundle);
	}

	/**
	 * Internal Method - to get MetaTypeProvider object.
	 */
	private EquinoxMetaTypeInformation getMetaTypeProvider(final Bundle b) {
		// Avoid synthetic accessor method warnings.
		final LogTracker loggerTemp = this.logger;
		final ServiceTracker<Object, Object> tracker = this.metaTypeProviderTracker;
		Long bID = Long.valueOf(b.getBundleId());
		synchronized (_mtps) {
			if (_mtps.containsKey(bID))
				return _mtps.get(bID);
			EquinoxMetaTypeInformation mti = AccessController.doPrivileged(new PrivilegedAction<EquinoxMetaTypeInformation>() {
				public EquinoxMetaTypeInformation run() {
					MetaTypeInformationImpl impl = null;
					try {
						impl = new MetaTypeInformationImpl(b, newParser(), loggerTemp);
					} catch (Exception e) {
						loggerTemp.log(LogService.LOG_ERROR, NLS.bind(MetaTypeMsg.METADATA_PARSE_ERROR, b.getBundleId(), b.getSymbolicName()), e);
					}
					if (impl == null || !impl._isThereMeta)
						return new MetaTypeProviderTracker(b, loggerTemp, tracker);
					return impl;
				}
			});
			_mtps.put(bID, mti);
			return mti;
		}
	}

	SAXParser newParser() throws ParserConfigurationException, SAXException {
		boolean namespaceAware = _parserFactory.isNamespaceAware();
		boolean validating = _parserFactory.isValidating();
		// Always want a non-validating parser.
		_parserFactory.setValidating(false);
		try {
			// If the factory is already namespace aware, we know it can create namespace aware parsers
			// because that was checked in the service tracker.
			if (namespaceAware) {
				return _parserFactory.newSAXParser();
			}
			// If the factory is not already namespace aware, it may or may not be able to create
			// namespace aware parsers.
			_parserFactory.setNamespaceAware(true);
			try {
				return _parserFactory.newSAXParser();
			} catch (Exception e) {
				// Factory cannot create namespace aware parsers. Go with the last resort.
				_parserFactory.setNamespaceAware(false);
				return _parserFactory.newSAXParser();
			}
		} finally {
			// Restore the previous settings in all cases.
			_parserFactory.setNamespaceAware(namespaceAware);
			_parserFactory.setValidating(validating);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent)
	 */
	public void bundleChanged(BundleEvent event) {

		int type = event.getType();
		Long bID = Long.valueOf(event.getBundle().getBundleId());

		switch (type) {
			case BundleEvent.UPDATED :
			case BundleEvent.UNINSTALLED :
				_mtps.remove(bID);
				break;
			case BundleEvent.INSTALLED :
			case BundleEvent.RESOLVED :
			case BundleEvent.STARTED :
			case BundleEvent.STOPPED :
			case BundleEvent.UNRESOLVED :
			default :
				break;
		}
	}
}
