2 * Copyright (c) 2010-2024 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.binding.fsinternetradio.internal.radio;
15 import java.io.IOException;
16 import java.io.StringReader;
18 import javax.xml.parsers.DocumentBuilder;
19 import javax.xml.parsers.DocumentBuilderFactory;
20 import javax.xml.parsers.ParserConfigurationException;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24 import org.w3c.dom.CharacterData;
25 import org.w3c.dom.Document;
26 import org.w3c.dom.Element;
27 import org.w3c.dom.Node;
28 import org.w3c.dom.NodeList;
29 import org.xml.sax.InputSource;
30 import org.xml.sax.SAXException;
33 * This class hold the result of a request read from the radio. Upon a request the radio returns a XML document like
39 * <fsapiResponse> <status>FS_OK</status> <value><u8>1</u8></value> </fsapiResponse>
44 * This class parses this XML data and provides functions for reading and casting typical fields.
46 * @author Rainer Ostendorf
47 * @author Patrick Koenemann
50 public class FrontierSiliconRadioApiResult {
53 * XML structure holding the parsed response
55 final Document xmlDoc;
57 private final Logger logger = LoggerFactory.getLogger(FrontierSiliconRadioApiResult.class);
60 * Create result object from XML that was received from the radio.
62 * @param requestResultString
63 * The XML string received from the radio.
64 * @throws IOException in case the XML returned by the radio is invalid.
66 public FrontierSiliconRadioApiResult(String requestResultString) throws IOException {
69 xml = getXmlDocFromString(requestResultString);
70 } catch (Exception e) {
71 logger.trace("converting to XML failed: '{}' with {}: {}", requestResultString, e.getClass().getName(),
73 logger.debug("converting to XML failed with {}: {}", e.getClass().getName(), e.getMessage());
74 if (e instanceof IOException exception) {
77 throw new IOException(e);
83 * Extract the field "status" from the result and return it
85 * @return result field as string.
87 private String getStatus() {
88 final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
89 final Element statusNode = (Element) fsApiResult.getElementsByTagName("status").item(0);
91 final String status = getCharacterDataFromElement(statusNode);
92 logger.trace("status is: {}", status);
98 * checks if the responses status code was "FS_OK"
100 * @return true if status is "FS_OK", false else
102 public boolean isStatusOk() {
103 return ("FS_OK").equals(getStatus());
107 * read the <value><u8> field as boolean
109 * @return value.u8 field as bool
111 public boolean getValueU8AsBoolean() {
113 final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
114 final Element valueNode = (Element) fsApiResult.getElementsByTagName("value").item(0);
115 final Element u8Node = (Element) valueNode.getElementsByTagName("u8").item(0);
117 final String value = getCharacterDataFromElement(u8Node);
118 logger.trace("value is: {}", value);
120 return "1".equals(value);
121 } catch (Exception e) {
122 logger.error("getting Value.U8 failed with {}: {}", e.getClass().getName(), e.getMessage());
128 * read the <value><u8> field as int
130 * @return value.u8 field as int
132 public int getValueU8AsInt() {
134 final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
135 final Element valueNode = (Element) fsApiResult.getElementsByTagName("value").item(0);
136 final Element u8Node = (Element) valueNode.getElementsByTagName("u8").item(0);
138 final String value = getCharacterDataFromElement(u8Node);
139 logger.trace("value is: {}", value);
141 return Integer.parseInt(value);
142 } catch (Exception e) {
143 logger.error("getting Value.U8 failed with {}: {}", e.getClass().getName(), e.getMessage());
149 * read the <value><u32> field as int
151 * @return value.u32 field as int
153 public int getValueU32AsInt() {
155 final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
156 final Element valueNode = (Element) fsApiResult.getElementsByTagName("value").item(0);
157 final Element u32Node = (Element) valueNode.getElementsByTagName("u32").item(0);
159 final String value = getCharacterDataFromElement(u32Node);
160 logger.trace("value is: {}", value);
162 return Integer.parseInt(value);
163 } catch (Exception e) {
164 logger.error("getting Value.U32 failed with {}: {}", e.getClass().getName(), e.getMessage());
170 * read the <value><c8_array> field as String
172 * @return value.c8_array field as String
174 public String getValueC8ArrayAsString() {
176 final Element fsApiResult = (Element) xmlDoc.getElementsByTagName("fsapiResponse").item(0);
177 final Element valueNode = (Element) fsApiResult.getElementsByTagName("value").item(0);
178 final Element c8Array = (Element) valueNode.getElementsByTagName("c8_array").item(0);
180 final String value = getCharacterDataFromElement(c8Array);
181 logger.trace("value is: {}", value);
184 } catch (Exception e) {
185 logger.error("getting Value.c8array failed with {}: {}", e.getClass().getName(), e.getMessage());
191 * read the <sessionId> field as String
193 * @return value of sessionId field
195 public String getSessionId() {
196 final NodeList sessionIdTagList = xmlDoc.getElementsByTagName("sessionId");
197 return getCharacterDataFromElement((Element) sessionIdTagList.item(0));
201 * converts the string we got from the radio to a parsable XML document
204 * the XML string read from the radio
205 * @return the parsed XML document
206 * @throws ParserConfigurationException
207 * @throws SAXException
208 * @throws IOException
210 private Document getXmlDocFromString(String xmlString)
211 throws ParserConfigurationException, SAXException, IOException {
212 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
213 // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
214 factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
215 factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
216 factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
217 factory.setXIncludeAware(false);
218 factory.setExpandEntityReferences(false);
219 final DocumentBuilder builder = factory.newDocumentBuilder();
220 return builder.parse(new InputSource(new StringReader(xmlString)));
224 * convert the value of a given XML element to a string for further processing
228 * @return the elements value converted to string
230 private static String getCharacterDataFromElement(Element e) {
231 final Node child = e.getFirstChild();
232 if (child instanceof CharacterData cd) {