2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.persistence.dynamodb.internal;
15 import java.math.BigDecimal;
16 import java.time.ZoneId;
17 import java.time.ZonedDateTime;
18 import java.time.format.DateTimeFormatter;
19 import java.time.format.DateTimeParseException;
20 import java.util.HashMap;
23 import org.openhab.core.items.Item;
24 import org.openhab.core.library.items.CallItem;
25 import org.openhab.core.library.items.ColorItem;
26 import org.openhab.core.library.items.ContactItem;
27 import org.openhab.core.library.items.DateTimeItem;
28 import org.openhab.core.library.items.DimmerItem;
29 import org.openhab.core.library.items.LocationItem;
30 import org.openhab.core.library.items.NumberItem;
31 import org.openhab.core.library.items.PlayerItem;
32 import org.openhab.core.library.items.RollershutterItem;
33 import org.openhab.core.library.items.StringItem;
34 import org.openhab.core.library.items.SwitchItem;
35 import org.openhab.core.library.types.DateTimeType;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.HSBType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.OpenClosedType;
40 import org.openhab.core.library.types.PercentType;
41 import org.openhab.core.library.types.PlayPauseType;
42 import org.openhab.core.library.types.PointType;
43 import org.openhab.core.library.types.RewindFastforwardType;
44 import org.openhab.core.library.types.StringListType;
45 import org.openhab.core.library.types.StringType;
46 import org.openhab.core.library.types.UpDownType;
47 import org.openhab.core.persistence.HistoricItem;
48 import org.openhab.core.types.State;
49 import org.openhab.core.types.UnDefType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
53 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter;
56 * Base class for all DynamoDBItem. Represents openHAB Item serialized in a suitable format for the database
58 * @param <T> Type of the state as accepted by the AWS SDK.
60 * @author Sami Salonen - Initial contribution
62 public abstract class AbstractDynamoDBItem<T> implements DynamoDBItem<T> {
64 private static final ZoneId UTC = ZoneId.of("UTC");
65 public static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT).withZone(UTC);
67 private static final String UNDEFINED_PLACEHOLDER = "<org.openhab.core.types.UnDefType.UNDEF>";
69 private static final Map<Class<? extends Item>, Class<? extends DynamoDBItem<?>>> ITEM_CLASS_MAP = new HashMap<>();
72 ITEM_CLASS_MAP.put(CallItem.class, DynamoDBStringItem.class);
73 ITEM_CLASS_MAP.put(ContactItem.class, DynamoDBBigDecimalItem.class);
74 ITEM_CLASS_MAP.put(DateTimeItem.class, DynamoDBStringItem.class);
75 ITEM_CLASS_MAP.put(LocationItem.class, DynamoDBStringItem.class);
76 ITEM_CLASS_MAP.put(NumberItem.class, DynamoDBBigDecimalItem.class);
77 ITEM_CLASS_MAP.put(RollershutterItem.class, DynamoDBBigDecimalItem.class);
78 ITEM_CLASS_MAP.put(StringItem.class, DynamoDBStringItem.class);
79 ITEM_CLASS_MAP.put(SwitchItem.class, DynamoDBBigDecimalItem.class);
80 ITEM_CLASS_MAP.put(DimmerItem.class, DynamoDBBigDecimalItem.class); // inherited from SwitchItem (!)
81 ITEM_CLASS_MAP.put(ColorItem.class, DynamoDBStringItem.class); // inherited from DimmerItem
82 ITEM_CLASS_MAP.put(PlayerItem.class, DynamoDBStringItem.class);
85 public static final Class<DynamoDBItem<?>> getDynamoItemClass(Class<? extends Item> itemClass)
86 throws NullPointerException {
87 @SuppressWarnings("unchecked")
88 Class<DynamoDBItem<?>> dtoclass = (Class<DynamoDBItem<?>>) ITEM_CLASS_MAP.get(itemClass);
89 if (dtoclass == null) {
90 throw new IllegalArgumentException(String.format("Unknown item class %s", itemClass));
96 * Custom converter for serialization/deserialization of ZonedDateTime.
98 * Serialization: ZonedDateTime is first converted to UTC and then stored with format yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
99 * This allows easy sorting of values since all timestamps are in UTC and string ordering can be used.
101 * @author Sami Salonen - Initial contribution
104 public static final class ZonedDateTimeConverter implements DynamoDBTypeConverter<String, ZonedDateTime> {
107 public String convert(ZonedDateTime time) {
108 return DATEFORMATTER.format(time.withZoneSameInstant(UTC));
112 public ZonedDateTime unconvert(String serialized) {
113 return ZonedDateTime.parse(serialized, DATEFORMATTER);
117 private static final ZonedDateTimeConverter zonedDateTimeConverter = new ZonedDateTimeConverter();
118 private final Logger logger = LoggerFactory.getLogger(AbstractDynamoDBItem.class);
120 protected String name;
122 protected ZonedDateTime time;
124 public AbstractDynamoDBItem(String name, T state, ZonedDateTime time) {
130 public static DynamoDBItem<?> fromState(String name, State state, ZonedDateTime time) {
131 if (state instanceof DecimalType && !(state instanceof HSBType)) {
132 // also covers PercentType which is inherited from DecimalType
133 return new DynamoDBBigDecimalItem(name, ((DecimalType) state).toBigDecimal(), time);
134 } else if (state instanceof OnOffType) {
135 return new DynamoDBBigDecimalItem(name,
136 ((OnOffType) state) == OnOffType.ON ? BigDecimal.ONE : BigDecimal.ZERO, time);
137 } else if (state instanceof OpenClosedType) {
138 return new DynamoDBBigDecimalItem(name,
139 ((OpenClosedType) state) == OpenClosedType.OPEN ? BigDecimal.ONE : BigDecimal.ZERO, time);
140 } else if (state instanceof UpDownType) {
141 return new DynamoDBBigDecimalItem(name,
142 ((UpDownType) state) == UpDownType.UP ? BigDecimal.ONE : BigDecimal.ZERO, time);
143 } else if (state instanceof DateTimeType) {
144 return new DynamoDBStringItem(name,
145 zonedDateTimeConverter.convert(((DateTimeType) state).getZonedDateTime()), time);
146 } else if (state instanceof UnDefType) {
147 return new DynamoDBStringItem(name, UNDEFINED_PLACEHOLDER, time);
148 } else if (state instanceof StringListType) {
149 return new DynamoDBStringItem(name, state.toFullString(), time);
151 // HSBType, PointType, PlayPauseType and StringType
152 return new DynamoDBStringItem(name, state.toFullString(), time);
157 public HistoricItem asHistoricItem(final Item item) {
158 final State[] state = new State[1];
159 accept(new DynamoDBItemVisitor() {
162 public void visit(DynamoDBStringItem dynamoStringItem) {
163 if (item instanceof ColorItem) {
164 state[0] = new HSBType(dynamoStringItem.getState());
165 } else if (item instanceof LocationItem) {
166 state[0] = new PointType(dynamoStringItem.getState());
167 } else if (item instanceof PlayerItem) {
168 String value = dynamoStringItem.getState();
170 state[0] = PlayPauseType.valueOf(value);
171 } catch (IllegalArgumentException e) {
172 state[0] = RewindFastforwardType.valueOf(value);
174 } else if (item instanceof DateTimeItem) {
176 // Parse ZoneDateTime from string. DATEFORMATTER assumes UTC in case it is not clear
177 // from the string (should be).
178 // We convert to default/local timezone for user convenience (e.g. display)
179 state[0] = new DateTimeType(zonedDateTimeConverter.unconvert(dynamoStringItem.getState())
180 .withZoneSameInstant(ZoneId.systemDefault()));
181 } catch (DateTimeParseException e) {
182 logger.warn("Failed to parse {} as date. Outputting UNDEF instead",
183 dynamoStringItem.getState());
184 state[0] = UnDefType.UNDEF;
186 } else if (dynamoStringItem.getState().equals(UNDEFINED_PLACEHOLDER)) {
187 state[0] = UnDefType.UNDEF;
188 } else if (item instanceof CallItem) {
189 String parts = dynamoStringItem.getState();
190 String[] strings = parts.split(",");
191 String orig = strings[0];
192 String dest = strings[1];
193 state[0] = new StringListType(orig, dest);
195 state[0] = new StringType(dynamoStringItem.getState());
200 public void visit(DynamoDBBigDecimalItem dynamoBigDecimalItem) {
201 if (item instanceof NumberItem) {
202 state[0] = new DecimalType(dynamoBigDecimalItem.getState());
203 } else if (item instanceof DimmerItem) {
204 state[0] = new PercentType(dynamoBigDecimalItem.getState());
205 } else if (item instanceof SwitchItem) {
206 state[0] = dynamoBigDecimalItem.getState().compareTo(BigDecimal.ONE) == 0 ? OnOffType.ON
208 } else if (item instanceof ContactItem) {
209 state[0] = dynamoBigDecimalItem.getState().compareTo(BigDecimal.ONE) == 0 ? OpenClosedType.OPEN
210 : OpenClosedType.CLOSED;
211 } else if (item instanceof RollershutterItem) {
212 state[0] = new PercentType(dynamoBigDecimalItem.getState());
214 logger.warn("Not sure how to convert big decimal item {} to type {}. Using StringType as fallback",
215 dynamoBigDecimalItem.getName(), item.getClass());
216 state[0] = new StringType(dynamoBigDecimalItem.getState().toString());
220 return new DynamoDBHistoricItem(getName(), state[0], getTime());
224 * We define all getter and setters in the child class implement those. Having the getter
225 * and setter implementations here in the parent class does not work with introspection done by AWS SDK (1.11.56).
231 * @see org.openhab.persistence.dynamodb.internal.DynamoItem#accept(org.openhab.persistence.dynamodb.internal.
235 public abstract void accept(DynamoDBItemVisitor visitor);
238 public String toString() {
239 return DATEFORMATTER.format(time) + ": " + name + " -> " + state.toString();