]> git.basschouten.com Git - openhab-addons.git/blob
e8991a18f68f8e183afabe20cbfb620cf6387f9e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.insteon.internal.message;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.HashMap;
18 import java.util.LinkedHashMap;
19 import java.util.Map;
20 import java.util.Map.Entry;
21
22 import javax.xml.parsers.DocumentBuilder;
23 import javax.xml.parsers.DocumentBuilderFactory;
24 import javax.xml.parsers.ParserConfigurationException;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.openhab.binding.insteon.internal.utils.Pair;
28 import org.openhab.binding.insteon.internal.utils.Utils.DataTypeParser;
29 import org.openhab.binding.insteon.internal.utils.Utils.ParsingException;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Element;
32 import org.w3c.dom.Node;
33 import org.w3c.dom.NodeList;
34 import org.xml.sax.SAXException;
35
36 /**
37  * Reads the Msg definitions from an XML file
38  *
39  * @author Daniel Pfrommer - Initial contribution
40  * @author Rob Nielsen - Port to openHAB 2 insteon binding
41  */
42 @NonNullByDefault
43 public class XMLMessageReader {
44     /**
45      * Reads the message definitions from an xml file
46      *
47      * @param input input stream from which to read
48      * @return what was read from file: the map between clear text string and Msg objects
49      * @throws IOException couldn't read file etc
50      * @throws ParsingException something wrong with the file format
51      * @throws FieldException something wrong with the field definition
52      */
53     public static Map<String, Msg> readMessageDefinitions(InputStream input)
54             throws IOException, ParsingException, FieldException {
55         Map<String, Msg> messageMap = new HashMap<>();
56         try {
57             DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
58             // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
59             dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
60             dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
61             dbFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
62             dbFactory.setXIncludeAware(false);
63             dbFactory.setExpandEntityReferences(false);
64             DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
65             // Parse it!
66             Document doc = dBuilder.parse(input);
67             doc.getDocumentElement().normalize();
68
69             Node root = doc.getDocumentElement();
70
71             NodeList nodes = root.getChildNodes();
72
73             for (int i = 0; i < nodes.getLength(); i++) {
74                 Node node = nodes.item(i);
75                 if (node.getNodeType() == Node.ELEMENT_NODE) {
76                     if ("msg".equals(node.getNodeName())) {
77                         Pair<String, Msg> msgDef = readMessageDefinition((Element) node);
78                         messageMap.put(msgDef.getKey(), msgDef.getValue());
79                     }
80                 }
81             }
82         } catch (SAXException e) {
83             throw new ParsingException("Failed to parse XML!", e);
84         } catch (ParserConfigurationException e) {
85             throw new ParsingException("Got parser config exception! ", e);
86         }
87         return messageMap;
88     }
89
90     private static Pair<String, Msg> readMessageDefinition(Element msg) throws FieldException, ParsingException {
91         int length = 0;
92         int hlength = 0;
93         LinkedHashMap<Field, Object> fieldMap = new LinkedHashMap<>();
94         String dir = msg.getAttribute("direction");
95         String name = msg.getAttribute("name");
96         Msg.Direction direction = Msg.Direction.getDirectionFromString(dir);
97
98         if (msg.hasAttribute("length")) {
99             length = Integer.parseInt(msg.getAttribute("length"));
100         }
101
102         NodeList nodes = msg.getChildNodes();
103
104         int offset = 0;
105
106         for (int i = 0; i < nodes.getLength(); i++) {
107             Node node = nodes.item(i);
108             if (node.getNodeType() == Node.ELEMENT_NODE) {
109                 if ("header".equals(node.getNodeName())) {
110                     int o = readHeaderElement((Element) node, fieldMap);
111                     hlength = o;
112                     // Increment the offset by the header length
113                     offset += o;
114                 } else {
115                     Pair<Field, Object> field = readField((Element) node, offset);
116                     fieldMap.put(field.getKey(), field.getValue());
117                     // Increment the offset
118                     offset += field.getKey().getType().getSize();
119                 }
120             }
121         }
122         if (offset != length) {
123             throw new ParsingException(
124                     "Actual msg length " + offset + " differs from given msg length " + length + "!");
125         }
126         if (length == 0) {
127             length = offset;
128         }
129
130         return new Pair<>(name, createMsg(fieldMap, length, hlength, direction));
131     }
132
133     private static int readHeaderElement(Element header, LinkedHashMap<Field, Object> fields) throws ParsingException {
134         int offset = 0;
135         int headerLen = Integer.parseInt(header.getAttribute("length"));
136
137         NodeList nodes = header.getChildNodes();
138         for (int i = 0; i < nodes.getLength(); i++) {
139             Node node = nodes.item(i);
140             if (node.getNodeType() == Node.ELEMENT_NODE) {
141                 Pair<Field, Object> definition = readField((Element) node, offset);
142                 offset += definition.getKey().getType().getSize();
143                 fields.put(definition.getKey(), definition.getValue());
144             }
145         }
146         if (headerLen != offset) {
147             throw new ParsingException(
148                     "Actual header length " + offset + " differs from given length " + headerLen + "!");
149         }
150         return headerLen;
151     }
152
153     private static Pair<Field, Object> readField(Element field, int offset) {
154         DataType dType = DataType.getDataType(field.getTagName());
155         // Will return blank if no name attribute
156         String name = field.getAttribute("name");
157         Field f = new Field(name, dType, offset);
158         // Now we have field, only need value
159         String sVal = field.getTextContent();
160         Object val = DataTypeParser.parseDataType(dType, sVal);
161         return new Pair<>(f, val);
162     }
163
164     private static Msg createMsg(HashMap<Field, Object> values, int length, int headerLength, Msg.Direction dir)
165             throws FieldException {
166         Msg msg = new Msg(headerLength, new byte[length], length, dir);
167         for (Entry<Field, Object> e : values.entrySet()) {
168             Field f = e.getKey();
169             byte[] data = msg.getData();
170             if (data != null) {
171                 f.set(data, e.getValue());
172             } else {
173                 throw new FieldException("data is null");
174             }
175             if (!"".equals(f.getName())) {
176                 msg.addField(f);
177             }
178         }
179         return msg;
180     }
181 }