/*******************************************************************************
*
* Copyright (c) 2016 Bosch Software Innovations GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* The Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
*******************************************************************************/
package org.eclipse.smarthome.automation.internal.core.provider;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.smarthome.automation.ManagedRuleProvider;
import org.eclipse.smarthome.automation.Rule;
import org.eclipse.smarthome.automation.parser.Parser;
import org.eclipse.smarthome.automation.template.Template;
import org.eclipse.smarthome.automation.type.ModuleType;
import org.eclipse.smarthome.core.common.registry.Provider;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
/**
* This class is responsible for tracking the bundles - suppliers of automation resources. It implements
* {@link BundleTrackerCustomizer} and is notified for events for adding, modifying or removing the bundles.
*
* @author Ana Dimova
*
*/
@SuppressWarnings("deprecation")
public class AutomationResourceBundlesTracker implements BundleTrackerCustomizer<Bundle> {
/**
* This field holds a list with an {@link AutomationResourceBundlesEventQueue} instances owned by
* {@link AbstractResourceBundleProvider}s of {@link ModuleType}s, {@link Template}s and {@link Rule}s.
*/
@SuppressWarnings("rawtypes")
private List<AutomationResourceBundlesEventQueue> providerEventsQueue = new ArrayList<AutomationResourceBundlesEventQueue>();
/**
* This field holds a reference to an importer of {@link Rule}s.
*/
protected RuleResourceBundleImporter rImporter;
/**
* This field is a bundle tracker for bundles providing automation resources.
*/
private BundleTracker<Bundle> bTracker;
/**
* This field serves for saving the BundleEvents for the bundles providing automation resources until their
* processing completes. The events have been for adding, modifying or removing a bundle.
*/
private List<BundleEvent> queue = new LinkedList<BundleEvent>();
public AutomationResourceBundlesTracker() {
rImporter = createImporter();
}
protected RuleResourceBundleImporter createImporter() {
return new RuleResourceBundleImporter();
}
protected void activate(BundleContext bc) {
bTracker = new BundleTracker<Bundle>(bc, ~Bundle.UNINSTALLED, this);
bTracker.open();
}
protected void deactivate(BundleContext bc) {
bTracker.close();
bTracker = null;
rImporter.deactivate();
}
@SuppressWarnings({ "rawtypes" })
protected void addProvider(Provider provider) {
if (provider instanceof AbstractResourceBundleProvider) {
addAbstractResourceBundleProvider((AbstractResourceBundleProvider) provider);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected void addAbstractResourceBundleProvider(AbstractResourceBundleProvider provider) {
AutomationResourceBundlesEventQueue queue = provider.getQueue();
synchronized (this.queue) {
queue.addAll(this.queue);
providerEventsQueue.add(queue);
}
}
@SuppressWarnings({ "rawtypes" })
protected void removeProvider(Provider provider) {
if (provider instanceof AbstractResourceBundleProvider) {
removeAbstractResourceBundleProvider((AbstractResourceBundleProvider) provider);
}
}
@SuppressWarnings({ "rawtypes" })
protected void removeAbstractResourceBundleProvider(AbstractResourceBundleProvider provider) {
AutomationResourceBundlesEventQueue queue = provider.getQueue();
synchronized (this.queue) {
providerEventsQueue.remove(queue);
}
}
protected void setManagedRuleProvider(ManagedRuleProvider mProvider) {
rImporter.setManagedRuleProvider(mProvider);
rImporter.activate(null);
addAbstractResourceBundleProvider(rImporter);
}
protected void removeManagedRuleProvider(ManagedRuleProvider mProvider) {
removeAbstractResourceBundleProvider(rImporter);
rImporter.deactivate();
}
/**
* This method provides functionality for tracking {@link Parser} services.
*
* @param parser {@link Parser} service
* @param properties of the service that has been added.
*/
protected void addParser(Parser<Rule> parser, Map<String, String> properties) {
rImporter.addParser(parser, properties);
}
/**
* This method provides functionality for tracking {@link Parser} services.
*
* @param parser {@link Parser} service
* @param properties of the service that has been removed.
*/
protected void removeParser(Parser<Rule> parser, Map<String, String> properties) {
rImporter.removeParser(parser, properties);
}
protected void setPackageAdmin(PackageAdmin pkgAdmin) {
HostFragmentMappingUtil.pkgAdmin = pkgAdmin;
}
protected void removePackageAdmin(PackageAdmin pkgAdmin) {
HostFragmentMappingUtil.pkgAdmin = null;
}
/**
* A bundle that provides automation resources is being added to the {@code BundleTracker}.
*
* <p>
* This method is called before a bundle that provides automation resources is added to the {@code BundleTracker}.
* This method returns the object to be tracked for the specified {@code Bundle}. The returned object is stored in
* the {@code BundleTracker} and is available from the {@link BundleTracker#getObject(Bundle) getObject} method.
*
* @param bundle The {@code Bundle} being added to the {@code BundleTracker} .
* @param event The bundle event which caused this customizer method to be
* called or {@code null} if there is no bundle event associated with
* the call to this method.
* @return The object to be tracked for the specified {@code Bundle} object
* or {@code null} if the specified {@code Bundle} object should not
* be tracked.
*/
@Override
public Bundle addingBundle(Bundle bundle, BundleEvent event) {
if (isAnAutomationProvider(bundle)) {
if (HostFragmentMappingUtil.isFragmentBundle(bundle)) {
List<Bundle> hosts = HostFragmentMappingUtil.returnHostBundles(bundle);
if (HostFragmentMappingUtil.needToProcessFragment(bundle, hosts)) {
addEvent(bundle, event);
HostFragmentMappingUtil.fillHostFragmentMapping(hosts);
}
} else {
HostFragmentMappingUtil.fillHostFragmentMapping(bundle);
addEvent(bundle, event);
}
} else if (!HostFragmentMappingUtil.isFragmentBundle(bundle)) {
List<Bundle> fragments = HostFragmentMappingUtil.fillHostFragmentMapping(bundle);
for (Bundle fragment : fragments) {
if (isAnAutomationProvider(fragment)) {
addEvent(bundle, event);
break;
}
}
}
return bundle;
}
/**
* A bundle tracked by the {@code BundleTracker} has been modified.
*
* <p>
* This method is called when a bundle being tracked by the {@code BundleTracker} has had its state modified.
*
* @param bundle The {@code Bundle} whose state has been modified.
* @param event The bundle event which caused this customizer method to be
* called or {@code null} if there is no bundle event associated with
* the call to this method.
* @param object The tracked object for the specified bundle.
*/
@Override
public void modifiedBundle(Bundle bundle, BundleEvent event, Bundle object) {
int type = event.getType();
if (type == BundleEvent.UPDATED || type == BundleEvent.RESOLVED) {
addEvent(bundle, event);
}
}
/**
* A bundle tracked by the {@code BundleTracker} has been removed.
*
* <p>
* This method is called after a bundle is no longer being tracked by the {@code BundleTracker}.
*
* @param bundle The {@code Bundle} that has been removed.
* @param event The bundle event which caused this customizer method to be
* called or {@code null} if there is no bundle event associated with
* the call to this method.
* @param object The tracked object for the specified bundle.
*/
@Override
public void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
if (HostFragmentMappingUtil.isFragmentBundle(bundle)) {
for (Entry<Bundle, List<Bundle>> entry : HostFragmentMappingUtil.getMapping()) {
if (entry.getValue().contains(bundle)) {
Bundle host = entry.getKey();
addEvent(host, new BundleEvent(BundleEvent.UPDATED, host));
}
}
} else {
addEvent(bundle, event);
}
}
/**
* This method is called when a new event for a bundle providing automation resources is received. It causes a
* creation of a new thread if there is no other created yet and starting the thread. If the thread already exists,
* it is waiting for events and will be notified for the event.
*
* @param bundle
*
* @param event for a bundle tracked by the {@code BundleTracker}. It has been for adding, modifying or removing the
* bundle.
*/
@SuppressWarnings({ "rawtypes" })
protected void addEvent(Bundle bundle, BundleEvent event) {
if (event == null) {
event = initializeEvent(bundle);
}
synchronized (queue) {
queue.add(event);
for (AutomationResourceBundlesEventQueue queue : providerEventsQueue) {
queue.addEvent(bundle, event);
}
}
}
private BundleEvent initializeEvent(Bundle bundle) {
switch (bundle.getState()) {
case Bundle.INSTALLED:
return new BundleEvent(BundleEvent.INSTALLED, bundle);
case Bundle.RESOLVED:
return new BundleEvent(BundleEvent.RESOLVED, bundle);
default:
return new BundleEvent(BundleEvent.STARTED, bundle);
}
}
/**
* This method is used to check if the specified {@code Bundle} contains resource files providing automation
* resources.
*
* @param bundle is a {@link Bundle} object to check.
* @return <tt>true</tt> if the specified {@link Bundle} contains resource files providing automation
* resources, <tt>false</tt> otherwise.
*/
private boolean isAnAutomationProvider(Bundle bundle) {
return bundle.getEntryPaths(AbstractResourceBundleProvider.PATH) != null;
}
}