Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[entsoe] Refactor HTTP error handling #17616

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@

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;
Expand All @@ -54,22 +55,18 @@
public class EntsoeHandler extends BaseThingHandler {

private final Logger logger = LoggerFactory.getLogger(EntsoeHandler.class);
private final ZoneId cetZoneId = ZoneId.of("CET");
private final Client client;

private EntsoeConfiguration config;

private EntsoeConfiguration config = new EntsoeConfiguration();
private @Nullable ScheduledFuture<?> refreshJob;

private Map<Instant, SpotPrice> entsoeTimeSeries = new LinkedHashMap<>();

private final ZoneId cetZoneId = ZoneId.of("CET");

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
Expand Down Expand Up @@ -101,7 +98,12 @@ public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("handleCommand(channelUID:{}, command:{})", channelUID.getAsString(), command.toFullString());

if (command instanceof RefreshType) {
fetchNewPrices();
try {
fetchNewPrices();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}

Expand Down Expand Up @@ -199,7 +201,11 @@ private void refreshPrices() {
}

if (entsoeTimeSeries.isEmpty()) {
fetchNewPrices();
try {
fetchNewPrices();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return;
}

Expand All @@ -211,27 +217,31 @@ private void refreshPrices() {
.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)
.minusDays(needToFetchHistoricDays() ? config.historicDays - 1 : 0).with(LocalTime.MIDNIGHT)
.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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@

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
Expand All @@ -33,6 +38,15 @@
@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);
Expand All @@ -43,7 +57,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (THING_TYPE_DAY_AHEAD.equals(thingTypeUID)) {
return new EntsoeHandler(thing);
return new EntsoeHandler(thing, httpClient);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,25 @@
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;
Expand All @@ -46,27 +56,59 @@
@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) {
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 ");
}

String responseContent = response.getContentAsString();
if (responseContent == 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")) {
throw new EntsoeConfigurationException("Authentication failed. Please check your security token");
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*
*/
@NonNullByDefault
public class Request {
public class EntsoeRequest {

private static DateTimeFormatter requestFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmm");

Expand All @@ -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;
Expand Down