Lazily load current status of Authorization and Registration.

pull/30/head
Richard Körber 2016-07-21 00:56:51 +02:00
parent 5049cd5ffd
commit 9b86b88e4a
4 changed files with 146 additions and 22 deletions

View File

@ -48,6 +48,7 @@ public class Authorization extends AcmeResource {
private Date expires; private Date expires;
private List<Challenge> challenges; private List<Challenge> challenges;
private List<List<Challenge>> combinations; private List<List<Challenge>> combinations;
private boolean loaded = false;
/** /**
* Creates a new instance of {@link Authorization} and binds it to the {@link Session}. * 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. * Gets the domain name to be authorized.
*/ */
public String getDomain() { public String getDomain() {
load();
return domain; return domain;
} }
@ -77,6 +79,7 @@ public class Authorization extends AcmeResource {
* Gets the authorization status. * Gets the authorization status.
*/ */
public Status getStatus() { public Status getStatus() {
load();
return status; return status;
} }
@ -84,6 +87,7 @@ public class Authorization extends AcmeResource {
* Gets the expiry date of the authorization, if set by the server. * Gets the expiry date of the authorization, if set by the server.
*/ */
public Date getExpires() { public Date getExpires() {
load();
return expires; return expires;
} }
@ -91,6 +95,7 @@ public class Authorization extends AcmeResource {
* Gets a list of all challenges offered by the server. * Gets a list of all challenges offered by the server.
*/ */
public List<Challenge> getChallenges() { public List<Challenge> getChallenges() {
load();
return challenges; return challenges;
} }
@ -98,6 +103,7 @@ public class Authorization extends AcmeResource {
* Gets all combinations of challenges supported by the server. * Gets all combinations of challenges supported by the server.
*/ */
public List<List<Challenge>> getCombinations() { public List<List<Challenge>> getCombinations() {
load();
return combinations; return combinations;
} }
@ -136,7 +142,7 @@ public class Authorization extends AcmeResource {
* validation. * validation.
*/ */
public Collection<Challenge> findCombination(String... types) { public Collection<Challenge> findCombination(String... types) {
if (combinations == null) { if (getCombinations() == null) {
return null; return null;
} }
@ -145,7 +151,7 @@ public class Authorization extends AcmeResource {
Collection<Challenge> result = null; Collection<Challenge> result = null;
for (List<Challenge> combination : combinations) { for (List<Challenge> combination : getCombinations()) {
combinationTypes.clear(); combinationTypes.clear();
for (Challenge c : combination) { for (Challenge c : combination) {
combinationTypes.add(c.getType()); 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. * Sets the properties according to the given JSON data.
* *
@ -264,6 +285,8 @@ public class Authorization extends AcmeResource {
cmb.add(cr); cmb.add(cr);
this.combinations = cmb; this.combinations = cmb;
} }
loaded = true;
} }
} }

View File

@ -36,6 +36,7 @@ import org.shredzone.acme4j.connector.ResourceIterator;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.SignatureUtils; import org.shredzone.acme4j.util.SignatureUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -55,6 +56,7 @@ public class Registration extends AcmeResource {
private URI authorizations; private URI authorizations;
private URI certificates; private URI certificates;
private Status status; private Status status;
private boolean loaded = false;
/** /**
* Creates a new instance of {@link Registration} and binds it to the {@link Session}. * 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. * Returns the URI of the agreement document the user is required to accept.
*/ */
public URI getAgreement() { public URI getAgreement() {
if (agreement == null) {
load();
}
return agreement; return agreement;
} }
@ -90,6 +95,7 @@ public class Registration extends AcmeResource {
* List of contact addresses (emails, phone numbers etc). * List of contact addresses (emails, phone numbers etc).
*/ */
public List<URI> getContacts() { public List<URI> getContacts() {
load();
return Collections.unmodifiableList(contacts); return Collections.unmodifiableList(contacts);
} }
@ -97,6 +103,7 @@ public class Registration extends AcmeResource {
* Returns the current status of the registration. * Returns the current status of the registration.
*/ */
public Status getStatus() { public Status getStatus() {
load();
return status; return status;
} }
@ -113,6 +120,7 @@ public class Registration extends AcmeResource {
*/ */
public Iterator<Authorization> getAuthorizations() throws AcmeException { public Iterator<Authorization> getAuthorizations() throws AcmeException {
LOG.debug("getAuthorizations"); LOG.debug("getAuthorizations");
load();
return new ResourceIterator<Authorization>(getSession(), "authorizations", authorizations) { return new ResourceIterator<Authorization>(getSession(), "authorizations", authorizations) {
@Override @Override
protected Authorization create(Session session, URI uri) { protected Authorization create(Session session, URI uri) {
@ -134,6 +142,7 @@ public class Registration extends AcmeResource {
*/ */
public Iterator<Certificate> getCertificates() throws AcmeException { public Iterator<Certificate> getCertificates() throws AcmeException {
LOG.debug("getCertificates"); LOG.debug("getCertificates");
load();
return new ResourceIterator<Certificate>(getSession(), "certificates", certificates) { return new ResourceIterator<Certificate>(getSession(), "certificates", certificates) {
@Override @Override
protected Certificate create(Session session, URI uri) { 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. * Sets registration properties according to the given JSON data.
* *
@ -373,6 +397,8 @@ public class Registration extends AcmeResource {
if (tos != null) { if (tos != null) {
this.agreement = tos; this.agreement = tos;
} }
loaded = true;
} }
/** /**

View File

@ -23,6 +23,7 @@ import java.net.URI;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Challenge;
@ -158,6 +159,49 @@ public class AuthorizationTest {
provider.close(); 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<String, Object> 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. * Test that authorization is properly updated, with retry-after header set.
*/ */

View File

@ -21,13 +21,13 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jose4j.jws.JsonWebSignature; import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
@ -55,25 +55,6 @@ public class RegistrationTest {
private URI agreementUri = URI.create("http://example.com/agreement.pdf"); private URI agreementUri = URI.create("http://example.com/agreement.pdf");
private URI chainUri = URI.create("http://example.com/acme/chain"); 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. * Test that a registration can be updated.
*/ */
@ -154,6 +135,56 @@ public class RegistrationTest {
provider.close(); 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<String, Object> 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. * Test that a new {@link Authorization} can be created.
*/ */