]> git.basschouten.com Git - openhab-addons.git/blob
ac47d4e9104f9b92583466eafa301756cf72f218
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.binding.dwdunwetter.internal.data;
14
15 import java.io.StringReader;
16 import java.math.BigDecimal;
17 import java.time.Duration;
18 import java.time.Instant;
19 import java.time.OffsetDateTime;
20 import java.time.ZoneId;
21 import java.time.ZonedDateTime;
22 import java.time.format.DateTimeFormatter;
23 import java.time.format.DateTimeFormatterBuilder;
24 import java.time.format.DateTimeParseException;
25 import java.util.Collections;
26 import java.util.LinkedList;
27 import java.util.List;
28
29 import javax.xml.stream.XMLEventReader;
30 import javax.xml.stream.XMLInputFactory;
31 import javax.xml.stream.XMLStreamException;
32 import javax.xml.stream.XMLStreamReader;
33 import javax.xml.stream.events.XMLEvent;
34
35 import org.apache.commons.lang.StringUtils;
36 import org.openhab.core.cache.ExpiringCache;
37 import org.openhab.core.library.types.DateTimeType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.library.unit.ImperialUnits;
42 import org.openhab.core.types.State;
43 import org.openhab.core.types.UnDefType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * Contains the Data for all retrieved warnings for one thing.
49  *
50  * @author Martin Koehler - Initial contribution
51  */
52 public class DwdWarningsData {
53
54     private static final int MIN_REFRESH_WAIT_MINUTES = 5;
55
56     private final Logger logger = LoggerFactory.getLogger(DwdWarningsData.class);
57
58     private List<DwdWarningData> cityData = new LinkedList<>();
59
60     private DwdWarningCache cache = new DwdWarningCache();
61
62     private ExpiringCache<String> dataAccessCached;
63
64     private DateTimeFormatter formatter = new DateTimeFormatterBuilder()
65             // date/time
66             .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
67             // offset (hh:mm - "+00:00" when it's zero)
68             .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
69             // offset (hhmm - "+0000" when it's zero)
70             .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd()
71             // offset (hh - "Z" when it's zero)
72             .optionalStart().appendOffset("+HH", "Z").optionalEnd()
73             // create formatter
74             .toFormatter();
75
76     public DwdWarningsData(String cellId) {
77         DwdWarningDataAccess dataAccess = new DwdWarningDataAccess();
78         this.dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES),
79                 () -> dataAccess.getDataFromEndpoint(cellId));
80     }
81
82     private String getValue(XMLEventReader eventReader) throws XMLStreamException {
83         XMLEvent event = eventReader.nextEvent();
84         return event.asCharacters().getData();
85     }
86
87     private BigDecimal getBigDecimalValue(XMLEventReader eventReader) throws XMLStreamException {
88         XMLEvent event = eventReader.nextEvent();
89         try {
90             return new BigDecimal(event.asCharacters().getData());
91         } catch (NumberFormatException e) {
92             logger.debug("Exception while parsing a BigDecimal", e);
93             return BigDecimal.ZERO;
94         }
95     }
96
97     private Instant getTimestampValue(XMLEventReader eventReader) throws XMLStreamException {
98         XMLEvent event = eventReader.nextEvent();
99         String dateTimeString = event.asCharacters().getData();
100         try {
101             OffsetDateTime dateTime = OffsetDateTime.parse(dateTimeString, formatter);
102             return dateTime.toInstant();
103         } catch (DateTimeParseException e) {
104             logger.debug("Exception while parsing a DateTime", e);
105             return Instant.MIN;
106         }
107     }
108
109     /**
110      * Refreshes the Warnings Data
111      */
112     public boolean refresh() {
113         String rawData = dataAccessCached.getValue();
114         if (StringUtils.isEmpty(rawData)) {
115             logger.debug("No Data from Endpoint");
116             return false;
117         }
118
119         cityData.clear();
120
121         try {
122             XMLInputFactory inputFactory = XMLInputFactory.newInstance();
123             XMLStreamReader reader = inputFactory.createXMLStreamReader(new StringReader(rawData));
124             XMLEventReader eventReader = inputFactory.createXMLEventReader(reader);
125             DwdWarningData gemeindeData = new DwdWarningData();
126             boolean insideGemeinde = false;
127             while (eventReader.hasNext()) {
128                 XMLEvent event = eventReader.nextEvent();
129                 if (!insideGemeinde && event.isStartElement()) {
130                     DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart());
131                     switch (xmlTag) {
132                         case WARNUNGEN_GEMEINDEN:
133                             gemeindeData = new DwdWarningData();
134                             insideGemeinde = true;
135                             break;
136                         default:
137                             break;
138                     }
139                 } else if (insideGemeinde && event.isStartElement()) {
140                     DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asStartElement().getName().getLocalPart());
141                     switch (xmlTag) {
142                         case SEVERITY:
143                             gemeindeData.setSeverity(Severity.getSeverity(getValue(eventReader)));
144                             break;
145                         case DESCRIPTION:
146                             gemeindeData.setDescription(getValue(eventReader));
147                             break;
148                         case EFFECTIVE:
149                             gemeindeData.setEffective(getTimestampValue(eventReader));
150                             break;
151                         case EXPIRES:
152                             gemeindeData.setExpires(getTimestampValue(eventReader));
153                             break;
154                         case EVENT:
155                             gemeindeData.setEvent(getValue(eventReader));
156                             break;
157                         case STATUS:
158                             gemeindeData.setStatus(getValue(eventReader));
159                             break;
160                         case MSGTYPE:
161                             gemeindeData.setMsgType(getValue(eventReader));
162                             break;
163                         case HEADLINE:
164                             gemeindeData.setHeadline(getValue(eventReader));
165                             break;
166                         case ONSET:
167                             gemeindeData.setOnset(getTimestampValue(eventReader));
168                             break;
169                         case ALTITUDE:
170                             gemeindeData.setAltitude(getBigDecimalValue(eventReader));
171                             break;
172                         case CEILING:
173                             gemeindeData.setCeiling(getBigDecimalValue(eventReader));
174                             break;
175                         case IDENTIFIER:
176                             gemeindeData.setId(getValue(eventReader));
177                             break;
178                         case INSTRUCTION:
179                             gemeindeData.setInstruction(getValue(eventReader));
180                             break;
181                         case URGENCY:
182                             gemeindeData.setUrgency(Urgency.getUrgency(getValue(eventReader)));
183                             break;
184                         default:
185                             break;
186                     }
187                 } else if (insideGemeinde && event.isEndElement()) {
188                     DwdXmlTag xmlTag = DwdXmlTag.getDwdXmlTag(event.asEndElement().getName().getLocalPart());
189                     switch (xmlTag) {
190                         case WARNUNGEN_GEMEINDEN:
191                             if (!gemeindeData.isTest() && !gemeindeData.isCancel()) {
192                                 cityData.add(gemeindeData);
193                             }
194                             insideGemeinde = false;
195                             break;
196                         default:
197                             break;
198                     }
199                 }
200             }
201         } catch (XMLStreamException e) {
202             logger.warn("Exception occurred while parsing the XML response: {}", e.getMessage());
203             logger.debug("Exception trace", e);
204             return false;
205         }
206
207         Collections.sort(cityData, new SeverityComparator());
208         return true;
209     }
210
211     private DwdWarningData getGemeindeData(int number) {
212         return cityData.size() <= number ? null : cityData.get(number);
213     }
214
215     public State getWarning(int number) {
216         DwdWarningData data = getGemeindeData(number);
217         return data == null ? OnOffType.OFF : OnOffType.ON;
218     }
219
220     public State getSeverity(int number) {
221         DwdWarningData data = getGemeindeData(number);
222         return data == null ? UnDefType.NULL : StringType.valueOf(data.getSeverity().getText());
223     }
224
225     public State getDescription(int number) {
226         DwdWarningData data = getGemeindeData(number);
227         return data == null ? UnDefType.NULL : StringType.valueOf(data.getDescription());
228     }
229
230     public State getEffective(int number) {
231         DwdWarningData data = getGemeindeData(number);
232         if (data == null) {
233             return UnDefType.NULL;
234         }
235         ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getEffective(), ZoneId.systemDefault());
236         return new DateTimeType(zoned);
237     }
238
239     public State getExpires(int number) {
240         DwdWarningData data = getGemeindeData(number);
241         if (data == null) {
242             return UnDefType.NULL;
243         }
244         ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getExpires(), ZoneId.systemDefault());
245         return new DateTimeType(zoned);
246     }
247
248     public State getOnset(int number) {
249         DwdWarningData data = getGemeindeData(number);
250         if (data == null) {
251             return UnDefType.NULL;
252         }
253         ZonedDateTime zoned = ZonedDateTime.ofInstant(data.getOnset(), ZoneId.systemDefault());
254         return new DateTimeType(zoned);
255     }
256
257     public State getEvent(int number) {
258         DwdWarningData data = getGemeindeData(number);
259         return data == null ? UnDefType.NULL : StringType.valueOf(data.getEvent());
260     }
261
262     public State getHeadline(int number) {
263         DwdWarningData data = getGemeindeData(number);
264         return data == null ? UnDefType.NULL : StringType.valueOf(data.getHeadline());
265     }
266
267     public State getAltitude(int number) {
268         DwdWarningData data = getGemeindeData(number);
269         if (data == null) {
270             return UnDefType.NULL;
271         }
272         return new QuantityType<>(data.getAltitude(), ImperialUnits.FOOT);
273     }
274
275     public State getCeiling(int number) {
276         DwdWarningData data = getGemeindeData(number);
277         if (data == null) {
278             return UnDefType.NULL;
279         }
280         return new QuantityType<>(data.getCeiling(), ImperialUnits.FOOT);
281     }
282
283     public State getInstruction(int number) {
284         DwdWarningData data = getGemeindeData(number);
285         return data == null ? UnDefType.NULL : StringType.valueOf(data.getInstruction());
286     }
287
288     public State getUrgency(int number) {
289         DwdWarningData data = getGemeindeData(number);
290         return data == null ? UnDefType.NULL : StringType.valueOf(data.getUrgency().getText());
291     }
292
293     public boolean isNew(int number) {
294         DwdWarningData data = getGemeindeData(number);
295         if (data == null) {
296             return false;
297         }
298         return cache.addEntry(data);
299     }
300
301     public void updateCache() {
302         cache.deleteOldEntries();
303     }
304
305     /**
306      * Only for Tests
307      */
308     protected void setDataAccess(DwdWarningDataAccess dataAccess) {
309         dataAccessCached = new ExpiringCache<>(Duration.ofMinutes(MIN_REFRESH_WAIT_MINUTES),
310                 () -> dataAccess.getDataFromEndpoint(""));
311     }
312 }