From 8065145391473c0e4181c6abd350c32fbd28f507 Mon Sep 17 00:00:00 2001 From: Andreas Buchen Date: Thu, 19 Sep 2024 07:40:07 +0200 Subject: [PATCH] Unlink security from Portfolio Report if the onlineId does not exist anymore --- .../META-INF/MANIFEST.MF | 3 +- .../ui/jobs/SyncOnlineSecuritiesJob.java | 26 ++++++- .../abuchen/portfolio/util/WebAccess.java | 67 +++++++++++++++++-- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/name.abuchen.portfolio.ui/META-INF/MANIFEST.MF b/name.abuchen.portfolio.ui/META-INF/MANIFEST.MF index fa63c5021b..cf4b1456d6 100644 --- a/name.abuchen.portfolio.ui/META-INF/MANIFEST.MF +++ b/name.abuchen.portfolio.ui/META-INF/MANIFEST.MF @@ -121,7 +121,8 @@ Require-Bundle: name.abuchen.portfolio;bundle-version="0.70.5", org.eclipse.ui.forms, org.eclipse.e4.ui.css.core, org.eclipse.ui.themes, - org.eclipse.jface + org.eclipse.jface, + org.apache.httpcomponents.core5.httpcore5 Automatic-Module-Name: name.abuchen.portfolio.ui Service-Component: OSGI-INF/name.abuchen.portfolio.ui.util.theme.ColorAndFontProviderImpl.xml, OSGI-INF/name.abuchen.portfolio.ui.theme.CustomThemeEngineManager.xml diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/jobs/SyncOnlineSecuritiesJob.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/jobs/SyncOnlineSecuritiesJob.java index cc5b066949..0661b153fe 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/jobs/SyncOnlineSecuritiesJob.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/jobs/SyncOnlineSecuritiesJob.java @@ -5,14 +5,17 @@ import java.util.List; import java.util.Optional; +import org.apache.hc.core5.http.HttpStatus; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.Security; +import name.abuchen.portfolio.online.QuoteFeed; import name.abuchen.portfolio.online.SecuritySearchProvider.ResultItem; import name.abuchen.portfolio.online.impl.PortfolioReportNet; +import name.abuchen.portfolio.online.impl.PortfolioReportQuoteFeed; import name.abuchen.portfolio.ui.Messages; import name.abuchen.portfolio.ui.PortfolioPlugin; import name.abuchen.portfolio.util.WebAccess.WebAccessException; @@ -60,7 +63,28 @@ protected IStatus run(IProgressMonitor monitor) } catch (WebAccessException e) { - PortfolioPlugin.log(security.getName() + ": " + e.getMessage()); //$NON-NLS-1$ + // check if the onlineId has become permanently unavailable and, + // if necessary, remove the online id + + if (e.getHttpErrorCode() == HttpStatus.SC_NOT_FOUND + && "Security not found".equals(e.getHeader("X-Error"))) //$NON-NLS-1$ //$NON-NLS-2$ + { + security.setOnlineId(null); + + if (PortfolioReportQuoteFeed.ID.equals(security.getFeed())) + security.setFeed(QuoteFeed.MANUAL); + + if (PortfolioReportQuoteFeed.ID.equals(security.getLatestFeed())) + security.setFeed(null); + + isDirty = true; + + PortfolioPlugin.log("Unlinking " + security.getName() + ": " + e.getMessage()); //$NON-NLS-1$ + } + else + { + PortfolioPlugin.log(security.getName() + ": " + e.getMessage()); //$NON-NLS-1$ + } } catch (IOException e) { diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/util/WebAccess.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/util/WebAccess.java index 0573c77f16..ae7473d8ad 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/util/WebAccess.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/util/WebAccess.java @@ -4,10 +4,15 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.HttpResponseException; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; @@ -15,12 +20,17 @@ import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.cookie.StandardCookieSpec; -import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.ParseException; import org.apache.hc.core5.http.impl.EnglishReasonPhraseCatalog; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.net.URIBuilder; @@ -102,17 +112,62 @@ public static class WebAccessException extends IOException { private static final long serialVersionUID = 1L; private final int httpErrorCode; + private final Map headers; - public WebAccessException(String message, int httpErrorCode) + public WebAccessException(String message, int httpErrorCode, Map headers) { super(message); this.httpErrorCode = httpErrorCode; + this.headers = headers; } public int getHttpErrorCode() { return httpErrorCode; } + + public String getHeader(String key) + { + return headers.get(key); + } + } + + private static class CustomResponseHandler implements HttpClientResponseHandler + { + private final String uri; + + public CustomResponseHandler(String uri) + { + super(); + this.uri = uri; + } + + @Override + public String handleResponse(final ClassicHttpResponse response) throws IOException + { + final HttpEntity entity = response.getEntity(); + if (response.getCode() >= HttpStatus.SC_REDIRECTION) + { + EntityUtils.consume(entity); + + var headers = Stream.of(response.getHeaders()) + .collect(Collectors.toMap(Header::getName, Header::getValue)); + + throw new WebAccessException(buildMessage(uri, response.getCode()), response.getCode(), headers); + } + + if (entity == null) + return null; + + try + { + return EntityUtils.toString(entity); + } + catch (final ParseException ex) + { + throw new ClientProtocolException(ex); + } + } } public static final RequestConfig defaultRequestConfig = RequestConfig.custom() @@ -215,11 +270,13 @@ private String executeWith(Request function) throws IOException URI uri = builder.build(); HttpUriRequestBase request = function.create(uri); - return client.execute(request, new BasicHttpClientResponseHandler()); + + return client.execute(request, new CustomResponseHandler(uri.toString())); } catch (HttpResponseException e) { - throw new WebAccessException(buildMessage(builder.toString(), e.getStatusCode()), e.getStatusCode()); + throw new WebAccessException(buildMessage(builder.toString(), e.getStatusCode()), e.getStatusCode(), + new HashMap<>()); } catch (URISyntaxException e) { @@ -227,7 +284,7 @@ private String executeWith(Request function) throws IOException } } - private String buildMessage(String uri, int statusCode) + private static String buildMessage(String uri, int statusCode) { String message = String.valueOf(statusCode); try