From 9b86b88e4a508d73f4ec17ff344004625d37f52e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Thu, 21 Jul 2016 00:56:51 +0200 Subject: [PATCH] Lazily load current status of Authorization and Registration. --- .../org/shredzone/acme4j/Authorization.java | 27 ++++++- .../org/shredzone/acme4j/Registration.java | 26 +++++++ .../shredzone/acme4j/AuthorizationTest.java | 44 ++++++++++++ .../shredzone/acme4j/RegistrationTest.java | 71 +++++++++++++------ 4 files changed, 146 insertions(+), 22 deletions(-) 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 793b75de..91eeb780 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java @@ -48,6 +48,7 @@ public class Authorization extends AcmeResource { private Date expires; private List challenges; private List> combinations; + private boolean loaded = false; /** * Creates a new instance of {@link Authorization} and binds it to the {@link Session}. @@ -70,6 +71,7 @@ public class Authorization extends AcmeResource { * Gets the domain name to be authorized. */ public String getDomain() { + load(); return domain; } @@ -77,6 +79,7 @@ public class Authorization extends AcmeResource { * Gets the authorization status. */ public Status getStatus() { + load(); return status; } @@ -84,6 +87,7 @@ public class Authorization extends AcmeResource { * Gets the expiry date of the authorization, if set by the server. */ public Date getExpires() { + load(); return expires; } @@ -91,6 +95,7 @@ public class Authorization extends AcmeResource { * Gets a list of all challenges offered by the server. */ public List getChallenges() { + load(); return challenges; } @@ -98,6 +103,7 @@ public class Authorization extends AcmeResource { * Gets all combinations of challenges supported by the server. */ public List> getCombinations() { + load(); return combinations; } @@ -136,7 +142,7 @@ public class Authorization extends AcmeResource { * validation. */ public Collection findCombination(String... types) { - if (combinations == null) { + if (getCombinations() == null) { return null; } @@ -145,7 +151,7 @@ public class Authorization extends AcmeResource { Collection result = null; - for (List combination : combinations) { + for (List combination : getCombinations()) { combinationTypes.clear(); for (Challenge c : combination) { combinationTypes.add(c.getType()); @@ -212,6 +218,21 @@ public class Authorization extends AcmeResource { } } + /** + * Lazily updates the object's state when one of the getters is invoked. + */ + protected void load() { + if (!loaded) { + try { + update(); + } catch (AcmeRetryAfterException ex) { + // ignore... The object was still updated. + } catch (AcmeException ex) { + throw new AcmeProtocolException("Could not load lazily", ex); + } + } + } + /** * Sets the properties according to the given JSON data. * @@ -264,6 +285,8 @@ public class Authorization extends AcmeResource { cmb.add(cr); this.combinations = cmb; } + + loaded = true; } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java index 8a0737ef..cd45ee2d 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java @@ -36,6 +36,7 @@ import org.shredzone.acme4j.connector.ResourceIterator; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeProtocolException; +import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.SignatureUtils; import org.slf4j.Logger; @@ -55,6 +56,7 @@ public class Registration extends AcmeResource { private URI authorizations; private URI certificates; private Status status; + private boolean loaded = false; /** * Creates a new instance of {@link Registration} and binds it to the {@link Session}. @@ -83,6 +85,9 @@ public class Registration extends AcmeResource { * Returns the URI of the agreement document the user is required to accept. */ public URI getAgreement() { + if (agreement == null) { + load(); + } return agreement; } @@ -90,6 +95,7 @@ public class Registration extends AcmeResource { * List of contact addresses (emails, phone numbers etc). */ public List getContacts() { + load(); return Collections.unmodifiableList(contacts); } @@ -97,6 +103,7 @@ public class Registration extends AcmeResource { * Returns the current status of the registration. */ public Status getStatus() { + load(); return status; } @@ -113,6 +120,7 @@ public class Registration extends AcmeResource { */ public Iterator getAuthorizations() throws AcmeException { LOG.debug("getAuthorizations"); + load(); return new ResourceIterator(getSession(), "authorizations", authorizations) { @Override protected Authorization create(Session session, URI uri) { @@ -134,6 +142,7 @@ public class Registration extends AcmeResource { */ public Iterator getCertificates() throws AcmeException { LOG.debug("getCertificates"); + load(); return new ResourceIterator(getSession(), "certificates", certificates) { @Override protected Certificate create(Session session, URI uri) { @@ -311,6 +320,21 @@ public class Registration extends AcmeResource { } } + /** + * Lazily updates the object's state when one of the getters is invoked. + */ + protected void load() { + if (!loaded) { + try { + update(); + } catch (AcmeRetryAfterException ex) { + // ignore... The object was still updated. + } catch (AcmeException ex) { + throw new AcmeProtocolException("Could not load lazily", ex); + } + } + } + /** * Sets registration properties according to the given JSON data. * @@ -373,6 +397,8 @@ public class Registration extends AcmeResource { if (tos != null) { this.agreement = tos; } + + loaded = true; } /** 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 628e7e6a..e2b7c9b7 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java @@ -23,6 +23,7 @@ import java.net.URI; import java.util.Collection; import java.util.Date; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.shredzone.acme4j.challenge.Challenge; @@ -158,6 +159,49 @@ public class AuthorizationTest { provider.close(); } + /** + * Test lazy loading. + */ + @Test + public void testLazyLoading() throws Exception { + final AtomicBoolean requestWasSent = new AtomicBoolean(false); + + TestableConnectionProvider provider = new TestableConnectionProvider() { + @Override + public int sendRequest(URI uri) { + requestWasSent.set(true); + assertThat(uri, is(locationUri)); + return HttpURLConnection.HTTP_OK; + } + + @Override + public Map readJsonResponse() { + return getJsonAsMap("updateAuthorizationResponse"); + } + }; + + Session session = provider.createSession(); + + provider.putTestChallenge("http-01", new Http01Challenge(session)); + provider.putTestChallenge("dns-01", new Dns01Challenge(session)); + + Authorization auth = new Authorization(session, locationUri); + + // Lazy loading + assertThat(requestWasSent.get(), is(false)); + assertThat(auth.getDomain(), is("example.org")); + assertThat(requestWasSent.get(), is(true)); + + // Subsequent queries do not trigger another load + requestWasSent.set(false); + assertThat(auth.getDomain(), is("example.org")); + assertThat(auth.getStatus(), is(Status.VALID)); + assertThat(auth.getExpires(), is(TimestampParser.parse("2016-01-02T17:12:40Z"))); + assertThat(requestWasSent.get(), is(false)); + + provider.close(); + } + /** * Test that authorization is properly updated, with retry-after header set. */ diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/RegistrationTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/RegistrationTest.java index 8c38f433..65c3137b 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/RegistrationTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/RegistrationTest.java @@ -21,13 +21,13 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; -import java.net.URISyntaxException; import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import org.jose4j.jws.JsonWebSignature; import org.jose4j.lang.JoseException; @@ -55,25 +55,6 @@ public class RegistrationTest { private URI agreementUri = URI.create("http://example.com/agreement.pdf"); private URI chainUri = URI.create("http://example.com/acme/chain"); - /** - * Test getters. Make sure object cannot be modified. - */ - @Test - public void testGetters() throws IOException, URISyntaxException { - Session session = TestUtils.session(); - Registration registration = new Registration(session, locationUri); - - assertThat(registration.getAgreement(), is(nullValue())); - assertThat(registration.getContacts(), is(empty())); - - try { - registration.getContacts().add(new URI("mailto:foo@example.com")); - fail("could modify contacts list"); - } catch (UnsupportedOperationException ex) { - // expected - } - } - /** * Test that a registration can be updated. */ @@ -154,6 +135,56 @@ public class RegistrationTest { provider.close(); } + /** + * Test lazy loading. + */ + @Test + public void testLazyLoading() throws AcmeException, IOException { + final AtomicBoolean requestWasSent = new AtomicBoolean(false); + + TestableConnectionProvider provider = new TestableConnectionProvider() { + @Override + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session) { + requestWasSent.set(true); + assertThat(uri, is(locationUri)); + return HttpURLConnection.HTTP_ACCEPTED; + } + + @Override + public Map readJsonResponse() { + return getJsonAsMap("updateRegistrationResponse"); + } + + @Override + public URI getLocation() { + return locationUri; + } + + @Override + public URI getLink(String relation) { + switch(relation) { + case "terms-of-service": return agreementUri; + default: return null; + } + } + }; + + Registration registration = new Registration(provider.createSession(), locationUri); + + // Lazy loading + assertThat(requestWasSent.get(), is(false)); + assertThat(registration.getAgreement(), is(agreementUri)); + assertThat(requestWasSent.get(), is(true)); + + // Subsequent queries do not trigger another load + requestWasSent.set(false); + assertThat(registration.getAgreement(), is(agreementUri)); + assertThat(registration.getStatus(), is(Status.GOOD)); + assertThat(requestWasSent.get(), is(false)); + + provider.close(); + } + /** * Test that a new {@link Authorization} can be created. */