/** * Copyright (c) 1997, 2015 by ProSyst Software 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 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.automation.internal.commands; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Set; import org.eclipse.smarthome.automation.parser.Parser; import org.eclipse.smarthome.automation.parser.ParsingException; import org.eclipse.smarthome.automation.parser.ParsingNestedException; import org.eclipse.smarthome.automation.template.RuleTemplate; import org.eclipse.smarthome.automation.template.RuleTemplateProvider; import org.eclipse.smarthome.automation.template.Template; import org.eclipse.smarthome.automation.template.TemplateProvider; import org.eclipse.smarthome.automation.type.ModuleType; import org.eclipse.smarthome.automation.type.ModuleTypeProvider; import org.eclipse.smarthome.core.common.registry.ProviderChangeListener; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; /** * This class is implementation of {@link TemplateProvider}. It extends functionality of {@link AbstractCommandProvider} * <p> * It is responsible for execution of {@link AutomationCommandsPluggable}, corresponding to the {@link RuleTemplate}s: * <ul> * <li>imports the {@link RuleTemplate}s from local files or from URL resources * <li>provides functionality for persistence of the {@link RuleTemplate}s * <li>removes the {@link RuleTemplate}s and their persistence * </ul> * * @author Ana Dimova - Initial Contribution * @author Kai Kreuzer - refactored (managed) provider and registry implementation * */ public class CommandlineTemplateProvider extends AbstractCommandProvider<RuleTemplate> implements RuleTemplateProvider { /** * This field holds a reference to the {@link ModuleTypeProvider} service registration. */ @SuppressWarnings("rawtypes") protected ServiceRegistration tpReg; /** * This constructor creates instances of this particular implementation of {@link TemplateProvider}. It does not add * any new functionality to the constructors of the providers. Only provides consistency by invoking the parent's * constructor. * * @param context is the {@link BundleContext}, used for creating a tracker for {@link Parser} services. */ public CommandlineTemplateProvider(BundleContext context) { super(context); listeners = new LinkedList<ProviderChangeListener<RuleTemplate>>(); tpReg = bc.registerService(RuleTemplateProvider.class.getName(), this, null); } /** * This method differentiates what type of {@link Parser}s is tracked by the tracker. * For this concrete provider, this type is a {@link RuleTemplate} {@link Parser}. * * @see AbstractCommandProvider#addingService(org.osgi.framework.ServiceReference) */ @Override public Object addingService(@SuppressWarnings("rawtypes") ServiceReference reference) { if (reference.getProperty(Parser.PARSER_TYPE).equals(Parser.PARSER_TEMPLATE)) { return super.addingService(reference); } return null; } /** * This method is responsible for exporting a set of RuleTemplates in a specified file. * * @param parserType is relevant to the format that you need for conversion of the RuleTemplates in text. * @param set a set of RuleTemplates to export. * @param file a specified file for export. * @throws Exception when I/O operation has failed or has been interrupted or generating of the text fails * for some reasons. * @see AutomationCommandsPluggable#exportTemplates(String, Set, File) */ public String exportTemplates(String parserType, Set<RuleTemplate> set, File file) throws Exception { return super.exportData(parserType, set, file); } /** * This method is responsible for importing a set of RuleTemplates from a specified file or URL resource. * * @param parserType is relevant to the format that you need for conversion of the RuleTemplates in text. * @param url a specified URL for import. * @throws IOException when I/O operation has failed or has been interrupted. * @throws ParsingException when parsing of the text fails for some reasons. * @see AutomationCommandsPluggable#importTemplates(String, URL) */ public Set<RuleTemplate> importTemplates(String parserType, URL url) throws IOException, ParsingException { Parser<RuleTemplate> parser = parsers.get(parserType); if (parser != null) { InputStreamReader inputStreamReader = new InputStreamReader(new BufferedInputStream(url.openStream())); try { return importData(url, parser, inputStreamReader); } finally { inputStreamReader.close(); } } else { throw new ParsingException(new ParsingNestedException(ParsingNestedException.TEMPLATE, null, new Exception("Parser " + parserType + " not available"))); } } @Override public RuleTemplate getTemplate(String UID, Locale locale) { synchronized (providerPortfolio) { return providedObjectsHolder.get(UID); } } @Override public Collection<RuleTemplate> getTemplates(Locale locale) { synchronized (providedObjectsHolder) { return providedObjectsHolder.values(); } } /** * This method is responsible for removing a set of objects loaded from a specified file or URL resource. * * @param providerType specifies the provider responsible for removing the objects loaded from a specified file or * URL resource. * @param url is a specified file or URL resource. * @return the string <b>SUCCESS</b>. */ public String remove(URL url) { List<String> portfolio = null; synchronized (providerPortfolio) { portfolio = providerPortfolio.remove(url); } if (portfolio != null && !portfolio.isEmpty()) { synchronized (providedObjectsHolder) { for (String uid : portfolio) { notifyListeners(providedObjectsHolder.remove(uid)); } } } return AutomationCommand.SUCCESS; } @Override public void close() { if (tpReg != null) { tpReg.unregister(); tpReg = null; } super.close(); } @Override protected Set<RuleTemplate> importData(URL url, Parser<RuleTemplate> parser, InputStreamReader inputStreamReader) throws ParsingException { Set<RuleTemplate> providedObjects = parser.parse(inputStreamReader); if (providedObjects != null && !providedObjects.isEmpty()) { List<String> portfolio = new ArrayList<String>(); synchronized (providerPortfolio) { providerPortfolio.put(url, portfolio); } List<ParsingNestedException> importDataExceptions = new ArrayList<ParsingNestedException>(); for (RuleTemplate ruleT : providedObjects) { List<ParsingNestedException> exceptions = new ArrayList<ParsingNestedException>(); String uid = ruleT.getUID(); checkExistence(uid, exceptions); if (exceptions.isEmpty()) { portfolio.add(uid); synchronized (providedObjectsHolder) { notifyListeners(providedObjectsHolder.put(uid, ruleT), ruleT); } } else { importDataExceptions.addAll(exceptions); } } if (!importDataExceptions.isEmpty()) { throw new ParsingException(importDataExceptions); } } return providedObjects; } /** * This method is responsible for checking the existence of {@link Template}s with the same * UIDs before these objects to be added in the system. * * @param uid UID of the newly created {@link Template}, which to be checked. * @param exceptions accumulates exceptions if {@link ModuleType} with the same UID exists. */ protected void checkExistence(String uid, List<ParsingNestedException> exceptions) { if (AutomationCommandsPluggable.templateRegistry == null) { exceptions.add(new ParsingNestedException(ParsingNestedException.TEMPLATE, uid, new IllegalArgumentException("Failed to create Rule Template with UID \"" + uid + "\"! Can't guarantee yet that other Rule Template with the same UID does not exist."))); } if (AutomationCommandsPluggable.templateRegistry.get(uid) != null) { exceptions.add(new ParsingNestedException(ParsingNestedException.TEMPLATE, uid, new IllegalArgumentException("Rule Template with UID \"" + uid + "\" already exists! Failed to create a second with the same UID!"))); } } @Override public Collection<RuleTemplate> getAll() { return new LinkedList<RuleTemplate>(providedObjectsHolder.values()); } @Override public void addProviderChangeListener(ProviderChangeListener<RuleTemplate> listener) { synchronized (listeners) { listeners.add(listener); } } @Override public void removeProviderChangeListener(ProviderChangeListener<RuleTemplate> listener) { synchronized (listeners) { listeners.remove(listener); } } protected void notifyListeners(RuleTemplate oldElement, RuleTemplate newElement) { synchronized (listeners) { for (ProviderChangeListener<RuleTemplate> listener : listeners) { if (oldElement != null) { listener.updated(this, oldElement, newElement); } listener.added(this, newElement); } } } protected void notifyListeners(RuleTemplate removedObject) { if (removedObject != null) { synchronized (listeners) { for (ProviderChangeListener<RuleTemplate> listener : listeners) { listener.removed(this, removedObject); } } } } }