From 1907545e5d320ab15b6dab8407395dcaf8720633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Sat, 6 May 2023 17:08:20 +0200 Subject: [PATCH] Review all @Nullable return values - Most methods now return an Optional instead of a null value - Reviewed all Collection return values. They may now be empty, but never null. --- .../java/org/shredzone/acme4j/Account.java | 27 +++---- .../org/shredzone/acme4j/AccountBuilder.java | 6 +- .../org/shredzone/acme4j/Authorization.java | 31 ++++--- .../java/org/shredzone/acme4j/Metadata.java | 53 +++++------- .../main/java/org/shredzone/acme4j/Order.java | 80 +++++++------------ .../org/shredzone/acme4j/OrderBuilder.java | 6 +- .../java/org/shredzone/acme4j/Problem.java | 38 ++++----- .../shredzone/acme4j/RevocationReason.java | 8 +- .../shredzone/acme4j/challenge/Challenge.java | 14 ++-- .../acme4j/connector/Connection.java | 11 +-- .../acme4j/connector/DefaultConnection.java | 44 ++++------ .../exception/AcmeRateLimitedException.java | 15 ++-- .../AcmeUserActionRequiredException.java | 15 ++-- .../acme4j/provider/AbstractAcmeProvider.java | 5 +- .../shredzone/acme4j/AccountBuilderTest.java | 17 ++-- .../org/shredzone/acme4j/AccountTest.java | 31 +++---- .../shredzone/acme4j/AuthorizationTest.java | 30 +++---- .../org/shredzone/acme4j/CertificateTest.java | 3 +- .../shredzone/acme4j/OrderBuilderTest.java | 38 +++++---- .../java/org/shredzone/acme4j/OrderTest.java | 78 ++++++++++-------- .../org/shredzone/acme4j/ProblemTest.java | 25 +++--- .../org/shredzone/acme4j/SessionTest.java | 19 +++-- .../acme4j/challenge/ChallengeTest.java | 11 +-- .../connector/DefaultConnectionTest.java | 18 +++-- .../acme4j/connector/DummyConnection.java | 4 +- .../acme4j/connector/NetworkSettingsTest.java | 2 - .../AcmeRateLimitedExceptionTest.java | 6 +- .../AcmeUserActionRequiredExceptionTest.java | 4 +- .../shredzone/acme4j/toolbox/JSONTest.java | 5 +- .../shredzone/acme4j/toolbox/TestUtils.java | 3 +- .../shredzone/acme4j/example/ClientTest.java | 34 ++++---- .../acme4j/it/boulder/OrderHttpIT.java | 10 +-- .../shredzone/acme4j/it/pebble/OrderIT.java | 18 ++--- .../acme4j/it/pebble/OrderWildcardIT.java | 13 +-- .../shredzone/acme4j/it/pebble/SessionIT.java | 5 +- src/doc/docs/migration.md | 1 + 36 files changed, 345 insertions(+), 383 deletions(-) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java index 5e7deb81..c67b160e 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java @@ -22,8 +22,8 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.Optional; -import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.ResourceIterator; import org.shredzone.acme4j.exception.AcmeException; @@ -57,17 +57,17 @@ public class Account extends AcmeJsonResource { * Returns if the user agreed to the terms of service. * * @return {@code true} if the user agreed to the terms of service. May be - * {@code null} if the server did not provide such an information. + * empty if the server did not provide such an information. */ - @Nullable - public Boolean getTermsOfServiceAgreed() { - return getJSON().get(KEY_TOS_AGREED).map(Value::asBoolean).orElse(null); + public Optional getTermsOfServiceAgreed() { + return getJSON().get(KEY_TOS_AGREED).map(Value::asBoolean); } /** * List of registered contact addresses (emails, phone numbers etc). *

- * This list is unmodifiable. Use {@link #modify()} to change the contacts. + * This list is unmodifiable. Use {@link #modify()} to change the contacts. May be + * empty, but is never {@code null}. */ public List getContacts() { return getJSON().get(KEY_CONTACT) @@ -98,17 +98,15 @@ public class Account extends AcmeJsonResource { /** * Returns the key identifier of the external non-ACME account. If this account is - * not bound to an external account, {@code null} is returned instead. + * not bound to an external account, the result is empty. * * @since 2.8 */ - @Nullable - public String getKeyIdentifier() { + public Optional getKeyIdentifier() { return getJSON().get(KEY_EXTERNAL_ACCOUNT_BINDING) .optional().map(Value::asObject) .map(j -> j.get("protected")).map(Value::asEncodedObject) - .map(j -> j.get("kid")).map(Value::asString) - .orElse(null); + .map(j -> j.get("kid")).map(Value::asString); } /** @@ -197,11 +195,8 @@ public class Account extends AcmeJsonResource { conn.sendSignedRequest(newAuthzUrl, claims, getLogin()); - var authLocation = conn.getLocation(); - if (authLocation == null) { - throw new AcmeProtocolException("Server did not provide an authorization location"); - } - + var authLocation = conn.getLocation() + .orElseThrow(() -> new AcmeProtocolException("Server did not provide an authorization location")); var auth = getLogin().bindAuthorization(authLocation); auth.setJSON(conn.readJsonResponse()); return auth; diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java index 63e170d9..4986860c 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java @@ -261,10 +261,8 @@ public class AccountBuilder { conn.sendSignedRequest(resourceUrl, claims, session, keyPair); - var location = conn.getLocation(); - if (location == null) { - throw new AcmeProtocolException("Server did not provide an account location"); - } + var location = conn.getLocation() + .orElseThrow(() -> new AcmeProtocolException("Server did not provide an account location")); var login = new Login(location, keyPair, session); login.getAccount().setJSON(conn.readJsonResponse()); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java index 8a798526..2baf91f7 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java @@ -18,8 +18,8 @@ import static java.util.stream.Collectors.toUnmodifiableList; import java.net.URL; import java.time.Instant; import java.util.List; +import java.util.Optional; -import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeProtocolException; @@ -67,12 +67,10 @@ public class Authorization extends AcmeJsonResource { /** * Gets the expiry date of the authorization, if set by the server. */ - @Nullable - public Instant getExpires() { + public Optional getExpires() { return getJSON().get("expires") .map(Value::asString) - .map(AcmeUtils::parseTimestamp) - .orElse(null); + .map(AcmeUtils::parseTimestamp); } /** @@ -108,17 +106,18 @@ public class Authorization extends AcmeJsonResource { * * @param type * Challenge name (e.g. "http-01") - * @return {@link Challenge} matching that name, or {@code null} if there is no such + * @return {@link Challenge} matching that name, or empty if there is no such * challenge, or if the challenge alone is not sufficient for authorization. * @throws ClassCastException * if the type does not match the expected Challenge class type */ - @Nullable - public T findChallenge(final String type) { - return (T) getChallenges().stream() + @SuppressWarnings("unchecked") + public Optional findChallenge(final String type) { + return (Optional) getChallenges().stream() .filter(ch -> type.equals(ch.getType())) - .reduce((a, b) -> {throw new AcmeProtocolException("Found more than one challenge of type " + type);}) - .orElse(null); + .reduce((a, b) -> { + throw new AcmeProtocolException("Found more than one challenge of type " + type); + }); } /** @@ -127,17 +126,17 @@ public class Authorization extends AcmeJsonResource { * * @param type * Challenge type (e.g. "Http01Challenge.class") - * @return {@link Challenge} of that type, or {@code null} if there is no such + * @return {@link Challenge} of that type, or empty if there is no such * challenge, or if the challenge alone is not sufficient for authorization. * @since 2.8 */ - @Nullable - public T findChallenge(Class type) { + public Optional findChallenge(Class type) { return getChallenges().stream() .filter(type::isInstance) .map(type::cast) - .reduce((a, b) -> {throw new AcmeProtocolException("Found more than one challenge of type " + type.getName());}) - .orElse(null); + .reduce((a, b) -> { + throw new AcmeProtocolException("Found more than one challenge of type " + type.getName()); + }); } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Metadata.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Metadata.java index 93c7cde3..3294096f 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Metadata.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Metadata.java @@ -19,8 +19,8 @@ import java.net.URI; import java.net.URL; import java.time.Duration; import java.util.Collection; +import java.util.Optional; -import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON.Value; @@ -42,21 +42,18 @@ public class Metadata { } /** - * Returns an {@link URI} of the current terms of service, or {@code null} if not - * available. + * Returns an {@link URI} of the current terms of service, or empty if not available. */ - @Nullable - public URI getTermsOfService() { - return meta.get("termsOfService").map(Value::asURI).orElse(null); + public Optional getTermsOfService() { + return meta.get("termsOfService").map(Value::asURI); } /** * Returns an {@link URL} of a website providing more information about the ACME - * server. {@code null} if not available. + * server. Empty if not available. */ - @Nullable - public URL getWebsite() { - return meta.get("website").map(Value::asURL).orElse(null); + public Optional getWebsite() { + return meta.get("website").map(Value::asURL); } /** @@ -89,33 +86,26 @@ public class Metadata { /** * Returns the minimum acceptable value for the maximum validity of a certificate - * before auto-renewal. {@code null} if the CA does not support short-term - * auto-renewal. + * before auto-renewal. Empty if the CA does not support short-term auto-renewal. * * @since 2.3 */ - @Nullable - public Duration getAutoRenewalMinLifetime() { - var ar = meta.get("auto-renewal").optional().map(Value::asObject); - if (ar.isEmpty()) { - return null; - } - return ar.get().get("min-lifetime").map(Value::asDuration).orElse(null); + public Optional getAutoRenewalMinLifetime() { + return meta.get("auto-renewal").optional().map(Value::asObject) + .map(j -> j.get("min-lifetime")) + .map(Value::asDuration); } /** * Returns the maximum delta between auto-renewal end date and auto-renewal start - * date. {@code null} if the CA does not support short-term auto-renewal. + * date. * * @since 2.3 */ - @Nullable - public Duration getAutoRenewalMaxDuration() { - var ar = meta.get("auto-renewal").optional().map(Value::asObject); - if (ar.isEmpty()) { - return null; - } - return ar.get().get("max-duration").map(Value::asDuration).orElse(null); + public Optional getAutoRenewalMaxDuration() { + return meta.get("auto-renewal").optional().map(Value::asObject) + .map(j -> j.get("max-duration")) + .map(Value::asDuration); } /** @@ -124,11 +114,10 @@ public class Metadata { * @since 2.6 */ public boolean isAutoRenewalGetAllowed() { - var ar = meta.get("auto-renewal").optional().map(Value::asObject); - if (ar.isEmpty()) { - return false; - } - return ar.get().get("allow-certificate-get").map(Value::asBoolean).orElse(false); + return meta.get("auto-renewal").optional().map(Value::asObject) + .map(j -> j.get("allow-certificate-get")) + .map(Value::asBoolean) + .orElse(false); } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java index 54c00cdd..4e22c3d4 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java @@ -19,8 +19,8 @@ import java.net.URL; import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Optional; -import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON.Value; @@ -52,18 +52,15 @@ public class Order extends AcmeJsonResource { /** * Returns a {@link Problem} document with the reason if the order has failed. */ - @Nullable - public Problem getError() { - return getJSON().get("error").map(v -> v.asProblem(getLocation())).orElse(null); + public Optional getError() { + return getJSON().get("error").map(v -> v.asProblem(getLocation())); } /** - * Gets the expiry date of the authorization, if set by the server, {@code null} - * otherwise. + * Gets the expiry date of the authorization, if set by the server. */ - @Nullable - public Instant getExpires() { - return getJSON().get("expires").map(Value::asInstant).orElse(null); + public Optional getExpires() { + return getJSON().get("expires").map(Value::asInstant); } /** @@ -80,19 +77,17 @@ public class Order extends AcmeJsonResource { } /** - * Gets the "not before" date that was used for the order, or {@code null}. + * Gets the "not before" date that was used for the order. */ - @Nullable - public Instant getNotBefore() { - return getJSON().get("notBefore").map(Value::asInstant).orElse(null); + public Optional getNotBefore() { + return getJSON().get("notBefore").map(Value::asInstant); } /** - * Gets the "not after" date that was used for the order, or {@code null}. + * Gets the "not after" date that was used for the order. */ - @Nullable - public Instant getNotAfter() { - return getJSON().get("notAfter").map(Value::asInstant).orElse(null); + public Optional getNotAfter() { + return getJSON().get("notAfter").map(Value::asInstant); } /** @@ -119,28 +114,23 @@ public class Order extends AcmeJsonResource { } /** - * Gets the {@link Certificate} if it is available. {@code null} otherwise. + * Gets the {@link Certificate} if it is available. */ - @Nullable - public Certificate getCertificate() { + public Optional getCertificate() { return getJSON().get("certificate") .map(Value::asURL) - .map(getLogin()::bindCertificate) - .orElse(null); + .map(getLogin()::bindCertificate); } /** - * Gets the STAR extension's {@link Certificate} if it is available. {@code null} - * otherwise. + * Gets the STAR extension's {@link Certificate} if it is available. * * @since 2.6 */ - @Nullable - public Certificate getAutoRenewalCertificate() { + public Optional getAutoRenewalCertificate() { return getJSON().get("star-certificate") .map(Value::asURL) - .map(getLogin()::bindCertificate) - .orElse(null); + .map(getLogin()::bindCertificate); } /** @@ -180,73 +170,63 @@ public class Order extends AcmeJsonResource { } /** - * Returns the earliest date of validity of the first certificate issued, or - * {@code null}. + * Returns the earliest date of validity of the first certificate issued. * * @since 2.3 */ - @Nullable - public Instant getAutoRenewalStartDate() { + public Optional getAutoRenewalStartDate() { return getJSON().get("auto-renewal") .optional() .map(Value::asObject) .orElseGet(JSON::empty) .get("start-date") .optional() - .map(Value::asInstant) - .orElse(null); + .map(Value::asInstant); } /** - * Returns the latest date of validity of the last certificate issued, or - * {@code null}. + * Returns the latest date of validity of the last certificate issued. * * @since 2.3 */ - @Nullable - public Instant getAutoRenewalEndDate() { + public Optional getAutoRenewalEndDate() { return getJSON().get("auto-renewal") .optional() .map(Value::asObject) .orElseGet(JSON::empty) .get("end-date") .optional() - .map(Value::asInstant) - .orElse(null); + .map(Value::asInstant); } /** - * Returns the maximum lifetime of each certificate, or {@code null}. + * Returns the maximum lifetime of each certificate. * * @since 2.3 */ - @Nullable - public Duration getAutoRenewalLifetime() { + public Optional getAutoRenewalLifetime() { return getJSON().get("auto-renewal") .optional() .map(Value::asObject) .orElseGet(JSON::empty) .get("lifetime") .optional() - .map(Value::asDuration) - .orElse(null); + .map(Value::asDuration); } /** - * Returns the pre-date period of each certificate, or {@code null}. + * Returns the pre-date period of each certificate. * * @since 2.7 */ - @Nullable - public Duration getAutoRenewalLifetimeAdjust() { + public Optional getAutoRenewalLifetimeAdjust() { return getJSON().get("auto-renewal") .optional() .map(Value::asObject) .orElseGet(JSON::empty) .get("lifetime-adjust") .optional() - .map(Value::asDuration) - .orElse(null); + .map(Value::asDuration); } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java index b9cd6cff..f33ebbf9 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java @@ -314,10 +314,8 @@ public class OrderBuilder { conn.sendSignedRequest(session.resourceUrl(Resource.NEW_ORDER), claims, login); - var orderLocation = conn.getLocation(); - if (orderLocation == null) { - throw new AcmeProtocolException("Server did not provide an order location"); - } + var orderLocation = conn.getLocation() + .orElseThrow(() -> new AcmeProtocolException("Server did not provide an order location")); var order = new Order(login, orderLocation); order.setJSON(conn.readJsonResponse()); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Problem.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Problem.java index 457f79ba..cf5f50f3 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Problem.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Problem.java @@ -20,8 +20,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.List; +import java.util.Optional; -import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON.Value; @@ -69,13 +69,12 @@ public class Problem implements Serializable { /** * Returns a short, human-readable summary of the problem. The text may be localized - * if supported by the server. {@code null} if the server did not provide a title. + * if supported by the server. Empty if the server did not provide a title. * * @see #toString() */ - @Nullable - public String getTitle() { - return problemJson.get("title").map(Value::asString).orElse(null); + public Optional getTitle() { + return problemJson.get("title").map(Value::asString); } /** @@ -84,17 +83,15 @@ public class Problem implements Serializable { * * @see #toString() */ - @Nullable - public String getDetail() { - return problemJson.get("detail").map(Value::asString).orElse(null); + public Optional getDetail() { + return problemJson.get("detail").map(Value::asString); } /** * Returns a URI that identifies the specific occurence of the problem. It is always * an absolute URI. */ - @Nullable - public URI getInstance() { + public Optional getInstance() { return problemJson.get("instance") .map(Value::asString) .map(it -> { @@ -103,25 +100,22 @@ public class Problem implements Serializable { } catch (URISyntaxException ex) { throw new IllegalArgumentException("Bad base URL", ex); } - }) - .orElse(null); + }); } /** - * Returns the {@link Identifier} this problem relates to. May be {@code null}. + * Returns the {@link Identifier} this problem relates to. * * @since 2.3 */ - @Nullable - public Identifier getIdentifier() { + public Optional getIdentifier() { return problemJson.get("identifier") .optional() - .map(Value::asIdentifier) - .orElse(null); + .map(Value::asIdentifier); } /** - * Returns a list of sub-problems. May be empty, but is never {@code null}. + * Returns a list of sub-problems. */ public List getSubProblems() { return problemJson.get("subproblems") @@ -153,10 +147,10 @@ public class Problem implements Serializable { public String toString() { var sb = new StringBuilder(); - if (getDetail() != null) { - sb.append(getDetail()); - } else if (getTitle() != null) { - sb.append(getTitle()); + if (getDetail().isPresent()) { + sb.append(getDetail().get()); + } else if (getTitle().isPresent()) { + sb.append(getTitle().get()); } else { sb.append(getType()); } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/RevocationReason.java b/acme4j-client/src/main/java/org/shredzone/acme4j/RevocationReason.java index e99ed5cf..60959ae1 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/RevocationReason.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/RevocationReason.java @@ -15,8 +15,6 @@ package org.shredzone.acme4j; import java.util.Arrays; -import edu.umd.cs.findbugs.annotations.Nullable; - /** * An enumeration of revocation reasons. * @@ -54,14 +52,14 @@ public enum RevocationReason { * * @param reasonCode * Reason code as defined in RFC 5280 - * @return Matching {@link RevocationReason}, or {@code null} if not known + * @return Matching {@link RevocationReason} + * @throws IllegalArgumentException if the reason code is unknown or invalid */ - @Nullable public static RevocationReason code(int reasonCode) { return Arrays.stream(values()) .filter(rr -> rr.reasonCode == reasonCode) .findFirst() - .orElse(null); + .orElseThrow(() -> new IllegalArgumentException("Unknown revocation reason code: " + reasonCode)); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java index 8e3e3475..fc70afc0 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java @@ -14,8 +14,8 @@ package org.shredzone.acme4j.challenge; import java.time.Instant; +import java.util.Optional; -import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.AcmeJsonResource; import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Problem; @@ -83,9 +83,8 @@ public class Challenge extends AcmeJsonResource { /** * Returns the validation date, if returned by the server. */ - @Nullable - public Instant getValidated() { - return getJSON().get(KEY_VALIDATED).map(Value::asInstant).orElse(null); + public Optional getValidated() { + return getJSON().get(KEY_VALIDATED).map(Value::asInstant); } /** @@ -93,11 +92,8 @@ public class Challenge extends AcmeJsonResource { * server. If there are multiple errors, they can be found in * {@link Problem#getSubProblems()}. */ - @Nullable - public Problem getError() { - return getJSON().get(KEY_ERROR) - .map(it -> it.asProblem(getLocation())) - .orElse(null); + public Optional getError() { + return getJSON().get(KEY_ERROR).map(it -> it.asProblem(getLocation())); } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java index d6b68ffc..1935fe86 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java @@ -25,7 +25,6 @@ import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSONBuilder; @@ -161,20 +160,18 @@ public interface Connection extends AutoCloseable { /** * Gets the nonce from the nonce header. * - * @return Base64 encoded nonce, or {@code null} if no nonce header was set + * @return Base64 encoded nonce, or empty if no nonce header was set */ - @Nullable - String getNonce(); + Optional getNonce(); /** * Gets a location from the {@code Location} header. *

* Relative links are resolved against the last request's URL. * - * @return Location {@link URL}, or {@code null} if no Location header was set + * @return Location {@link URL}, or empty if no Location header was set */ - @Nullable - URL getLocation(); + Optional getLocation(); /** * Returns the content of the last-modified header, if present. diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java index e03bbc88..cdb0059f 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java @@ -136,11 +136,9 @@ public class DefaultConnection implements Connection { throwAcmeException(); } - var nonce = getNonce(); - if (nonce == null) { - throw new AcmeProtocolException("Server did not provide a nonce"); - } - session.setNonce(nonce); + session.setNonce(getNonce() + .orElseThrow(() -> new AcmeProtocolException("Server did not provide a nonce")) + ); } catch (IOException ex) { throw new AcmeNetworkException(ex); } finally { @@ -168,10 +166,7 @@ public class DefaultConnection implements Connection { logHeaders(); - var nonce = getNonce(); - if (nonce != null) { - session.setNonce(nonce); - } + getNonce().ifPresent(session::setNonce); var rc = getResponse().statusCode(); if (rc != HTTP_OK && rc != HTTP_CREATED && (rc != HTTP_NOT_MODIFIED || ifModifiedSince == null)) { @@ -245,37 +240,32 @@ public class DefaultConnection implements Connection { } @Override - @Nullable - public String getNonce() { + public Optional getNonce() { var nonceHeaderOpt = getResponse().headers() .firstValue(REPLAY_NONCE_HEADER) .map(String::trim) .filter(not(String::isEmpty)); - if (nonceHeaderOpt.isEmpty()) { - return null; + if (nonceHeaderOpt.isPresent()) { + var nonceHeader = nonceHeaderOpt.get(); + + if (!AcmeUtils.isValidBase64Url(nonceHeader)) { + throw new AcmeProtocolException("Invalid replay nonce: " + nonceHeader); + } + + LOG.debug("Replay Nonce: {}", nonceHeader); } - - var nonceHeader = nonceHeaderOpt.get(); - if (!AcmeUtils.isValidBase64Url(nonceHeader)) { - throw new AcmeProtocolException("Invalid replay nonce: " + nonceHeader); - } - - LOG.debug("Replay Nonce: {}", nonceHeader); - - return nonceHeader; + return nonceHeaderOpt; } @Override - @Nullable - public URL getLocation() { + public Optional getLocation() { return getResponse().headers() .firstValue(LOCATION_HEADER) .map(l -> { LOG.debug("Location: {}", l); return l; }) - .map(this::resolveRelative) - .orElse(null); + .map(this::resolveRelative); } @Override @@ -451,7 +441,7 @@ public class DefaultConnection implements Connection { logHeaders(); - session.setNonce(getNonce()); + session.setNonce(getNonce().orElse(null)); var rc = getResponse().statusCode(); if (rc != HTTP_OK && rc != HTTP_CREATED) { diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeRateLimitedException.java b/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeRateLimitedException.java index a79ac321..9ab3a983 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeRateLimitedException.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeRateLimitedException.java @@ -17,6 +17,7 @@ import java.net.URL; import java.time.Instant; import java.util.Collection; import java.util.Collections; +import java.util.Optional; import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.Problem; @@ -30,7 +31,7 @@ public class AcmeRateLimitedException extends AcmeServerException { private static final long serialVersionUID = 4150484059796413069L; private final @Nullable Instant retryAfter; - private final @Nullable Collection documents; + private final Collection documents; /** * Creates a new {@link AcmeRateLimitedException}. @@ -49,23 +50,21 @@ public class AcmeRateLimitedException extends AcmeServerException { super(problem); this.retryAfter = retryAfter; this.documents = - documents != null ? Collections.unmodifiableCollection(documents) : null; + documents != null ? Collections.unmodifiableCollection(documents) : Collections.emptyList(); } /** - * Returns the instant of time the request is expected to succeed again. {@code null} + * Returns the instant of time the request is expected to succeed again. Empty * if this moment is not known. */ - @Nullable - public Instant getRetryAfter() { - return retryAfter; + public Optional getRetryAfter() { + return Optional.ofNullable(retryAfter); } /** * Collection of URLs pointing to documents about the rate limit that was hit. - * {@code null} if the server did not provide such URLs. + * Empty if the server did not provide such URLs. */ - @Nullable public Collection getDocuments() { return documents; } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeUserActionRequiredException.java b/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeUserActionRequiredException.java index 432f5797..0c43ee86 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeUserActionRequiredException.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeUserActionRequiredException.java @@ -16,6 +16,7 @@ package org.shredzone.acme4j.exception; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.util.Optional; import edu.umd.cs.findbugs.annotations.Nullable; import org.shredzone.acme4j.Problem; @@ -46,12 +47,11 @@ public class AcmeUserActionRequiredException extends AcmeServerException { } /** - * Returns the {@link URI} of the terms-of-service document to accept, or {@code null} + * Returns the {@link URI} of the terms-of-service document to accept. Empty * if the server did not provide a link to such a document. */ - @Nullable - public URI getTermsOfServiceUri() { - return tosUri; + public Optional getTermsOfServiceUri() { + return Optional.ofNullable(tosUri); } /** @@ -59,11 +59,8 @@ public class AcmeUserActionRequiredException extends AcmeServerException { * taken by a human. */ public URL getInstance() { - var instance = getProblem().getInstance(); - - if (instance == null) { - throw new AcmeProtocolException("Instance URL required, but missing."); - } + var instance = getProblem().getInstance() + .orElseThrow(() -> new AcmeProtocolException("Instance URL required, but missing.")); try { return instance.toURL(); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java index 5e032917..3e153270 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java @@ -73,10 +73,7 @@ public abstract class AbstractAcmeProvider implements AcmeProvider { session.setDirectoryExpires(conn.getExpiration().orElse(null)); // use nonce header if there is one, saves a HEAD request... - var nonce = conn.getNonce(); - if (nonce != null) { - session.setNonce(nonce); - } + conn.getNonce().ifPresent(session::setNonce); return conn.readJsonResponse(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java index 5c80f60d..41b1f22b 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java @@ -21,6 +21,7 @@ import static org.shredzone.acme4j.toolbox.TestUtils.url; import java.net.HttpURLConnection; import java.net.URL; import java.security.KeyPair; +import java.util.Optional; import org.jose4j.jwx.CompactSerializer; import org.junit.jupiter.api.Test; @@ -71,8 +72,8 @@ public class AccountBuilderTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } @Override @@ -94,10 +95,10 @@ public class AccountBuilderTest { assertThat(login.getAccountLocation()).isEqualTo(locationUrl); var account = login.getAccount(); - assertThat(account.getTermsOfServiceAgreed()).isTrue(); + assertThat(account.getTermsOfServiceAgreed().orElseThrow()).isTrue(); assertThat(account.getLocation()).isEqualTo(locationUrl); assertThat(account.hasExternalAccountBinding()).isFalse(); - assertThat(account.getKeyIdentifier()).isNull(); + assertThat(account.getKeyIdentifier()).isEmpty(); provider.close(); } @@ -133,8 +134,8 @@ public class AccountBuilderTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } @Override @@ -175,8 +176,8 @@ public class AccountBuilderTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } @Override diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java index ce1f6bad..c205d49e 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java @@ -27,6 +27,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.util.Collection; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import org.jose4j.jws.JsonWebSignature; @@ -87,8 +88,8 @@ public class AccountTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } @Override @@ -108,12 +109,12 @@ public class AccountTest { assertThat(login.getAccountLocation()).isEqualTo(locationUrl); assertThat(account.getLocation()).isEqualTo(locationUrl); - assertThat(account.getTermsOfServiceAgreed()).isTrue(); + assertThat(account.getTermsOfServiceAgreed().orElseThrow()).isTrue(); assertThat(account.getContacts()).hasSize(1); assertThat(account.getContacts().get(0)).isEqualTo(URI.create("mailto:foo2@example.com")); assertThat(account.getStatus()).isEqualTo(Status.VALID); assertThat(account.hasExternalAccountBinding()).isTrue(); - assertThat(account.getKeyIdentifier()).isEqualTo("NCC-1701"); + assertThat(account.getKeyIdentifier().orElseThrow()).isEqualTo("NCC-1701"); var orderIt = account.getOrders(); assertThat(orderIt).isNotNull(); @@ -144,8 +145,8 @@ public class AccountTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } @Override @@ -166,12 +167,12 @@ public class AccountTest { // Lazy loading assertThat(requestWasSent.get()).isFalse(); - assertThat(account.getTermsOfServiceAgreed()).isTrue(); + assertThat(account.getTermsOfServiceAgreed().orElseThrow()).isTrue(); assertThat(requestWasSent.get()).isTrue(); // Subsequent queries do not trigger another load requestWasSent.set(false); - assertThat(account.getTermsOfServiceAgreed()).isTrue(); + assertThat(account.getTermsOfServiceAgreed().orElseThrow()).isTrue(); assertThat(account.getStatus()).isEqualTo(Status.VALID); assertThat(requestWasSent.get()).isFalse(); @@ -198,8 +199,8 @@ public class AccountTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } }; @@ -216,7 +217,7 @@ public class AccountTest { assertThat(auth.getIdentifier().getDomain()).isEqualTo(domainName); assertThat(auth.getStatus()).isEqualTo(Status.PENDING); - assertThat(auth.getExpires()).isNull(); + assertThat(auth.getExpires()).isEmpty(); assertThat(auth.getLocation()).isEqualTo(locationUrl); assertThat(auth.getChallenges()).containsExactlyInAnyOrder( @@ -326,8 +327,8 @@ public class AccountTest { } @Override - public URL getLocation() { - return resourceUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } }; @@ -423,8 +424,8 @@ public class AccountTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } }; diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java index 07e3acb2..f4aa4e95 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java @@ -58,22 +58,22 @@ public class AuthorizationTest { // A snail mail challenge is not available at all var c1 = authorization.findChallenge(SNAILMAIL_TYPE); - assertThat(c1).isNull(); + assertThat(c1).isEmpty(); // HttpChallenge is available var c2 = authorization.findChallenge(Http01Challenge.TYPE); - assertThat(c2).isNotNull(); - assertThat(c2).isInstanceOf(Http01Challenge.class); + assertThat(c2).isNotEmpty(); + assertThat(c2.get()).isInstanceOf(Http01Challenge.class); // Dns01Challenge is available var c3 = authorization.findChallenge(Dns01Challenge.TYPE); - assertThat(c3).isNotNull(); - assertThat(c3).isInstanceOf(Dns01Challenge.class); + assertThat(c3).isNotEmpty(); + assertThat(c3.get()).isInstanceOf(Dns01Challenge.class); // TlsAlpn01Challenge is available var c4 = authorization.findChallenge(TlsAlpn01Challenge.TYPE); - assertThat(c4).isNotNull(); - assertThat(c4).isInstanceOf(TlsAlpn01Challenge.class); + assertThat(c4).isNotEmpty(); + assertThat(c4.get()).isInstanceOf(TlsAlpn01Challenge.class); } /** @@ -85,19 +85,19 @@ public class AuthorizationTest { // A snail mail challenge is not available at all var c1 = authorization.findChallenge(NonExistingChallenge.class); - assertThat(c1).isNull(); + assertThat(c1).isEmpty(); // HttpChallenge is available var c2 = authorization.findChallenge(Http01Challenge.class); - assertThat(c2).isNotNull(); + assertThat(c2).isNotEmpty(); // Dns01Challenge is available var c3 = authorization.findChallenge(Dns01Challenge.class); - assertThat(c3).isNotNull(); + assertThat(c3).isNotEmpty(); // TlsAlpn01Challenge is available var c4 = authorization.findChallenge(TlsAlpn01Challenge.class); - assertThat(c4).isNotNull(); + assertThat(c4).isNotEmpty(); } /** @@ -147,7 +147,7 @@ public class AuthorizationTest { assertThat(auth.getIdentifier().getDomain()).isEqualTo("example.org"); assertThat(auth.getStatus()).isEqualTo(Status.VALID); assertThat(auth.isWildcard()).isFalse(); - assertThat(auth.getExpires()).isCloseTo("2016-01-02T17:12:40Z", within(1, ChronoUnit.SECONDS)); + assertThat(auth.getExpires().orElseThrow()).isCloseTo("2016-01-02T17:12:40Z", within(1, ChronoUnit.SECONDS)); assertThat(auth.getLocation()).isEqualTo(locationUrl); assertThat(auth.getChallenges()).containsExactlyInAnyOrder( @@ -191,7 +191,7 @@ public class AuthorizationTest { assertThat(auth.getIdentifier().getDomain()).isEqualTo("example.org"); assertThat(auth.getStatus()).isEqualTo(Status.VALID); assertThat(auth.isWildcard()).isTrue(); - assertThat(auth.getExpires()).isCloseTo("2016-01-02T17:12:40Z", within(1, ChronoUnit.SECONDS)); + assertThat(auth.getExpires().orElseThrow()).isCloseTo("2016-01-02T17:12:40Z", within(1, ChronoUnit.SECONDS)); assertThat(auth.getLocation()).isEqualTo(locationUrl); assertThat(auth.getChallenges()).containsExactlyInAnyOrder( @@ -244,7 +244,7 @@ public class AuthorizationTest { assertThat(auth.getIdentifier().getDomain()).isEqualTo("example.org"); assertThat(auth.getStatus()).isEqualTo(Status.VALID); assertThat(auth.isWildcard()).isFalse(); - assertThat(auth.getExpires()).isCloseTo("2016-01-02T17:12:40Z", within(1, ChronoUnit.SECONDS)); + assertThat(auth.getExpires().orElseThrow()).isCloseTo("2016-01-02T17:12:40Z", within(1, ChronoUnit.SECONDS)); assertThat(requestWasSent).isFalse(); provider.close(); @@ -288,7 +288,7 @@ public class AuthorizationTest { assertThat(auth.getIdentifier().getDomain()).isEqualTo("example.org"); assertThat(auth.getStatus()).isEqualTo(Status.VALID); assertThat(auth.isWildcard()).isFalse(); - assertThat(auth.getExpires()).isCloseTo("2016-01-02T17:12:40Z", within(1, ChronoUnit.SECONDS)); + assertThat(auth.getExpires().orElseThrow()).isCloseTo("2016-01-02T17:12:40Z", within(1, ChronoUnit.SECONDS)); assertThat(auth.getLocation()).isEqualTo(locationUrl); assertThat(auth.getChallenges()).containsExactlyInAnyOrder( diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java index daa17e19..551db8db 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java @@ -223,7 +223,8 @@ public class CertificateTest { */ @Test public void testRevocationReason() { - assertThat(RevocationReason.code(1)).isEqualTo(RevocationReason.KEY_COMPROMISE); + assertThat(RevocationReason.code(1)) + .isEqualTo(RevocationReason.KEY_COMPROMISE); } /** diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java index f41ca38f..6dad5a6b 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java @@ -25,6 +25,7 @@ import java.net.InetAddress; import java.net.URL; import java.time.Duration; import java.util.Arrays; +import java.util.Optional; import org.assertj.core.api.AutoCloseableSoftAssertions; import org.junit.jupiter.api.Test; @@ -66,8 +67,8 @@ public class OrderBuilderTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } }; @@ -98,15 +99,18 @@ public class OrderBuilderTest { Identifier.dns("d.example.com"), Identifier.dns("d2.example.com"), Identifier.ip(InetAddress.getByName("192.168.1.2"))); - softly.assertThat(order.getNotBefore()).isEqualTo("2016-01-01T00:10:00Z"); - softly.assertThat(order.getNotAfter()).isEqualTo("2016-01-08T00:10:00Z"); - softly.assertThat(order.getExpires()).isEqualTo("2016-01-10T00:00:00Z"); + softly.assertThat(order.getNotBefore().orElseThrow()) + .isEqualTo("2016-01-01T00:10:00Z"); + softly.assertThat(order.getNotAfter().orElseThrow()) + .isEqualTo("2016-01-08T00:10:00Z"); + softly.assertThat(order.getExpires().orElseThrow()) + .isEqualTo("2016-01-10T00:00:00Z"); softly.assertThat(order.getStatus()).isEqualTo(Status.PENDING); softly.assertThat(order.isAutoRenewing()).isFalse(); - softly.assertThat(order.getAutoRenewalStartDate()).isNull(); - softly.assertThat(order.getAutoRenewalEndDate()).isNull(); - softly.assertThat(order.getAutoRenewalLifetime()).isNull(); - softly.assertThat(order.getAutoRenewalLifetimeAdjust()).isNull(); + softly.assertThat(order.getAutoRenewalStartDate()).isEmpty(); + softly.assertThat(order.getAutoRenewalEndDate()).isEmpty(); + softly.assertThat(order.getAutoRenewalLifetime()).isEmpty(); + softly.assertThat(order.getAutoRenewalLifetimeAdjust()).isEmpty(); softly.assertThat(order.isAutoRenewalGetEnabled()).isFalse(); softly.assertThat(order.getLocation()).isEqualTo(locationUrl); softly.assertThat(order.getAuthorizations()).isNotNull(); @@ -141,8 +145,8 @@ public class OrderBuilderTest { } @Override - public URL getLocation() { - return locationUrl; + public Optional getLocation() { + return Optional.of(locationUrl); } }; @@ -164,13 +168,13 @@ public class OrderBuilderTest { try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(order.getIdentifiers()).containsExactlyInAnyOrder(Identifier.dns("example.org")); - softly.assertThat(order.getNotBefore()).isNull(); - softly.assertThat(order.getNotAfter()).isNull(); + softly.assertThat(order.getNotBefore()).isEmpty(); + softly.assertThat(order.getNotAfter()).isEmpty(); softly.assertThat(order.isAutoRenewing()).isTrue(); - softly.assertThat(order.getAutoRenewalStartDate()).isEqualTo(autoRenewStart); - softly.assertThat(order.getAutoRenewalEndDate()).isEqualTo(autoRenewEnd); - softly.assertThat(order.getAutoRenewalLifetime()).isEqualTo(validity); - softly.assertThat(order.getAutoRenewalLifetimeAdjust()).isEqualTo(predate); + softly.assertThat(order.getAutoRenewalStartDate().orElseThrow()).isEqualTo(autoRenewStart); + softly.assertThat(order.getAutoRenewalEndDate().orElseThrow()).isEqualTo(autoRenewEnd); + softly.assertThat(order.getAutoRenewalLifetime().orElseThrow()).isEqualTo(validity); + softly.assertThat(order.getAutoRenewalLifetimeAdjust().orElseThrow()).isEqualTo(predate); softly.assertThat(order.isAutoRenewalGetEnabled()).isTrue(); softly.assertThat(order.getLocation()).isEqualTo(locationUrl); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java index 4e2be715..0631bf1f 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java @@ -69,29 +69,31 @@ public class OrderTest { try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(order.getStatus()).isEqualTo(Status.PENDING); - softly.assertThat(order.getExpires()).isEqualTo("2015-03-01T14:09:00Z"); + softly.assertThat(order.getExpires().orElseThrow()).isEqualTo("2015-03-01T14:09:00Z"); softly.assertThat(order.getLocation()).isEqualTo(locationUrl); softly.assertThat(order.getIdentifiers()).containsExactlyInAnyOrder( Identifier.dns("example.com"), Identifier.dns("www.example.com")); - softly.assertThat(order.getNotBefore()).isEqualTo("2016-01-01T00:00:00Z"); - softly.assertThat(order.getNotAfter()).isEqualTo("2016-01-08T00:00:00Z"); - softly.assertThat(order.getCertificate().getLocation()) + softly.assertThat(order.getNotBefore().orElseThrow()) + .isEqualTo("2016-01-01T00:00:00Z"); + softly.assertThat(order.getNotAfter().orElseThrow()) + .isEqualTo("2016-01-08T00:00:00Z"); + softly.assertThat(order.getCertificate().orElseThrow().getLocation()) .isEqualTo(url("https://example.com/acme/cert/1234")); softly.assertThat(order.getFinalizeLocation()).isEqualTo(finalizeUrl); softly.assertThat(order.isAutoRenewing()).isFalse(); - softly.assertThat(order.getAutoRenewalStartDate()).isNull(); - softly.assertThat(order.getAutoRenewalEndDate()).isNull(); - softly.assertThat(order.getAutoRenewalLifetime()).isNull(); - softly.assertThat(order.getAutoRenewalLifetimeAdjust()).isNull(); + softly.assertThat(order.getAutoRenewalStartDate()).isEmpty(); + softly.assertThat(order.getAutoRenewalEndDate()).isEmpty(); + softly.assertThat(order.getAutoRenewalLifetime()).isEmpty(); + softly.assertThat(order.getAutoRenewalLifetimeAdjust()).isEmpty(); softly.assertThat(order.isAutoRenewalGetEnabled()).isFalse(); - softly.assertThat(order.getError()).isNotNull(); - softly.assertThat(order.getError().getType()) + softly.assertThat(order.getError()).isNotEmpty(); + softly.assertThat(order.getError().orElseThrow().getType()) .isEqualTo(URI.create("urn:ietf:params:acme:error:connection")); - softly.assertThat(order.getError().getDetail()) + softly.assertThat(order.getError().flatMap(Problem::getDetail).orElseThrow()) .isEqualTo("connection refused"); var auths = order.getAuthorizations(); @@ -139,16 +141,16 @@ public class OrderTest { try (var softly = new AutoCloseableSoftAssertions()) { // Lazy loading softly.assertThat(requestWasSent).isFalse(); - softly.assertThat(order.getCertificate().getLocation()) + softly.assertThat(order.getCertificate().orElseThrow().getLocation()) .isEqualTo(url("https://example.com/acme/cert/1234")); softly.assertThat(requestWasSent).isTrue(); // Subsequent queries do not trigger another load requestWasSent.set(false); - softly.assertThat(order.getCertificate().getLocation()) + softly.assertThat(order.getCertificate().orElseThrow().getLocation()) .isEqualTo(url("https://example.com/acme/cert/1234")); softly.assertThat(order.getStatus()).isEqualTo(Status.PENDING); - softly.assertThat(order.getExpires()).isEqualTo("2015-03-01T14:09:00Z"); + softly.assertThat(order.getExpires().orElseThrow()).isEqualTo("2015-03-01T14:09:00Z"); softly.assertThat(requestWasSent).isFalse(); } @@ -198,17 +200,19 @@ public class OrderTest { try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(order.getStatus()).isEqualTo(Status.VALID); - softly.assertThat(order.getExpires()).isEqualTo("2015-03-01T14:09:00Z"); + softly.assertThat(order.getExpires().orElseThrow()).isEqualTo("2015-03-01T14:09:00Z"); softly.assertThat(order.getLocation()).isEqualTo(locationUrl); softly.assertThat(order.getIdentifiers()).containsExactlyInAnyOrder( Identifier.dns("example.com"), Identifier.dns("www.example.com")); - softly.assertThat(order.getNotBefore()).isEqualTo("2016-01-01T00:00:00Z"); - softly.assertThat(order.getNotAfter()).isEqualTo("2016-01-08T00:00:00Z"); - softly.assertThat(order.getCertificate().getLocation()) + softly.assertThat(order.getNotBefore().orElseThrow()) + .isEqualTo("2016-01-01T00:00:00Z"); + softly.assertThat(order.getNotAfter().orElseThrow()) + .isEqualTo("2016-01-08T00:00:00Z"); + softly.assertThat(order.getCertificate().orElseThrow().getLocation()) .isEqualTo(url("https://example.com/acme/cert/1234")); - softly.assertThat(order.getAutoRenewalCertificate()).isNull(); + softly.assertThat(order.getAutoRenewalCertificate()).isEmpty(); softly.assertThat(order.getFinalizeLocation()).isEqualTo(finalizeUrl); var auths = order.getAuthorizations(); @@ -255,12 +259,16 @@ public class OrderTest { try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(order.isAutoRenewing()).isTrue(); - softly.assertThat(order.getAutoRenewalStartDate()).isEqualTo("2016-01-01T00:00:00Z"); - softly.assertThat(order.getAutoRenewalEndDate()).isEqualTo("2017-01-01T00:00:00Z"); - softly.assertThat(order.getAutoRenewalLifetime()).isEqualTo(Duration.ofHours(168)); - softly.assertThat(order.getAutoRenewalLifetimeAdjust()).isEqualTo(Duration.ofDays(6)); - softly.assertThat(order.getNotBefore()).isNull(); - softly.assertThat(order.getNotAfter()).isNull(); + softly.assertThat(order.getAutoRenewalStartDate().orElseThrow()) + .isEqualTo("2016-01-01T00:00:00Z"); + softly.assertThat(order.getAutoRenewalEndDate().orElseThrow()) + .isEqualTo("2017-01-01T00:00:00Z"); + softly.assertThat(order.getAutoRenewalLifetime().orElseThrow()) + .isEqualTo(Duration.ofHours(168)); + softly.assertThat(order.getAutoRenewalLifetimeAdjust().orElseThrow()) + .isEqualTo(Duration.ofDays(6)); + softly.assertThat(order.getNotBefore()).isEmpty(); + softly.assertThat(order.getNotAfter()).isEmpty(); softly.assertThat(order.isAutoRenewalGetEnabled()).isTrue(); } @@ -294,16 +302,20 @@ public class OrderTest { var order = login.bindOrder(locationUrl); try (var softly = new AutoCloseableSoftAssertions()) { - softly.assertThat(order.getCertificate()).isNull(); - softly.assertThat(order.getAutoRenewalCertificate().getLocation()) + softly.assertThat(order.getCertificate()).isEmpty(); + softly.assertThat(order.getAutoRenewalCertificate().orElseThrow().getLocation()) .isEqualTo(url("https://example.com/acme/cert/1234")); softly.assertThat(order.isAutoRenewing()).isTrue(); - softly.assertThat(order.getAutoRenewalStartDate()).isEqualTo("2018-01-01T00:00:00Z"); - softly.assertThat(order.getAutoRenewalEndDate()).isEqualTo("2019-01-01T00:00:00Z"); - softly.assertThat(order.getAutoRenewalLifetime()).isEqualTo(Duration.ofHours(168)); - softly.assertThat(order.getAutoRenewalLifetimeAdjust()).isEqualTo(Duration.ofDays(6)); - softly.assertThat(order.getNotBefore()).isNull(); - softly.assertThat(order.getNotAfter()).isNull(); + softly.assertThat(order.getAutoRenewalStartDate().orElseThrow()) + .isEqualTo("2018-01-01T00:00:00Z"); + softly.assertThat(order.getAutoRenewalEndDate().orElseThrow()) + .isEqualTo("2019-01-01T00:00:00Z"); + softly.assertThat(order.getAutoRenewalLifetime().orElseThrow()) + .isEqualTo(Duration.ofHours(168)); + softly.assertThat(order.getAutoRenewalLifetimeAdjust().orElseThrow()) + .isEqualTo(Duration.ofDays(6)); + softly.assertThat(order.getNotBefore()).isEmpty(); + softly.assertThat(order.getNotAfter()).isEmpty(); softly.assertThat(order.isAutoRenewalGetEnabled()).isTrue(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/ProblemTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/ProblemTest.java index d4d5206d..6863234f 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/ProblemTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/ProblemTest.java @@ -40,10 +40,13 @@ public class ProblemTest { try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(problem.getType()).isEqualTo(URI.create("urn:ietf:params:acme:error:malformed")); - softly.assertThat(problem.getTitle()).isEqualTo("Some of the identifiers requested were rejected"); - softly.assertThat(problem.getDetail()).isEqualTo("Identifier \"abc12_\" is malformed"); - softly.assertThat(problem.getInstance()).isEqualTo(URI.create("https://example.com/documents/error.html")); - softly.assertThat(problem.getIdentifier()).isNull(); + softly.assertThat(problem.getTitle().orElseThrow()) + .isEqualTo("Some of the identifiers requested were rejected"); + softly.assertThat(problem.getDetail().orElseThrow()) + .isEqualTo("Identifier \"abc12_\" is malformed"); + softly.assertThat(problem.getInstance().orElseThrow()) + .isEqualTo(URI.create("https://example.com/documents/error.html")); + softly.assertThat(problem.getIdentifier()).isEmpty(); softly.assertThat(problem.toString()).isEqualTo( "Identifier \"abc12_\" is malformed (" + "Invalid underscore in DNS name \"_example.com\" ‒ " @@ -54,16 +57,18 @@ public class ProblemTest { var p1 = subs.get(0); softly.assertThat(p1.getType()).isEqualTo(URI.create("urn:ietf:params:acme:error:malformed")); - softly.assertThat(p1.getTitle()).isNull(); - softly.assertThat(p1.getDetail()).isEqualTo("Invalid underscore in DNS name \"_example.com\""); - softly.assertThat(p1.getIdentifier().getDomain()).isEqualTo("_example.com"); + softly.assertThat(p1.getTitle()).isEmpty(); + softly.assertThat(p1.getDetail().orElseThrow()) + .isEqualTo("Invalid underscore in DNS name \"_example.com\""); + softly.assertThat(p1.getIdentifier().orElseThrow().getDomain()).isEqualTo("_example.com"); softly.assertThat(p1.toString()).isEqualTo("Invalid underscore in DNS name \"_example.com\""); var p2 = subs.get(1); softly.assertThat(p2.getType()).isEqualTo(URI.create("urn:ietf:params:acme:error:rejectedIdentifier")); - softly.assertThat(p2.getTitle()).isNull(); - softly.assertThat(p2.getDetail()).isEqualTo("This CA will not issue for \"example.net\""); - softly.assertThat(p2.getIdentifier().getDomain()).isEqualTo("example.net"); + softly.assertThat(p2.getTitle()).isEmpty(); + softly.assertThat(p2.getDetail().orElseThrow()) + .isEqualTo("This CA will not issue for \"example.net\""); + softly.assertThat(p2.getIdentifier().orElseThrow().getDomain()).isEqualTo("example.net"); softly.assertThat(p2.toString()).isEqualTo("This CA will not issue for \"example.net\""); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/SessionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/SessionTest.java index 048ec042..c1cee8e8 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/SessionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/SessionTest.java @@ -163,13 +163,16 @@ public class SessionTest { var meta = session.getMetadata(); try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(meta).isNotNull(); - softly.assertThat(meta.getTermsOfService()) + softly.assertThat(meta.getTermsOfService().orElseThrow()) .isEqualTo(URI.create("https://example.com/acme/terms")); - softly.assertThat(meta.getWebsite()).isEqualTo(url("https://www.example.com/")); + softly.assertThat(meta.getWebsite().orElseThrow().toExternalForm()) + .isEqualTo("https://www.example.com/"); softly.assertThat(meta.getCaaIdentities()).containsExactlyInAnyOrder("example.com"); softly.assertThat(meta.isAutoRenewalEnabled()).isTrue(); - softly.assertThat(meta.getAutoRenewalMaxDuration()).isEqualTo(Duration.ofDays(365)); - softly.assertThat(meta.getAutoRenewalMinLifetime()).isEqualTo(Duration.ofHours(24)); + softly.assertThat(meta.getAutoRenewalMaxDuration().orElseThrow()) + .isEqualTo(Duration.ofDays(365)); + softly.assertThat(meta.getAutoRenewalMinLifetime().orElseThrow()) + .isEqualTo(Duration.ofHours(24)); softly.assertThat(meta.isAutoRenewalGetAllowed()).isTrue(); softly.assertThat(meta.isExternalAccountRequired()).isTrue(); softly.assertThat(meta.getJSON()).isNotNull(); @@ -211,12 +214,12 @@ public class SessionTest { var meta = session.getMetadata(); try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(meta).isNotNull(); - softly.assertThat(meta.getTermsOfService()).isNull(); - softly.assertThat(meta.getWebsite()).isNull(); + softly.assertThat(meta.getTermsOfService()).isEmpty(); + softly.assertThat(meta.getWebsite()).isEmpty(); softly.assertThat(meta.getCaaIdentities()).isEmpty(); softly.assertThat(meta.isAutoRenewalEnabled()).isFalse(); - softly.assertThat(meta.getAutoRenewalMaxDuration()).isNull(); - softly.assertThat(meta.getAutoRenewalMinLifetime()).isNull(); + softly.assertThat(meta.getAutoRenewalMaxDuration()).isEmpty(); + softly.assertThat(meta.getAutoRenewalMinLifetime()).isEmpty(); softly.assertThat(meta.isAutoRenewalGetAllowed()).isFalse(); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java index fef3c2b0..37662511 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java @@ -57,15 +57,16 @@ public class ChallengeTest { softly.assertThat(challenge.getType()).isEqualTo("generic-01"); softly.assertThat(challenge.getStatus()).isEqualTo(Status.INVALID); softly.assertThat(challenge.getLocation()).isEqualTo(url("http://example.com/challenge/123")); - softly.assertThat(challenge.getValidated()).isCloseTo("2015-12-12T17:19:36.336Z", within(1, ChronoUnit.MILLIS)); + softly.assertThat(challenge.getValidated().orElseThrow()) + .isCloseTo("2015-12-12T17:19:36.336Z", within(1, ChronoUnit.MILLIS)); softly.assertThat(challenge.getJSON().get("type").asString()).isEqualTo("generic-01"); softly.assertThat(challenge.getJSON().get("url").asURL()).isEqualTo(url("http://example.com/challenge/123")); - var error = challenge.getError(); - softly.assertThat(error).isNotNull(); + var error = challenge.getError().orElseThrow(); softly.assertThat(error.getType()).isEqualTo(URI.create("urn:ietf:params:acme:error:incorrectResponse")); - softly.assertThat(error.getDetail()).isEqualTo("bad token"); - softly.assertThat(error.getInstance()).isEqualTo(URI.create("http://example.com/documents/faq.html")); + softly.assertThat(error.getDetail().orElseThrow()).isEqualTo("bad token"); + softly.assertThat(error.getInstance().orElseThrow()) + .isEqualTo(URI.create("http://example.com/documents/faq.html")); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java index 60fb4f32..60215174 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java @@ -106,7 +106,7 @@ public class DefaultConnectionTest { } /** - * Test that {@link DefaultConnection#getNonce()} returns {@code null} if there is no + * Test that {@link DefaultConnection#getNonce()} is empty if there is no * {@code Replay-Nonce} header. */ @Test @@ -117,7 +117,7 @@ public class DefaultConnectionTest { try (var conn = session.connect()) { conn.sendRequest(directoryUrl, session, null); - assertThat(conn.getNonce()).isNull(); + assertThat(conn.getNonce()).isEmpty(); } } @@ -135,7 +135,7 @@ public class DefaultConnectionTest { try (var conn = session.connect()) { conn.sendRequest(requestUrl, session, null); - assertThat(conn.getNonce()).isEqualTo(TestUtils.DUMMY_NONCE); + assertThat(conn.getNonce().orElseThrow()).isEqualTo(TestUtils.DUMMY_NONCE); assertThat(session.getNonce()).isEqualTo(TestUtils.DUMMY_NONCE); } @@ -215,7 +215,8 @@ public class DefaultConnectionTest { try (var conn = session.connect()) { conn.sendRequest(requestUrl, session, null); var location = conn.getLocation(); - assertThat(location).isEqualTo(new URL("https://example.com/otherlocation")); + assertThat(location.orElseThrow()) + .isEqualTo(new URL("https://example.com/otherlocation")); } } @@ -231,7 +232,8 @@ public class DefaultConnectionTest { try (var conn = session.connect()) { conn.sendRequest(requestUrl, session, null); var location = conn.getLocation(); - assertThat(location).isEqualTo(new URL(baseUrl + "/otherlocation")); + assertThat(location.orElseThrow()) + .isEqualTo(new URL(baseUrl + "/otherlocation")); } } @@ -298,7 +300,7 @@ public class DefaultConnectionTest { try (var conn = session.connect()) { conn.sendRequest(requestUrl, session, null); - assertThat(conn.getLocation()).isNull(); + assertThat(conn.getLocation()).isEmpty(); } verify(getRequestedFor(urlEqualTo(REQUEST_PATH))); @@ -445,7 +447,7 @@ public class DefaultConnectionTest { assertThat(((AcmeUserActionRequiredException) ex).getType()) .isEqualTo(URI.create("urn:ietf:params:acme:error:userActionRequired")); assertThat(ex.getMessage()).isEqualTo("Accept the TOS"); - assertThat(((AcmeUserActionRequiredException) ex).getTermsOfServiceUri()) + assertThat(((AcmeUserActionRequiredException) ex).getTermsOfServiceUri().orElseThrow()) .isEqualTo(URI.create("https://example.com/tos.pdf")); } @@ -478,7 +480,7 @@ public class DefaultConnectionTest { assertThat(ex.getType()).isEqualTo(URI.create("urn:ietf:params:acme:error:rateLimited")); assertThat(ex.getMessage()).isEqualTo("Too many invocations"); - assertThat(ex.getRetryAfter()).isEqualTo(retryAfter); + assertThat(ex.getRetryAfter().orElseThrow()).isEqualTo(retryAfter); assertThat(ex.getDocuments()).isNotNull(); assertThat(ex.getDocuments()).hasSize(1); assertThat(ex.getDocuments().iterator().next()).isEqualTo(url("https://example.com/rates.pdf")); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java index 7a7e960f..1ce01998 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java @@ -80,12 +80,12 @@ public class DummyConnection implements Connection { } @Override - public String getNonce() { + public Optional getNonce() { throw new UnsupportedOperationException(); } @Override - public URL getLocation() { + public Optional getLocation() { throw new UnsupportedOperationException(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/NetworkSettingsTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/NetworkSettingsTest.java index 5603fd07..c5d0aefa 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/NetworkSettingsTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/NetworkSettingsTest.java @@ -18,9 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.Authenticator; import java.net.InetSocketAddress; -import java.net.Proxy; import java.net.ProxySelector; -import java.net.URI; import java.net.http.HttpClient; import java.time.Duration; diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeRateLimitedExceptionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeRateLimitedExceptionTest.java index a24decde..f978d4a4 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeRateLimitedExceptionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeRateLimitedExceptionTest.java @@ -47,7 +47,7 @@ public class AcmeRateLimitedExceptionTest { assertThat(ex.getType()).isEqualTo(type); assertThat(ex.getMessage()).isEqualTo(detail); - assertThat(ex.getRetryAfter()).isEqualTo(retryAfter); + assertThat(ex.getRetryAfter().orElseThrow()).isEqualTo(retryAfter); assertThat(ex.getDocuments()).containsAll(documents); } @@ -65,8 +65,8 @@ public class AcmeRateLimitedExceptionTest { assertThat(ex.getType()).isEqualTo(type); assertThat(ex.getMessage()).isEqualTo(detail); - assertThat(ex.getRetryAfter()).isNull(); - assertThat(ex.getDocuments()).isNull(); + assertThat(ex.getRetryAfter()).isEmpty(); + assertThat(ex.getDocuments()).isEmpty(); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeUserActionRequiredExceptionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeUserActionRequiredExceptionTest.java index 19435ee8..ff744c45 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeUserActionRequiredExceptionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeUserActionRequiredExceptionTest.java @@ -43,7 +43,7 @@ public class AcmeUserActionRequiredExceptionTest { assertThat(ex.getType()).isEqualTo(type); assertThat(ex.getMessage()).isEqualTo(detail); - assertThat(ex.getTermsOfServiceUri()).isEqualTo(tosUri); + assertThat(ex.getTermsOfServiceUri().orElseThrow()).isEqualTo(tosUri); assertThat(ex.getInstance()).isEqualTo(instanceUrl); } @@ -62,7 +62,7 @@ public class AcmeUserActionRequiredExceptionTest { assertThat(ex.getType()).isEqualTo(type); assertThat(ex.getMessage()).isEqualTo(detail); - assertThat(ex.getTermsOfServiceUri()).isNull(); + assertThat(ex.getTermsOfServiceUri()).isEmpty(); assertThat(ex.getInstance()).isEqualTo(instanceUrl); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONTest.java index f37e0c6e..188cecb7 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONTest.java @@ -218,8 +218,9 @@ public class JSONTest { var problem = json.get("problem").asProblem(BASE_URL); assertThat(problem).isNotNull(); assertThat(problem.getType()).isEqualTo(URI.create("urn:ietf:params:acme:error:rateLimited")); - assertThat(problem.getDetail()).isEqualTo("too many requests"); - assertThat(problem.getInstance()).isEqualTo(URI.create("https://example.com/documents/errors.html")); + assertThat(problem.getDetail().orElseThrow()).isEqualTo("too many requests"); + assertThat(problem.getInstance().orElseThrow()) + .isEqualTo(URI.create("https://example.com/documents/errors.html")); } /** diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java index 33c8ca00..6c147e97 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java @@ -43,6 +43,7 @@ import java.util.TreeMap; import javax.crypto.SecretKey; +import edu.umd.cs.findbugs.annotations.Nullable; import org.jose4j.json.JsonUtil; import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKey.OutputControlLevel; @@ -282,7 +283,7 @@ public final class TestUtils { * Instance, or {@code null} * @return Created {@link Problem} object */ - public static Problem createProblem(URI type, String detail, URL instance) { + public static Problem createProblem(URI type, String detail, @Nullable URL instance) { var jb = new JSONBuilder(); jb.put("type", type); jb.put("detail", detail); diff --git a/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java b/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java index 51676d62..2efcfa75 100644 --- a/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java +++ b/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java @@ -24,6 +24,7 @@ import java.security.KeyPair; import java.security.Security; import java.util.Arrays; import java.util.Collection; +import java.util.Optional; import javax.swing.JOptionPane; @@ -33,6 +34,7 @@ import org.shredzone.acme4j.AccountBuilder; import org.shredzone.acme4j.Authorization; import org.shredzone.acme4j.Certificate; import org.shredzone.acme4j.Order; +import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.challenge.Challenge; @@ -121,7 +123,10 @@ public class ClientTest { while (order.getStatus() != Status.VALID && attempts-- > 0) { // Did the order fail? if (order.getStatus() == Status.INVALID) { - LOG.error("Order has failed, reason: {}", order.getError()); + LOG.error("Order has failed, reason: {}", order.getError() + .map(Problem::toString) + .orElse("unknown") + ); throw new AcmeException("Order failed... Giving up."); } @@ -137,7 +142,7 @@ public class ClientTest { } // Get the certificate - Certificate certificate = order.getCertificate(); + Certificate certificate = order.getCertificate().orElseThrow(); LOG.info("Success! The certificate for domains {} has been generated!", domains); LOG.info("Certificate URL: {}", certificate.getLocation()); @@ -213,9 +218,9 @@ public class ClientTest { */ private Account findOrRegisterAccount(Session session, KeyPair accountKey) throws AcmeException { // Ask the user to accept the TOS, if server provides us with a link. - URI tos = session.getMetadata().getTermsOfService(); - if (tos != null) { - acceptAgreement(tos); + Optional tos = session.getMetadata().getTermsOfService(); + if (tos.isPresent()) { + acceptAgreement(tos.get()); } Account account = new AccountBuilder() @@ -272,7 +277,9 @@ public class ClientTest { while (challenge.getStatus() != Status.VALID && attempts-- > 0) { // Did the authorization fail? if (challenge.getStatus() == Status.INVALID) { - LOG.error("Challenge has failed, reason: {}", challenge.getError()); + LOG.error("Challenge has failed, reason: {}", challenge.getError() + .map(Problem::toString) + .orElse("unknown")); throw new AcmeException("Challenge failed... Giving up."); } @@ -313,10 +320,9 @@ public class ClientTest { */ public Challenge httpChallenge(Authorization auth) throws AcmeException { // Find a single http-01 challenge - Http01Challenge challenge = auth.findChallenge(Http01Challenge.class); - if (challenge == null) { - throw new AcmeException("Found no " + Http01Challenge.TYPE + " challenge, don't know what to do..."); - } + Http01Challenge challenge = auth.findChallenge(Http01Challenge.class) + .orElseThrow(() -> new AcmeException("Found no " + Http01Challenge.TYPE + + " challenge, don't know what to do...")); // Output the challenge, wait for acknowledge... LOG.info("Please create a file in your web server's base directory."); @@ -355,10 +361,10 @@ public class ClientTest { */ public Challenge dnsChallenge(Authorization auth) throws AcmeException { // Find a single dns-01 challenge - Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.TYPE); - if (challenge == null) { - throw new AcmeException("Found no " + Dns01Challenge.TYPE + " challenge, don't know what to do..."); - } + Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.TYPE) + .map(Dns01Challenge.class::cast) + .orElseThrow(() -> new AcmeException("Found no " + Dns01Challenge.TYPE + + " challenge, don't know what to do...")); // Output the challenge, wait for acknowledge... LOG.info("Please create a TXT record:"); diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderHttpIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderHttpIT.java index 1b574a9a..c585e59b 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderHttpIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderHttpIT.java @@ -23,6 +23,7 @@ import java.security.KeyPair; import org.junit.jupiter.api.Test; import org.shredzone.acme4j.AccountBuilder; import org.shredzone.acme4j.Authorization; +import org.shredzone.acme4j.Certificate; import org.shredzone.acme4j.Order; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Status; @@ -62,8 +63,7 @@ public class OrderHttpIT { var order = account.newOrder().domain(TEST_DOMAIN).create(); for (var auth : order.getAuthorizations()) { - var challenge = auth.findChallenge(Http01Challenge.class); - assertThat(challenge).isNotNull(); + var challenge = auth.findChallenge(Http01Challenge.class).orElseThrow(); client.httpAddToken(challenge.getToken(), challenge.getAuthorization()); @@ -93,9 +93,9 @@ public class OrderHttpIT { .conditionEvaluationListener(cond -> updateOrder(order)) .untilAsserted(() -> assertThat(order.getStatus()).isNotIn(Status.PENDING, Status.PROCESSING)); - var certificate = order.getCertificate(); - var cert = certificate.getCertificate(); - assertThat(cert).isNotNull(); + var cert = order.getCertificate() + .map(Certificate::getCertificate) + .orElseThrow(); assertThat(cert.getNotAfter()).isNotNull(); assertThat(cert.getNotBefore()).isNotNull(); assertThat(cert.getSubjectX500Principal().getName()).contains("CN=" + TEST_DOMAIN); diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java index 1b74de76..55fd6081 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java @@ -56,8 +56,7 @@ public class OrderIT extends PebbleITBase { orderCertificate(TEST_DOMAIN, auth -> { var client = getBammBammClient(); - var challenge = auth.findChallenge(Http01Challenge.class); - assertThat(challenge).isNotNull(); + var challenge = auth.findChallenge(Http01Challenge.class).orElseThrow(); client.httpAddToken(challenge.getToken(), challenge.getAuthorization()); @@ -75,8 +74,7 @@ public class OrderIT extends PebbleITBase { orderCertificate(TEST_DOMAIN, auth -> { var client = getBammBammClient(); - var challenge = auth.findChallenge(Dns01Challenge.class); - assertThat(challenge).isNotNull(); + var challenge = auth.findChallenge(Dns01Challenge.class).orElseThrow(); var challengeDomainName = Dns01Challenge.toRRName(auth.getIdentifier()); @@ -96,8 +94,7 @@ public class OrderIT extends PebbleITBase { orderCertificate(TEST_DOMAIN, auth -> { var client = getBammBammClient(); - var challenge = auth.findChallenge(TlsAlpn01Challenge.class); - assertThat(challenge).isNotNull(); + var challenge = auth.findChallenge(TlsAlpn01Challenge.class).orElseThrow(); client.tlsAlpnAddCertificate( auth.getIdentifier().getDomain(), @@ -117,8 +114,7 @@ public class OrderIT extends PebbleITBase { orderCertificate(TEST_DOMAIN, auth -> { var client = getBammBammClient(); - var challenge = auth.findChallenge(Http01Challenge.class); - assertThat(challenge).isNotNull(); + var challenge = auth.findChallenge(Http01Challenge.class).orElseThrow(); client.httpAddToken(challenge.getToken(), challenge.getAuthorization()); @@ -159,8 +155,8 @@ public class OrderIT extends PebbleITBase { .notBefore(notBefore) .notAfter(notAfter) .create(); - assertThat(order.getNotBefore()).isEqualTo(notBefore); - assertThat(order.getNotAfter()).isEqualTo(notAfter); + assertThat(order.getNotBefore().orElseThrow()).isEqualTo(notBefore); + assertThat(order.getNotAfter().orElseThrow()).isEqualTo(notAfter); assertThat(order.getStatus()).isEqualTo(Status.PENDING); for (var auth : order.getAuthorizations()) { @@ -199,7 +195,7 @@ public class OrderIT extends PebbleITBase { assertThat(order.getStatus()).isEqualTo(Status.VALID); - var certificate = order.getCertificate(); + var certificate = order.getCertificate().orElseThrow(); var cert = certificate.getCertificate(); assertThat(cert).isNotNull(); assertThat(cert.getNotBefore().toInstant()).isEqualTo(notBefore); diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java index 2bd23f97..2286ca22 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java @@ -25,6 +25,7 @@ import java.time.temporal.ChronoUnit; import org.bouncycastle.asn1.x509.GeneralName; import org.junit.jupiter.api.Test; import org.shredzone.acme4j.AccountBuilder; +import org.shredzone.acme4j.Certificate; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.challenge.Dns01Challenge; @@ -64,8 +65,8 @@ public class OrderWildcardIT extends PebbleITBase { .notBefore(notBefore) .notAfter(notAfter) .create(); - assertThat(order.getNotBefore()).isEqualTo(notBefore); - assertThat(order.getNotAfter()).isEqualTo(notAfter); + assertThat(order.getNotBefore().orElseThrow()).isEqualTo(notBefore); + assertThat(order.getNotAfter().orElseThrow()).isEqualTo(notAfter); assertThat(order.getStatus()).isEqualTo(Status.PENDING); for (var auth : order.getAuthorizations()) { @@ -76,8 +77,7 @@ public class OrderWildcardIT extends PebbleITBase { continue; } - var challenge = auth.findChallenge(Dns01Challenge.class); - assertThat(challenge).isNotNull(); + var challenge = auth.findChallenge(Dns01Challenge.class).orElseThrow(); var challengeDomainName = Dns01Challenge.toRRName(TEST_DOMAIN); @@ -112,8 +112,9 @@ public class OrderWildcardIT extends PebbleITBase { order.getStatus()).isNotIn(Status.PENDING, Status.PROCESSING)); - var certificate = order.getCertificate(); - var cert = certificate.getCertificate(); + var cert = order.getCertificate() + .map(Certificate::getCertificate) + .orElseThrow(); assertThat(cert).isNotNull(); assertThat(cert.getNotAfter()).isNotEqualTo(notBefore); assertThat(cert.getNotBefore()).isNotEqualTo(notAfter); diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/SessionIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/SessionIT.java index f5d554b7..34587d1e 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/SessionIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/SessionIT.java @@ -44,8 +44,9 @@ public class SessionIT extends PebbleITBase { var meta = session.getMetadata(); assertThat(meta).isNotNull(); - assertThat(meta.getTermsOfService()).isEqualTo(URI.create("data:text/plain,Do%20what%20thou%20wilt")); - assertThat(meta.getWebsite()).isNull(); + assertThat(meta.getTermsOfService().orElseThrow()) + .isEqualTo(URI.create("data:text/plain,Do%20what%20thou%20wilt")); + assertThat(meta.getWebsite()).isEmpty(); assertThat(meta.getCaaIdentities()).isEmpty(); assertThatJson(meta.getJSON().toString()).isEqualTo("{" + "'termsOfService': 'data:text/plain,Do%20what%20thou%20wilt'," diff --git a/src/doc/docs/migration.md b/src/doc/docs/migration.md index 5e524f6d..6223d0f6 100644 --- a/src/doc/docs/migration.md +++ b/src/doc/docs/migration.md @@ -4,6 +4,7 @@ This document will help you migrate your code to the latest _acme4j_ version. ## Migration to Version 3.0.0 +- All `@Nullable` return values have been reviewed. Collections may now be empty, but never `null`. Most of the other return values are now either `Optional`, or throwing an exception if more reasonable. If your code fails to compile because the return type has changed to `Optional`, you can simply add `.orElse(null)` to restore the old behavior. But often your code will reveal a better way to handle the former `null` pointer instead. - Starting with _acme4j_ v3, we will require the smallest Java SE LTS version that is still receiving premier support according to the [Oracle Java SE Support Roadmap](https://www.oracle.com/java/technologies/java-se-support-roadmap.html). At the moment of writing, these are Java 11 and Java 17, so _acme4j_ requires Java 11 starting from now. With the prospected release of Java 21 (LTS) in September 2023, we will start to require Java 17, and so on. If you still need Java 8, you can use _acme4j_ v2, which will receive bugfixes until September 2023. - Changed to `java.net.http` client. Due to limitations of the API, HTTP errors are only thrown with the error code, but not with the error message. If you checked the message in unit tests, be prepared that the error message might have changed. - acme4j now accepts HTTP gzip compression. It is enabled by default, but can be disabled in the `NetworkSettings` if it causes problems or impedes debugging.