diff --git a/docs/SNAPSHOT.md b/docs/SNAPSHOT.md index ae3756c..08ca7e6 100644 --- a/docs/SNAPSHOT.md +++ b/docs/SNAPSHOT.md @@ -170,4 +170,6 @@ Location: Minsk(BY), Weather: слегка облачно, 20.0 ℃, 1019.0 hPa, ### Dependencies * com.fasterxml.jackson.core:jackson-databind:2.9.9 +* org.slf4j:slf4j-api:1.7.26 (*compile*) +* org.jetbrains:annotations:17.0.0 (*compile*) * junit:junit:4.12 (*test*) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 56f844a..6ee4b16 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ org.apache.maven.plugins maven-compiler-plugin + 3.8.1 8 8 @@ -152,6 +153,18 @@ jackson-databind 2.9.9 + + org.slf4j + slf4j-api + 1.7.26 + compile + + + org.jetbrains + annotations + 17.0.0 + compile + junit diff --git a/src/main/java/com/github/prominence/openweathermap/api/exception/DataNotFoundException.java b/src/main/java/com/github/prominence/openweathermap/api/exception/DataNotFoundException.java index 6d5c074..8eb9aac 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/exception/DataNotFoundException.java +++ b/src/main/java/com/github/prominence/openweathermap/api/exception/DataNotFoundException.java @@ -25,7 +25,11 @@ package com.github.prominence.openweathermap.api.exception; public class DataNotFoundException extends RuntimeException { public DataNotFoundException() { - super("Data for provided parameters wasn't found. Please, check your request."); + super("Data for provided parameters wasn't found. Please, check requested location."); + } + + public DataNotFoundException(Throwable throwable) { + super(throwable.getMessage(), throwable.getCause()); } } diff --git a/src/main/java/com/github/prominence/openweathermap/api/exception/InvalidAuthTokenException.java b/src/main/java/com/github/prominence/openweathermap/api/exception/InvalidAuthTokenException.java index f578e31..b1e642f 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/exception/InvalidAuthTokenException.java +++ b/src/main/java/com/github/prominence/openweathermap/api/exception/InvalidAuthTokenException.java @@ -25,7 +25,7 @@ package com.github.prominence.openweathermap.api.exception; public class InvalidAuthTokenException extends RuntimeException { public InvalidAuthTokenException() { - super("Check your authentication token! You can get it here: https://home.openweathermap.org/api_keys/."); + super("Authentication token wasn't set or requested functionality is not permitted for your subscription plan. Please, check https://home.openweathermap.org/api_keys/ and https://openweathermap.org/price."); } } diff --git a/src/main/java/com/github/prominence/openweathermap/api/impl/MultipleResultCurrentWeatherAsyncRequestTerminatorImpl.java b/src/main/java/com/github/prominence/openweathermap/api/impl/MultipleResultCurrentWeatherAsyncRequestTerminatorImpl.java index bbae6a2..f3dcd21 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/impl/MultipleResultCurrentWeatherAsyncRequestTerminatorImpl.java +++ b/src/main/java/com/github/prominence/openweathermap/api/impl/MultipleResultCurrentWeatherAsyncRequestTerminatorImpl.java @@ -63,6 +63,6 @@ public class MultipleResultCurrentWeatherAsyncRequestTerminatorImpl implements M } private String getRawResponse() { - return RequestUtils.getRawResponse(urlBuilder.buildUrl()); + return RequestUtils.getResponse(urlBuilder.buildUrl()); } } diff --git a/src/main/java/com/github/prominence/openweathermap/api/impl/MultipleResultCurrentWeatherRequestTerminatorImpl.java b/src/main/java/com/github/prominence/openweathermap/api/impl/MultipleResultCurrentWeatherRequestTerminatorImpl.java index 09ba0a2..f6d093d 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/impl/MultipleResultCurrentWeatherRequestTerminatorImpl.java +++ b/src/main/java/com/github/prominence/openweathermap/api/impl/MultipleResultCurrentWeatherRequestTerminatorImpl.java @@ -27,7 +27,6 @@ import com.github.prominence.openweathermap.api.enums.Unit; import com.github.prominence.openweathermap.api.model.Weather; import com.github.prominence.openweathermap.api.utils.RequestUtils; -import java.io.InputStream; import java.util.List; public class MultipleResultCurrentWeatherRequestTerminatorImpl implements MultipleResultCurrentWeatherRequestTerminator { @@ -63,6 +62,6 @@ public class MultipleResultCurrentWeatherRequestTerminatorImpl implements Multip } private String getRawResponse() { - return RequestUtils.getRawResponse(urlBuilder.buildUrl()); + return RequestUtils.getResponse(urlBuilder.buildUrl()); } } diff --git a/src/main/java/com/github/prominence/openweathermap/api/impl/SingleResultCurrentWeatherAsyncRequestTerminatorImpl.java b/src/main/java/com/github/prominence/openweathermap/api/impl/SingleResultCurrentWeatherAsyncRequestTerminatorImpl.java index dad66fc..b54d51d 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/impl/SingleResultCurrentWeatherAsyncRequestTerminatorImpl.java +++ b/src/main/java/com/github/prominence/openweathermap/api/impl/SingleResultCurrentWeatherAsyncRequestTerminatorImpl.java @@ -62,6 +62,6 @@ public class SingleResultCurrentWeatherAsyncRequestTerminatorImpl implements Sin } private String getRawResponse() { - return RequestUtils.getRawResponse(urlBuilder.buildUrl()); + return RequestUtils.getResponse(urlBuilder.buildUrl()); } } diff --git a/src/main/java/com/github/prominence/openweathermap/api/impl/SingleResultCurrentWeatherRequestTerminatorImpl.java b/src/main/java/com/github/prominence/openweathermap/api/impl/SingleResultCurrentWeatherRequestTerminatorImpl.java index ed7306b..4d7b4b0 100644 --- a/src/main/java/com/github/prominence/openweathermap/api/impl/SingleResultCurrentWeatherRequestTerminatorImpl.java +++ b/src/main/java/com/github/prominence/openweathermap/api/impl/SingleResultCurrentWeatherRequestTerminatorImpl.java @@ -27,8 +27,6 @@ import com.github.prominence.openweathermap.api.enums.Unit; import com.github.prominence.openweathermap.api.model.Weather; import com.github.prominence.openweathermap.api.utils.RequestUtils; -import java.io.InputStream; - public class SingleResultCurrentWeatherRequestTerminatorImpl implements SingleResultCurrentWeatherRequestTerminator { private RequestUrlBuilder urlBuilder; @@ -62,6 +60,6 @@ public class SingleResultCurrentWeatherRequestTerminatorImpl implements SingleRe } private String getRawResponse() { - return RequestUtils.getRawResponse(urlBuilder.buildUrl()); + return RequestUtils.getResponse(urlBuilder.buildUrl()); } } 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 44eec3c..141ed87 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 @@ -24,6 +24,9 @@ package com.github.prominence.openweathermap.api.utils; import com.github.prominence.openweathermap.api.exception.DataNotFoundException; import com.github.prominence.openweathermap.api.exception.InvalidAuthTokenException; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; @@ -34,13 +37,28 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; +/** + * Utility class for API calls execution. + */ public final class RequestUtils { + private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class); + private RequestUtils() { } - private static InputStream executeGetRequest(URL requestUrl) { - InputStream resultStream = null; + /** + * Executes call to provided API url and retrieves response as an InputStream instance. + * + * @param requestUrl url for API call execution. + * @return InputStream instance containing http response body. + * @throws InvalidAuthTokenException in case if authentication token wasn't set or requested functionality is not permitted for its subscription plan. + * @throws DataNotFoundException in case if there is no any data for requested location(s) or request is invalid. + * @throws IllegalStateException in case of unexpected response or error. + */ + @NotNull + private static InputStream executeRequest(@NotNull URL requestUrl) { + InputStream resultStream; try { HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection(); @@ -55,37 +73,57 @@ public final class RequestUtils { case HttpURLConnection.HTTP_NOT_FOUND: case HttpURLConnection.HTTP_BAD_REQUEST: throw new DataNotFoundException(); + default: + throw new IllegalStateException("Unexpected value: " + connection.getResponseCode()); } - } catch (IOException ex) { - ex.printStackTrace(); + } catch (IllegalStateException | IOException ex) { + logger.error("An error occurred during OpenWeatherMap API response parsing: ", ex); + throw new DataNotFoundException(ex); } return resultStream; } - public static InputStream executeGetRequest(String requestUrl) { + /** + * Executes call to provided API url and retrieves response in String representation. + * + * @param url the url to make API request. + * @return response from the request in String representation. + * @throws IllegalArgumentException in case if provided parameter isn't a valid url for {@link URL} instance. + */ + @NotNull + public static String getResponse(@NotNull String url) { + URL requestUrl; try { - return executeGetRequest(new URL(requestUrl)); - } catch (MalformedURLException e) { - e.printStackTrace(); - return null; + requestUrl = new URL(url); + } catch (MalformedURLException ex) { + logger.error("Invalid URL: ", ex); + throw new IllegalArgumentException(ex); } + final InputStream requestInputStream = executeRequest(requestUrl); + + return convertInputStreamToString(requestInputStream); } - public static String getRawResponse(String url) { - return getRawResponse(executeGetRequest(url)); - } - - private static String getRawResponse(InputStream inputStream) { + /** + * Reads the input stream line-by-line and returns its content in String representation. + * + * @param inputStream input stream to convert. + * @return converted InputStream content. + * @throws IllegalArgumentException in case if input stream is unable to be read. + */ + @NotNull + private static String convertInputStreamToString(@NotNull InputStream inputStream) { StringBuilder result = new StringBuilder(); - try(BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { result.append(line); } - } catch (Exception ex) { - ex.printStackTrace(); + } catch (IOException ex) { + logger.error("Error during response reading: ", ex); + throw new IllegalArgumentException(ex); } return result.toString(); diff --git a/src/main/java/com/github/prominence/openweathermap/api/utils/TimeFrameUtils.java b/src/main/java/com/github/prominence/openweathermap/api/utils/TimeFrameUtils.java deleted file mode 100644 index 8fcb73d..0000000 --- a/src/main/java/com/github/prominence/openweathermap/api/utils/TimeFrameUtils.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2019 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.utils; - -import com.github.prominence.openweathermap.api.enums.TimeFrame; - -import java.text.SimpleDateFormat; -import java.util.Date; - -public final class TimeFrameUtils { - - private TimeFrameUtils() { - - } - - /* - 2016-01-02T15:04:05Z - searches between 2016-01-02T15:04:05Z and 2016-01-02T15:04:05.9999Z - 2016-01-02T15:04Z - searches between 2016-01-02T15:04:00Z and 2016-01-02T15:04:59.9999Z - 2016-01-02T15Z - searches between 2016-01-02T15:00:00Z and 2016-01-02T15:59:59.9999Z - 2016-01-02Z - searches between 2016-01-02T00:00:00Z and 2016-01-02T23:59:59.9999Z - 2016-01Z - searches between 2016-01-01T00:00:00Z and 2016-12-31T23:59:59.9999Z - 2016Z - searches between 2016-01-01T00:00:00Z and 2016-12-31T23:59:99.9999Z - */ - - public static String formatDate(Date date, TimeFrame timeFrame) { - - SimpleDateFormat formatter; - - switch (timeFrame) { - case YEAR: - formatter = new SimpleDateFormat("yyyy'Z'"); - break; - case MONTH: - formatter = new SimpleDateFormat("yyyy-MM'Z'"); - break; - case DAY: - formatter = new SimpleDateFormat("yyyy-MM-dd'Z'"); - break; - case HOUR: - formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH'Z'"); - break; - case MINUTE: - formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); - break; - case SECOND: - formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - break; - default: - formatter = new SimpleDateFormat("yyyy-MM'Z'"); - } - - return formatter.format(date); - } - -}