]> git.basschouten.com Git - openhab-addons.git/blob
7aefc827354b111d8026dd80565fa6b17cd49830
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.persistence.dynamodb.internal;
14
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;
21 import java.util.Map;
22
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;
52
53 import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter;
54
55 /**
56  * Base class for all DynamoDBItem. Represents openHAB Item serialized in a suitable format for the database
57  *
58  * @param <T> Type of the state as accepted by the AWS SDK.
59  *
60  * @author Sami Salonen - Initial contribution
61  */
62 public abstract class AbstractDynamoDBItem<T> implements DynamoDBItem<T> {
63
64     private static final ZoneId UTC = ZoneId.of("UTC");
65     public static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT).withZone(UTC);
66
67     private static final String UNDEFINED_PLACEHOLDER = "<org.openhab.core.types.UnDefType.UNDEF>";
68
69     private static final Map<Class<? extends Item>, Class<? extends DynamoDBItem<?>>> ITEM_CLASS_MAP = new HashMap<>();
70
71     static {
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);
83     }
84
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));
91         }
92         return dtoclass;
93     }
94
95     /**
96      * Custom converter for serialization/deserialization of ZonedDateTime.
97      *
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.
100      *
101      * @author Sami Salonen - Initial contribution
102      *
103      */
104     public static final class ZonedDateTimeConverter implements DynamoDBTypeConverter<String, ZonedDateTime> {
105
106         @Override
107         public String convert(ZonedDateTime time) {
108             return DATEFORMATTER.format(time.withZoneSameInstant(UTC));
109         }
110
111         @Override
112         public ZonedDateTime unconvert(String serialized) {
113             return ZonedDateTime.parse(serialized, DATEFORMATTER);
114         }
115     }
116
117     private static final ZonedDateTimeConverter zonedDateTimeConverter = new ZonedDateTimeConverter();
118     private final Logger logger = LoggerFactory.getLogger(AbstractDynamoDBItem.class);
119
120     protected String name;
121     protected T state;
122     protected ZonedDateTime time;
123
124     public AbstractDynamoDBItem(String name, T state, ZonedDateTime time) {
125         this.name = name;
126         this.state = state;
127         this.time = time;
128     }
129
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);
150         } else {
151             // HSBType, PointType, PlayPauseType and StringType
152             return new DynamoDBStringItem(name, state.toFullString(), time);
153         }
154     }
155
156     @Override
157     public HistoricItem asHistoricItem(final Item item) {
158         final State[] state = new State[1];
159         accept(new DynamoDBItemVisitor() {
160
161             @Override
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();
169                     try {
170                         state[0] = PlayPauseType.valueOf(value);
171                     } catch (IllegalArgumentException e) {
172                         state[0] = RewindFastforwardType.valueOf(value);
173                     }
174                 } else if (item instanceof DateTimeItem) {
175                     try {
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;
185                     }
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);
194                 } else {
195                     state[0] = new StringType(dynamoStringItem.getState());
196                 }
197             }
198
199             @Override
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
207                             : OnOffType.OFF;
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());
213                 } else {
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());
217                 }
218             }
219         });
220         return new DynamoDBHistoricItem(getName(), state[0], getTime());
221     }
222
223     /**
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).
226      */
227
228     /*
229      * (non-Javadoc)
230      *
231      * @see org.openhab.persistence.dynamodb.internal.DynamoItem#accept(org.openhab.persistence.dynamodb.internal.
232      * DynamoItemVisitor)
233      */
234     @Override
235     public abstract void accept(DynamoDBItemVisitor visitor);
236
237     @Override
238     public String toString() {
239         return DATEFORMATTER.format(time) + ": " + name + " -> " + state.toString();
240     }
241 }