From 2f35a79de77a49a1736f030a88fed8eaf14a5486 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Mon, 15 Jul 2024 13:57:51 +0200 Subject: [PATCH] Fix blocking initialization (#17057) Resolves #16806 Signed-off-by: Jacob Laursen --- .../internal/handler/DenonMarantzHandler.java | 193 ++++++++++-------- 1 file changed, 103 insertions(+), 90 deletions(-) diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/handler/DenonMarantzHandler.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/handler/DenonMarantzHandler.java index b821905e86..a1c95500fa 100644 --- a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/handler/DenonMarantzHandler.java +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/handler/DenonMarantzHandler.java @@ -83,6 +83,7 @@ public class DenonMarantzHandler extends BaseThingHandler implements DenonMarant private DenonMarantzConnectorFactory connectorFactory = new DenonMarantzConnectorFactory(); private DenonMarantzState denonMarantzState; private @Nullable ScheduledFuture retryJob; + private @Nullable ScheduledFuture initJob; public DenonMarantzHandler(Thing thing, HttpClient httpClient) { super(thing); @@ -211,120 +212,123 @@ public class DenonMarantzHandler extends BaseThingHandler implements DenonMarant * When not set we will try to auto-detect the correct values * for isTelnet and zoneCount and update the Thing accordingly. */ - if (config.isTelnet() == null) { - logger.debug("Trying to auto-detect the connection."); - ContentResponse response; - boolean telnetEnable = true; - int httpPort = 80; - boolean httpApiUsable = false; - - // try to reach the HTTP API at port 80 (most models, except Denon ...H should respond. - String host = config.getHost(); + if (config.isTelnet() != null) { + return; + } + logger.debug("Trying to auto-detect the connection."); + ContentResponse response; + boolean telnetEnable = true; + int httpPort = 80; + boolean httpApiUsable = false; + + // try to reach the HTTP API at port 80 (most models, except Denon ...H should respond. + String host = config.getHost(); + try { + response = httpClient.newRequest("http://" + host + "/goform/Deviceinfo.xml").timeout(3, TimeUnit.SECONDS) + .send(); + if (response.getStatus() == HttpURLConnection.HTTP_OK) { + logger.debug("We can access the HTTP API, disabling the Telnet mode by default."); + telnetEnable = false; + httpApiUsable = true; + } + } catch (TimeoutException | ExecutionException e) { + logger.debug("Error when trying to access AVR using HTTP on port 80.", e); + } + + if (telnetEnable) { + // the above attempt failed. Let's try on port 8080, as for some models a subset of the HTTP API is + // available try { - response = httpClient.newRequest("http://" + host + "/goform/Deviceinfo.xml") + response = httpClient.newRequest("http://" + host + ":8080/goform/Deviceinfo.xml") .timeout(3, TimeUnit.SECONDS).send(); if (response.getStatus() == HttpURLConnection.HTTP_OK) { - logger.debug("We can access the HTTP API, disabling the Telnet mode by default."); + logger.debug("This model responds to HTTP port 8080, disabling the Telnet mode by default."); telnetEnable = false; + httpPort = 8080; httpApiUsable = true; } } catch (TimeoutException | ExecutionException e) { - logger.debug("Error when trying to access AVR using HTTP on port 80.", e); + logger.debug("Additionally tried to connect to port 8080, this also failed. Reverting to Telnet mode.", + e); } + } - if (telnetEnable) { - // the above attempt failed. Let's try on port 8080, as for some models a subset of the HTTP API is - // available - try { - response = httpClient.newRequest("http://" + host + ":8080/goform/Deviceinfo.xml") - .timeout(3, TimeUnit.SECONDS).send(); - if (response.getStatus() == HttpURLConnection.HTTP_OK) { - logger.debug("This model responds to HTTP port 8080, disabling the Telnet mode by default."); - telnetEnable = false; - httpPort = 8080; - httpApiUsable = true; - } - } catch (TimeoutException | ExecutionException e) { - logger.debug( - "Additionally tried to connect to port 8080, this also failed. Reverting to Telnet mode.", - e); - } - } + // default zone count + int zoneCount = 2; - // default zone count - int zoneCount = 2; + // try to determine the zone count by checking the Deviceinfo.xml file + if (httpApiUsable) { + int status = 0; + response = null; + try { + response = httpClient.newRequest("http://" + host + ":" + httpPort + "/goform/Deviceinfo.xml") + .timeout(3, TimeUnit.SECONDS).send(); + status = response.getStatus(); + } catch (TimeoutException | ExecutionException e) { + logger.debug("Failed in fetching the Deviceinfo.xml to determine zone count", e); + } - // try to determine the zone count by checking the Deviceinfo.xml file - if (httpApiUsable) { - int status = 0; - response = null; + if (status == HttpURLConnection.HTTP_OK && response != null) { + DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); try { - response = httpClient.newRequest("http://" + host + ":" + httpPort + "/goform/Deviceinfo.xml") - .timeout(3, TimeUnit.SECONDS).send(); - status = response.getStatus(); - } catch (TimeoutException | ExecutionException e) { - logger.debug("Failed in fetching the Deviceinfo.xml to determine zone count", e); - } - - if (status == HttpURLConnection.HTTP_OK && response != null) { - DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); - try { - // see - // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - domFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); - domFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - domFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - domFactory.setXIncludeAware(false); - domFactory.setExpandEntityReferences(false); - DocumentBuilder builder; - builder = domFactory.newDocumentBuilder(); - Document dDoc = builder.parse(new InputSource(new StringReader(response.getContentAsString()))); - XPath xPath = XPathFactory.newInstance().newXPath(); - Node node = (Node) xPath.evaluate("/Device_Info/DeviceZones/text()", dDoc, XPathConstants.NODE); - if (node != null) { - String nodeValue = node.getNodeValue(); - logger.trace("/Device_Info/DeviceZones/text() = {}", nodeValue); - zoneCount = Integer.parseInt(nodeValue); - logger.debug("Discovered number of zones: {}", zoneCount); - } - } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException - | NumberFormatException e) { - logger.debug("Something went wrong with looking up the zone count in Deviceinfo.xml: {}", - e.getMessage()); + // see + // https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + domFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + domFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + domFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + domFactory.setXIncludeAware(false); + domFactory.setExpandEntityReferences(false); + DocumentBuilder builder; + builder = domFactory.newDocumentBuilder(); + Document dDoc = builder.parse(new InputSource(new StringReader(response.getContentAsString()))); + XPath xPath = XPathFactory.newInstance().newXPath(); + Node node = (Node) xPath.evaluate("/Device_Info/DeviceZones/text()", dDoc, XPathConstants.NODE); + if (node != null) { + String nodeValue = node.getNodeValue(); + logger.trace("/Device_Info/DeviceZones/text() = {}", nodeValue); + zoneCount = Integer.parseInt(nodeValue); + logger.debug("Discovered number of zones: {}", zoneCount); } + } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException + | NumberFormatException e) { + logger.debug("Something went wrong with looking up the zone count in Deviceinfo.xml: {}", + e.getMessage()); } } - config.setTelnet(telnetEnable); - config.setZoneCount(zoneCount); - Configuration configuration = editConfiguration(); - configuration.put(PARAMETER_TELNET_ENABLED, telnetEnable); - configuration.put(PARAMETER_ZONE_COUNT, zoneCount); - updateConfiguration(configuration); } + config.setTelnet(telnetEnable); + config.setZoneCount(zoneCount); + Configuration configuration = editConfiguration(); + configuration.put(PARAMETER_TELNET_ENABLED, telnetEnable); + configuration.put(PARAMETER_ZONE_COUNT, zoneCount); + updateConfiguration(configuration); } @Override public void initialize() { config = getConfigAs(DenonMarantzConfiguration.class); - // Configure Connection type (Telnet/HTTP) and number of zones - // Note: this only happens for discovered Things - try { - autoConfigure(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } + updateStatus(ThingStatus.UNKNOWN); - if (!checkConfiguration()) { - return; - } + initJob = scheduler.schedule(() -> { + // Configure Connection type (Telnet/HTTP) and number of zones + // Note: this only happens for discovered Things + try { + autoConfigure(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } - configureZoneChannels(); - updateStatus(ThingStatus.UNKNOWN); - // create connection (either Telnet or HTTP) - // ThingStatus ONLINE/OFFLINE is set when AVR status is known. - createConnection(); + if (!checkConfiguration()) { + return; + } + + configureZoneChannels(); + // create connection (either Telnet or HTTP) + // ThingStatus ONLINE/OFFLINE is set when AVR status is known. + createConnection(); + }, 0, TimeUnit.SECONDS); } private void createConnection() { @@ -337,6 +341,14 @@ public class DenonMarantzHandler extends BaseThingHandler implements DenonMarant connector.connect(); } + private void cancelInitJob() { + ScheduledFuture initJob = this.initJob; + if (initJob != null) { + initJob.cancel(true); + } + this.initJob = null; + } + private void cancelRetry() { ScheduledFuture retryJob = this.retryJob; if (retryJob != null) { @@ -430,6 +442,7 @@ public class DenonMarantzHandler extends BaseThingHandler implements DenonMarant } this.connector = null; cancelRetry(); + cancelInitJob(); super.dispose(); } -- 2.47.3