2 * Copyright (c) 2010-2023 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.easee.internal.command;
15 import static org.openhab.binding.easee.internal.EaseeBindingConstants.WEB_REQUEST_BEARER_TOKEN_PREFIX;
17 import java.net.SocketTimeoutException;
18 import java.net.UnknownHostException;
19 import java.nio.ByteBuffer;
20 import java.nio.charset.StandardCharsets;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.eclipse.jetty.client.api.Request;
28 import org.eclipse.jetty.client.api.Response;
29 import org.eclipse.jetty.client.api.Result;
30 import org.eclipse.jetty.client.util.BufferingResponseListener;
31 import org.eclipse.jetty.http.HttpHeader;
32 import org.eclipse.jetty.http.HttpStatus;
33 import org.eclipse.jetty.http.HttpStatus.Code;
34 import org.openhab.binding.easee.internal.EaseeBindingConstants;
35 import org.openhab.binding.easee.internal.connector.CommunicationStatus;
36 import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
37 import org.openhab.binding.easee.internal.model.GenericResponseTransformer;
38 import org.openhab.binding.easee.internal.model.ValidationException;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 import com.google.gson.Gson;
43 import com.google.gson.GsonBuilder;
44 import com.google.gson.JsonObject;
45 import com.google.gson.ToNumberPolicy;
48 * base class for all commands. common logic should be implemented here
50 * @author Alexander Friese - initial contribution
53 public abstract class AbstractCommand extends BufferingResponseListener implements EaseeCommand {
55 public static enum RetryOnFailure {
60 public static enum ProcessFailureResponse {
68 private final Logger logger = LoggerFactory.getLogger(AbstractCommand.class);
73 protected final EaseeThingHandler handler;
78 protected final Gson gson;
81 * status code of fulfilled request
83 private final CommunicationStatus communicationStatus;
86 * generic transformer which just transfers all values in a plain map.
88 protected final GenericResponseTransformer transformer;
93 private int retries = 0;
98 private final RetryOnFailure retryOnFailure;
101 * process error response, e.g. set handler offline on error
103 private final ProcessFailureResponse processFailureResponse;
106 * allows further processing of the json result data, if set.
108 private final JsonResultProcessor resultProcessor;
113 public AbstractCommand(EaseeThingHandler handler, RetryOnFailure retryOnFailure,
114 ProcessFailureResponse processFailureResponse, JsonResultProcessor resultProcessor) {
115 this.gson = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create();
116 this.communicationStatus = new CommunicationStatus();
117 this.transformer = new GenericResponseTransformer(handler);
118 this.handler = handler;
119 this.processFailureResponse = processFailureResponse;
120 this.retryOnFailure = retryOnFailure;
121 this.resultProcessor = resultProcessor;
125 * Log request success
128 public final void onSuccess(@Nullable Response response) {
129 super.onSuccess(response);
130 if (response != null) {
131 communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
132 logger.debug("HTTP response {}", response.getStatus());
137 * Log request failure
140 public final void onFailure(@Nullable Response response, @Nullable Throwable failure) {
141 super.onFailure(response, failure);
142 if (failure != null) {
143 logger.info("Request failed: {}", failure.toString());
144 communicationStatus.setError((Exception) failure);
145 if (failure instanceof SocketTimeoutException || failure instanceof TimeoutException) {
146 communicationStatus.setHttpCode(Code.REQUEST_TIMEOUT);
147 } else if (failure instanceof UnknownHostException) {
148 communicationStatus.setHttpCode(Code.BAD_GATEWAY);
150 communicationStatus.setHttpCode(Code.INTERNAL_SERVER_ERROR);
153 logger.info("Request failed");
155 if (response != null && response.getStatus() > 0) {
156 communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
161 * just for logging of content
164 public void onContent(@Nullable Response response, @Nullable ByteBuffer content) {
165 super.onContent(response, content);
166 logger.debug("received content, length: {}", getContentAsString().length());
170 * default handling of successful requests.
173 public void onComplete(@Nullable Result result) {
174 String json = getContentAsString(StandardCharsets.UTF_8);
176 logger.debug("JSON String: {}", json);
177 switch (getCommunicationStatus().getHttpCode()) {
180 onCompleteCodeOk(json);
183 onCompleteCodeDefault(json);
188 * handling of result in case of HTTP response OK.
192 protected void onCompleteCodeOk(@Nullable String json) {
193 JsonObject jsonObject = transform(json);
194 if (jsonObject != null) {
195 logger.debug("success");
196 handler.updateChannelStatus(transformer.transform(jsonObject, getChannelGroup()));
197 processResult(jsonObject);
202 * handling of result in default case, this means error handling of http codes where no specific handling applies.
206 protected void onCompleteCodeDefault(@Nullable String json) {
207 JsonObject jsonObject = transform(json);
208 if (jsonObject == null) {
209 jsonObject = new JsonObject();
211 if (processFailureResponse == ProcessFailureResponse.YES) {
212 processResult(jsonObject);
214 logger.info("command failed, url: {} - code: {} - result: {}", getURL(),
215 getCommunicationStatus().getHttpCode(), jsonObject.get(EaseeBindingConstants.JSON_KEY_ERROR_TITLE));
218 if (retryOnFailure == RetryOnFailure.YES && retries++ < MAX_RETRIES) {
219 handler.enqueueCommand(this);
224 * error safe json transformer.
229 protected @Nullable JsonObject transform(@Nullable String json) {
232 return gson.fromJson(json, JsonObject.class);
233 } catch (Exception ex) {
234 logger.debug("JSON could not be parsed: {}\nError: {}", json, ex.getMessage());
241 * preparation of the request. will call a hook (prepareRequest) that has to be implemented in the subclass to add
242 * content to the request.
244 * @throws ValidationException
247 public void performAction(HttpClient asyncclient, String accessToken) throws ValidationException {
248 Request request = asyncclient.newRequest(getURL()).timeout(handler.getBridgeConfiguration().getAsyncTimeout(),
251 // we want to send and receive json only, so explicitely set this!
252 request.header(HttpHeader.ACCEPT, "application/json");
253 request.header(HttpHeader.CONTENT_TYPE, "application/json");
255 // this should be the default for Easee Cloud API
256 request.followRedirects(false);
258 // add authentication data for every request. Handling this here makes it obsolete to implement for each and
260 if (!accessToken.isBlank()) {
261 request.header(HttpHeader.AUTHORIZATION, WEB_REQUEST_BEARER_TOKEN_PREFIX + accessToken);
264 prepareRequest(request).send(this);
268 * @return returns Http Status Code
270 public CommunicationStatus getCommunicationStatus() {
271 return communicationStatus;
275 * calls the registered resultProcessor.
279 protected final void processResult(JsonObject jsonObject) {
281 resultProcessor.processResult(getCommunicationStatus(), jsonObject);
282 } catch (Exception ex) {
283 // this should not happen
284 logger.warn("Exception caught: {}", ex.getMessage(), ex);
289 * concrete implementation has to prepare the requests with additional parameters, etc
291 * @param requestToPrepare the request to prepare
292 * @return prepared Request object
293 * @throws ValidationException
295 protected abstract Request prepareRequest(Request requestToPrepare) throws ValidationException;
298 * concrete implementation has to provide the channel group.
302 protected abstract String getChannelGroup();
305 * concrete implementation has to provide the URL
309 protected abstract String getURL();