From 2b6d15b1a221887a1197845ccc6b42343969b88e Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Wed, 12 Aug 2020 12:32:43 +0200 Subject: [PATCH] introduce custom exception class instead of RuntimeExceptions We now use a custom, checked exceptions on errors that can occur with the API communication or configuration. instead of throwing na unchecked IllegalStateException. --- CHANGELOG.md | 1 + .../de/stklcode/pubtrans/ura/UraClient.java | 129 ++++++++++++------ .../UraClientConfigurationException.java | 37 +++++ .../ura/exception/UraClientException.java | 39 ++++++ src/main/java/module-info.java | 1 + .../stklcode/pubtrans/ura/UraClientTest.java | 23 ++-- 6 files changed, 175 insertions(+), 55 deletions(-) create mode 100644 src/main/java/de/stklcode/pubtrans/ura/exception/UraClientConfigurationException.java create mode 100644 src/main/java/de/stklcode/pubtrans/ura/exception/UraClientException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3691156..ec83bd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. ### Changes * Using native Java 11 HTTP client * Client configuration with separate `UraClientConfiguration` class and builder +* Client throws custom checked exception `UraClientException` instead of runtime exceptions on errors ## 1.3.0 - 2019-12-04 diff --git a/src/main/java/de/stklcode/pubtrans/ura/UraClient.java b/src/main/java/de/stklcode/pubtrans/ura/UraClient.java index eb12944..fa61785 100644 --- a/src/main/java/de/stklcode/pubtrans/ura/UraClient.java +++ b/src/main/java/de/stklcode/pubtrans/ura/UraClient.java @@ -17,12 +17,15 @@ package de.stklcode.pubtrans.ura; import com.fasterxml.jackson.databind.ObjectMapper; +import de.stklcode.pubtrans.ura.exception.UraClientConfigurationException; +import de.stklcode.pubtrans.ura.exception.UraClientException; import de.stklcode.pubtrans.ura.model.Message; import de.stklcode.pubtrans.ura.model.Stop; import de.stklcode.pubtrans.ura.model.Trip; import de.stklcode.pubtrans.ura.reader.AsyncUraTripReader; import java.io.*; +import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLEncoder; @@ -207,8 +210,11 @@ public final Query forPosition(final Double latitude, final Double longitude, fi * If forStops() and/or forLines() has been called, those will be used as filter. * * @return List of trips. + * @throws UraClientException Error with API communication. + * @since 1.0 + * @since 2.0 Throws {@link UraClientException}. */ - public List getTrips() { + public List getTrips() throws UraClientException { return getTrips(new Query(), null); } @@ -218,8 +224,11 @@ public List getTrips() { * * @param limit Maximum number of results. * @return List of trips. + * @throws UraClientException Error with API communication. + * @since 1.0 + * @since 2.0 Throws {@link UraClientException}. */ - public List getTrips(final Integer limit) { + public List getTrips(final Integer limit) throws UraClientException { return getTrips(new Query(), limit); } @@ -229,8 +238,12 @@ public List getTrips(final Integer limit) { * * @param query The query. * @return List of trips. + * @throws UraClientException Error with API communication. + * @throws UraClientException Error with API communication. + * @since 1.0 + * @since 2.0 Throws {@link UraClientException}. */ - public List getTrips(final Query query) { + public List getTrips(final Query query) throws UraClientException { return getTrips(query, null); } @@ -240,8 +253,11 @@ public List getTrips(final Query query) { * @param query The query. * @param limit Maximum number of results. * @return List of trips. + * @throws UraClientException Error with API communication. + * @since 1.0 + * @since 2.0 Throws {@link UraClientException}. */ - public List getTrips(final Query query, final Integer limit) { + public List getTrips(final Query query, final Integer limit) throws UraClientException { List trips = new ArrayList<>(); try (InputStream is = requestInstant(REQUEST_TRIP, query); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { @@ -260,7 +276,7 @@ public List getTrips(final Query query, final Integer limit) { line = br.readLine(); } } catch (IOException e) { - throw new IllegalStateException("Failed to read trips from API", e); + throw new UraClientException("Failed to read trips from API", e); } return trips; } @@ -271,11 +287,11 @@ public List getTrips(final Query query, final Integer limit) { * @param query The query. * @param consumer Consumer(s) for single trips. * @return Trip reader. - * @throws IOException Error reading response. + * @throws UraClientConfigurationException Error reading response. * @see #getTripsStream(Query, List) - * @since 1.2.0 + * @since 1.2 */ - public AsyncUraTripReader getTripsStream(final Query query, final Consumer consumer) throws IOException { + public AsyncUraTripReader getTripsStream(final Query query, final Consumer consumer) throws UraClientConfigurationException { return getTripsStream(query, Collections.singletonList(consumer)); } @@ -285,28 +301,36 @@ public AsyncUraTripReader getTripsStream(final Query query, final Consumer * @param query The query. * @param consumers Consumer(s) for single trips. * @return Trip reader. - * @throws IOException Error retrieving stream response. - * @since 1.2.0 + * @throws UraClientConfigurationException Error retrieving stream response. + * @since 1.2 + * @since 2.0 Throws {@link UraClientConfigurationException}. */ - public AsyncUraTripReader getTripsStream(final Query query, final List> consumers) throws IOException { + public AsyncUraTripReader getTripsStream(final Query query, final List> consumers) throws UraClientConfigurationException { // Create the reader. - AsyncUraTripReader reader = new AsyncUraTripReader( - new URL(requestURL(config.getBaseURL() + config.getStreeamPath(), REQUEST_TRIP, query)), - consumers - ); + try { + AsyncUraTripReader reader = new AsyncUraTripReader( + new URL(requestURL(config.getBaseURL() + config.getStreeamPath(), REQUEST_TRIP, query)), + consumers + ); - // Open the reader, i.e. start reading from API. - reader.open(); + // Open the reader, i.e. start reading from API. + reader.open(); - return reader; + return reader; + } catch (MalformedURLException e) { + throw new UraClientConfigurationException("Invalid API URL, check client configuration.", e); + } } /** * Get list of stops without filters. * * @return The list of stops. + * @throws UraClientException Error with API communication. + * @since 1.0 + * @since 2.0 Throws {@link UraClientException}. */ - public List getStops() { + public List getStops() throws UraClientException { return getStops(new Query()); } @@ -316,8 +340,11 @@ public List getStops() { * * @param query The query. * @return The list. + * @throws UraClientException Error with API communication. + * @since 1.0 + * @since 2.0 Throws {@link UraClientException}. */ - public List getStops(final Query query) { + public List getStops(final Query query) throws UraClientException { List stops = new ArrayList<>(); try (InputStream is = requestInstant(REQUEST_STOP, query); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { @@ -330,7 +357,7 @@ public List getStops(final Query query) { } } } catch (IOException e) { - throw new IllegalStateException("Failed to read stops from API", e); + throw new UraClientException("Failed to read stops from API", e); } return stops; } @@ -339,9 +366,11 @@ public List getStops(final Query query) { * Get list of messages. * * @return List of messages. + * @throws UraClientException Error with API communication. * @since 1.3 + * @since 2.0 Throw {@link UraClientException}. */ - public List getMessages() { + public List getMessages() throws UraClientException { return getMessages(new Query(), null); } @@ -352,9 +381,11 @@ public List getMessages() { * * @param query The query. * @return List of trips. + * @throws UraClientException Error with API communication. * @since 1.3 + * @since 2.0 Throw {@link UraClientException}. */ - public List getMessages(final Query query) { + public List getMessages(final Query query) throws UraClientException { return getMessages(query, null); } @@ -364,9 +395,11 @@ public List getMessages(final Query query) { * @param query The query. * @param limit Maximum number of results. * @return List of trips. + * @throws UraClientException Error with API communication. * @since 1.3 + * @since 2.0 Throw {@link UraClientException}. */ - public List getMessages(final Query query, final Integer limit) { + public List getMessages(final Query query, final Integer limit) throws UraClientException { List messages = new ArrayList<>(); try (InputStream is = requestInstant(REQUEST_MESSAGE, query); BufferedReader br = new BufferedReader(new InputStreamReader(is))) { @@ -385,7 +418,7 @@ public List getMessages(final Query query, final Integer limit) { line = br.readLine(); } } catch (IOException e) { - throw new IllegalStateException("Failed to read messages from API", e); + throw new UraClientException("Failed to read messages from API", e); } return messages; } @@ -409,35 +442,35 @@ private InputStream requestInstant(final String[] returnList, final Query query) * @param returnList Fields to fetch. * @param query The query. * @return The URL - * @throws IOException on errors - * @since 1.2.0 + * @since 1.2 + * @since 2.0 Does not throw exception anymore. */ - private String requestURL(final String endpointURL, final String[] returnList, final Query query) throws IOException { + private String requestURL(final String endpointURL, final String[] returnList, final Query query) { String urlStr = endpointURL + "?ReturnList=" + String.join(",", returnList); if (query.stopIDs != null && query.stopIDs.length > 0) { - urlStr += "&" + PAR_STOP_ID + "=" + URLEncoder.encode(String.join(",", query.stopIDs), UTF_8.name()); + urlStr += "&" + PAR_STOP_ID + "=" + URLEncoder.encode(String.join(",", query.stopIDs), UTF_8); } if (query.stopNames != null && query.stopNames.length > 0) { - urlStr += "&" + PAR_STOP_NAME + "=" + URLEncoder.encode(String.join(",", query.stopNames), UTF_8.name()); + urlStr += "&" + PAR_STOP_NAME + "=" + URLEncoder.encode(String.join(",", query.stopNames), UTF_8); } if (query.lineIDs != null && query.lineIDs.length > 0) { - urlStr += "&" + PAR_LINE_ID + "=" + URLEncoder.encode(String.join(",", query.lineIDs), UTF_8.name()); + urlStr += "&" + PAR_LINE_ID + "=" + URLEncoder.encode(String.join(",", query.lineIDs), UTF_8); } if (query.lineNames != null && query.lineNames.length > 0) { - urlStr += "&" + PAR_LINE_NAME + "=" + URLEncoder.encode(String.join(",", query.lineNames), UTF_8.name()); + urlStr += "&" + PAR_LINE_NAME + "=" + URLEncoder.encode(String.join(",", query.lineNames), UTF_8); } if (query.direction != null) { urlStr += "&" + PAR_DIR_ID + "=" + query.direction; } if (query.destinationNames != null) { - urlStr += "&" + PAR_DEST_NAME + "=" + URLEncoder.encode(String.join(",", query.destinationNames), UTF_8.name()); + urlStr += "&" + PAR_DEST_NAME + "=" + URLEncoder.encode(String.join(",", query.destinationNames), UTF_8); } if (query.towards != null) { - urlStr += "&" + PAR_TOWARDS + "=" + URLEncoder.encode(String.join(",", query.towards), UTF_8.name()); + urlStr += "&" + PAR_TOWARDS + "=" + URLEncoder.encode(String.join(",", query.towards), UTF_8); } if (query.circle != null) { - urlStr += "&" + PAR_CIRCLE + "=" + URLEncoder.encode(query.circle, UTF_8.name()); + urlStr += "&" + PAR_CIRCLE + "=" + URLEncoder.encode(query.circle, UTF_8); } return urlStr; @@ -572,8 +605,11 @@ public Query forPosition(final Double latitude, final Double longitude, final In * Get stops for set filters. * * @return List of matching trips. + * @throws UraClientException Error with API communication. + * @since 1.0 + * @since 2.0 Throws {@link UraClientException}. */ - public List getStops() { + public List getStops() throws UraClientException { return UraClient.this.getStops(this); } @@ -581,8 +617,11 @@ public List getStops() { * Get trips for set filters. * * @return List of matching trips. + * @throws UraClientException Error with API communication. + * @since 1.0 + * @since 2.0 Throws {@link UraClientException}. */ - public List getTrips() { + public List getTrips() throws UraClientException { return UraClient.this.getTrips(this); } @@ -591,11 +630,11 @@ public List getTrips() { * * @param consumer Consumer for single trips. * @return Trip reader. - * @throws IOException Errors retrieving stream response. + * @throws UraClientConfigurationException Error reading response. * @see #getTripsStream(List) - * @since 1.2.0 + * @since 1.2 */ - public AsyncUraTripReader getTripsStream(Consumer consumer) throws IOException { + public AsyncUraTripReader getTripsStream(Consumer consumer) throws UraClientConfigurationException { return UraClient.this.getTripsStream(this, consumer); } @@ -604,10 +643,10 @@ public AsyncUraTripReader getTripsStream(Consumer consumer) throws IOExcep * * @param consumers Consumers for single trips. * @return Trip reader. - * @throws IOException Errors retrieving stream response. - * @since 1.2.0 + * @throws UraClientConfigurationException Errors retrieving stream response. + * @since 1.2 */ - public AsyncUraTripReader getTripsStream(List> consumers) throws IOException { + public AsyncUraTripReader getTripsStream(List> consumers) throws UraClientConfigurationException { return UraClient.this.getTripsStream(this, consumers); } @@ -615,9 +654,11 @@ public AsyncUraTripReader getTripsStream(List> consumers) throws * Get trips for set filters. * * @return List of matching messages. + * @throws UraClientException Error with API communication. * @since 1.3 + * @since 2.0 Throws {@link UraClientException}. */ - public List getMessages() { + public List getMessages() throws UraClientException { return UraClient.this.getMessages(this); } } diff --git a/src/main/java/de/stklcode/pubtrans/ura/exception/UraClientConfigurationException.java b/src/main/java/de/stklcode/pubtrans/ura/exception/UraClientConfigurationException.java new file mode 100644 index 0000000..e2d413f --- /dev/null +++ b/src/main/java/de/stklcode/pubtrans/ura/exception/UraClientConfigurationException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016-2020 Stefan Kalscheuer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.stklcode.pubtrans.ura.exception; + +/** + * Custom exception class indicating an error with the URA client configuration. + * + * @author Stefan Kalscheuer + * @since 2.0 + */ +public class UraClientConfigurationException extends UraClientException { + private static final long serialVersionUID = -8035752391477338659L; + + /** + * Default constructor. + * + * @param message The detail message (which is saved for later retrieval by the {@link #getMessage()} method) + * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). + */ + public UraClientConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/stklcode/pubtrans/ura/exception/UraClientException.java b/src/main/java/de/stklcode/pubtrans/ura/exception/UraClientException.java new file mode 100644 index 0000000..7de90bf --- /dev/null +++ b/src/main/java/de/stklcode/pubtrans/ura/exception/UraClientException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016-2020 Stefan Kalscheuer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.stklcode.pubtrans.ura.exception; + +import java.io.IOException; + +/** + * Custom exception class indicating an error with the URA API communication. + * + * @author Stefan Kalscheuer + * @since 2.0 + */ +public class UraClientException extends IOException { + private static final long serialVersionUID = 4585240685746203433L; + + /** + * Default constructor. + * + * @param message The detail message (which is saved for later retrieval by the {@link #getMessage()} method) + * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). + */ + public UraClientException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 76ecbcf..d92b502 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -16,6 +16,7 @@ module de.stklcode.pubtrans.juraclient { exports de.stklcode.pubtrans.ura; + exports de.stklcode.pubtrans.ura.exception; exports de.stklcode.pubtrans.ura.model; exports de.stklcode.pubtrans.ura.reader; diff --git a/src/test/java/de/stklcode/pubtrans/ura/UraClientTest.java b/src/test/java/de/stklcode/pubtrans/ura/UraClientTest.java index 054e2ea..86779c0 100644 --- a/src/test/java/de/stklcode/pubtrans/ura/UraClientTest.java +++ b/src/test/java/de/stklcode/pubtrans/ura/UraClientTest.java @@ -19,6 +19,7 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import de.stklcode.pubtrans.ura.exception.UraClientException; import de.stklcode.pubtrans.ura.model.Message; import de.stklcode.pubtrans.ura.model.Stop; import de.stklcode.pubtrans.ura.model.Trip; @@ -63,7 +64,7 @@ public static void tearDown() { } @Test - public void getStopsTest() { + public void getStopsTest() throws UraClientException { // Mock the HTTP call. mockHttpToFile(2, "instant_V2_stops.txt"); @@ -89,7 +90,7 @@ public void getStopsTest() { } @Test - public void getStopsForLineTest() { + public void getStopsForLineTest() throws UraClientException { // Mock the HTTP call. mockHttpToFile(2, "instant_V2_stops_line.txt"); @@ -107,7 +108,7 @@ public void getStopsForLineTest() { } @Test - public void getStopsForPositionTest() { + public void getStopsForPositionTest() throws UraClientException { // Mock the HTTP call. mockHttpToFile(1, "instant_V1_stops_circle.txt"); @@ -133,7 +134,7 @@ public void getStopsForPositionTest() { } @Test - public void getTripsForDestinationNamesTest() { + public void getTripsForDestinationNamesTest() throws UraClientException { // Mock the HTTP call. mockHttpToFile(1, "instant_V1_trips_destination.txt"); @@ -156,7 +157,7 @@ public void getTripsForDestinationNamesTest() { } @Test - public void getTripsTowardsTest() { + public void getTripsTowardsTest() throws UraClientException { // Mock the HTTP call. mockHttpToFile(1, "instant_V1_trips_towards.txt"); @@ -171,7 +172,7 @@ public void getTripsTowardsTest() { } @Test - public void getTripsTest() { + public void getTripsTest() throws UraClientException { // Mock the HTTP call. mockHttpToFile(1, "instant_V1_trips_all.txt"); @@ -224,7 +225,7 @@ public void getTripsTest() { } @Test - public void getTripsForStopTest() { + public void getTripsForStopTest() throws UraClientException { // Mock the HTTP call. mockHttpToFile(1, "instant_V1_trips_stop.txt"); @@ -254,7 +255,7 @@ public void getTripsForStopTest() { } @Test - public void getTripsForLine() { + public void getTripsForLine() throws UraClientException { // Mock the HTTP call. mockHttpToFile(1, "instant_V1_trips_line.txt"); @@ -303,7 +304,7 @@ public void getTripsForLine() { } @Test - public void getTripsForStopAndLine() { + public void getTripsForStopAndLine() throws UraClientException { // Mock the HTTP call. mockHttpToFile(1, "instant_V1_trips_stop_line.txt"); @@ -324,7 +325,7 @@ public void getTripsForStopAndLine() { @Test - public void getMessages() { + public void getMessages() throws UraClientException { // Mock the HTTP call. mockHttpToFile(1, "instant_V1_messages.txt"); @@ -343,7 +344,7 @@ public void getMessages() { } @Test - public void getMessagesForStop() { + public void getMessagesForStop() throws UraClientException { // Mock the HTTP call. mockHttpToFile(2, "instant_V2_messages_stop.txt");