/** * 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.core.items.events; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.List; import org.eclipse.smarthome.core.events.AbstractEventFactory; import org.eclipse.smarthome.core.events.Event; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.dto.ItemDTO; import org.eclipse.smarthome.core.items.dto.ItemDTOMapper; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.RefreshType; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.Type; import org.eclipse.smarthome.core.types.UnDefType; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; /** * An {@link ItemEventFactory} is responsible for creating item event instances, e.g. {@link ItemCommandEvent}s and * {@link ItemStateEvent}s. * * @author Stefan Bußweiler - Initial contribution */ public class ItemEventFactory extends AbstractEventFactory { private static final String CORE_LIBRARY_PACKAGE = "org.eclipse.smarthome.core.library.types."; private static final String ITEM_COMAND_EVENT_TOPIC = "smarthome/items/{itemName}/command"; private static final String ITEM_STATE_EVENT_TOPIC = "smarthome/items/{itemName}/state"; private static final String ITEM_STATE_CHANGED_EVENT_TOPIC = "smarthome/items/{itemName}/statechanged"; private static final String GROUPITEM_STATE_CHANGED_EVENT_TOPIC = "smarthome/items/{itemName}/{memberName}/statechanged"; private static final String ITEM_ADDED_EVENT_TOPIC = "smarthome/items/{itemName}/added"; private static final String ITEM_REMOVED_EVENT_TOPIC = "smarthome/items/{itemName}/removed"; private static final String ITEM_UPDATED_EVENT_TOPIC = "smarthome/items/{itemName}/updated"; /** * Constructs a new ItemEventFactory. */ public ItemEventFactory() { super(Sets.newHashSet(ItemCommandEvent.TYPE, ItemStateEvent.TYPE, ItemStateChangedEvent.TYPE, ItemAddedEvent.TYPE, ItemUpdatedEvent.TYPE, ItemRemovedEvent.TYPE, GroupItemStateChangedEvent.TYPE)); } @Override protected Event createEventByType(String eventType, String topic, String payload, String source) throws Exception { Event event = null; if (eventType.equals(ItemCommandEvent.TYPE)) { event = createCommandEvent(topic, payload, source); } else if (eventType.equals(ItemStateEvent.TYPE)) { event = createStateEvent(topic, payload, source); } else if (eventType.equals(ItemStateChangedEvent.TYPE)) { event = createStateChangedEvent(topic, payload); } else if (eventType.equals(ItemAddedEvent.TYPE)) { event = createAddedEvent(topic, payload); } else if (eventType.equals(ItemUpdatedEvent.TYPE)) { event = createUpdatedEvent(topic, payload); } else if (eventType.equals(ItemRemovedEvent.TYPE)) { event = createRemovedEvent(topic, payload); } else if (eventType.equals(GroupItemStateChangedEvent.TYPE)) { event = createGroupStateChangedEvent(topic, payload); } return event; } private Event createGroupStateChangedEvent(String topic, String payload) { String itemName = getItemName(topic); String memberName = getMemberName(topic); ItemStateChangedEventPayloadBean bean = deserializePayload(payload, ItemStateChangedEventPayloadBean.class); State state = getState(bean.getType(), bean.getValue()); State oldState = getState(bean.getOldType(), bean.getOldValue()); return new GroupItemStateChangedEvent(topic, payload, itemName, memberName, state, oldState); } private Event createCommandEvent(String topic, String payload, String source) { String itemName = getItemName(topic); ItemEventPayloadBean bean = deserializePayload(payload, ItemEventPayloadBean.class); Command command = null; try { command = (Command) parse(bean.getType(), bean.getValue()); } catch (Exception e) { throw new IllegalArgumentException("Parsing of item command event failed.", e); } return new ItemCommandEvent(topic, payload, itemName, command, source); } private Event createStateEvent(String topic, String payload, String source) { String itemName = getItemName(topic); ItemEventPayloadBean bean = deserializePayload(payload, ItemEventPayloadBean.class); State state = getState(bean.getType(), bean.getValue()); return new ItemStateEvent(topic, payload, itemName, state, source); } private Event createStateChangedEvent(String topic, String payload) { String itemName = getItemName(topic); ItemStateChangedEventPayloadBean bean = deserializePayload(payload, ItemStateChangedEventPayloadBean.class); State state = getState(bean.getType(), bean.getValue()); State oldState = getState(bean.getOldType(), bean.getOldValue()); return new ItemStateChangedEvent(topic, payload, itemName, state, oldState); } private State getState(String type, String value) { State state = null; try { state = (State) parse(type, value); } catch (Exception e) { throw new IllegalArgumentException("Parsing of item state event failed.", e); } return state; } private String getItemName(String topic) { String[] topicElements = getTopicElements(topic); if (topicElements.length < 4) { throw new IllegalArgumentException("Event creation failed, invalid topic: " + topic); } return topicElements[2]; } private String getMemberName(String topic) { String[] topicElements = getTopicElements(topic); if (topicElements.length < 5) { throw new IllegalArgumentException("Event creation failed, invalid topic: " + topic); } return topicElements[3]; } private Object parse(String typeName, String valueToParse) throws Exception { if (typeName.equals(UnDefType.class.getSimpleName())) { return UnDefType.valueOf(valueToParse); } if (typeName.equals(RefreshType.class.getSimpleName())) { return RefreshType.valueOf(valueToParse); } Class<?> stateClass = Class.forName(CORE_LIBRARY_PACKAGE + typeName); Method valueOfMethod = stateClass.getMethod("valueOf", String.class); return valueOfMethod.invoke(stateClass, valueToParse); } private Event createAddedEvent(String topic, String payload) { ItemDTO itemDTO = deserializePayload(payload, ItemDTO.class); return new ItemAddedEvent(topic, payload, itemDTO); } private Event createRemovedEvent(String topic, String payload) { ItemDTO itemDTO = deserializePayload(payload, ItemDTO.class); return new ItemRemovedEvent(topic, payload, itemDTO); } private Event createUpdatedEvent(String topic, String payload) { ItemDTO[] itemDTOs = deserializePayload(payload, ItemDTO[].class); if (itemDTOs.length != 2) { throw new IllegalArgumentException("ItemUpdateEvent creation failed, invalid payload: " + payload); } return new ItemUpdatedEvent(topic, payload, itemDTOs[0], itemDTOs[1]); } /** * Creates an item command event. * * @param itemName the name of the item to send the command for * @param command the command to send * @param source the name of the source identifying the sender (can be null) * * @return the created item command event * * @throws IllegalArgumentException if itemName or command is null */ public static ItemCommandEvent createCommandEvent(String itemName, Command command, String source) { assertValidArguments(itemName, command, "command"); String topic = buildTopic(ITEM_COMAND_EVENT_TOPIC, itemName); ItemEventPayloadBean bean = new ItemEventPayloadBean(command.getClass().getSimpleName(), command.toString()); String payload = serializePayload(bean); return new ItemCommandEvent(topic, payload, itemName, command, source); } /** * Creates an item command event. * * @param itemName the name of the item to send the command for * @param command the command to send * * @return the created item command event * * @throws IllegalArgumentException if itemName or command is null */ public static ItemCommandEvent createCommandEvent(String itemName, Command command) { return createCommandEvent(itemName, command, null); } /** * Creates an item state event. * * @param itemName the name of the item to send the state update for * @param state the new state to send * @param source the name of the source identifying the sender (can be null) * * @return the created item state event * * @throws IllegalArgumentException if itemName or state is null */ public static ItemStateEvent createStateEvent(String itemName, State state, String source) { assertValidArguments(itemName, state, "state"); String topic = buildTopic(ITEM_STATE_EVENT_TOPIC, itemName); ItemEventPayloadBean bean = new ItemEventPayloadBean(state.getClass().getSimpleName(), state.toFullString()); String payload = serializePayload(bean); return new ItemStateEvent(topic, payload, itemName, state, source); } /** * Creates an item state event. * * @param itemName the name of the item to send the state update for * @param state the new state to send * * @return the created item state event * * @throws IllegalArgumentException if itemName or state is null */ public static ItemStateEvent createStateEvent(String itemName, State state) { return createStateEvent(itemName, state, null); } /** * Creates an item state changed event. * * @param itemName the name of the item to send the state changed event for * @param newState the new state to send * @param oldState the old state of the item * * @return the created item state changed event * * @throws IllegalArgumentException if itemName or state is null */ public static ItemStateChangedEvent createStateChangedEvent(String itemName, State newState, State oldState) { assertValidArguments(itemName, newState, "state"); String topic = buildTopic(ITEM_STATE_CHANGED_EVENT_TOPIC, itemName); ItemStateChangedEventPayloadBean bean = new ItemStateChangedEventPayloadBean( newState.getClass().getSimpleName(), newState.toFullString(), oldState.getClass().getSimpleName(), oldState.toFullString()); String payload = serializePayload(bean); return new ItemStateChangedEvent(topic, payload, itemName, newState, oldState); } public static GroupItemStateChangedEvent createGroupStateChangedEvent(String itemName, String memberName, State newState, State oldState) { assertValidArguments(itemName, memberName, newState, "state"); String topic = buildGroupTopic(GROUPITEM_STATE_CHANGED_EVENT_TOPIC, itemName, memberName); ItemStateChangedEventPayloadBean bean = new ItemStateChangedEventPayloadBean( newState.getClass().getSimpleName(), newState.toString(), oldState.getClass().getSimpleName(), oldState.toString()); String payload = serializePayload(bean); return new GroupItemStateChangedEvent(topic, payload, itemName, memberName, newState, oldState); } /** * Creates an item added event. * * @param item the item * * @return the created item added event * * @throws IllegalArgumentException if item is null */ public static ItemAddedEvent createAddedEvent(Item item) { assertValidArgument(item, "item"); String topic = buildTopic(ITEM_ADDED_EVENT_TOPIC, item.getName()); ItemDTO itemDTO = map(item); String payload = serializePayload(itemDTO); return new ItemAddedEvent(topic, payload, itemDTO); } /** * Creates an item removed event. * * @param item the item * * @return the created item removed event * * @throws IllegalArgumentException if item is null */ public static ItemRemovedEvent createRemovedEvent(Item item) { assertValidArgument(item, "item"); String topic = buildTopic(ITEM_REMOVED_EVENT_TOPIC, item.getName()); ItemDTO itemDTO = map(item); String payload = serializePayload(itemDTO); return new ItemRemovedEvent(topic, payload, itemDTO); } /** * Creates an item updated event. * * @param item the item * @param oldItem the old item * * @return the created item updated event * * @throws IllegalArgumentException if item or oldItem is null */ public static ItemUpdatedEvent createUpdateEvent(Item item, Item oldItem) { assertValidArgument(item, "item"); assertValidArgument(oldItem, "oldItem"); String topic = buildTopic(ITEM_UPDATED_EVENT_TOPIC, item.getName()); ItemDTO itemDTO = map(item); ItemDTO oldItemDTO = map(oldItem); List<ItemDTO> itemDTOs = new LinkedList<ItemDTO>(); itemDTOs.add(itemDTO); itemDTOs.add(oldItemDTO); String payload = serializePayload(itemDTOs); return new ItemUpdatedEvent(topic, payload, itemDTO, oldItemDTO); } private static String buildTopic(String topic, String itemName) { return topic.replace("{itemName}", itemName); } private static String buildGroupTopic(String topic, String itemName, String memberName) { return buildTopic(topic, itemName).replace("{memberName}", memberName); } private static ItemDTO map(Item item) { return ItemDTOMapper.map(item); } private static void assertValidArguments(String itemName, Type type, String typeArgumentName) { Preconditions.checkArgument(itemName != null && !itemName.isEmpty(), "The argument 'itemName' must not be null or empty."); Preconditions.checkArgument(type != null, "The argument '" + typeArgumentName + "' must not be null or empty."); } private static void assertValidArguments(String itemName, String memberName, Type type, String typeArgumentName) { Preconditions.checkArgument(itemName != null && !itemName.isEmpty(), "The argument 'itemName' must not be null or empty."); Preconditions.checkArgument(memberName != null && !memberName.isEmpty(), "The argument 'memberName' must not be null or empty."); Preconditions.checkArgument(type != null, "The argument '" + typeArgumentName + "' must not be null or empty."); } private static void assertValidArgument(Item item, String argumentName) { Preconditions.checkArgument(item != null, "The argument '" + argumentName + "' must no be null."); } /** * This is a java bean that is used to serialize/deserialize item event payload. */ private static class ItemEventPayloadBean { private String type; private String value; /** * Default constructor for deserialization e.g. by Gson. */ protected ItemEventPayloadBean() { } public ItemEventPayloadBean(String type, String value) { this.type = type; this.value = value; } public String getType() { return type; } public String getValue() { return value; } } /** * This is a java bean that is used to serialize/deserialize item state changed event payload. */ private static class ItemStateChangedEventPayloadBean { private String type; private String value; private String oldType; private String oldValue; /** * Default constructor for deserialization e.g. by Gson. */ protected ItemStateChangedEventPayloadBean() { } public ItemStateChangedEventPayloadBean(String type, String value, String oldType, String oldValue) { this.type = type; this.value = value; this.oldType = oldType; this.oldValue = oldValue; } public String getType() { return type; } public String getValue() { return value; } public String getOldType() { return oldType; } public String getOldValue() { return oldValue; } } }