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 List<Challenge> challenges;
private List<List<Challenge>> 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<Challenge> getChallenges() {
load();
return challenges;
}
@ -98,6 +103,7 @@ public class Authorization extends AcmeResource {
* Gets all combinations of challenges supported by the server.
*/
public List<List<Challenge>> getCombinations() {
load();
return combinations;
}
@ -136,7 +142,7 @@ public class Authorization extends AcmeResource {
* validation.
*/
public Collection<Challenge> findCombination(String... types) {
if (combinations == null) {
if (getCombinations() == null) {
return null;
}
@ -145,7 +151,7 @@ public class Authorization extends AcmeResource {
Collection<Challenge> result = null;
for (List<Challenge> combination : combinations) {
for (List<Challenge> 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;
}
}

View File

@ -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<URI> 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<Authorization> getAuthorizations() throws AcmeException {
LOG.debug("getAuthorizations");
load();
return new ResourceIterator<Authorization>(getSession(), "authorizations", authorizations) {
@Override
protected Authorization create(Session session, URI uri) {
@ -134,6 +142,7 @@ public class Registration extends AcmeResource {
*/
public Iterator<Certificate> getCertificates() throws AcmeException {
LOG.debug("getCertificates");
load();
return new ResourceIterator<Certificate>(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;
}
/**

View File

@ -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<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.
*/

View File

@ -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<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.
*/