/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.config.xml.osgi; import java.net.URL; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import org.eclipse.smarthome.config.core.BundleProcessor; import org.eclipse.smarthome.config.xml.util.XmlDocumentReader; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.ServiceRegistration; import org.osgi.util.tracker.BundleTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@link XmlDocumentBundleTracker} tracks files in the specified XML folder * of modules and tries to parse them as XML file with the specified * {@link XmlDocumentReader}. Any converted XML files are assigned to its * according bundle and added to an {@link XmlDocumentProvider} for further * processing. For each module an own {@link XmlDocumentProvider} is created by * using the specified {@link XmlDocumentProviderFactory}. * * @author Michael Grammling - Initial Contribution * @author Benedikt Niehues - Changed resource handling so that resources can be * patched by fragments. * * @param <T> * the result type of the conversion */ public class XmlDocumentBundleTracker<T> extends BundleTracker<Bundle> { private final Logger logger = LoggerFactory.getLogger(XmlDocumentBundleTracker.class); private final String xmlDirectory; private final XmlDocumentReader<T> xmlDocumentTypeReader; private final XmlDocumentProviderFactory<T> xmlDocumentProviderFactory; private final Map<Bundle, XmlDocumentProvider<T>> bundleDocumentProviderMap; private final AbstractAsyncBundleProcessor asyncLoader; private BundleContext bundleContext; @SuppressWarnings("rawtypes") private ServiceRegistration asyncLoaderRegistration; /** * Creates a new instance of this class with the specified parameters. * * @param bundleContext * the bundle context to be used for tracking bundles (must not * be null) * @param xmlDirectory * the directory to search for XML files (must neither be null, * nor empty) * @param xmlDocumentTypeReader * the XML converter to be used (must not be null) * @param xmlDocumentProviderFactory * the result object processor to be used (must not be null) * * @throws IllegalArgumentException * if any of the arguments is null */ public XmlDocumentBundleTracker(BundleContext bundleContext, String xmlDirectory, XmlDocumentReader<T> xmlDocumentTypeReader, XmlDocumentProviderFactory<T> xmlDocumentProviderFactory) throws IllegalArgumentException { super(bundleContext, Bundle.ACTIVE, null); this.bundleContext = bundleContext; if (bundleContext == null) { throw new IllegalArgumentException("The BundleContext must not be null!"); } if ((xmlDirectory == null) || (xmlDirectory.isEmpty())) { throw new IllegalArgumentException("The XML directory must neither be null, nor empty!"); } if (xmlDocumentTypeReader == null) { throw new IllegalArgumentException("The XmlDocumentTypeReader must not be null!"); } if (xmlDocumentProviderFactory == null) { throw new IllegalArgumentException("The XmlDocumentProviderFactory must not be null!"); } this.xmlDirectory = xmlDirectory; this.xmlDocumentTypeReader = xmlDocumentTypeReader; this.xmlDocumentProviderFactory = xmlDocumentProviderFactory; this.bundleDocumentProviderMap = new HashMap<>(); this.asyncLoader = new AbstractAsyncBundleProcessor() { @Override protected boolean isBundleRelevant(Bundle bundle) { return isResourcePresent(bundle, XmlDocumentBundleTracker.this.xmlDirectory); } @Override protected void processBundle(Bundle bundle) { Enumeration<URL> xmlDocumentPaths = bundle.findEntries(XmlDocumentBundleTracker.this.xmlDirectory, "*.xml", true); if (xmlDocumentPaths != null) { Collection<URL> filteredPaths = filterPatches(xmlDocumentPaths, bundle); int numberOfParsedXmlDocuments = 0; for (URL xmlDocumentURL : filteredPaths) { String moduleName = bundle.getSymbolicName(); String xmlDocumentFile = xmlDocumentURL.getFile(); try { XmlDocumentBundleTracker.this.logger.debug( "Reading the XML document '{}' in module '{}'...", xmlDocumentFile, moduleName); T object = XmlDocumentBundleTracker.this.xmlDocumentTypeReader.readFromXML(xmlDocumentURL); addingObject(bundle, object); numberOfParsedXmlDocuments++; } catch (Exception ex) { XmlDocumentBundleTracker.this.logger .warn(String.format("The XML document '%s' in module '%s' could not be parsed: %s", xmlDocumentFile, moduleName, ex.getLocalizedMessage()), ex); } } if (numberOfParsedXmlDocuments > 0) { addingFinished(bundle); } } } @Override public String toString() { return super.toString() + "(" + XmlDocumentBundleTracker.this.xmlDirectory + ")"; } }; } @Override public final synchronized void open() { asyncLoaderRegistration = bundleContext.registerService(BundleProcessor.class.getName(), asyncLoader, null); super.open(); } @Override public final synchronized void close() { super.close(); this.bundleDocumentProviderMap.clear(); if (asyncLoaderRegistration != null) { asyncLoaderRegistration.unregister(); asyncLoaderRegistration = null; } } private XmlDocumentProvider<T> acquireXmlDocumentProvider(Bundle bundle) { if (bundle != null) { XmlDocumentProvider<T> xmlDocumentProvider = this.bundleDocumentProviderMap.get(bundle); if (xmlDocumentProvider == null) { xmlDocumentProvider = this.xmlDocumentProviderFactory.createDocumentProvider(bundle); this.logger.debug("Create an empty XmlDocumentProvider for the module '{}'.", bundle.getSymbolicName()); this.bundleDocumentProviderMap.put(bundle, xmlDocumentProvider); } return xmlDocumentProvider; } return null; } private void releaseXmlDocumentProvider(Bundle bundle) { if (bundle != null) { XmlDocumentProvider<T> xmlDocumentProvider = this.bundleDocumentProviderMap.get(bundle); if (xmlDocumentProvider != null) { try { this.logger.debug("Release the XmlDocumentProvider for the module '{}'.", bundle.getSymbolicName()); xmlDocumentProvider.release(); } catch (Exception ex) { this.logger.error("Could not release the XmlDocumentProvider for the module '" + bundle.getSymbolicName() + "'!", ex); } this.bundleDocumentProviderMap.remove(bundle); } } } private void addingObject(Bundle bundle, T object) { XmlDocumentProvider<T> xmlDocumentProvider = acquireXmlDocumentProvider(bundle); if (xmlDocumentProvider != null) { xmlDocumentProvider.addingObject(object); } } private void addingFinished(Bundle bundle) { XmlDocumentProvider<T> xmlDocumentProvider = this.bundleDocumentProviderMap.get(bundle); if (xmlDocumentProvider != null) { try { xmlDocumentProvider.addingFinished(); } catch (Exception ex) { this.logger.error( "Could not send adding finished event for the module '" + bundle.getSymbolicName() + "'!", ex); } } } @Override public final synchronized Bundle addingBundle(Bundle bundle, BundleEvent event) { asyncLoader.addingBundle(bundle); return bundle; } @Override public final synchronized void removedBundle(Bundle bundle, BundleEvent event, Bundle object) { this.logger.debug("Removing the XML related objects from module '{}'...", bundle.getSymbolicName()); asyncLoader.removeBundle(bundle); releaseXmlDocumentProvider(bundle); } }