mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[entsoe] Refactor HTTP error handling (#17616)
* Refactor HTTP error handling Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
10054a7d5c
commit
9f5349bae8
@ -26,8 +26,9 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.entsoe.internal.client.Client;
|
||||
import org.openhab.binding.entsoe.internal.client.Request;
|
||||
import org.openhab.binding.entsoe.internal.client.EntsoeRequest;
|
||||
import org.openhab.binding.entsoe.internal.client.SpotPrice;
|
||||
import org.openhab.binding.entsoe.internal.exception.EntsoeConfigurationException;
|
||||
import org.openhab.binding.entsoe.internal.exception.EntsoeResponseException;
|
||||
@ -54,22 +55,18 @@ import org.slf4j.LoggerFactory;
|
||||
public class EntsoeHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EntsoeHandler.class);
|
||||
|
||||
private EntsoeConfiguration config;
|
||||
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
|
||||
private Map<Instant, SpotPrice> entsoeTimeSeries = new LinkedHashMap<>();
|
||||
|
||||
private final ZoneId cetZoneId = ZoneId.of("CET");
|
||||
private final Client client;
|
||||
|
||||
private EntsoeConfiguration config = new EntsoeConfiguration();
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private Map<Instant, SpotPrice> entsoeTimeSeries = new LinkedHashMap<>();
|
||||
private ZonedDateTime lastDayAheadReceived = ZonedDateTime.of(LocalDateTime.MIN, cetZoneId);
|
||||
|
||||
private int historicDaysInitially = 0;
|
||||
|
||||
public EntsoeHandler(Thing thing) {
|
||||
public EntsoeHandler(final Thing thing, final HttpClient httpClient) {
|
||||
super(thing);
|
||||
config = new EntsoeConfiguration();
|
||||
this.client = new Client(httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,7 +98,12 @@ public class EntsoeHandler extends BaseThingHandler {
|
||||
logger.trace("handleCommand(channelUID:{}, command:{})", channelUID.getAsString(), command.toFullString());
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
fetchNewPrices();
|
||||
try {
|
||||
fetchNewPrices();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,7 +201,11 @@ public class EntsoeHandler extends BaseThingHandler {
|
||||
}
|
||||
|
||||
if (entsoeTimeSeries.isEmpty()) {
|
||||
fetchNewPrices();
|
||||
try {
|
||||
fetchNewPrices();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -211,14 +217,19 @@ public class EntsoeHandler extends BaseThingHandler {
|
||||
.isAfter(currentCetTimeWholeHours().withHour(config.spotPricesAvailableCetHour));
|
||||
|
||||
if (needsInitialUpdate || (!hasNextDayValue && readyForNextDayValue)) {
|
||||
fetchNewPrices();
|
||||
try {
|
||||
fetchNewPrices();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
updateCurrentState(EntsoeBindingConstants.CHANNEL_SPOT_PRICE);
|
||||
schedule(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchNewPrices() {
|
||||
private void fetchNewPrices() throws InterruptedException {
|
||||
logger.trace("Fetching new prices");
|
||||
|
||||
Instant startUtc = ZonedDateTime.now(cetZoneId)
|
||||
@ -226,12 +237,11 @@ public class EntsoeHandler extends BaseThingHandler {
|
||||
.toInstant();
|
||||
Instant endUtc = ZonedDateTime.now(cetZoneId).plusDays(2).with(LocalTime.MIDNIGHT).toInstant();
|
||||
|
||||
Request request = new Request(config.securityToken, config.area, startUtc, endUtc);
|
||||
Client client = new Client();
|
||||
EntsoeRequest request = new EntsoeRequest(config.securityToken, config.area, startUtc, endUtc);
|
||||
boolean success = false;
|
||||
|
||||
try {
|
||||
entsoeTimeSeries = client.doGetRequest(request, config.requestTimeout * 1000, config.resolution);
|
||||
entsoeTimeSeries = client.doGetRequest(request, config.requestTimeout, config.resolution);
|
||||
|
||||
TimeSeries baseTimeSeries = new TimeSeries(EntsoeBindingConstants.TIMESERIES_POLICY);
|
||||
for (Map.Entry<Instant, SpotPrice> entry : entsoeTimeSeries.entrySet()) {
|
||||
|
@ -16,12 +16,17 @@ import static org.openhab.binding.entsoe.internal.EntsoeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link EntsoeHandlerFactory} is responsible for creating things and thing
|
||||
@ -33,6 +38,15 @@ import org.osgi.service.component.annotations.Component;
|
||||
@Component(configurationPid = "binding.entsoe", service = ThingHandlerFactory.class)
|
||||
public class EntsoeHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public EntsoeHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
ComponentContext componentContext) {
|
||||
super.activate(componentContext);
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID);
|
||||
@ -43,7 +57,7 @@ public class EntsoeHandlerFactory extends BaseThingHandlerFactory {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_DAY_AHEAD.equals(thingTypeUID)) {
|
||||
return new EntsoeHandler(thing);
|
||||
return new EntsoeHandler(thing, httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -21,15 +21,25 @@ import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpResponseException;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.entsoe.internal.exception.EntsoeConfigurationException;
|
||||
import org.openhab.binding.entsoe.internal.exception.EntsoeResponseException;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
@ -46,27 +56,59 @@ import org.xml.sax.SAXException;
|
||||
@NonNullByDefault
|
||||
public class Client {
|
||||
private final Logger logger = LoggerFactory.getLogger(Client.class);
|
||||
private final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
private final HttpClient httpClient;
|
||||
private final String userAgent;
|
||||
|
||||
private DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
public Client(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
userAgent = "openHAB/" + FrameworkUtil.getBundle(this.getClass()).getVersion().toString();
|
||||
}
|
||||
|
||||
public Map<Instant, SpotPrice> doGetRequest(EntsoeRequest entsoeRequest, int timeout, String configResolution)
|
||||
throws EntsoeResponseException, EntsoeConfigurationException, InterruptedException {
|
||||
String url = entsoeRequest.toUrl();
|
||||
Request request = httpClient.newRequest(url) //
|
||||
.timeout(timeout, TimeUnit.SECONDS) //
|
||||
.agent(userAgent) //
|
||||
.method(HttpMethod.GET);
|
||||
|
||||
public Map<Instant, SpotPrice> doGetRequest(Request request, int timeout, String configResolution)
|
||||
throws EntsoeResponseException, EntsoeConfigurationException {
|
||||
try {
|
||||
logger.debug("Sending GET request with parameters: {}", request);
|
||||
String url = request.toUrl();
|
||||
String responseText = HttpUtil.executeUrl("GET", url, timeout);
|
||||
if (responseText == null) {
|
||||
throw new EntsoeResponseException("Request failed");
|
||||
}
|
||||
logger.trace("Response: {}", responseText);
|
||||
return parseXmlResponse(responseText, configResolution);
|
||||
} catch (IOException e) {
|
||||
String message = e.getMessage();
|
||||
if (message != null && message.contains("Authentication challenge without WWW-Authenticate header")) {
|
||||
logger.debug("Sending GET request with parameters: {}", entsoeRequest);
|
||||
|
||||
ContentResponse response = request.send();
|
||||
|
||||
int status = response.getStatus();
|
||||
if (status == HttpStatus.UNAUTHORIZED_401) {
|
||||
// This will currently not happen because "WWW-Authenticate" header is missing; see below.
|
||||
throw new EntsoeConfigurationException("Authentication failed. Please check your security token");
|
||||
}
|
||||
if (!HttpStatus.isSuccess(status)) {
|
||||
throw new EntsoeResponseException("The request failed with HTTP error " + status);
|
||||
}
|
||||
|
||||
String responseContent = response.getContentAsString();
|
||||
if (responseContent == null) {
|
||||
throw new EntsoeResponseException("Request failed");
|
||||
}
|
||||
logger.trace("Response: {}", responseContent);
|
||||
|
||||
return parseXmlResponse(responseContent, configResolution);
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause != null && cause instanceof HttpResponseException httpResponseException) {
|
||||
Response response = httpResponseException.getResponse();
|
||||
if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
|
||||
/*
|
||||
* The service may respond with HTTP code 401 without any "WWW-Authenticate"
|
||||
* header, violating RFC 7235. Jetty will then throw HttpResponseException.
|
||||
* We need to handle this in order to attempt reauthentication.
|
||||
*/
|
||||
throw new EntsoeConfigurationException("Authentication failed. Please check your security token");
|
||||
}
|
||||
}
|
||||
throw new EntsoeResponseException(e);
|
||||
} catch (ParserConfigurationException | SAXException e) {
|
||||
} catch (IOException | TimeoutException | ParserConfigurationException | SAXException e) {
|
||||
throw new EntsoeResponseException(e);
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Request {
|
||||
public class EntsoeRequest {
|
||||
|
||||
private static DateTimeFormatter requestFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
|
||||
|
||||
@ -35,7 +35,7 @@ public class Request {
|
||||
private final Instant periodStart;
|
||||
private final Instant periodEnd;
|
||||
|
||||
public Request(String securityToken, String area, Instant periodStart, Instant periodEnd) {
|
||||
public EntsoeRequest(String securityToken, String area, Instant periodStart, Instant periodEnd) {
|
||||
this.securityToken = securityToken;
|
||||
this.area = area;
|
||||
this.periodStart = periodStart;
|
Loading…
Reference in New Issue
Block a user