]> git.basschouten.com Git - openhab-addons.git/blob
52af9488ef46581b1479ef28819d06c65eb36d25
[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.device;
14
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.util.HashMap;
20 import java.util.Map;
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.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.insteon.internal.device.DeviceType.FeatureGroup;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import org.w3c.dom.Document;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.Node;
34 import org.w3c.dom.NodeList;
35 import org.xml.sax.SAXException;
36
37 /**
38  * Reads the device types from an xml file.
39  *
40  * @author Daniel Pfrommer - Initial contribution
41  * @author Bernd Pfrommer - openHAB 1 insteonplm binding
42  * @author Rob Nielsen - Port to openHAB 2 insteon binding
43  */
44 @NonNullByDefault
45 public class DeviceTypeLoader {
46     private static final Logger logger = LoggerFactory.getLogger(DeviceTypeLoader.class);
47     private Map<String, DeviceType> deviceTypes = new HashMap<>();
48     private static DeviceTypeLoader deviceTypeLoader = new DeviceTypeLoader();
49
50     private DeviceTypeLoader() {
51     } // private so nobody can call it
52
53     /**
54      * Finds the device type for a given product key
55      *
56      * @param aProdKey product key to search for
57      * @return the device type, or null if not found
58      */
59     public @Nullable DeviceType getDeviceType(String aProdKey) {
60         return (deviceTypes.get(aProdKey));
61     }
62
63     /**
64      * Must call loadDeviceTypesXML() before calling this function!
65      *
66      * @return currently known device types
67      */
68     public Map<String, DeviceType> getDeviceTypes() {
69         return (deviceTypes);
70     }
71
72     /**
73      * Reads the device types from input stream and stores them in memory for
74      * later access.
75      *
76      * @param in the input stream from which to read
77      */
78     public void loadDeviceTypesXML(InputStream in) throws ParserConfigurationException, SAXException, IOException {
79         DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
80         // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
81         dbFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
82         dbFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
83         dbFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
84         dbFactory.setXIncludeAware(false);
85         dbFactory.setExpandEntityReferences(false);
86         DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
87         Document doc = dBuilder.parse(in);
88         doc.getDocumentElement().normalize();
89         Node root = doc.getDocumentElement();
90         NodeList nodes = root.getChildNodes();
91         for (int i = 0; i < nodes.getLength(); i++) {
92             Node node = nodes.item(i);
93             if (node.getNodeType() == Node.ELEMENT_NODE && "device".equals(node.getNodeName())) {
94                 processDevice((Element) node);
95             }
96         }
97     }
98
99     /**
100      * Reads the device types from file and stores them in memory for later access.
101      *
102      * @param aFileName The name of the file to read from
103      * @throws ParserConfigurationException
104      * @throws SAXException
105      * @throws IOException
106      */
107     public void loadDeviceTypesXML(String aFileName) throws ParserConfigurationException, SAXException, IOException {
108         File file = new File(aFileName);
109         InputStream in = new FileInputStream(file);
110         loadDeviceTypesXML(in);
111     }
112
113     /**
114      * Process device node
115      *
116      * @param e name of the element to process
117      * @throws SAXException
118      */
119     private void processDevice(Element e) throws SAXException {
120         String productKey = e.getAttribute("productKey");
121         if ("".equals(productKey)) {
122             throw new SAXException("device in device_types file has no product key!");
123         }
124         if (deviceTypes.containsKey(productKey)) {
125             logger.warn("overwriting previous definition of device {}", productKey);
126             deviceTypes.remove(productKey);
127         }
128         DeviceType devType = new DeviceType(productKey);
129
130         NodeList nodes = e.getChildNodes();
131         for (int i = 0; i < nodes.getLength(); i++) {
132             Node node = nodes.item(i);
133             if (node.getNodeType() != Node.ELEMENT_NODE) {
134                 continue;
135             }
136             Element subElement = (Element) node;
137             String nodeName = subElement.getNodeName();
138             if ("model".equals(nodeName)) {
139                 devType.setModel(subElement.getTextContent());
140             } else if ("description".equals(nodeName)) {
141                 devType.setDescription(subElement.getTextContent());
142             } else if ("feature".equals(nodeName)) {
143                 processFeature(devType, subElement);
144             } else if ("feature_group".equals(nodeName)) {
145                 processFeatureGroup(devType, subElement);
146             }
147             deviceTypes.put(productKey, devType);
148         }
149     }
150
151     private String processFeature(DeviceType devType, Element e) throws SAXException {
152         String name = e.getAttribute("name");
153         if ("".equals(name)) {
154             throw new SAXException("feature " + e.getNodeName() + " has feature without name!");
155         }
156         if (!name.equals(name.toLowerCase())) {
157             throw new SAXException("feature name '" + name + "' must be lower case");
158         }
159         if (!devType.addFeature(name, e.getTextContent())) {
160             throw new SAXException("duplicate feature: " + name);
161         }
162         return (name);
163     }
164
165     private String processFeatureGroup(DeviceType devType, Element e) throws SAXException {
166         String name = e.getAttribute("name");
167         if ("".equals(name)) {
168             throw new SAXException("feature group " + e.getNodeName() + " has no name attr!");
169         }
170         String type = e.getAttribute("type");
171         if ("".equals(type)) {
172             throw new SAXException("feature group " + e.getNodeName() + " has no type attr!");
173         }
174         FeatureGroup fg = new FeatureGroup(name, type);
175         NodeList nodes = e.getChildNodes();
176         for (int i = 0; i < nodes.getLength(); i++) {
177             Node node = nodes.item(i);
178             if (node.getNodeType() != Node.ELEMENT_NODE) {
179                 continue;
180             }
181             Element subElement = (Element) node;
182             String nodeName = subElement.getNodeName();
183             if ("feature".equals(nodeName)) {
184                 fg.addFeature(processFeature(devType, subElement));
185             } else if ("feature_group".equals(nodeName)) {
186                 fg.addFeature(processFeatureGroup(devType, subElement));
187             }
188         }
189         if (!devType.addFeatureGroup(name, fg)) {
190             throw new SAXException("duplicate feature group " + name);
191         }
192         return (name);
193     }
194
195     /**
196      * Singleton instance function, creates DeviceTypeLoader
197      *
198      * @return DeviceTypeLoader singleton reference
199      */
200     @Nullable
201     public static synchronized DeviceTypeLoader instance() {
202         if (deviceTypeLoader.getDeviceTypes().isEmpty()) {
203             InputStream input = DeviceTypeLoader.class.getResourceAsStream("/device_types.xml");
204             try {
205                 if (input != null) {
206                     deviceTypeLoader.loadDeviceTypesXML(input);
207                 } else {
208                     logger.warn("Resource stream is null, cannot read xml file.");
209                 }
210             } catch (ParserConfigurationException e) {
211                 logger.warn("parser config error when reading device types xml file: ", e);
212             } catch (SAXException e) {
213                 logger.warn("SAX exception when reading device types xml file: ", e);
214             } catch (IOException e) {
215                 logger.warn("I/O exception when reading device types xml file: ", e);
216             }
217         }
218         return deviceTypeLoader;
219     }
220 }