From 23462b6d77a17580b9fb3a8171d19e5c6d3e1c42 Mon Sep 17 00:00:00 2001 From: dbadia Date: Thu, 14 Nov 2024 03:52:04 -0500 Subject: [PATCH] Add onecall API 3 support to allow access to daily summary (#58) * bump nexux-staging-maven-plugin version to resolve the following maven build error: [INFO] Scanning for projects... [WARNING] ClassRealm[extension>org.sonatype.plugins:nexus-staging-maven-plugin:1.6.9, parent: jdk.internal.loader.ClassLoaders$AppClassLoader@1d44bcfa] com.google.inject.CreationException: Unable to create injector, see the following errors: 1) No implementation for com.fasterxml.jackson.databind.ObjectMapper annotated with interface org.eclipse.sisu.inject.TypeArguments$Implicit was bound. Did you mean? com.fasterxml.jackson.databind.ObjectMapper annotated with @com.google.inject.name.Named(value="org.sonatype.sisu.siesta.jackson.ObjectMapperProvider") bound at ClassRealm[extension>org.sonatype.plugins:nexus-staging-maven-plugin:1.6.9, parent: jdk.internal.loader.ClassLoaders$AppClassLoader@1d44bcfa] (via modules: org.eclipse.sisu.wire.WireModule -> org.eclipse.sisu.plexus.PlexusBindingModule) com.fasterxml.jackson.databind.ObjectMapper bound at org.eclipse.sisu.wire.LocatorWiring at org.eclipse.sisu.wire.LocatorWiring 1 error at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist (Errors.java:543) at com.google.inject.internal.InternalInjectorCreator.initializeStatically (InternalInjectorCreator.java:159) at com.google.inject.internal.InternalInjectorCreator.build (InternalInjectorCreator.java:106) at com.google.inject.Guice.createInjector (Guice.java:87) at com.google.inject.Guice.createInjector (Guice.java:69) at com.google.inject.Guice.createInjector (Guice.java:59) at org.codehaus.plexus.DefaultPlexusContainer.addPlexusInjector (DefaultPlexusContainer.java:481) at org.codehaus.plexus.DefaultPlexusContainer.discoverComponents (DefaultPlexusContainer.java:460) at org.apache.maven.plugin.internal.DefaultMavenPluginManager.discoverPluginComponents (DefaultMavenPluginManager.java:436) at org.apache.maven.plugin.internal.DefaultMavenPluginManager.setupExtensionsRealm (DefaultMavenPluginManager.java:879) at org.apache.maven.project.DefaultProjectBuildingHelper.createProjectRealm (DefaultProjectBuildingHelper.java:196) at org.apache.maven.project.DefaultModelBuildingListener.buildExtensionsAssembled (DefaultModelBuildingListener.java:100) at org.apache.maven.model.building.ModelBuildingEventCatapult$1.fire (ModelBuildingEventCatapult.java:44) at org.apache.maven.model.building.DefaultModelBuilder.fireEvent (DefaultModelBuilder.java:1359) at org.apache.maven.model.building.DefaultModelBuilder.build (DefaultModelBuilder.java:452) at org.apache.maven.model.building.DefaultModelBuilder.build (DefaultModelBuilder.java:432) at org.apache.maven.project.DefaultProjectBuilder.build (DefaultProjectBuilder.java:583) at org.apache.maven.project.DefaultProjectBuilder.build (DefaultProjectBuilder.java:372) at org.apache.maven.graph.DefaultGraphBuilder.collectProjects (DefaultGraphBuilder.java:414) at org.apache.maven.graph.DefaultGraphBuilder.getProjectsForMavenReactor (DefaultGraphBuilder.java:405) at org.apache.maven.graph.DefaultGraphBuilder.build (DefaultGraphBuilder.java:82) at org.apache.maven.DefaultMaven.buildGraph (DefaultMaven.java:507) at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:219) * add onecall 3 API * Changed subscription plan for oneCall3 requester, deprecated oneCall. * Fixed query parameters encoding. --------- Co-authored-by: Prominence --- pom.xml | 4 +- .../api/OpenWeatherMapClient.java | 20 +++ .../api/enums/SubscriptionPlan.java | 5 + .../mapper/OneCallWeatherResponseMapper.java | 9 ++ .../api/model/onecall/current/Daily.java | 20 +++ .../api/request/RequestSettings.java | 9 ++ .../api/utils/RequestUtils.java | 20 ++- ...rentWeatherOneCallApi3IntegrationTest.java | 136 ++++++++++++++++++ 8 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/github/prominence/openweathermap/api/request/onecall/current/CurrentWeatherOneCallApi3IntegrationTest.java diff --git a/pom.xml b/pom.xml index 579637e..88e5314 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.prominence openweathermap-api - 2.3.0 + 2.4.0 jar Java OpenWeatherMap API @@ -80,7 +80,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.9 + 1.6.13 true ossrh diff --git a/src/main/java/com/github/prominence/openweathermap/api/OpenWeatherMapClient.java b/src/main/java/com/github/prominence/openweathermap/api/OpenWeatherMapClient.java index c29d1eb..a450b7d 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/OpenWeatherMapClient.java +++ b/src/main/java/com/github/prominence/openweathermap/api/OpenWeatherMapClient.java @@ -31,6 +31,7 @@ import com.github.prominence.openweathermap.api.request.onecall.OneCallWeatherRe import com.github.prominence.openweathermap.api.request.weather.CurrentWeatherRequester; import static com.github.prominence.openweathermap.api.enums.SubscriptionPlan.ALL; +import static com.github.prominence.openweathermap.api.enums.SubscriptionPlan.SPECIAL; /** * The main public API client to communicate with OpenWeatherMap services. @@ -80,10 +81,29 @@ public class OpenWeatherMapClient { * @return requester for retrieving one call weather information. */ @SubscriptionAvailability(plans = ALL) + @Deprecated public OneCallWeatherRequester oneCall() { return new OneCallWeatherRequester(new RequestSettings(apiKey, timeoutSettings)); } + /** + * One Call 3 API API. + * Includes a weather summary statement in addition to the information provided by {@link #oneCall()} + * + * Please note, that One Call API 3.0 is included in the "One Call by Call" subscription only. + * This separate subscription includes 1,000 calls/day for free and allows you to pay only for the number of API calls made to this product. + * Please note, that you do not need to subscribe to any other OpenWeather subscription plans to get access to the One Call API 3.0. + * Please find more details on the pricing page and FAQ or ask Ulla, OpenWeather AI assistant. + * + * @return requester for retrieving one call weather information for the OneCall 3 API. + */ + @SubscriptionAvailability(plans = SPECIAL) + public OneCallWeatherRequester oneCall3() { + RequestSettings requestSettings = new RequestSettings(apiKey, timeoutSettings); + requestSettings.setUseApi3(); + return new OneCallWeatherRequester(requestSettings); + } + /** * Air Pollution API. * Air Pollution API provides current, forecast and historical air pollution data for any coordinates on the globe. diff --git a/src/main/java/com/github/prominence/openweathermap/api/enums/SubscriptionPlan.java b/src/main/java/com/github/prominence/openweathermap/api/enums/SubscriptionPlan.java index b2419c7..d4ffbde 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/enums/SubscriptionPlan.java +++ b/src/main/java/com/github/prominence/openweathermap/api/enums/SubscriptionPlan.java @@ -56,4 +56,9 @@ public enum SubscriptionPlan { * All existing subscription plans. */ ALL, + + /** + * Special subscription cases. + */ + SPECIAL, } diff --git a/src/main/java/com/github/prominence/openweathermap/api/mapper/OneCallWeatherResponseMapper.java b/src/main/java/com/github/prominence/openweathermap/api/mapper/OneCallWeatherResponseMapper.java index d3477b4..e3ad567 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/mapper/OneCallWeatherResponseMapper.java +++ b/src/main/java/com/github/prominence/openweathermap/api/mapper/OneCallWeatherResponseMapper.java @@ -213,6 +213,7 @@ public class OneCallWeatherResponseMapper { daily.setMoonPhase(new MoonPhase(moonPhaseNode.asDouble())); } + daily.setSummary(parseSummary(dailyNode)); daily.setWeatherState(parseWeatherState(dailyNode.get("weather").get(0))); daily.setTemperature(parseDailyTemperature(dailyNode)); daily.setAtmosphericPressure(parsePressure(dailyNode)); @@ -439,4 +440,12 @@ public class OneCallWeatherResponseMapper { return null; } + + private String parseSummary(JsonNode dailyNode) { + final JsonNode summaryNode = dailyNode.get("summary"); + if(summaryNode != null) { + return summaryNode.asText(); + } + return null; + } } diff --git a/src/main/java/com/github/prominence/openweathermap/api/model/onecall/current/Daily.java b/src/main/java/com/github/prominence/openweathermap/api/model/onecall/current/Daily.java index e2f8b45..baeb87d 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/model/onecall/current/Daily.java +++ b/src/main/java/com/github/prominence/openweathermap/api/model/onecall/current/Daily.java @@ -43,6 +43,7 @@ public class Daily { private LocalDateTime moonsetTime; private MoonPhase moonPhase; + private String summary; private WeatherState weatherState; private DailyTemperature temperature; private AtmosphericPressure atmosphericPressure; @@ -162,6 +163,24 @@ public class Daily { this.moonPhase = moonPhase; } + /** + * Gets summary. + * + * @return the summary + */ + public String getSummary() { + return summary; + } + + /** + * Sets summary. + * + * @param summary the summary + */ + public void setSummary(String summary) { + this.summary = summary; + } + /** * Gets weather state. * @@ -435,4 +454,5 @@ public class Daily { } return stringBuilder.toString(); } + } diff --git a/src/main/java/com/github/prominence/openweathermap/api/request/RequestSettings.java b/src/main/java/com/github/prominence/openweathermap/api/request/RequestSettings.java index db0b103..5e3a0a7 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/request/RequestSettings.java +++ b/src/main/java/com/github/prominence/openweathermap/api/request/RequestSettings.java @@ -45,6 +45,7 @@ public class RequestSettings { private Language language = Language.ENGLISH; private UnitSystem unitSystem = UnitSystem.STANDARD; + private boolean useApi3 = false; public RequestSettings(String apiKey, TimeoutSettings timeoutSettings) { this.putRequestParameter(API_KEY_PARAM_NAME, apiKey); @@ -94,6 +95,14 @@ public class RequestSettings { urlAppenderBuilder.append(appendix); } + public void setUseApi3() { + this.useApi3 = true; + } + + public boolean getUseApi3() { + return this.useApi3; + } + public StringBuilder getUrlAppender() { return urlAppenderBuilder; } diff --git a/src/main/java/com/github/prominence/openweathermap/api/utils/RequestUtils.java b/src/main/java/com/github/prominence/openweathermap/api/utils/RequestUtils.java index c20ec6d..3d410f8 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/utils/RequestUtils.java +++ b/src/main/java/com/github/prominence/openweathermap/api/utils/RequestUtils.java @@ -29,13 +29,11 @@ import com.github.prominence.openweathermap.api.request.RequestSettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; @@ -44,7 +42,8 @@ import java.util.stream.Collectors; */ public final class RequestUtils { - private static final String OWM_URL_BASE = "http://api.openweathermap.org/data/2.5/"; + private static final String OWM_URL_BASE = "https://api.openweathermap.org/data/2.5/"; + private static final String OWM_URL_BASE_3_0 = "https://api.openweathermap.org/data/3.0/"; private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class); @@ -53,10 +52,19 @@ public final class RequestUtils { public static String getResponse(RequestSettings requestSettings) { StringBuilder requestUrlBuilder = new StringBuilder(OWM_URL_BASE); + if(requestSettings.getUseApi3()) { + requestUrlBuilder = new StringBuilder(OWM_URL_BASE_3_0); + } requestUrlBuilder.append(requestSettings.getUrlAppender()); requestUrlBuilder.append('?'); String parameters = requestSettings.getRequestParameters().entrySet().stream() - .map(entry -> entry.getKey() + "=" + entry.getValue()) + .map(entry -> { + try { + return entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + }) .collect(Collectors.joining("&")); requestUrlBuilder.append(parameters); diff --git a/src/test/java/com/github/prominence/openweathermap/api/request/onecall/current/CurrentWeatherOneCallApi3IntegrationTest.java b/src/test/java/com/github/prominence/openweathermap/api/request/onecall/current/CurrentWeatherOneCallApi3IntegrationTest.java new file mode 100644 index 0000000..bc4ff50 --- /dev/null +++ b/src/test/java/com/github/prominence/openweathermap/api/request/onecall/current/CurrentWeatherOneCallApi3IntegrationTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021 Alexey Zinchenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.prominence.openweathermap.api.request.onecall.current; + +import com.github.prominence.openweathermap.api.ApiTest; +import com.github.prominence.openweathermap.api.OpenWeatherMapClient; +import com.github.prominence.openweathermap.api.enums.Language; +import com.github.prominence.openweathermap.api.enums.OneCallResultOptions; +import com.github.prominence.openweathermap.api.enums.UnitSystem; +import com.github.prominence.openweathermap.api.exception.InvalidAuthTokenException; +import com.github.prominence.openweathermap.api.exception.NoDataFoundException; +import com.github.prominence.openweathermap.api.model.Coordinate; +import com.github.prominence.openweathermap.api.model.onecall.current.CurrentWeatherData; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.jupiter.api.Assertions.*; + +public class CurrentWeatherOneCallApi3IntegrationTest extends ApiTest { + @Test + public void whenRetrieveCurrentOneCallResponseAsJava_thenOk() { + final CurrentWeatherData currentWeatherData = getClient() + .oneCall3() + .current() + .byCoordinate(Coordinate.of(53.54, 27.34)) + .language(Language.ENGLISH) + .unitSystem(UnitSystem.METRIC) + .retrieve() + .asJava(); + + assertNotNull(currentWeatherData); + assertNotNull(currentWeatherData.getDailyList().get(0).getSummary()); + } + + @Test + public void whenRetrieveCurrentOneCallResponseAsJSON_thenOk() { + final String responseJson = getClient() + .oneCall3() + .current() + .byCoordinate(Coordinate.of(53.54, 27.34)) + .language(Language.ENGLISH) + .unitSystem(UnitSystem.METRIC) + .retrieve() + .asJSON(); + + assertNotNull(responseJson); + assertNotEquals("", responseJson); + } + + @Test + public void whenRetrieveCurrentOneCallResponseWithExclusionAsJava_thenOk() { + final CurrentWeatherData currentWeatherData = getClient() + .oneCall3() + .current() + .byCoordinate(Coordinate.of(53.54, 27.34)) + .language(Language.ENGLISH) + .unitSystem(UnitSystem.METRIC) + .exclude(OneCallResultOptions.CURRENT, OneCallResultOptions.MINUTELY) + .retrieve() + .asJava(); + + assertNotNull(currentWeatherData); + assertNull(currentWeatherData.getCurrent()); + assertNull(currentWeatherData.getMinutelyList()); + assertNotNull(currentWeatherData.getDailyList().get(0).getSummary()); + } + + @Test + public void whenRetrieveCurrentOneCallAsyncResponseAsJava_thenOk() throws ExecutionException, InterruptedException { + final CompletableFuture currentWeatherDataFuture = getClient() + .oneCall3() + .current() + .byCoordinate(Coordinate.of(53.54, 27.34)) + .language(Language.ENGLISH) + .unitSystem(UnitSystem.METRIC) + .retrieveAsync() + .asJava(); + + assertNotNull(currentWeatherDataFuture); + assertNotNull(currentWeatherDataFuture.get()); + assertNotNull(currentWeatherDataFuture.get().getDailyList().get(0).getSummary()); + } + + @Test + public void whenRetrieveCurrentOneCallAsyncResponseAsJSON_thenOk() throws ExecutionException, InterruptedException { + final CompletableFuture responseJsonFuture = getClient() + .oneCall3() + .current() + .byCoordinate(Coordinate.of(53.54, 27.34)) + .language(Language.ENGLISH) + .unitSystem(UnitSystem.METRIC) + .retrieveAsync() + .asJSON(); + + assertNotNull(responseJsonFuture); + final String responseJson = responseJsonFuture.get(); + assertNotNull(responseJson); + } + + @Test + public void whenRequestOnecallWithInvalidApiKey_thenThrowAnException() { + OpenWeatherMapClient client = new OpenWeatherMapClient("invalidKey"); + assertThrows(InvalidAuthTokenException.class, () -> + client + .oneCall3() + .current() + .byCoordinate(Coordinate.of(53.54, 27.34)) + .language(Language.ENGLISH) + .unitSystem(UnitSystem.METRIC) + .retrieve() + .asJSON() + ); + } +}