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