org.eclipse.smarthome.model.item.internal.GenericItemProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.smarthome.model.item.internal.GenericItemProvider.java

Source

/**
 * 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) {
                ModelGroupFunction function = modelGroupItem.getFunction();
                if (function == null) {
                    item = new GroupItem(modelGroupItem.getName(), baseItem);
                } else {
                    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 = getAll();
                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);
    }

}