/** * 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.model.item.internal; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.StringUtils; import org.eclipse.emf.common.util.EList; import org.eclipse.smarthome.core.common.registry.AbstractProvider; import org.eclipse.smarthome.core.items.GenericItem; import org.eclipse.smarthome.core.items.GroupFunction; import org.eclipse.smarthome.core.items.GroupItem; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.ItemFactory; import org.eclipse.smarthome.core.items.ItemProvider; import org.eclipse.smarthome.core.items.dto.GroupFunctionDTO; import org.eclipse.smarthome.core.items.dto.ItemDTOMapper; import org.eclipse.smarthome.core.types.StateDescription; import org.eclipse.smarthome.core.types.StateDescriptionProvider; import org.eclipse.smarthome.model.core.EventType; import org.eclipse.smarthome.model.core.ModelRepository; import org.eclipse.smarthome.model.core.ModelRepositoryChangeListener; import org.eclipse.smarthome.model.item.BindingConfigParseException; import org.eclipse.smarthome.model.item.BindingConfigReader; import org.eclipse.smarthome.model.items.ItemModel; import org.eclipse.smarthome.model.items.ModelBinding; import org.eclipse.smarthome.model.items.ModelGroupFunction; import org.eclipse.smarthome.model.items.ModelGroupItem; import org.eclipse.smarthome.model.items.ModelItem; import org.eclipse.smarthome.model.items.ModelNormalItem; import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ItemProvider implementation which computes *.items file based item configurations. * * @author Kai Kreuzer - Initial contribution and API * @author Thomas.Eichstaedt-Engelen */ public class GenericItemProvider extends AbstractProvider<Item> implements ModelRepositoryChangeListener, ItemProvider, StateDescriptionProvider { private final Logger logger = LoggerFactory.getLogger(GenericItemProvider.class); /** to keep track of all binding config readers */ private Map<String, BindingConfigReader> bindingConfigReaders = new HashMap<String, BindingConfigReader>(); private ModelRepository modelRepository = null; private Map<String, Collection<Item>> itemsMap = new ConcurrentHashMap<>(); private Collection<ItemFactory> itemFactorys = new ArrayList<ItemFactory>(); private Map<String, StateDescription> stateDescriptions = new ConcurrentHashMap<>(); private Integer rank; public GenericItemProvider() { } protected void activate(Map<String, Object> properties) { Object serviceRanking = properties.get(Constants.SERVICE_RANKING); if (serviceRanking instanceof Integer) { rank = (Integer) serviceRanking; } else { rank = 0; } } @Override public Integer getRank() { return rank; } public void setModelRepository(ModelRepository modelRepository) { this.modelRepository = modelRepository; modelRepository.addModelRepositoryChangeListener(this); } public void unsetModelRepository(ModelRepository modelRepository) { modelRepository.removeModelRepositoryChangeListener(this); this.modelRepository = null; } /** * Add another instance of an {@link ItemFactory}. Used by Declarative Services. * * @param factory The {@link ItemFactory} to add. */ public void addItemFactory(ItemFactory factory) { itemFactorys.add(factory); dispatchBindingsPerItemType(null, factory.getSupportedItemTypes()); } /** * Removes the given {@link ItemFactory}. Used by Declarative Services. * * @param factory The {@link ItemFactory} to remove. */ public void removeItemFactory(ItemFactory factory) { itemFactorys.remove(factory); } public void addBindingConfigReader(BindingConfigReader reader) { if (!bindingConfigReaders.containsKey(reader.getBindingType())) { bindingConfigReaders.put(reader.getBindingType(), reader); dispatchBindingsPerType(reader, new String[] { reader.getBindingType() }); } else { logger.warn("Attempted to register a second BindingConfigReader of type '{}'." + " The primaraly reader will remain active!", reader.getBindingType()); } } public void removeBindingConfigReader(BindingConfigReader reader) { if (bindingConfigReaders.get(reader.getBindingType()).equals(reader)) { bindingConfigReaders.remove(reader.getBindingType()); } } /** * {@inheritDoc} */ @Override public Collection<Item> getAll() { List<Item> items = new ArrayList<Item>(); stateDescriptions.clear(); for (String name : modelRepository.getAllModelNamesOfType("items")) { items.addAll(getItemsFromModel(name)); } return items; } private Collection<Item> getItemsFromModel(String modelName) { logger.debug("Read items from model '{}'", modelName); List<Item> items = new ArrayList<Item>(); if (modelRepository != null) { ItemModel model = (ItemModel) modelRepository.getModel(modelName); if (model != null) { for (ModelItem modelItem : model.getItems()) { Item item = createItemFromModelItem(modelItem); if (item != null) { for (String groupName : modelItem.getGroups()) { ((GenericItem) item).addGroupName(groupName); } items.add(item); } } } } return items; } private void processBindingConfigsFromModel(String modelName) { logger.debug("Processing binding configs for items from model '{}'", modelName); if (modelRepository != null) { ItemModel model = (ItemModel) modelRepository.getModel(modelName); if (model == null) { return; } // start binding configuration processing for (BindingConfigReader reader : bindingConfigReaders.values()) { reader.startConfigurationUpdate(modelName); } // create items and read new binding configuration for (ModelItem modelItem : model.getItems()) { Item item = createItemFromModelItem(modelItem); if (item != null) { internalDispatchBindings(modelName, item, modelItem.getBindings()); } } // end binding configuration processing for (BindingConfigReader reader : bindingConfigReaders.values()) { reader.stopConfigurationUpdate(modelName); } } } private Item createItemFromModelItem(ModelItem modelItem) { GenericItem item = null; if (modelItem instanceof ModelGroupItem) { ModelGroupItem modelGroupItem = (ModelGroupItem) modelItem; String baseItemType = modelGroupItem.getType(); GenericItem baseItem = createItemOfType(baseItemType, modelGroupItem.getName()); if (baseItem != null) { // if the user did not specify a function the first value of the enum in xtext (EQUAL) will be used ModelGroupFunction function = modelGroupItem.getFunction(); item = applyGroupFunction(baseItem, modelGroupItem, function); } else { item = new GroupItem(modelGroupItem.getName()); } } else { ModelNormalItem normalItem = (ModelNormalItem) modelItem; String itemName = normalItem.getName(); item = createItemOfType(normalItem.getType(), itemName); } if (item != null) { String label = modelItem.getLabel(); String format = StringUtils.substringBetween(label, "[", "]"); if (format != null) { label = StringUtils.substringBefore(label, "[").trim(); stateDescriptions.put(modelItem.getName(), new StateDescription(null, null, null, format, false, null)); } item.setLabel(label); item.setCategory(modelItem.getIcon()); assignTags(modelItem, item); return item; } else { return null; } } private void assignTags(ModelItem modelItem, GenericItem item) { List<String> tags = modelItem.getTags(); for (String tag : tags) { item.addTag(tag); } } private GroupItem applyGroupFunction(GenericItem baseItem, ModelGroupItem modelGroupItem, ModelGroupFunction function) { GroupFunctionDTO dto = new GroupFunctionDTO(); dto.name = function.getName(); dto.params = modelGroupItem.getArgs().toArray(new String[modelGroupItem.getArgs().size()]); GroupFunction groupFunction = ItemDTOMapper.mapFunction(baseItem, dto); return new GroupItem(modelGroupItem.getName(), baseItem, groupFunction); } private void dispatchBindingsPerItemType(BindingConfigReader reader, String[] itemTypes) { if (modelRepository != null) { for (String modelName : modelRepository.getAllModelNamesOfType("items")) { ItemModel model = (ItemModel) modelRepository.getModel(modelName); if (model != null) { for (ModelItem modelItem : model.getItems()) { for (String itemType : itemTypes) { if (itemType.equals(modelItem.getType())) { Item item = createItemFromModelItem(modelItem); if (item != null) { internalDispatchBindings(reader, modelName, item, modelItem.getBindings()); } } } } } else { logger.debug("Model repository returned NULL for model named '{}'", modelName); } } } else { logger.warn("ModelRepository is NULL > dispatch bindings aborted!"); } } private void dispatchBindingsPerType(BindingConfigReader reader, String[] bindingTypes) { if (modelRepository != null) { for (String modelName : modelRepository.getAllModelNamesOfType("items")) { ItemModel model = (ItemModel) modelRepository.getModel(modelName); if (model != null) { for (ModelItem modelItem : model.getItems()) { for (ModelBinding modelBinding : modelItem.getBindings()) { for (String bindingType : bindingTypes) { if (bindingType.equals(modelBinding.getType())) { Item item = createItemFromModelItem(modelItem); if (item != null) { internalDispatchBindings(reader, modelName, item, modelItem.getBindings()); } } } } } } else { logger.debug("Model repository returned NULL for model named '{}'", modelName); } } } else { logger.warn("ModelRepository is NULL > dispatch bindings aborted!"); } } private void internalDispatchBindings(String modelName, Item item, EList<ModelBinding> bindings) { internalDispatchBindings(null, modelName, item, bindings); } private void internalDispatchBindings(BindingConfigReader reader, String modelName, Item item, EList<ModelBinding> bindings) { for (ModelBinding binding : bindings) { String bindingType = binding.getType(); String config = binding.getConfiguration(); BindingConfigReader localReader = reader; if (reader == null) { logger.trace("Given binding config reader is null > query cache to find appropriate reader!"); localReader = bindingConfigReaders.get(bindingType); } else { if (!localReader.getBindingType().equals(binding.getType())) { logger.trace( "The Readers' binding type '{}' and the Bindings' type '{}' doesn't match > continue processing next binding.", localReader.getBindingType(), binding.getType()); continue; } else { logger.debug("Start processing binding configuration of Item '{}' with '{}' reader.", item, localReader.getClass().getSimpleName()); } } if (localReader != null) { try { localReader.validateItemType(item.getType(), config); localReader.processBindingConfiguration(modelName, item.getType(), item.getName(), config); } catch (BindingConfigParseException e) { logger.error("Binding configuration of type '" + bindingType + "' of item '" + item.getName() + "' could not be parsed correctly.", e); } catch (Exception e) { // Catch badly behaving binding exceptions and continue processing logger.error("Binding configuration of type '" + bindingType + "' of item '" + item.getName() + "' could not be parsed correctly.", e); } } else { logger.trace("Couldn't find config reader for binding type '{}' > " + "parsing binding configuration of Item '{}' aborted!", bindingType, item); } } } /** * {@inheritDoc} * <p> * Dispatches all binding configs and fires all {@link ItemsChangeListener}s if {@code modelName} ends with "items". */ @Override public void modelChanged(String modelName, EventType type) { if (modelName.endsWith("items")) { switch (type) { case ADDED: processBindingConfigsFromModel(modelName); Collection<Item> allNewItems = getItemsFromModel(modelName); itemsMap.put(modelName, allNewItems); for (Item item : allNewItems) { notifyListenersAboutAddedElement(item); } break; case MODIFIED: processBindingConfigsFromModel(modelName); Map<String, Item> oldItems = toItemMap(itemsMap.get(modelName)); Map<String, Item> newItems = toItemMap(getAll()); itemsMap.put(modelName, newItems.values()); for (Item newItem : newItems.values()) { if (oldItems.containsKey(newItem.getName())) { Item oldItem = oldItems.get(newItem.getName()); if (!oldItem.equals(newItem)) { notifyListenersAboutUpdatedElement(oldItem, newItem); } } else { notifyListenersAboutAddedElement(newItem); } } for (Item oldItem : oldItems.values()) { if (!newItems.containsKey(oldItem.getName())) { notifyListenersAboutRemovedElement(oldItem); } } break; case REMOVED: Collection<Item> itemsFromModel = getItemsFromModel(modelName); itemsMap.remove(modelName); for (Item item : itemsFromModel) { notifyListenersAboutRemovedElement(item); } break; } } } private Map<String, Item> toItemMap(Collection<Item> items) { Map<String, Item> ret = new HashMap<>(); for (Item item : items) { ret.put(item.getName(), item); } return ret; } /** * Creates a new item of type {@code itemType} by utilizing an appropriate {@link ItemFactory}. * * @param itemType The type to find the appropriate {@link ItemFactory} for. * @param itemName The name of the {@link Item} to create. * * @return An Item instance of type {@code itemType} null if no item factory for it was found. */ private GenericItem createItemOfType(String itemType, String itemName) { if (itemType == null) { return null; } for (ItemFactory factory : itemFactorys) { GenericItem item = factory.createItem(itemType, itemName); if (item != null) { logger.trace("Created item '{}' of type '{}'", itemName, itemType); return item; } } logger.debug("Couldn't find ItemFactory for item '{}' of type '{}'", itemName, itemType); return null; } @Override public StateDescription getStateDescription(String itemName, Locale locale) { return stateDescriptions.get(itemName); } }