/** * 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.core; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashSet; import java.util.Set; import org.eclipse.smarthome.config.core.BundleProcessor.BundleProcessorListener; import org.osgi.framework.Bundle; import org.osgi.framework.BundleReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; /** * Utility class in order to delay an action until a binding's XML loading completed. * * It takes an action and whenever it is instructed to execute this action, it is going to check first if there are any * vetoes due to the fact that the XML meta-data of the relevant binding might not be fully loaded. If this is the case, * then the action will be delayed until the loading was finished. * * As this class is not an OSGi service itself, it needs some help from the outside by injecting/removing * BundleProcessors via {@link #addBundleProcessor(BundleProcessor)} and * {@link #removeBundleProcessor(BundleProcessor)}. * * @author Simon Kaufmann - Initial contribution and API */ public class BundleProcessorVetoManager<T> implements BundleProcessorListener { public interface Action<T> { void apply(T object); } private final Logger logger = LoggerFactory.getLogger(BundleProcessorVetoManager.class); private final Set<BundleProcessor> bundleProcessors = new HashSet<BundleProcessor>(); private final Multimap<Long, BundleProcessor> vetoes = Multimaps .synchronizedListMultimap(LinkedListMultimap.<Long, BundleProcessor> create()); private final Multimap<Long, T> queue = Multimaps.synchronizedListMultimap(LinkedListMultimap.<Long, T> create()); private final Action<T> action; /** * Construct a BundleProcessorVetoManager for the given action * * @param action the action to run (potentially delayed) */ public BundleProcessorVetoManager(Action<T> action) { this.action = action; } @Override public void bundleFinished(final BundleProcessor context, final Bundle bundle) { vetoes.remove(bundle.getBundleId(), context); if (vetoes.get(bundle.getBundleId()).isEmpty()) { logger.debug("Finished loading meta-data of bundle '{}'.", bundle.getSymbolicName()); for (T object : queue.removeAll(bundle.getBundleId())) { action.apply(object); } } else { logger.debug("'{}' still vetoed by '{}', queueing '{}'", bundle.getSymbolicName(), vetoes.get(bundle.getBundleId()), queue.get(bundle.getBundleId())); } } private Bundle getBundle(final Class<?> classFromBundle) { final ClassLoader classLoader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { @Override public ClassLoader run() { return classFromBundle.getClassLoader(); } }); if (classLoader instanceof BundleReference) { Bundle bundle = ((BundleReference) classLoader).getBundle(); logger.trace("Bundle of {} is {}", classFromBundle, bundle.getSymbolicName()); return bundle; } return null; } /** * Run the given action. * * If necessary, it will get queued and executed once the binding's XML files have been loaded. * * @param object the argument for the action */ public void applyActionFor(final Class<?> classFromWatchedBundle, final T object) { boolean veto = false; Bundle bundle = getBundle(classFromWatchedBundle); long bundleId = bundle.getBundleId(); for (BundleProcessor proc : bundleProcessors) { if (!proc.hasFinishedLoading(bundle)) { veto = true; if (!vetoes.containsEntry(bundleId, proc)) { logger.trace("Marking '{}' as vetoed by '{}'", bundle.getSymbolicName(), proc); vetoes.put(bundleId, proc); } } else { logger.trace("'{}' already finished processing '{}'", proc, bundle.getSymbolicName()); } } if (veto) { if (!queue.containsEntry(bundleId, object)) { logger.trace("Queueing '{}' in bundle '{}'", object, bundle.getSymbolicName()); queue.put(bundleId, object); } logger.debug("Meta-data of bundle '{}' is not fully loaded ({}), deferring action for '{}'", bundle.getSymbolicName(), vetoes.get(bundleId), object); } else { logger.trace("No veto for bundle '{}', directly executing the action", bundle.getSymbolicName()); action.apply(object); } } /** * Add a {@link BundleProcessor} to listen to. * * @param bundleProcessor */ public void addBundleProcessor(BundleProcessor bundleProcessor) { bundleProcessors.add(bundleProcessor); bundleProcessor.registerListener(this); } /** * Remove a {@link BundleProcessor} again. * * @param bundleProcessor */ public void removeBundleProcessor(BundleProcessor bundleProcessor) { bundleProcessor.unregisterListener(this); bundleProcessors.remove(bundleProcessor); } }