builder()//
46 | .name("www.denominator.io.")//
47 | .type("A")//
48 | .ttl(0xFFFFFFFF)//
49 | .add(AData.create("192.0.2.1")).build();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/core/src/main/java/denominator/profile/GeoResourceRecordSetApi.java:
--------------------------------------------------------------------------------
1 | package denominator.profile;
2 |
3 | import java.util.Collection;
4 | import java.util.Map;
5 |
6 | import denominator.QualifiedResourceRecordSetApi;
7 |
8 | /**
9 | * list operations are filtered to only include those which are geo record sets.
10 | */
11 | public interface GeoResourceRecordSetApi extends QualifiedResourceRecordSetApi {
12 |
13 | /**
14 | * retrieve an organized list of regions by region. It is often the case that the keys correlate
15 | * to UN or otherwise defined regions such as {@code North America}. However, this can also
16 | * include special case keys, such as {@code Fallback} and {@code Anonymous Proxy}. ex.
17 | *
18 | *
19 | * {
20 | * "States and Provinces: Canada": ["ab", "bc", "mb", "nb", "nl", "nt", "ns", "nu", "on",
21 | * "pe", "qc", "sk", "yt"],
22 | * "Fallback": ["@@"],
23 | * "Anonymous Proxy": ["A1"],
24 | * "Other Country": ["O1"],
25 | * "Satellite Provider": ["A2"]
26 | * }
27 | *
28 | *
29 | * Note
30 | *
31 | * The values of this are not guaranteed portable across providers.
32 | *
33 | * @since 1.3
34 | */
35 | Map> supportedRegions();
36 |
37 | static interface Factory extends QualifiedResourceRecordSetApi.Factory {
38 |
39 | /**
40 | * @return null if this feature isn't supported on the provider.
41 | */
42 | @Override
43 | GeoResourceRecordSetApi create(String id);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/core/src/main/java/denominator/ZoneApi.java:
--------------------------------------------------------------------------------
1 | package denominator;
2 |
3 | import java.util.Iterator;
4 |
5 | import denominator.model.Zone;
6 |
7 | public interface ZoneApi extends Iterable {
8 |
9 | /**
10 | * Iterates across all zones, returning their name and id (when present). Implementations are lazy
11 | * when possible.
12 | */
13 | @Override
14 | Iterator iterator();
15 |
16 | /**
17 | * Returns a potentially empty iterator of zones with the supplied {@link Zone#name()}. This can
18 | * only have multiple results when {@link Provider#supportsDuplicateZoneNames()}.
19 | *
20 | * @since 4.5
21 | */
22 | Iterator iterateByName(String name);
23 |
24 | /**
25 | * Creates or updates a zone with corresponding to {@link Zone#name()}.
26 | *
27 | *
When {@linkplain Provider#supportsDuplicateZoneNames()} and {@link Zone#id()} is set, the
28 | * corresponding zone will be updated. Otherwise, a new zone will be created.
29 | *
30 | * Example adding a zone {@code denominator.io.}:
31 | *
32 | *
33 | * zoneId = zoneApi.put(Zone.create(null, "denominator.io.", 86400, "nil@denominator.io");
34 | *
35 | *
36 | * @return the {@link Zone#id() id} of the new or affected zone.
37 | * @since 4.5
38 | */
39 | String put(Zone zone);
40 |
41 | /**
42 | * Deletes a zone by id idempotently. This does not error if the zone doesn't exist.
43 | *
44 | * @param id {@link Zone#id() id} of the zone.
45 | * @since 4.5
46 | */
47 | void delete(String id);
48 | }
49 |
--------------------------------------------------------------------------------
/clouddns/src/test/java/denominator/clouddns/LimitsReadableMockTest.java:
--------------------------------------------------------------------------------
1 | package denominator.clouddns;
2 |
3 | import com.squareup.okhttp.mockwebserver.MockResponse;
4 |
5 | import org.junit.Rule;
6 | import org.junit.Test;
7 |
8 | import denominator.DNSApiManager;
9 |
10 | import static denominator.clouddns.RackspaceApisTest.limitsResponse;
11 | import static org.junit.Assert.assertFalse;
12 | import static org.junit.Assert.assertTrue;
13 |
14 | public class LimitsReadableMockTest {
15 |
16 | @Rule
17 | public final MockCloudDNSServer server = new MockCloudDNSServer();
18 |
19 | @Test
20 | public void implicitlyStartsSessionWhichIsReusedForLaterRequests() throws Exception {
21 | server.enqueueAuthResponse();
22 | server.enqueue(new MockResponse().setBody("{ \"domains\": [] }"));
23 | server.enqueue(new MockResponse().setBody(limitsResponse));
24 | server.enqueue(new MockResponse().setBody(limitsResponse));
25 |
26 | DNSApiManager api = server.connect();
27 | assertTrue(api.checkConnection());
28 | assertTrue(api.checkConnection());
29 |
30 | server.assertAuthRequest();
31 | server.assertRequest().hasPath("/v1.0/123123/limits");
32 | server.assertRequest().hasPath("/v1.0/123123/limits");
33 | }
34 |
35 | @Test
36 | public void singleRequestOnFailure() throws Exception {
37 | server.enqueueAuthResponse();
38 | server.enqueue(new MockResponse().setResponseCode(401));
39 |
40 | DNSApiManager api = server.connect();
41 | assertFalse(api.checkConnection());
42 |
43 | server.assertAuthRequest();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/example-android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
18 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/example-daemon/src/main/java/denominator/denominatord/JsonCodec.java:
--------------------------------------------------------------------------------
1 | package denominator.denominatord;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import com.google.gson.stream.JsonWriter;
6 |
7 | import com.squareup.okhttp.mockwebserver.MockResponse;
8 | import com.squareup.okhttp.mockwebserver.RecordedRequest;
9 |
10 | import java.io.IOException;
11 | import java.io.StringWriter;
12 | import java.util.Iterator;
13 |
14 | class JsonCodec {
15 |
16 | private final Gson json;
17 |
18 | JsonCodec() {
19 | this.json = new GsonBuilder().setPrettyPrinting().create();
20 | }
21 |
22 | T readJson(RecordedRequest request, Class clazz) {
23 | return json.fromJson(request.getUtf8Body(), clazz);
24 | }
25 |
26 | MockResponse toJsonArray(Iterator elements) {
27 | elements.hasNext(); // defensive to make certain error cases eager.
28 |
29 | StringWriter out = new StringWriter(); // MWS cannot do streaming responses.
30 | try {
31 | JsonWriter writer = new JsonWriter(out);
32 | writer.setIndent(" ");
33 | writer.beginArray();
34 | while (elements.hasNext()) {
35 | Object next = elements.next();
36 | json.toJson(next, next.getClass(), writer);
37 | }
38 | writer.endArray();
39 | writer.flush();
40 | } catch (IOException e) {
41 | throw new RuntimeException(e);
42 | }
43 |
44 | return new MockResponse().setResponseCode(200)
45 | .addHeader("Content-Type", "application/json")
46 | .setBody(out.toString() + "\n"); // curl nice
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/core/src/test/java/denominator/CheckConnectionLiveTest.java:
--------------------------------------------------------------------------------
1 | package denominator;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.junit.runners.Parameterized;
6 |
7 | import java.util.Collection;
8 | import java.util.LinkedHashMap;
9 | import java.util.Map;
10 |
11 | import denominator.Credentials.MapCredentials;
12 |
13 | import static denominator.CredentialsConfiguration.credentials;
14 | import static org.junit.Assert.assertFalse;
15 | import static org.junit.Assert.assertTrue;
16 | import static org.junit.Assume.assumeFalse;
17 |
18 | @RunWith(Live.class)
19 | public class CheckConnectionLiveTest {
20 |
21 | @Parameterized.Parameter
22 | public DNSApiManager manager;
23 |
24 | @Test
25 | public void success() {
26 | assertTrue(manager.checkConnection());
27 | }
28 |
29 | @Test
30 | public void failGracefullyOnIncorrectCredentials() {
31 | assumeFalse("This test only applies to providers that authenticate",
32 | manager.provider().credentialTypeToParameterNames().isEmpty());
33 |
34 | Collection
35 | parameters =
36 | manager.provider().credentialTypeToParameterNames().values().iterator().next();
37 |
38 | Map creds = new LinkedHashMap(parameters.size());
39 | for (String parameter : parameters) {
40 | creds.put(parameter, "foo");
41 | }
42 |
43 | DNSApiManager
44 | incorrectCredentials =
45 | Denominator.create(manager.provider(), credentials(MapCredentials.from(creds)));
46 | assertFalse(incorrectCredentials.checkConnection());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/route53/src/main/java/denominator/route53/ListHostedZonesResponseHandler.java:
--------------------------------------------------------------------------------
1 | package denominator.route53;
2 |
3 | import org.xml.sax.Attributes;
4 | import org.xml.sax.helpers.DefaultHandler;
5 |
6 | import denominator.route53.Route53.HostedZone;
7 | import denominator.route53.Route53.HostedZoneList;
8 | import feign.sax.SAXDecoder.ContentHandlerWithResult;
9 |
10 | /**
11 | * See docs
13 | */
14 | class ListHostedZonesResponseHandler extends DefaultHandler
15 | implements ContentHandlerWithResult {
16 |
17 | private final StringBuilder currentText = new StringBuilder();
18 | private final HostedZoneList zones = new HostedZoneList();
19 | private HostedZone zone = new HostedZone();
20 |
21 | @Override
22 | public HostedZoneList result() {
23 | return zones;
24 | }
25 |
26 | @Override
27 | public void endElement(String uri, String name, String qName) {
28 | if (qName.equals("Name")) {
29 | zone.name = currentText.toString().trim();
30 | } else if (qName.equals("Id")) {
31 | zone.id = currentText.toString().trim().replace("/hostedzone/", "");
32 | } else if (qName.equals("HostedZone")) {
33 | zones.add(zone);
34 | zone = new HostedZone();
35 | } else if (qName.equals("NextMarker")) {
36 | zones.next = currentText.toString().trim();
37 | }
38 | currentText.setLength(0);
39 | }
40 |
41 | @Override
42 | public void characters(char ch[], int start, int length) {
43 | currentText.append(ch, start, length);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/core/src/main/java/denominator/DNSApiManager.java:
--------------------------------------------------------------------------------
1 | package denominator;
2 |
3 | import java.io.Closeable;
4 | import java.io.IOException;
5 |
6 | import javax.inject.Inject;
7 |
8 | /**
9 | * represents the connection between a {@link DNSApi} interface and the {@link Provider} that
10 | * implements it.
11 | */
12 | public class DNSApiManager implements Closeable {
13 |
14 | private final Provider provider;
15 | private final DNSApi api;
16 | private final CheckConnection checkConnection;
17 | private final Closeable closer;
18 |
19 | @Inject
20 | DNSApiManager(Provider provider, DNSApi api, CheckConnection checkConnection, Closeable closer) {
21 | this.provider = provider;
22 | this.api = api;
23 | this.checkConnection = checkConnection;
24 | this.closer = closer;
25 | }
26 |
27 | /**
28 | * the currently configured {@link DNSApi}
29 | */
30 | public DNSApi api() {
31 | return api;
32 | }
33 |
34 | /**
35 | * Get the provider associated with this instance
36 | */
37 | public Provider provider() {
38 | return provider;
39 | }
40 |
41 | /**
42 | * Returns true, if api commands are likely to succeed.
43 | *
44 | * @see CheckConnection
45 | */
46 | public boolean checkConnection() {
47 | return checkConnection.ok();
48 | }
49 |
50 | /**
51 | * closes resources associated with the connections, such as thread pools or open files.
52 | */
53 | @Override
54 | public void close() throws IOException {
55 | closer.close();
56 | }
57 |
58 | @Override
59 | public String toString() {
60 | return provider.toString();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/clouddns/src/test/java/denominator/clouddns/CloudDNSConnection.java:
--------------------------------------------------------------------------------
1 | package denominator.clouddns;
2 |
3 | import javax.inject.Singleton;
4 |
5 | import dagger.Module;
6 | import dagger.Provides;
7 | import denominator.DNSApiManager;
8 | import denominator.Denominator;
9 | import feign.Logger;
10 |
11 | import static denominator.CredentialsConfiguration.credentials;
12 | import static feign.Util.emptyToNull;
13 | import static java.lang.System.getProperty;
14 |
15 | public class CloudDNSConnection {
16 |
17 | final DNSApiManager manager;
18 | final String mutableZone;
19 |
20 | CloudDNSConnection() {
21 | String username = emptyToNull(getProperty("clouddns.username"));
22 | String apiKey = emptyToNull(getProperty("clouddns.apiKey"));
23 | if (username != null && apiKey != null) {
24 | manager = create(username, apiKey);
25 | } else {
26 | manager = null;
27 | }
28 | mutableZone = emptyToNull(getProperty("clouddns.zone"));
29 | }
30 |
31 | static DNSApiManager create(String username, String apiKey) {
32 | CloudDNSProvider provider = new CloudDNSProvider(emptyToNull(getProperty("clouddns.url")));
33 | return Denominator.create(provider, credentials(username, apiKey), new Overrides());
34 | }
35 |
36 | @Module(overrides = true, library = true)
37 | static class Overrides {
38 |
39 | @Provides
40 | @Singleton
41 | Logger.Level provideLevel() {
42 | return Logger.Level.FULL;
43 | }
44 |
45 | @Provides
46 | @Singleton
47 | Logger provideLogger() {
48 | return new Logger.JavaLogger().appendToFile("build/http-wire.log");
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/model/src/test/java/denominator/assertj/ZoneAssert.java:
--------------------------------------------------------------------------------
1 | package denominator.assertj;
2 |
3 | import org.assertj.core.api.AbstractAssert;
4 | import org.assertj.core.internal.Objects;
5 | import org.assertj.core.internal.Strings;
6 |
7 | import denominator.model.Zone;
8 |
9 | public class ZoneAssert extends AbstractAssert {
10 |
11 | Objects objects = Objects.instance();
12 | Strings strings = Strings.instance();
13 |
14 | public ZoneAssert(Zone actual) {
15 | super(actual, ZoneAssert.class);
16 | }
17 |
18 | public ZoneAssert hasName(String expected) {
19 | isNotNull();
20 | objects.assertEqual(info, actual.name(), expected);
21 | return this;
22 | }
23 |
24 | public ZoneAssert hasId(String expected) {
25 | isNotNull();
26 | objects.assertEqual(info, actual.id(), expected);
27 | return this;
28 | }
29 |
30 | /**
31 | * Tolerates differences when the actual email ends with a trailing dot or when the first
32 | * {@literal @} with a dot.
33 | */
34 | public ZoneAssert hasEmail(String expected) {
35 | isNotNull();
36 | String actualEmail = actual.email();
37 | if (actualEmail.endsWith(".")) {
38 | actualEmail = actualEmail.substring(0, actualEmail.length() - 1);
39 | }
40 | if (actualEmail.indexOf('@') == -1) {
41 | actualEmail = actualEmail.replaceFirst("\\.", "@");
42 | }
43 | strings.assertStartsWith(info, actualEmail, expected);
44 | return this;
45 | }
46 |
47 | public ZoneAssert hasTtl(Integer expected) {
48 | isNotNull();
49 | objects.assertEqual(info, actual.ttl(), expected);
50 | return this;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/model/src/test/java/denominator/common/PreconditionsTest.java:
--------------------------------------------------------------------------------
1 | package denominator.common;
2 |
3 | import org.junit.Rule;
4 | import org.junit.Test;
5 | import org.junit.rules.ExpectedException;
6 |
7 | import static denominator.common.Preconditions.checkArgument;
8 | import static denominator.common.Preconditions.checkNotNull;
9 | import static denominator.common.Preconditions.checkState;
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | public class PreconditionsTest {
13 |
14 | @Rule
15 | public final ExpectedException thrown = ExpectedException.none();
16 |
17 | @Test
18 | public void checkArgumentFormatted() {
19 | thrown.expect(IllegalArgumentException.class);
20 | thrown.expectMessage("should be foo");
21 |
22 | checkArgument(false, "should be %s", "foo");
23 | }
24 |
25 | @Test
26 | public void checkArgumentPass() {
27 | checkArgument(true, "should be %s", "foo");
28 | }
29 |
30 | @Test
31 | public void checkStateFormatted() {
32 | thrown.expect(IllegalStateException.class);
33 | thrown.expectMessage("should be foo");
34 |
35 | checkState(false, "should be %s", "foo");
36 | }
37 |
38 | @Test
39 | public void checkStatePass() {
40 | checkState(true, "should be %s", "foo");
41 | }
42 |
43 | @Test
44 | public void checkNotNullFormatted() {
45 | thrown.expect(NullPointerException.class);
46 | thrown.expectMessage("should be foo");
47 |
48 | checkNotNull(null, "should be %s", "foo");
49 | }
50 |
51 | @Test
52 | public void checkNotNullPass() {
53 | assertThat(checkNotNull("foo", "should be %s", "foo")).isEqualTo("foo");
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ultradns/src/test/java/denominator/ultradns/UltraDNSConnection.java:
--------------------------------------------------------------------------------
1 | package denominator.ultradns;
2 |
3 | import javax.inject.Singleton;
4 |
5 | import dagger.Module;
6 | import dagger.Provides;
7 | import denominator.DNSApiManager;
8 | import denominator.Denominator;
9 | import feign.Logger;
10 |
11 | import static denominator.CredentialsConfiguration.credentials;
12 | import static feign.Util.emptyToNull;
13 | import static java.lang.System.getProperty;
14 |
15 | public class UltraDNSConnection {
16 |
17 | final DNSApiManager manager;
18 | final String mutableZone;
19 |
20 | UltraDNSConnection() {
21 | String username = emptyToNull(getProperty("ultradns.username"));
22 | String password = emptyToNull(getProperty("ultradns.password"));
23 | if (username != null && password != null) {
24 | manager = create(username, password);
25 | } else {
26 | manager = null;
27 | }
28 | mutableZone = emptyToNull(getProperty("ultradns.zone"));
29 | }
30 |
31 | static DNSApiManager create(String username, String password) {
32 | UltraDNSProvider provider = new UltraDNSProvider(emptyToNull(getProperty("ultradns.url")));
33 | return Denominator.create(provider, credentials(username, password), new Overrides());
34 | }
35 |
36 | @Module(overrides = true, library = true)
37 | static class Overrides {
38 |
39 | @Provides
40 | @Singleton
41 | Logger.Level provideLevel() {
42 | return Logger.Level.FULL;
43 | }
44 |
45 | @Provides
46 | @Singleton
47 | Logger provideLogger() {
48 | return new Logger.JavaLogger().appendToFile("build/http-wire.log");
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/example-android/src/main/java/denominator/example/android/ui/PreferencesActivity.java:
--------------------------------------------------------------------------------
1 | package denominator.example.android.ui;
2 |
3 | import android.os.Bundle;
4 | import android.preference.EditTextPreference;
5 | import android.preference.PreferenceActivity;
6 | import android.preference.PreferenceScreen;
7 |
8 | import javax.inject.Inject;
9 |
10 | import denominator.Provider;
11 | import denominator.example.android.DenominatorApplication;
12 |
13 | public class PreferencesActivity extends PreferenceActivity {
14 |
15 | @Inject
16 | Provider provider;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | DenominatorApplication.class.cast(getApplication()).getApplicationGraph().inject(this);
22 | // to support api level 9
23 | this.setPreferenceScreen(createFromProvider());
24 | }
25 |
26 | private PreferenceScreen createFromProvider() {
27 | PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this);
28 | if (provider.credentialTypeToParameterNames().isEmpty()) {
29 | return root;
30 | }
31 | String credentialType = provider.credentialTypeToParameterNames().keySet().iterator().next();
32 | root.setTitle(credentialType + " credentials for provider " + provider.name());
33 | for (String parameter : provider.credentialTypeToParameterNames().get(credentialType)) {
34 | EditTextPreference cred = new EditTextPreference(this);
35 | cred.setKey(parameter);
36 | cred.setTitle(parameter);
37 | cred.setDialogTitle(parameter);
38 | root.addPreference(cred);
39 | }
40 | return root;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/model/src/main/java/denominator/model/rdata/MXData.java:
--------------------------------------------------------------------------------
1 | package denominator.model.rdata;
2 |
3 | import denominator.model.NumbersAreUnsignedIntsLinkedHashMap;
4 |
5 | import static denominator.common.Preconditions.checkArgument;
6 | import static denominator.common.Preconditions.checkNotNull;
7 |
8 | /**
9 | * Corresponds to the binary representation of the {@code MX} (Mail Exchange) RData
10 | *
11 | * Example
12 | *
13 | *
14 | * MXData rdata = MXData.create(1, "mail.jclouds.org");
15 | *
16 | *
17 | * See RFC 1035
18 | */
19 | public final class MXData extends NumbersAreUnsignedIntsLinkedHashMap {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | MXData(int preference, String exchange) {
24 | checkArgument(preference <= 0xFFFF, "preference must be 65535 or less");
25 | checkNotNull(exchange, "exchange");
26 | put("preference", preference);
27 | put("exchange", exchange);
28 | }
29 |
30 | public static MXData create(int preference, String exchange) {
31 | return new MXData(preference, exchange);
32 | }
33 |
34 | /**
35 | * specifies the preference given to this RR among others at the same owner. Lower values are
36 | * preferred.
37 | *
38 | * @since 1.3
39 | */
40 | public int preference() {
41 | return Integer.class.cast(get("preference"));
42 | }
43 |
44 | /**
45 | * domain-name which specifies a host willing to act as a mail exchange for the owner name.
46 | *
47 | * @since 1.3
48 | */
49 | public String exchange() {
50 | return get("exchange").toString();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/route53/src/test/java/denominator/route53/HostedZonesReadableMockTest.java:
--------------------------------------------------------------------------------
1 | package denominator.route53;
2 |
3 | import com.squareup.okhttp.mockwebserver.MockResponse;
4 |
5 | import org.junit.Rule;
6 | import org.junit.Test;
7 |
8 | import denominator.DNSApiManager;
9 |
10 | import static org.junit.Assert.assertFalse;
11 | import static org.junit.Assert.assertTrue;
12 |
13 | public class HostedZonesReadableMockTest {
14 |
15 | @Rule
16 | public MockRoute53Server server = new MockRoute53Server();
17 |
18 | @Test
19 | public void singleRequestOnSuccess() throws Exception {
20 | server.enqueue(new MockResponse().setBody(" "));
21 |
22 | DNSApiManager api = server.connect();
23 | assertTrue(api.checkConnection());
24 |
25 | server.assertRequest()
26 | .hasMethod("GET")
27 | .hasPath("/2012-12-12/hostedzone");
28 | }
29 |
30 | @Test
31 | public void singleRequestOnFailure() throws Exception {
32 | server.enqueue(new MockResponse().setResponseCode(403).setBody(
33 | "\n"
34 | + " \n"
35 | + " Sender \n"
36 | + " InvalidClientTokenId\n"
37 | + " The security token included in the request is invalid \n"
38 | + " \n"
39 | + " d3801bc8-f70d-11e2-8a6e-435ba83aa63f \n"
40 | + " "));
41 |
42 | DNSApiManager api = server.connect();
43 | assertFalse(api.checkConnection());
44 |
45 | server.assertRequest()
46 | .hasMethod("GET")
47 | .hasPath("/2012-12-12/hostedzone");
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/designate/src/main/java/denominator/designate/DesignateZoneApi.java:
--------------------------------------------------------------------------------
1 | package denominator.designate;
2 |
3 | import java.util.Iterator;
4 |
5 | import javax.inject.Inject;
6 |
7 | import denominator.model.Zone;
8 | import feign.FeignException;
9 |
10 | import static denominator.common.Util.filter;
11 | import static denominator.model.Zones.nameEqualTo;
12 |
13 | class DesignateZoneApi implements denominator.ZoneApi {
14 |
15 | private final Designate api;
16 |
17 | @Inject
18 | DesignateZoneApi(Designate api) {
19 | this.api = api;
20 | }
21 |
22 | @Override
23 | public Iterator iterator() {
24 | return api.domains().iterator();
25 | }
26 |
27 | /**
28 | * Designate V1 does not have a filter by name api.
29 | */
30 | @Override
31 | public Iterator iterateByName(String name) {
32 | return filter(iterator(), nameEqualTo(name));
33 | }
34 |
35 | @Override
36 | public String put(Zone zone) {
37 | if (zone.id() != null) {
38 | return api.updateDomain(zone.id(), zone.name(), zone.email(), zone.ttl()).id();
39 | }
40 | try {
41 | return api.createDomain(zone.name(), zone.email(), zone.ttl()).id();
42 | } catch (FeignException e) {
43 | if (e.getMessage().indexOf(" 409 ") == -1) {
44 | throw e;
45 | }
46 | String id = iterateByName(zone.name()).next().id();
47 | return api.updateDomain(id, zone.name(), zone.email(), zone.ttl()).id();
48 | }
49 | }
50 |
51 | @Override
52 | public void delete(String id) {
53 | try {
54 | api.deleteDomain(id);
55 | } catch (FeignException e) {
56 | if (e.getMessage().indexOf(" 404 ") == -1) {
57 | throw e;
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/model/src/main/java/denominator/model/profile/Weighted.java:
--------------------------------------------------------------------------------
1 | package denominator.model.profile;
2 |
3 | import static denominator.common.Preconditions.checkArgument;
4 |
5 | /**
6 | * Record sets with this profile are load balanced, differentiated by an integer {@code weight}.
7 | *
8 | * Example
9 | *
10 | *
11 | * Weighted profile = Weighted.create(2);
12 | *
13 | *
14 | * @since 1.3
15 | */
16 | public class Weighted {
17 |
18 | private final int weight;
19 |
20 | private Weighted(int weight) {
21 | checkArgument(weight >= 0, "weight must be positive");
22 | this.weight = weight;
23 | }
24 |
25 | /**
26 | * @param weight corresponds to {@link #weight()}
27 | */
28 | public static Weighted create(int weight) {
29 | return new Weighted(weight);
30 | }
31 |
32 | /**
33 | * {@code 0} to always serve the record. Otherwise, provider-specific range of positive numbers
34 | * which differentiate the load to send to this record set vs another.
35 | *
36 | * Note
37 | *
38 | * In some implementation, such as UltraDNS, only even number weights are supported! For highest
39 | * portability, use even numbers between {@code 0-100}
40 | */
41 | public int weight() {
42 | return weight;
43 | }
44 |
45 | @Override
46 | public boolean equals(Object obj) {
47 | return obj instanceof Weighted && weight() == ((Weighted) obj).weight();
48 | }
49 |
50 | @Override
51 | public int hashCode() {
52 | return 37 * 17 + weight();
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return new StringBuilder().append("Weighted [weight=").append(weight()).append("]").toString();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/model/src/main/java/denominator/model/profile/Geo.java:
--------------------------------------------------------------------------------
1 | package denominator.model.profile;
2 |
3 | import java.util.Collection;
4 | import java.util.Collections;
5 | import java.util.Map;
6 |
7 | import static denominator.common.Preconditions.checkNotNull;
8 |
9 | /**
10 | * Record sets with this profile are visible to the regions specified.
11 | *
12 | * Example
13 | *
14 | *
15 | * Geo profile = Geo.create(Map.of("United States (US)", Collection.of("Maryland")));
16 | *
17 | */
18 | public class Geo {
19 |
20 | private final Map> regions;
21 |
22 | private Geo(Map> regions) {
23 | this.regions = Collections.unmodifiableMap(checkNotNull(regions, "regions"));
24 | }
25 |
26 | /**
27 | * @param regions corresponds to {@link #regions()}
28 | * @since 1.3
29 | */
30 | public static Geo create(Map> regions) {
31 | return new Geo(regions);
32 | }
33 |
34 | /**
35 | * a filtered view of {@code denominator.profile.GeoResourceRecordSetApi.supportedRegions()} ,
36 | * which describes the traffic desired for this profile.
37 | *
38 | * @since 1.3
39 | */
40 | public Map> regions() {
41 | return regions;
42 | }
43 |
44 | @Override
45 | public boolean equals(Object obj) {
46 | return obj instanceof Geo && regions().equals(((Geo) obj).regions());
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | return regions().hashCode();
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return new StringBuilder().append("Geo [regions=").append(regions()).append("]").toString();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/dynect/src/test/resources/recordsByName.json:
--------------------------------------------------------------------------------
1 | {"status": "success",
2 | "data": {
3 | "cname_records": [],
4 | "cert_records": [],
5 | "dname_records": [],
6 | "aaaa_records": [],
7 | "ipseckey_records": [],
8 | "loc_records": [],
9 | "ptr_records": [],
10 | "soa_records": [],
11 | "kx_records": [],
12 | "dnskey_records": [],
13 | "naptr_records": [],
14 | "rp_records": [],
15 | "mx_records": [],
16 | "key_records": [],
17 | "px_records": [],
18 | "ds_records": [],
19 | "sshfp_records": [],
20 | "ns_records": [],
21 | "dhcid_records": [],
22 | "srv_records": [],
23 | "nsap_records": [],
24 | "txt_records": [],
25 | "spf_records": [],
26 | "a_records": [{
27 | "zone": "denominator.io",
28 | "ttl": 3600,
29 | "fqdn": "www.denominator.io",
30 | "record_type": "A",
31 | "rdata": {
32 | "address": "192.0.2.1"
33 | },
34 | "record_id": 1
35 | }, {
36 | "zone": "denominator.io",
37 | "ttl": 3600,
38 | "fqdn": "www.denominator.io",
39 | "record_type": "A",
40 | "rdata": {
41 | "address": "198.51.100.1"
42 | },
43 | "record_id": 2
44 | }]
45 | },
46 | "job_id": 371281087,
47 | "msgs": [{
48 | "INFO": "get_tree: Here is your zone tree",
49 | "SOURCE": "BLL",
50 | "ERR_CD": null,
51 | "LVL": "INFO"
52 | }
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/designate/src/main/java/denominator/designate/DesignateFunctions.java:
--------------------------------------------------------------------------------
1 | package denominator.designate;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import denominator.designate.Designate.Record;
7 | import denominator.model.rdata.AAAAData;
8 | import denominator.model.rdata.AData;
9 | import denominator.model.rdata.CNAMEData;
10 | import denominator.model.rdata.MXData;
11 | import denominator.model.rdata.NSData;
12 | import denominator.model.rdata.SRVData;
13 | import denominator.model.rdata.TXTData;
14 |
15 | import static denominator.common.Util.split;
16 |
17 | public final class DesignateFunctions {
18 |
19 | private DesignateFunctions() { /* */
20 | }
21 |
22 | static Map toRDataMap(Record record) {
23 | if ("A".equals(record.type)) {
24 | return AData.create(record.data);
25 | } else if ("AAAA".equals(record.type)) {
26 | return AAAAData.create(record.data);
27 | } else if ("CNAME".equals(record.type)) {
28 | return CNAMEData.create(record.data);
29 | } else if ("MX".equals(record.type)) {
30 | return MXData.create(record.priority, record.data);
31 | } else if ("NS".equals(record.type)) {
32 | return NSData.create(record.data);
33 | } else if ("SRV".equals(record.type)) {
34 | List rdata = split(' ', record.data);
35 | return SRVData.builder()
36 | .priority(record.priority)
37 | .weight(Integer.valueOf(rdata.get(0)))
38 | .port(Integer.valueOf(rdata.get(1)))
39 | .target(rdata.get(2)).build();
40 | } else if ("TXT".equals(record.type)) {
41 | return TXTData.create(record.data);
42 | } else {
43 | throw new UnsupportedOperationException("record type not yet supported" + record);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/model/src/test/java/denominator/common/PeekingIteratorTest.java:
--------------------------------------------------------------------------------
1 | package denominator.common;
2 |
3 | import org.junit.Rule;
4 | import org.junit.Test;
5 | import org.junit.rules.ExpectedException;
6 |
7 | import java.util.NoSuchElementException;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 | import static org.junit.Assert.assertTrue;
11 |
12 | public class PeekingIteratorTest {
13 |
14 | @Rule
15 | public final ExpectedException thrown = ExpectedException.none();
16 |
17 | @Test
18 | public void unmodifiable() {
19 | thrown.expect(UnsupportedOperationException.class);
20 |
21 | PeekingIterator it = TrueThenDone.INSTANCE.iterator();
22 | assertThat(it).containsExactly(true);
23 | it.remove();
24 | }
25 |
26 | @Test
27 | public void next() {
28 | thrown.expect(NoSuchElementException.class);
29 |
30 | PeekingIterator it = TrueThenDone.INSTANCE.iterator();
31 | assertThat(it).containsExactly(true);
32 | it.next();
33 | }
34 |
35 | @Test
36 | public void peek() {
37 | thrown.expect(NoSuchElementException.class);
38 |
39 | PeekingIterator it = TrueThenDone.INSTANCE.iterator();
40 | assertTrue(it.peek());
41 | assertThat(it).containsExactly(true);
42 | it.peek();
43 | }
44 |
45 | enum TrueThenDone implements Iterable {
46 | INSTANCE;
47 |
48 | @Override
49 | public PeekingIterator iterator() {
50 | return new PeekingIterator() {
51 | boolean val = true;
52 |
53 | @Override
54 | public Boolean computeNext() {
55 | if (val) {
56 | val = false;
57 | return true;
58 | }
59 | return endOfData();
60 | }
61 | };
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/core/src/test/java/denominator/assertj/RecordedRequestAssert.java:
--------------------------------------------------------------------------------
1 | package denominator.assertj;
2 |
3 | import com.squareup.okhttp.mockwebserver.RecordedRequest;
4 |
5 | import org.assertj.core.api.AbstractAssert;
6 | import org.assertj.core.internal.Objects;
7 | import org.assertj.core.internal.Strings;
8 |
9 | public final class RecordedRequestAssert
10 | extends AbstractAssert {
11 |
12 | Strings strings = Strings.instance();
13 | Objects objects = Objects.instance();
14 |
15 | public RecordedRequestAssert(RecordedRequest actual) {
16 | super(actual, RecordedRequestAssert.class);
17 | }
18 |
19 | public RecordedRequestAssert hasMethod(String expected) {
20 | isNotNull();
21 | objects.assertEqual(info, actual.getMethod(), expected);
22 | return this;
23 | }
24 |
25 | public RecordedRequestAssert hasPath(String expected) {
26 | isNotNull();
27 | objects.assertEqual(info, actual.getPath(), expected);
28 | return this;
29 | }
30 |
31 | public RecordedRequestAssert hasBody(String utf8Expected) {
32 | isNotNull();
33 | objects.assertEqual(info, actual.getBody().readUtf8(), utf8Expected);
34 | return this;
35 | }
36 |
37 | public RecordedRequestAssert hasXMLBody(String utf8Expected) {
38 | isNotNull();
39 | hasHeaderContaining("Content-Type", "application/xml");
40 | strings.assertXmlEqualsTo(info, actual.getBody().readUtf8(), utf8Expected);
41 | return this;
42 | }
43 |
44 | public RecordedRequestAssert hasHeaderContaining(String name, String value) {
45 | isNotNull();
46 | if (actual.getHeader(name) == null) {
47 | failWithMessage("\nExpecting request to have header:<%s>", name);
48 | }
49 | strings.assertContains(info, actual.getHeader(name), value);
50 | return this;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ultradns/src/test/java/denominator/ultradns/UltraDNSErrorDecoderTest.java:
--------------------------------------------------------------------------------
1 | package denominator.ultradns;
2 |
3 | import org.junit.Rule;
4 | import org.junit.Test;
5 | import org.junit.rules.ExpectedException;
6 |
7 | import java.util.Collection;
8 | import java.util.Collections;
9 |
10 | import feign.FeignException;
11 | import feign.Response;
12 | import feign.RetryableException;
13 | import feign.codec.ErrorDecoder;
14 |
15 | import static denominator.ultradns.MockUltraDNSServer.FAULT_TEMPLATE;
16 | import static denominator.ultradns.UltraDNSException.SYSTEM_ERROR;
17 | import static feign.Util.UTF_8;
18 | import static java.lang.String.format;
19 |
20 | /**
21 | * Error decode tests not implicitly tested in {@linkplain denominator.ultradns.UltraDNSTest}.
22 | */
23 | public class UltraDNSErrorDecoderTest {
24 |
25 | @Rule
26 | public final ExpectedException thrown = ExpectedException.none();
27 |
28 | ErrorDecoder errors = new UltraDNSErrorDecoder(UltraDNSProvider.FeignModule.decoder());
29 |
30 | static Response errorResponse(String body) {
31 | return Response
32 | .create(500, "Server Error", Collections.>emptyMap(), body,
33 | UTF_8);
34 | }
35 |
36 | @Test
37 | public void noBody() throws Exception {
38 | thrown.expect(FeignException.class);
39 | thrown.expectMessage("status 500 reading UltraDNS#accountId()");
40 |
41 | throw errors.decode("UltraDNS#accountId()", errorResponse(null));
42 | }
43 |
44 | @Test
45 | public void systemError() throws Exception {
46 | thrown.expect(RetryableException.class);
47 | thrown.expectMessage("UltraDNS#networkStatus() failed with error 9999: System Error");
48 | throw errors.decode("UltraDNS#networkStatus()",
49 | errorResponse(format(FAULT_TEMPLATE, SYSTEM_ERROR, "System Error")));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ultradns/src/main/java/denominator/ultradns/UltraDNSFilters.java:
--------------------------------------------------------------------------------
1 | package denominator.ultradns;
2 |
3 | import java.io.Serializable;
4 |
5 | import denominator.common.Filter;
6 | import denominator.ultradns.UltraDNS.Record;
7 |
8 | import static denominator.common.Preconditions.checkNotNull;
9 |
10 | final class UltraDNSFilters {
11 |
12 | private UltraDNSFilters() {
13 | }
14 |
15 | public static Filter resourceTypeEqualTo(int typeValue) {
16 | return new ResourceTypeEqualToPredicate(typeValue);
17 | }
18 |
19 | public static Filter recordIdEqualTo(String id) {
20 | return new RecordIdEqualToPredicate(id);
21 | }
22 |
23 | /**
24 | * @see UltraDNSFilters#resourceTypeEqualTo(int)
25 | */
26 | private static class ResourceTypeEqualToPredicate implements Filter, Serializable {
27 |
28 | private static final long serialVersionUID = 0;
29 | private final int typeValue;
30 |
31 | private ResourceTypeEqualToPredicate(int typeValue) {
32 | this.typeValue = checkNotNull(typeValue, "typeValue");
33 | }
34 |
35 | @Override
36 | public boolean apply(Record in) {
37 | return typeValue == in.typeCode;
38 | }
39 |
40 | @Override
41 | public String toString() {
42 | return "ResourceTypeEqualTo(" + typeValue + ")";
43 | }
44 | }
45 |
46 | private static class RecordIdEqualToPredicate implements Filter, Serializable {
47 |
48 | private static final long serialVersionUID = 0;
49 | private final String id;
50 |
51 | private RecordIdEqualToPredicate(String id) {
52 | this.id = checkNotNull(id, "id");
53 | }
54 |
55 | @Override
56 | public boolean apply(Record in) {
57 | return id.equals(in.id);
58 | }
59 |
60 | @Override
61 | public String toString() {
62 | return "RecordIdEqualTo(" + id + ")";
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/designate/src/test/java/denominator/designate/LimitsReadableMockTest.java:
--------------------------------------------------------------------------------
1 | package denominator.designate;
2 |
3 | import com.squareup.okhttp.mockwebserver.MockResponse;
4 |
5 | import org.junit.Rule;
6 | import org.junit.Test;
7 |
8 | import denominator.DNSApiManager;
9 |
10 | import static org.junit.Assert.assertFalse;
11 | import static org.junit.Assert.assertTrue;
12 |
13 | public class LimitsReadableMockTest {
14 |
15 | @Rule
16 | public MockDesignateServer server = new MockDesignateServer();
17 |
18 | String limitsResponse = "{\n"
19 | + " \"limits\": {\n"
20 | + " \"absolute\": {\n"
21 | + " \"maxDomains\": 20,\n"
22 | + " \"maxDomainRecords\": 5000\n"
23 | + " }\n"
24 | + " }\n"
25 | + "}";
26 |
27 | @Test
28 | public void implicitlyStartsSessionWhichIsReusedForLaterRequests() throws Exception {
29 | server.enqueueAuthResponse();
30 | server.enqueue(new MockResponse().setBody("{ \"domains\": [] }"));
31 | server.enqueue(new MockResponse().setBody(limitsResponse));
32 | server.enqueue(new MockResponse().setBody(limitsResponse));
33 |
34 | DNSApiManager api = server.connect();
35 | assertTrue(api.checkConnection());
36 | assertTrue(api.checkConnection());
37 |
38 | server.assertAuthRequest();
39 | server.assertRequest().hasPath("/v1/limits");
40 | server.assertRequest().hasPath("/v1/limits");
41 | }
42 |
43 | @Test
44 | public void singleRequestOnFailure() throws Exception {
45 | server.enqueueAuthResponse();
46 | server.enqueue(new MockResponse().setResponseCode(401));
47 |
48 | DNSApiManager api = server.connect();
49 | assertFalse(api.checkConnection());
50 |
51 | server.assertAuthRequest();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/dynect/src/test/java/denominator/dynect/InvalidatableTokenProviderMockTest.java:
--------------------------------------------------------------------------------
1 | package denominator.dynect;
2 |
3 | import com.squareup.okhttp.mockwebserver.MockResponse;
4 |
5 | import org.junit.Rule;
6 | import org.junit.Test;
7 |
8 | import denominator.DNSApiManager;
9 |
10 | import static denominator.dynect.DynECTProviderDynamicUpdateMockTest.sessionValid;
11 | import static org.junit.Assert.assertFalse;
12 | import static org.junit.Assert.assertTrue;
13 |
14 | public class InvalidatableTokenProviderMockTest {
15 |
16 | @Rule
17 | public MockDynECTServer server = new MockDynECTServer();
18 |
19 | @Test
20 | public void successThenFailure() throws Exception {
21 | server.enqueueSessionResponse();
22 | server.enqueue(new MockResponse().setBody(sessionValid));
23 | server.enqueue(new MockResponse().setBody(sessionValid));
24 | server.enqueue(new MockResponse().setResponseCode(400).setBody(
25 | "{\"status\": \"failure\", \"data\": {}, \"job_id\": 427275274, \"msgs\": [{\"INFO\": \"login: Bad or expired credentials\", \"SOURCE\": \"BLL\", \"ERR_CD\": \"INVALID_DATA\", \"LVL\": \"ERROR\"}, {\"INFO\": \"login: There was a problem with your credentials\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}]}"));
26 |
27 | DNSApiManager api = server.connect();
28 |
29 | assertTrue(api.checkConnection());
30 | assertTrue(api.checkConnection());
31 | assertFalse(api.checkConnection());
32 |
33 | server.assertSessionRequest();
34 | server.assertRequest().hasMethod("GET").hasPath("/Session");
35 | server.assertRequest().hasMethod("GET").hasPath("/Session");
36 | }
37 |
38 | @Test
39 | public void singleRequestOnFailure() throws Exception {
40 | server.enqueue(new MockResponse().setResponseCode(401));
41 |
42 | assertFalse(server.connect().checkConnection());
43 |
44 | server.assertSessionRequest();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/clouddns/src/main/java/denominator/clouddns/GroupByRecordNameAndTypeIterator.java:
--------------------------------------------------------------------------------
1 | package denominator.clouddns;
2 |
3 | import java.util.Iterator;
4 | import java.util.Map;
5 |
6 | import denominator.clouddns.RackspaceApis.Record;
7 | import denominator.common.PeekingIterator;
8 | import denominator.model.ResourceRecordSet;
9 | import denominator.model.ResourceRecordSet.Builder;
10 |
11 | import static denominator.clouddns.CloudDNSFunctions.toRDataMap;
12 | import static denominator.common.Util.peekingIterator;
13 |
14 | class GroupByRecordNameAndTypeIterator implements Iterator> {
15 |
16 | private final PeekingIterator peekingIterator;
17 |
18 | public GroupByRecordNameAndTypeIterator(Iterator sortedIterator) {
19 | this.peekingIterator = peekingIterator(sortedIterator);
20 | }
21 |
22 | private static boolean nameAndTypeEquals(Record actual, Record expected) {
23 | return actual.name.equals(expected.name) && actual.type.equals(expected.type);
24 | }
25 |
26 | @Override
27 | public boolean hasNext() {
28 | return peekingIterator.hasNext();
29 | }
30 |
31 | @Override
32 | public ResourceRecordSet> next() {
33 | Record recordDetail = peekingIterator.next();
34 | Builder> builder = ResourceRecordSet.builder()
35 | .name(recordDetail.name)
36 | .type(recordDetail.type)
37 | .ttl(recordDetail.ttl)
38 | .add(toRDataMap(recordDetail));
39 | while (hasNext()) {
40 | Record next = peekingIterator.peek();
41 | if (next == null) {
42 | continue;
43 | }
44 | if (nameAndTypeEquals(next, recordDetail)) {
45 | builder.add(toRDataMap(peekingIterator.next()));
46 | } else {
47 | break;
48 | }
49 | }
50 | return builder.build();
51 | }
52 |
53 | @Override
54 | public void remove() {
55 | throw new UnsupportedOperationException();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/designate/src/main/java/denominator/designate/GroupByRecordNameAndTypeIterator.java:
--------------------------------------------------------------------------------
1 | package denominator.designate;
2 |
3 | import java.util.Iterator;
4 | import java.util.Map;
5 |
6 | import denominator.common.PeekingIterator;
7 | import denominator.designate.Designate.Record;
8 | import denominator.model.ResourceRecordSet;
9 | import denominator.model.ResourceRecordSet.Builder;
10 |
11 | import static denominator.common.Util.peekingIterator;
12 | import static denominator.designate.DesignateFunctions.toRDataMap;
13 |
14 | class GroupByRecordNameAndTypeIterator implements Iterator> {
15 |
16 | private final PeekingIterator peekingIterator;
17 |
18 | public GroupByRecordNameAndTypeIterator(Iterator sortedIterator) {
19 | this.peekingIterator = peekingIterator(sortedIterator);
20 | }
21 |
22 | private static boolean nameAndTypeEquals(Record actual, Record expected) {
23 | return actual.name.equals(expected.name) && actual.type.equals(expected.type);
24 | }
25 |
26 | @Override
27 | public boolean hasNext() {
28 | return peekingIterator.hasNext();
29 | }
30 |
31 | @Override
32 | public ResourceRecordSet> next() {
33 | Record recordDetail = peekingIterator.next();
34 | Builder> builder = ResourceRecordSet.builder()
35 | .name(recordDetail.name)
36 | .type(recordDetail.type)
37 | .ttl(recordDetail.ttl)
38 | .add(toRDataMap(recordDetail));
39 | while (hasNext()) {
40 | Record next = peekingIterator.peek();
41 | if (next == null) {
42 | continue;
43 | }
44 | if (nameAndTypeEquals(next, recordDetail)) {
45 | builder.add(toRDataMap(peekingIterator.next()));
46 | } else {
47 | break;
48 | }
49 | }
50 | return builder.build();
51 | }
52 |
53 | @Override
54 | public void remove() {
55 | throw new UnsupportedOperationException();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/example-daemon/src/main/java/denominator/denominatord/DenominatorDispatcher.java:
--------------------------------------------------------------------------------
1 | package denominator.denominatord;
2 |
3 | import com.squareup.okhttp.mockwebserver.Dispatcher;
4 | import com.squareup.okhttp.mockwebserver.MockResponse;
5 | import com.squareup.okhttp.mockwebserver.RecordedRequest;
6 |
7 | import denominator.DNSApiManager;
8 |
9 | import static denominator.denominatord.RecordSetDispatcher.RECORDSET_PATTERN;
10 |
11 | public class DenominatorDispatcher extends Dispatcher {
12 |
13 | private final DNSApiManager mgr;
14 | private final Dispatcher zones;
15 | private final Dispatcher recordSets;
16 |
17 | DenominatorDispatcher(DNSApiManager mgr, JsonCodec codec) {
18 | this.mgr = mgr;
19 | this.zones = new ZoneDispatcher(mgr.api().zones(), codec);
20 | this.recordSets = new RecordSetDispatcher(mgr, codec);
21 | }
22 |
23 | @Override
24 | public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
25 | try {
26 | if ("/healthcheck".equals(request.getPath())) {
27 | if (!request.getMethod().equals("GET")) {
28 | return new MockResponse().setResponseCode(405);
29 | }
30 | return new MockResponse().setResponseCode(mgr.checkConnection() ? 200 : 503);
31 | } else if (RECORDSET_PATTERN.matcher(request.getPath()).matches()) {
32 | return recordSets.dispatch(request);
33 | } else if (request.getPath() != null && request.getPath().startsWith("/zones")) {
34 | return zones.dispatch(request);
35 | } else {
36 | return new MockResponse().setResponseCode(404);
37 | }
38 | } catch (InterruptedException e) {
39 | throw e;
40 | } catch (RuntimeException e) {
41 | return new MockResponse().setResponseCode(e instanceof IllegalArgumentException ? 400 : 500)
42 | .addHeader("Content-Type", "text/plain")
43 | .setBody(e.getMessage() + "\n"); // curl nice
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/ultradns/src/main/java/denominator/ultradns/GroupByRecordNameAndTypeIterator.java:
--------------------------------------------------------------------------------
1 | package denominator.ultradns;
2 |
3 | import java.util.Iterator;
4 | import java.util.Map;
5 |
6 | import denominator.ResourceTypeToValue;
7 | import denominator.common.PeekingIterator;
8 | import denominator.model.ResourceRecordSet;
9 | import denominator.model.ResourceRecordSet.Builder;
10 | import denominator.ultradns.UltraDNS.Record;
11 |
12 | import static denominator.common.Util.peekingIterator;
13 | import static denominator.common.Util.toMap;
14 |
15 | class GroupByRecordNameAndTypeIterator implements Iterator> {
16 |
17 | private final PeekingIterator peekingIterator;
18 |
19 | public GroupByRecordNameAndTypeIterator(Iterator sortedIterator) {
20 | this.peekingIterator = peekingIterator(sortedIterator);
21 | }
22 |
23 | static boolean fqdnAndTypeEquals(Record actual, Record expected) {
24 | return actual.name.equals(expected.name) && actual.typeCode == expected.typeCode;
25 | }
26 |
27 | @Override
28 | public boolean hasNext() {
29 | return peekingIterator.hasNext();
30 | }
31 |
32 | @Override
33 | public ResourceRecordSet> next() {
34 | Record record = peekingIterator.next();
35 | String type = ResourceTypeToValue.lookup(record.typeCode);
36 | Builder> builder = ResourceRecordSet.builder()
37 | .name(record.name)
38 | .type(type)
39 | .ttl(record.ttl);
40 |
41 | builder.add(toMap(type, record.rdata));
42 |
43 | while (hasNext()) {
44 | Record next = peekingIterator.peek();
45 | if (fqdnAndTypeEquals(next, record)) {
46 | peekingIterator.next();
47 | builder.add(toMap(type, next.rdata));
48 | } else {
49 | break;
50 | }
51 | }
52 | return builder.build();
53 | }
54 |
55 | @Override
56 | public void remove() {
57 | throw new UnsupportedOperationException();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/route53/src/main/java/denominator/route53/AliasTarget.java:
--------------------------------------------------------------------------------
1 | package denominator.route53;
2 |
3 | import java.util.LinkedHashMap;
4 |
5 | import denominator.model.ResourceRecordSet;
6 |
7 | import static denominator.common.Preconditions.checkNotNull;
8 |
9 | /**
10 | * Reference to the addresses in a CloudFront distribution, Amazon S3 bucket, Elastic Load Balancing
11 | * load balancer, or Route 53 hosted zone. Only valid when {@link ResourceRecordSet#type()} is
12 | * {@code A} or {@code AAAA} .
13 | *
14 | * Example
15 | *
16 | *
17 | * AliasTarget rdata = AliasTarget.create("Z3DZXE0Q79N41H",
18 | * "nccp-cbp-frontend-12345678.us-west-2.elb.amazonaws.com.");
19 | *
20 | *
21 | * See API to create aliases
23 | *
24 | * @since 4.2
25 | */
26 | public final class AliasTarget extends LinkedHashMap {
27 |
28 | private static final long serialVersionUID = 1L;
29 |
30 | AliasTarget(String hostedZoneId, String dnsName) {
31 | put("HostedZoneId", checkNotNull(hostedZoneId, "HostedZoneId"));
32 | put("DNSName", checkNotNull(dnsName, "DNSName"));
33 | }
34 |
35 | public static AliasTarget create(String hostedZoneId, String dnsName) {
36 | return new AliasTarget(hostedZoneId, dnsName);
37 | }
38 |
39 | /**
40 | * Hosted zone ID for your CloudFront distribution, Amazon S3 bucket, Elastic Load Balancing load
41 | * balancer, or Route 53 hosted zone.
42 | */
43 | public String hostedZoneId() {
44 | return get("HostedZoneId").toString();
45 | }
46 |
47 | /**
48 | * DNS domain name for your CloudFront distribution, Amazon S3 bucket, Elastic Load Balancing load
49 | * balancer, or another resource record set in this hosted zone.
50 | */
51 | public String dnsName() {
52 | return get("DNSName").toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/model/src/main/java/denominator/common/PeekingIterator.java:
--------------------------------------------------------------------------------
1 | package denominator.common;
2 |
3 | import java.util.Iterator;
4 | import java.util.NoSuchElementException;
5 |
6 | /**
7 | * adapted from guava's {@code com.google.common.collect.AbstractIterator}.
8 | */
9 | public abstract class PeekingIterator implements Iterator {
10 |
11 | private PeekingIterator.State state = State.NOT_READY;
12 | private T next;
13 |
14 | /**
15 | * Constructor for use by subclasses.
16 | */
17 | protected PeekingIterator() {
18 | }
19 |
20 | protected abstract T computeNext();
21 |
22 | protected final T endOfData() {
23 | state = State.DONE;
24 | return null;
25 | }
26 |
27 | @Override
28 | public final boolean hasNext() {
29 | switch (state) {
30 | case DONE:
31 | return false;
32 | case READY:
33 | return true;
34 | default:
35 | }
36 | return tryToComputeNext();
37 | }
38 |
39 | private boolean tryToComputeNext() {
40 | next = computeNext();
41 | if (state != State.DONE) {
42 | state = State.READY;
43 | return true;
44 | }
45 | return false;
46 | }
47 |
48 | @Override
49 | public final T next() {
50 | if (!hasNext()) {
51 | throw new NoSuchElementException();
52 | }
53 | state = State.NOT_READY;
54 | return next;
55 | }
56 |
57 | public T peek() {
58 | if (!hasNext()) {
59 | throw new NoSuchElementException();
60 | }
61 | return next;
62 | }
63 |
64 | @Override
65 | public void remove() {
66 | throw new UnsupportedOperationException();
67 | }
68 |
69 | private enum State {
70 | /**
71 | * We have computed the next element and haven't returned it yet.
72 | */
73 | READY,
74 |
75 | /**
76 | * We haven't yet computed or have already returned the element.
77 | */
78 | NOT_READY,
79 |
80 | /**
81 | * We have reached the end of the data and are finished.
82 | */
83 | DONE,
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/core/src/test/java/denominator/profile/Regions.java:
--------------------------------------------------------------------------------
1 | package denominator.profile;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collection;
6 | import java.util.LinkedHashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | final class Regions {
11 |
12 | private final Map> all;
13 | private final String lastRegion;
14 | private final String lastTerritory;
15 | private final List exceptLastTerritory;
16 |
17 | Regions(Map> input) {
18 | all = input;
19 | String[] names = input.keySet().toArray(new String[input.size()]);
20 | lastRegion = names[names.length - 1];
21 | exceptLastTerritory = new ArrayList(input.get(lastRegion));
22 | lastTerritory = exceptLastTerritory.remove(exceptLastTerritory.size() - 1);
23 | }
24 |
25 | Map> allButLastTerritory() {
26 | Map> result = new LinkedHashMap>(all);
27 | if (exceptLastTerritory.isEmpty()) {
28 | result.remove(lastRegion);
29 | } else {
30 | result.put(lastRegion, exceptLastTerritory);
31 | }
32 | return result;
33 | }
34 |
35 | Map> onlyLastTerritory() {
36 | Map> result = new LinkedHashMap>(1);
37 | result.put(lastRegion, Arrays.asList(lastTerritory));
38 | return result;
39 | }
40 |
41 | Map> plusLastTerritory(Map> input) {
42 | Map> result = new LinkedHashMap>(input);
43 | if (result.containsKey(lastRegion)) {
44 | List moreTerritories = new ArrayList(result.get(lastRegion));
45 | moreTerritories.add(lastTerritory);
46 | result.put(lastRegion, moreTerritories);
47 | } else {
48 | result.put(lastRegion, Arrays.asList(lastTerritory));
49 | }
50 | return result;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/core/src/main/java/denominator/ReadOnlyResourceRecordSetApi.java:
--------------------------------------------------------------------------------
1 | package denominator;
2 |
3 | import java.util.Iterator;
4 |
5 | import denominator.model.ResourceRecordSet;
6 |
7 | public interface ReadOnlyResourceRecordSetApi extends Iterable> {
8 |
9 | /**
10 | * Iterates across all record sets in the zone. Implementations are lazy when possible.
11 | *
12 | * @return iterator which is lazy where possible
13 | * @throws IllegalArgumentException if the zone is not found.
14 | */
15 | @Override
16 | Iterator> iterator();
17 |
18 | /**
19 | * a listing of all resource record sets which have the specified name.
20 | *
21 | * @return iterator which is lazy where possible, empty if there are no records with that name.
22 | * @throws IllegalArgumentException if the zone is not found.
23 | * @since 1.3
24 | */
25 | Iterator> iterateByName(String name);
26 |
27 | /**
28 | * a listing of all resource record sets by name and type.
29 | *
30 | * @param name {@link ResourceRecordSet#name() name} of the rrset
31 | * @param type {@link ResourceRecordSet#type() type} of the rrset
32 | * @return iterator which is lazy where possible, empty if there are no records with that name.
33 | * @throws IllegalArgumentException if the zone is not found.
34 | * @since 1.3
35 | */
36 | Iterator> iterateByNameAndType(String name, String type);
37 |
38 | /**
39 | * retrieve a resource record set by name, type, and qualifier
40 | *
41 | * @param name {@link ResourceRecordSet#name() name} of the rrset
42 | * @param type {@link ResourceRecordSet#type() type} of the rrset
43 | * @param qualifier {@link ResourceRecordSet#qualifier() qualifier} of the rrset
44 | * @return null unless a resource record exists with the same {@code name}, {@code type}, and
45 | * {@code qualifier}
46 | * @throws IllegalArgumentException if the zone is not found.
47 | */
48 | ResourceRecordSet> getByNameTypeAndQualifier(String name, String type, String qualifier);
49 | }
50 |
--------------------------------------------------------------------------------
/example-daemon/src/main/java/denominator/denominatord/Query.java:
--------------------------------------------------------------------------------
1 | package denominator.denominatord;
2 |
3 | import java.net.URI;
4 | import java.util.List;
5 |
6 | import denominator.common.Util;
7 | import denominator.model.ResourceRecordSet;
8 |
9 | import static denominator.common.Preconditions.checkArgument;
10 |
11 | class Query {
12 |
13 | static Query from(String path) {
14 | String decoded = URI.create(path).getQuery();
15 | if (decoded == null) {
16 | return new Query(null, null, null);
17 | }
18 | String name = null;
19 | String type = null;
20 | String qualifier = null;
21 | for (String nameValueString : Util.split('&', decoded)) {
22 | List nameValue = Util.split('=', nameValueString);
23 | String queryName = nameValue.get(0);
24 | String queryValue = nameValue.size() > 1 ? nameValue.get(1) : null;
25 | if (queryName.equals("name")) {
26 | name = queryValue;
27 | } else if (queryName.equals("type")) {
28 | type = queryValue;
29 | } else if (queryName.equals("qualifier")) {
30 | qualifier = queryValue;
31 | }
32 | }
33 | return new Query(name, type, qualifier);
34 | }
35 |
36 | static Query from(ResourceRecordSet> recordSet) {
37 | return new Query(recordSet.name(), recordSet.type(), recordSet.qualifier());
38 | }
39 |
40 | final String name;
41 | final String type;
42 | final String qualifier;
43 |
44 | private Query(String name, String type, String qualifier) {
45 | this.name = name;
46 | this.type = type;
47 | this.qualifier = qualifier;
48 | if (qualifier != null) {
49 | checkArgument(type != null && name != null, "name and type query required with qualifier");
50 | } else if (type != null) {
51 | checkArgument(name != null, "name query required with type");
52 | }
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return new StringBuilder()
58 | .append("name=").append(name)
59 | .append(", type=").append(type)
60 | .append(", qualifier=").append(qualifier).toString();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/example-android/src/main/java/denominator/example/android/zone/ZoneListTaskService.java:
--------------------------------------------------------------------------------
1 | package denominator.example.android.zone;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 | import android.util.Log;
7 |
8 | import com.squareup.otto.Bus;
9 | import com.squareup.tape.TaskQueue;
10 |
11 | import java.util.Iterator;
12 |
13 | import javax.inject.Inject;
14 |
15 | import denominator.example.android.DenominatorApplication;
16 | import denominator.example.android.zone.ZoneList.Callback;
17 | import denominator.model.Zone;
18 |
19 | /**
20 | * This service guarantees that zone lists happen in the background and only once at a time.
21 | */
22 | public class ZoneListTaskService extends Service implements Callback {
23 |
24 | private static final String TAG = "Denominator:ZoneListTaskService";
25 |
26 | @Inject
27 | TaskQueue taskQueue;
28 | @Inject
29 | Bus bus;
30 |
31 | private boolean running;
32 |
33 | @Override
34 | public void onCreate() {
35 | super.onCreate();
36 | DenominatorApplication.class.cast(getApplication()).getApplicationGraph().inject(this);
37 | Log.i(TAG, " starting");
38 | }
39 |
40 | @Override
41 | public int onStartCommand(Intent intent, int flags, int startId) {
42 | executeNext();
43 | return START_STICKY;
44 | }
45 |
46 | private void executeNext() {
47 | // no duplicates
48 | if (running) {
49 | taskQueue.remove();
50 | }
51 | ZoneList task = taskQueue.peek();
52 | if (task != null) {
53 | running = true;
54 | task.execute(this);
55 | } else {
56 | Log.i(TAG, " stopping");
57 | stopSelf();
58 | }
59 | }
60 |
61 | @Override
62 | public void onSuccess(Iterator zones, long duration) {
63 | running = false;
64 | taskQueue.remove();
65 | bus.post(new ZoneList.SuccessEvent(zones, duration));
66 | executeNext();
67 | }
68 |
69 | @Override
70 | public void onFailure(Throwable t) {
71 | bus.post(t);
72 | }
73 |
74 | @Override
75 | public IBinder onBind(Intent intent) {
76 | return null;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/model/src/main/java/denominator/model/StringRecordBuilder.java:
--------------------------------------------------------------------------------
1 | package denominator.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collection;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import denominator.model.rdata.CNAMEData;
10 |
11 | import static denominator.common.Preconditions.checkNotNull;
12 |
13 | /**
14 | * Capable of building record sets where rdata types more easily expressed as Strings, such as
15 | * {@link CNAMEData}
16 | *
17 | * @param portable type of the rdata in the {@link ResourceRecordSet}
18 | */
19 | abstract class StringRecordBuilder> extends
20 | AbstractRecordSetBuilder> {
21 |
22 | private List records = new ArrayList();
23 |
24 | /**
25 | * adds a value to the builder.
26 | *
27 | * ex.
28 | *
29 | *
30 | * builder.add("192.0.2.1");
31 | *
32 | */
33 | public StringRecordBuilder add(String record) {
34 | this.records.add(apply(checkNotNull(record, "record")));
35 | return this;
36 | }
37 |
38 | /**
39 | * adds values to the builder
40 | *
41 | * ex.
42 | *
43 | *
44 | * builder.addAll("192.0.2.1", "192.0.2.2");
45 | *
46 | */
47 | public StringRecordBuilder addAll(String... records) {
48 | return addAll(Arrays.asList(checkNotNull(records, "records")));
49 | }
50 |
51 | /**
52 | * adds a value to the builder.
53 | *
54 | * ex.
55 | *
56 | *
57 | * builder.addAll("192.0.2.1", "192.0.2.2");
58 | *
59 | */
60 | public StringRecordBuilder addAll(Collection records) {
61 | for (String value : checkNotNull(records, "records")) {
62 | add(value);
63 | }
64 | return this;
65 | }
66 |
67 | @Override
68 | protected List records() {
69 | return records;
70 | }
71 |
72 | /**
73 | * Override to properly convert the input to a string-based RData value;
74 | */
75 | protected abstract D apply(String in);
76 | }
77 |
--------------------------------------------------------------------------------
/example-daemon/src/main/java/denominator/denominatord/ZoneDispatcher.java:
--------------------------------------------------------------------------------
1 | package denominator.denominatord;
2 |
3 |
4 | import com.squareup.okhttp.mockwebserver.Dispatcher;
5 | import com.squareup.okhttp.mockwebserver.MockResponse;
6 | import com.squareup.okhttp.mockwebserver.RecordedRequest;
7 |
8 | import java.util.logging.Logger;
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | import denominator.ZoneApi;
13 | import denominator.model.Zone;
14 |
15 | import static java.lang.String.format;
16 | import static java.lang.System.currentTimeMillis;
17 |
18 | public class ZoneDispatcher extends Dispatcher {
19 | private final Logger log = Logger.getLogger(Dispatcher.class.getName());
20 | private final ZoneApi api;
21 | private final JsonCodec codec;
22 |
23 | ZoneDispatcher(ZoneApi api, JsonCodec codec) {
24 | this.api = api;
25 | this.codec = codec;
26 | }
27 |
28 | @Override
29 | public MockResponse dispatch(RecordedRequest request) {
30 | if (request.getMethod().equals("GET")) {
31 | Query query = Query.from(request.getPath());
32 | if (query.name != null) {
33 | return codec.toJsonArray(api.iterateByName(query.name));
34 | }
35 | return codec.toJsonArray(api.iterator());
36 | } else if (request.getMethod().equals("PUT")) {
37 | Zone zone = codec.readJson(request, Zone.class);
38 | long s = currentTimeMillis();
39 | log.info(format("replacing zone %s", zone));
40 | String id = api.put(zone);
41 | log.info(format("replaced zone %s in %sms", zone, currentTimeMillis() - s));
42 | return new MockResponse().setResponseCode(201).addHeader("Location", "/zones/" + id);
43 | } else if (request.getMethod().equals("DELETE")) {
44 | String zoneId = request.getPath().replace("/zones/", "");
45 | long s = currentTimeMillis();
46 | log.info(format("deleting zone %s ", zoneId));
47 | api.delete(zoneId);
48 | log.info(format("deleted zone %s in %sms", zoneId, currentTimeMillis() - s));
49 | return new MockResponse().setResponseCode(204);
50 | } else {
51 | return new MockResponse().setResponseCode(405);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/ultradns/src/main/java/denominator/ultradns/UltraDNSException.java:
--------------------------------------------------------------------------------
1 | package denominator.ultradns;
2 |
3 | import feign.FeignException;
4 |
5 | class UltraDNSException extends FeignException {
6 |
7 | /**
8 | * System Error
9 | */
10 | static final int SYSTEM_ERROR = 9999;
11 | /**
12 | * Zone does not exist in the system.
13 | */
14 | static final int ZONE_NOT_FOUND = 1801;
15 | /**
16 | * Zone already exists in the system.
17 | */
18 | static final int ZONE_ALREADY_EXISTS = 1802;
19 | /**
20 | * No resource record with GUID found in the system.
21 | */
22 | static final int RESOURCE_RECORD_NOT_FOUND = 2103;
23 | /**
24 | * Resource record exists with the same name and type.
25 | */
26 | static final int RESOURCE_RECORD_ALREADY_EXISTS = 2111;
27 |
28 | // there are 51002 potential codes. These are the ones we are handling.
29 | /**
30 | * No Pool or Multiple pools of same type exists for the PoolName
31 | */
32 | static final int DIRECTIONALPOOL_NOT_FOUND = 2142;
33 | /**
34 | * Invalid zone name
35 | */
36 | static final int INVALID_ZONE_NAME = 2507;
37 | /**
38 | * Directional Pool Record does not exist in the system
39 | */
40 | static final int DIRECTIONALPOOL_RECORD_NOT_FOUND = 2705;
41 | /**
42 | * Pool does not exist in the system.
43 | */
44 | static final int POOL_NOT_FOUND = 2911;
45 | /**
46 | * Pool already created for the given rrGUID.
47 | */
48 | static final int POOL_ALREADY_EXISTS = 2912;
49 | /**
50 | * Group does not exist.
51 | */
52 | static final int GROUP_NOT_FOUND = 4003;
53 | /**
54 | * Directional feature not Enabled or Directional migration is not done.
55 | */
56 | static final int DIRECTIONAL_NOT_ENABLED = 4006;
57 | /**
58 | * Resource Record already exists.
59 | */
60 | static final int POOL_RECORD_ALREADY_EXISTS = 4009;
61 | private static final long serialVersionUID = 1L;
62 | private final int code;
63 |
64 | UltraDNSException(String message, int code) {
65 | super(message);
66 | this.code = code;
67 | }
68 |
69 | /**
70 | * The error code. ex {@code 1801}
71 | */
72 | public int code() {
73 | return code;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/core/src/main/java/denominator/mock/MockResourceRecordSetApi.java:
--------------------------------------------------------------------------------
1 | package denominator.mock;
2 |
3 | import java.util.Collection;
4 | import java.util.Iterator;
5 | import java.util.Map;
6 |
7 | import denominator.model.ResourceRecordSet;
8 |
9 | import static denominator.common.Preconditions.checkNotNull;
10 | import static denominator.common.Util.nextOrNull;
11 | import static denominator.model.ResourceRecordSets.alwaysVisible;
12 |
13 | final class MockResourceRecordSetApi implements denominator.ResourceRecordSetApi {
14 |
15 | private final MockAllProfileResourceRecordSetApi delegate;
16 |
17 | MockResourceRecordSetApi(Map>> data, String zoneName) {
18 | this.delegate = new MockAllProfileResourceRecordSetApi(data, zoneName, alwaysVisible());
19 | }
20 |
21 | @Override
22 | public Iterator> iterator() {
23 | return delegate.iterator();
24 | }
25 |
26 | @Override
27 | public Iterator> iterateByName(String name) {
28 | return delegate.iterateByName(name);
29 | }
30 |
31 | @Override
32 | public ResourceRecordSet> getByNameAndType(String name, String type) {
33 | return nextOrNull(delegate.iterateByNameAndType(name, type));
34 | }
35 |
36 | @Override
37 | public void put(ResourceRecordSet> rrset) {
38 | checkNotNull(rrset, "rrset was null");
39 | Collection> records = delegate.records();
40 | synchronized (records) {
41 | removeByNameAndType(records.iterator(), rrset.name(), rrset.type());
42 | records.add(rrset);
43 | }
44 | }
45 |
46 | private void removeByNameAndType(Iterator> i, String name, String type) {
47 | while (i.hasNext()) {
48 | ResourceRecordSet> test = i.next();
49 | if (test.name().equals(name) && test.type().equals(type) && test.qualifier() == null) {
50 | i.remove();
51 | }
52 | }
53 | }
54 |
55 | @Override
56 | public void deleteByNameAndType(String name, String type) {
57 | Collection> records = delegate.records();
58 | synchronized (records) {
59 | removeByNameAndType(records.iterator(), name, type);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/ultradns/src/main/java/denominator/ultradns/InvalidatableAccountIdSupplier.java:
--------------------------------------------------------------------------------
1 | package denominator.ultradns;
2 |
3 | import javax.inject.Inject;
4 | import javax.inject.Provider;
5 | import javax.inject.Singleton;
6 |
7 | import denominator.Credentials;
8 |
9 | /**
10 | * gets the last account id, expiring if the url or credentials changed
11 | */
12 | // similar to guava MemoizingSupplier
13 | @Singleton
14 | class InvalidatableAccountIdSupplier {
15 |
16 | private final denominator.Provider provider;
17 | private final UltraDNS api;
18 | private final Provider credentials;
19 | transient volatile String lastUrl;
20 | transient volatile int lastCredentialsHashCode;
21 | transient volatile boolean initialized;
22 | transient String value; // "value" does not need to be volatile; visibility piggy-backs on above
23 |
24 | @Inject
25 | InvalidatableAccountIdSupplier(denominator.Provider provider, UltraDNS api,
26 | javax.inject.Provider credentials) {
27 | this.provider = provider;
28 | this.api = api;
29 | this.credentials = credentials;
30 | this.lastUrl = provider.url(); // for toString
31 | }
32 |
33 | public void invalidate() {
34 | initialized = false;
35 | }
36 |
37 | public String get() {
38 | String currentUrl = provider.url();
39 | Credentials currentCreds = credentials.get();
40 |
41 | if (needsRefresh(currentUrl, currentCreds)) {
42 | synchronized (this) {
43 | if (needsRefresh(currentUrl, currentCreds)) {
44 | lastCredentialsHashCode = currentCreds.hashCode();
45 | lastUrl = currentUrl;
46 | String accountId = api.getAccountsListOfUser();
47 | value = accountId;
48 | initialized = true;
49 | return accountId;
50 | }
51 | }
52 | }
53 | return value;
54 | }
55 |
56 | private boolean needsRefresh(String currentUrl, Credentials currentCreds) {
57 | return !initialized || currentCreds.hashCode() != lastCredentialsHashCode || !currentUrl
58 | .equals(lastUrl);
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return "InvalidatableAccountIdSupplier(" + lastUrl + ")";
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/example-android/src/main/java/denominator/example/android/zone/ZoneList.java:
--------------------------------------------------------------------------------
1 | package denominator.example.android.zone;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.util.Log;
6 |
7 | import com.squareup.tape.Task;
8 |
9 | import java.util.Iterator;
10 |
11 | import javax.inject.Inject;
12 |
13 | import denominator.DNSApiManager;
14 | import denominator.model.Zone;
15 |
16 | public class ZoneList implements Task {
17 |
18 | private static final String TAG = "Denominator:ZoneList";
19 | private static final Handler MAIN_THREAD = new Handler(Looper.getMainLooper());
20 | private final DNSApiManager mgr;
21 |
22 | @Inject
23 | ZoneList(DNSApiManager mgr) {
24 | this.mgr = mgr;
25 | }
26 |
27 | @Override
28 | public void execute(final ZoneList.Callback callback) {
29 | new Thread(new Runnable() {
30 | @Override
31 | public void run() {
32 | Log.i(TAG, "Listing Zones in " + mgr.provider().name());
33 | try {
34 | long start = System.currentTimeMillis();
35 | final Iterator zones = mgr.api().zones().iterator();
36 | final long duration = System.currentTimeMillis() - start;
37 | Log.i(TAG, "success! " + mgr.provider().name());
38 | MAIN_THREAD.post(new Runnable() {
39 | @Override
40 | public void run() {
41 | callback.onSuccess(zones, duration);
42 | }
43 | });
44 | } catch (final RuntimeException e) {
45 | e.printStackTrace();
46 | MAIN_THREAD.post(new Runnable() {
47 | @Override
48 | public void run() {
49 | callback.onFailure(e);
50 | }
51 | });
52 | }
53 | }
54 | }).start();
55 | }
56 |
57 | public interface Callback {
58 |
59 | void onSuccess(Iterator zones, long duration);
60 |
61 | void onFailure(Throwable t);
62 | }
63 |
64 | public static class SuccessEvent {
65 |
66 | public final Iterator zones;
67 | public final long duration;
68 |
69 | SuccessEvent(Iterator zones, long duration) {
70 | this.zones = zones;
71 | this.duration = duration;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/model/src/main/java/denominator/model/AbstractRecordSetBuilder.java:
--------------------------------------------------------------------------------
1 | package denominator.model;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import denominator.model.profile.Geo;
7 | import denominator.model.profile.Weighted;
8 |
9 | /**
10 | * Capable of building record sets from rdata input types expressed as {@code E}
11 | *
12 | * @param input type of rdata
13 | * @param portable type of the rdata in the {@link ResourceRecordSet}
14 | */
15 | abstract class AbstractRecordSetBuilder, B extends AbstractRecordSetBuilder> {
16 |
17 | private String name;
18 | private String type;
19 | private String qualifier;
20 | private Integer ttl;
21 | private Geo geo;
22 | private Weighted weighted;
23 |
24 | /**
25 | * @see ResourceRecordSet#name()
26 | */
27 | @SuppressWarnings("unchecked")
28 | public B name(String name) {
29 | this.name = name;
30 | return (B) this;
31 | }
32 |
33 | /**
34 | * @see ResourceRecordSet#type()
35 | */
36 | @SuppressWarnings("unchecked")
37 | public B type(String type) {
38 | this.type = type;
39 | return (B) this;
40 | }
41 |
42 | /**
43 | * @see ResourceRecordSet#qualifier()
44 | */
45 | @SuppressWarnings("unchecked")
46 | public B qualifier(String qualifier) {
47 | this.qualifier = qualifier;
48 | return (B) this;
49 | }
50 |
51 | /**
52 | * @see ResourceRecordSet#ttl()
53 | */
54 | @SuppressWarnings("unchecked")
55 | public B ttl(Integer ttl) {
56 | this.ttl = ttl;
57 | return (B) this;
58 | }
59 |
60 | /**
61 | * @see ResourceRecordSet#geo()
62 | */
63 | @SuppressWarnings("unchecked")
64 | public B geo(Geo geo) {
65 | this.geo = geo;
66 | return (B) this;
67 | }
68 |
69 | /**
70 | * @see ResourceRecordSet#weighted()
71 | */
72 | @SuppressWarnings("unchecked")
73 | public B weighted(Weighted weighted) {
74 | this.weighted = weighted;
75 | return (B) this;
76 | }
77 |
78 | public ResourceRecordSet build() {
79 | return new ResourceRecordSet(name, type, qualifier, ttl, records(), geo, weighted);
80 | }
81 |
82 | /**
83 | * aggregate collected rdata values
84 | */
85 | protected abstract List records();
86 | }
87 |
--------------------------------------------------------------------------------
/core/src/test/java/denominator/DNSApiManagerFactory.java:
--------------------------------------------------------------------------------
1 | package denominator;
2 |
3 | import java.io.File;
4 | import java.util.ArrayList;
5 | import java.util.Collection;
6 | import java.util.LinkedHashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | import javax.inject.Singleton;
11 |
12 | import dagger.Module;
13 | import dagger.Provides;
14 | import feign.Logger;
15 |
16 | import static feign.Util.emptyToNull;
17 |
18 | public final class DNSApiManagerFactory {
19 |
20 | public static DNSApiManager create(denominator.Provider provider) {
21 | List modules = new ArrayList(2);
22 | modules.add(credentialsModule(provider));
23 | for (Class> inner : provider.getClass().getDeclaredClasses()) {
24 | if (inner.getSimpleName().equals("FeignModule")) {
25 | modules.add(new HttpLog());
26 | }
27 | }
28 | return Denominator.create(provider, modules.toArray());
29 | }
30 |
31 | /**
32 | * Looks for {@link denominator.Provider#credentialTypeToParameterNames() credential parameters}
33 | * in system properties as {@code ${provider.name}.${parameter}.
34 | */
35 | static Object credentialsModule(denominator.Provider provider) {
36 | Map credentials = new LinkedHashMap(3);
37 | for (Collection parameters : provider.credentialTypeToParameterNames().values()) {
38 | for (String parameter : parameters) {
39 | String systemProperty = provider.name() + "." + parameter;
40 | String value = emptyToNull(System.getProperty(systemProperty));
41 | if (value != null) {
42 | credentials.put(parameter, value);
43 | }
44 | }
45 | }
46 | return CredentialsConfiguration.credentials(Credentials.MapCredentials.from(credentials));
47 | }
48 |
49 | @Module(overrides = true, library = true)
50 | public static final class HttpLog {
51 |
52 | @Provides
53 | @Singleton
54 | Logger.Level provideLevel() {
55 | return Logger.Level.FULL;
56 | }
57 |
58 | @Provides
59 | @Singleton
60 | Logger provideLogger() {
61 | new File(System.getProperty("user.dir"), "build").mkdirs();
62 | return new Logger.JavaLogger().appendToFile("build/http-wire.log");
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/core/src/main/java/denominator/QualifiedResourceRecordSetApi.java:
--------------------------------------------------------------------------------
1 | package denominator;
2 |
3 | import denominator.model.ResourceRecordSet;
4 |
5 | /**
6 | * Write operations for {@link ResourceRecordSet record sets} who have a {@link
7 | * ResourceRecordSet#qualifier()}.
8 | *
9 | * @since 1.3
10 | */
11 | public interface QualifiedResourceRecordSetApi extends ReadOnlyResourceRecordSetApi {
12 |
13 | /**
14 | * Idempotently replaces any existing records with {@link ResourceRecordSet#name() name}, {@link
15 | * ResourceRecordSet#type() type}, and {@link ResourceRecordSet#qualifier() qualifier}
16 | * corresponding to {@code rrset}. If no records exist, they will be added.
17 | *
18 | * Example of replacing the {@code A} record set for {@code www.denominator.io.} qualified as
19 | * {@code US-West}:
20 | *
21 | *
22 | * rrsApi.put(ResourceRecordSet.<AData> builder().name("www.denominator.io.").type("A").qualifier("US-West").ttl(3600)
23 | * .add(AData.create("192.0.2.1")).build());
24 | *
25 | *
26 | * @param rrset contains the {@code rdata} elements ensure exist. If {@link
27 | * ResourceRecordSet#ttl() ttl} is not present, zone default is used.
28 | * @throws IllegalArgumentException if the zone is not found.
29 | */
30 | void put(ResourceRecordSet> rrset);
31 |
32 | /**
33 | * Idempotently deletes a resource record set by {@link ResourceRecordSet#name() name}, {@link
34 | * ResourceRecordSet#type() type}, and {@link ResourceRecordSet#qualifier() qualifier}. This does
35 | * not error if the record set doesn't exist.
36 | *
37 | * @param name {@link ResourceRecordSet#name() name} of the rrset
38 | * @param type {@link ResourceRecordSet#type() type} of the rrset
39 | * @param qualifier {@link ResourceRecordSet#qualifier() qualifier} of the rrset
40 | * @throws IllegalArgumentException if the zone is not found.
41 | */
42 | void deleteByNameTypeAndQualifier(String name, String type, String qualifier);
43 |
44 | static interface Factory {
45 |
46 | /**
47 | * @return null if this feature isn't supported on the provider.
48 | */
49 | QualifiedResourceRecordSetApi create(String id);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/example-android/src/main/java/denominator/example/android/ui/ZoneListFragment.java:
--------------------------------------------------------------------------------
1 | package denominator.example.android.ui;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.support.v4.app.Fragment;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ScrollView;
10 | import android.widget.TableLayout;
11 | import android.widget.TableRow;
12 | import android.widget.TextView;
13 |
14 | import com.squareup.otto.Bus;
15 | import com.squareup.otto.Subscribe;
16 |
17 | import javax.inject.Inject;
18 |
19 | import denominator.example.android.zone.ZoneList;
20 | import denominator.model.Zone;
21 |
22 | import static android.view.Gravity.CENTER;
23 |
24 | public class ZoneListFragment extends Fragment {
25 |
26 | @Inject
27 | Activity activity;
28 | @Inject
29 | Bus bus;
30 |
31 | TableLayout zones;
32 |
33 | @Override
34 | public void onActivityCreated(Bundle savedInstanceState) {
35 | super.onActivityCreated(savedInstanceState);
36 | HomeActivity.class.cast(getActivity()).inject(this);
37 | }
38 |
39 | @Override
40 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
41 | Bundle savedInstanceState) {
42 | zones = new TableLayout(getActivity());
43 | zones.setGravity(CENTER);
44 | ScrollView view = new ScrollView(getActivity());
45 | view.addView(zones);
46 | return view;
47 | }
48 |
49 | @Subscribe
50 | public void onZones(ZoneList.SuccessEvent event) {
51 | zones.removeAllViews();
52 | while (event.zones.hasNext()) {
53 | Zone zone = event.zones.next();
54 | TableRow row = new TableRow(activity);
55 | TextView name = new TextView(activity);
56 | name.setText(zone.name());
57 | row.addView(name);
58 | if (zone.id() != null) {
59 | TextView id = new TextView(activity);
60 | id.setText(zone.id());
61 | row.addView(id);
62 | }
63 | zones.addView(row);
64 | }
65 | }
66 |
67 | @Override
68 | public void onResume() {
69 | super.onResume();
70 | bus.register(this);
71 | }
72 |
73 | @Override
74 | public void onPause() {
75 | super.onPause();
76 | bus.unregister(this);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/designate/src/main/java/denominator/designate/Designate.java:
--------------------------------------------------------------------------------
1 | package denominator.designate;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import denominator.model.Zone;
7 | import feign.Body;
8 | import feign.Headers;
9 | import feign.Param;
10 | import feign.RequestLine;
11 |
12 | // http://designate.readthedocs.org/en/latest/rest.html#v1-api
13 | public interface Designate {
14 |
15 | @RequestLine("GET /limits")
16 | Map limits();
17 |
18 | @RequestLine("GET /domains")
19 | List domains();
20 |
21 | @RequestLine("POST /domains")
22 | @Body("%7B\"name\":\"{name}\",\"ttl\":{ttl},\"email\":\"{email}\"%7D")
23 | @Headers("Content-Type: application/json")
24 | Zone createDomain(@Param("name") String name, @Param("email") String email,
25 | @Param("ttl") int ttl);
26 |
27 | @RequestLine("PUT /domains/{id}")
28 | @Body("%7B\"id\":\"{id}\",\"name\":\"{name}\",\"ttl\":{ttl},\"email\":\"{email}\"%7D")
29 | @Headers("Content-Type: application/json")
30 | Zone updateDomain(@Param("id") String id, @Param("name") String name,
31 | @Param("email") String email, @Param("ttl") int ttl);
32 |
33 | @RequestLine("DELETE /domains/{domainId}")
34 | void deleteDomain(@Param("domainId") String domainId);
35 |
36 | @RequestLine("GET /domains/{domainId}/records")
37 | List records(@Param("domainId") String domainId);
38 |
39 | @RequestLine("POST /domains/{domainId}/records")
40 | @Headers("Content-Type: application/json")
41 | Record createRecord(@Param("domainId") String domainId, Record record);
42 |
43 | @RequestLine("PUT /domains/{domainId}/records/{recordId}")
44 | @Headers("Content-Type: application/json")
45 | Record updateRecord(@Param("domainId") String domainId, @Param("recordId") String recordId,
46 | Record record);
47 |
48 | @RequestLine("DELETE /domains/{domainId}/records/{recordId}")
49 | void deleteRecord(@Param("domainId") String domainId, @Param("recordId") String recordId);
50 |
51 | class Record {
52 |
53 | String id;
54 | String name;
55 | String type;
56 | Integer ttl;
57 | String data;
58 | Integer priority;
59 |
60 | // toString ordering
61 | @Override
62 | public String toString() {
63 | return new StringBuilder(name).append(type).append(ttl).append(data).append(priority)
64 | .toString();
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/route53/src/test/java/denominator/route53/MockRoute53Server.java:
--------------------------------------------------------------------------------
1 | package denominator.route53;
2 |
3 | import com.squareup.okhttp.mockwebserver.MockResponse;
4 | import com.squareup.okhttp.mockwebserver.MockWebServer;
5 |
6 | import org.junit.rules.TestRule;
7 | import org.junit.runner.Description;
8 | import org.junit.runners.model.Statement;
9 |
10 | import java.io.IOException;
11 |
12 | import denominator.Credentials;
13 | import denominator.CredentialsConfiguration;
14 | import denominator.DNSApiManager;
15 | import denominator.Denominator;
16 | import denominator.assertj.RecordedRequestAssert;
17 |
18 | import static denominator.Credentials.ListCredentials;
19 | import static denominator.assertj.MockWebServerAssertions.assertThat;
20 |
21 | final class MockRoute53Server extends Route53Provider implements TestRule {
22 |
23 | private final MockWebServer delegate = new MockWebServer();
24 | private String accessKey = "accessKey";
25 | private String secretKey = "secretKey";
26 | private String token = null;
27 |
28 | MockRoute53Server() {
29 | credentials(accessKey, secretKey, token);
30 | }
31 |
32 | @Override
33 | public String url() {
34 | return "http://localhost:" + delegate.getPort();
35 | }
36 |
37 | DNSApiManager connect() {
38 | return Denominator.create(this, CredentialsConfiguration.credentials(credentials()));
39 | }
40 |
41 | Credentials credentials() {
42 | return token == null ? ListCredentials.from(accessKey, secretKey)
43 | : ListCredentials.from(accessKey, secretKey, token);
44 | }
45 |
46 | MockRoute53Server credentials(String accessKey, String secretKey, String token) {
47 | this.accessKey = accessKey;
48 | this.secretKey = secretKey;
49 | this.token = token;
50 | return this;
51 | }
52 |
53 | void enqueue(MockResponse mockResponse) {
54 | delegate.enqueue(mockResponse);
55 | }
56 |
57 | RecordedRequestAssert assertRequest() throws InterruptedException {
58 | return assertThat(delegate.takeRequest());
59 | }
60 |
61 | void shutdown() throws IOException {
62 | delegate.shutdown();
63 | }
64 |
65 | @Override
66 | public Statement apply(Statement base, Description description) {
67 | return delegate.apply(base, description);
68 | }
69 |
70 | @dagger.Module(injects = DNSApiManager.class, complete = false, includes =
71 | Route53Provider.Module.class)
72 | static final class Module {
73 |
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/route53/src/main/java/denominator/route53/Route53ResourceRecordSetApi.java:
--------------------------------------------------------------------------------
1 | package denominator.route53;
2 |
3 | import java.util.Arrays;
4 | import java.util.Iterator;
5 |
6 | import javax.inject.Inject;
7 |
8 | import denominator.ResourceRecordSetApi;
9 | import denominator.model.ResourceRecordSet;
10 |
11 | import static denominator.common.Util.filter;
12 | import static denominator.model.ResourceRecordSets.alwaysVisible;
13 | import static denominator.route53.Route53.ActionOnResourceRecordSet.delete;
14 |
15 | public final class Route53ResourceRecordSetApi implements ResourceRecordSetApi {
16 |
17 | private final Route53AllProfileResourceRecordSetApi allApi;
18 | private final Route53 api;
19 | private final String zoneId;
20 |
21 | Route53ResourceRecordSetApi(Route53AllProfileResourceRecordSetApi allProfileResourceRecordSetApi,
22 | Route53 api,
23 | String zoneId) {
24 | this.allApi = allProfileResourceRecordSetApi;
25 | this.api = api;
26 | this.zoneId = zoneId;
27 | }
28 |
29 | @Override
30 | public Iterator> iterator() {
31 | return filter(allApi.iterator(), alwaysVisible());
32 | }
33 |
34 | @Override
35 | public Iterator> iterateByName(String name) {
36 | return filter(allApi.iterateByName(name), alwaysVisible());
37 | }
38 |
39 | @Override
40 | public ResourceRecordSet> getByNameAndType(String name, String type) {
41 | ResourceRecordSet> rrset = allApi.getByNameAndType(name, type);
42 | return alwaysVisible().apply(rrset) ? rrset : null;
43 | }
44 |
45 | @Override
46 | public void put(ResourceRecordSet> rrset) {
47 | allApi.put(rrset);
48 | }
49 |
50 | @Override
51 | public void deleteByNameAndType(String name, String type) {
52 | ResourceRecordSet> oldRRS = getByNameAndType(name, type);
53 | if (oldRRS == null) {
54 | return;
55 | }
56 | api.changeResourceRecordSets(zoneId, Arrays.asList(delete(oldRRS)));
57 | }
58 |
59 | static final class Factory implements denominator.ResourceRecordSetApi.Factory {
60 |
61 | private final Route53AllProfileResourceRecordSetApi.Factory allApi;
62 | private final Route53 api;
63 |
64 | @Inject
65 | Factory(Route53AllProfileResourceRecordSetApi.Factory allApi, Route53 api) {
66 | this.allApi = allApi;
67 | this.api = api;
68 | }
69 |
70 | @Override
71 | public ResourceRecordSetApi create(String id) {
72 | return new Route53ResourceRecordSetApi(allApi.create(id), api, id);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/route53/src/main/java/denominator/route53/ListResourceRecordSetsResponseHandler.java:
--------------------------------------------------------------------------------
1 | package denominator.route53;
2 |
3 | import org.xml.sax.Attributes;
4 | import org.xml.sax.helpers.DefaultHandler;
5 |
6 | import denominator.route53.Route53.ResourceRecordSetList;
7 | import denominator.route53.Route53.ResourceRecordSetList.NextRecord;
8 | import feign.sax.SAXDecoder.ContentHandlerWithResult;
9 |
10 | /**
11 | * See
13 | */
14 | class ListResourceRecordSetsResponseHandler extends DefaultHandler implements
15 | ContentHandlerWithResult {
16 |
17 | private final StringBuilder currentText = new StringBuilder();
18 | private final ResourceRecordSetList rrsets = new ResourceRecordSetList();
19 | private ResourceRecordSetHandler resourceRecordSetHandler = new ResourceRecordSetHandler();
20 | private NextRecord next = null;
21 | private boolean inResourceRecordSets;
22 |
23 | @Override
24 | public ResourceRecordSetList result() {
25 | rrsets.next = next;
26 | return rrsets;
27 | }
28 |
29 | @Override
30 | public void startElement(String url, String name, String qName, Attributes attributes) {
31 | if ("ResourceRecordSets".equals(qName)) {
32 | inResourceRecordSets = true;
33 | }
34 | }
35 |
36 | @Override
37 | public void endElement(String uri, String name, String qName) {
38 | if (inResourceRecordSets) {
39 | if ("ResourceRecordSets".equals(qName)) {
40 | inResourceRecordSets = false;
41 | } else if (qName.equals("ResourceRecordSet")) {
42 | rrsets.add(resourceRecordSetHandler.result());
43 | resourceRecordSetHandler = new ResourceRecordSetHandler();
44 | } else {
45 | resourceRecordSetHandler.endElement(uri, name, qName);
46 | }
47 | } else if (qName.equals("NextRecordName")) {
48 | next = new NextRecord(currentText.toString().trim());
49 | } else if (qName.equals("NextRecordType")) {
50 | next.type = currentText.toString().trim();
51 | } else if (qName.equals("NextRecordIdentifier")) {
52 | next.identifier = currentText.toString().trim();
53 | }
54 | currentText.setLength(0);
55 | }
56 |
57 | @Override
58 | public void characters(char ch[], int start, int length) {
59 | if (inResourceRecordSets) {
60 | resourceRecordSetHandler.characters(ch, start, length);
61 | } else {
62 | currentText.append(ch, start, length);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/clouddns/src/main/java/denominator/clouddns/KeystoneAccessAdapter.java:
--------------------------------------------------------------------------------
1 | package denominator.clouddns;
2 |
3 | import com.google.gson.JsonElement;
4 | import com.google.gson.JsonIOException;
5 | import com.google.gson.JsonObject;
6 | import com.google.gson.JsonParser;
7 | import com.google.gson.TypeAdapter;
8 | import com.google.gson.stream.JsonReader;
9 | import com.google.gson.stream.JsonWriter;
10 |
11 | import java.io.IOException;
12 |
13 | import denominator.clouddns.RackspaceApis.TokenIdAndPublicURL;
14 |
15 | import static denominator.common.Preconditions.checkNotNull;
16 |
17 | class KeystoneAccessAdapter extends TypeAdapter {
18 |
19 | // rax:dns
20 | private final String type;
21 |
22 | KeystoneAccessAdapter(String type) {
23 | this.type = checkNotNull(type, "type was null");
24 | }
25 |
26 | static boolean isNull(JsonElement element) {
27 | return element == null || element.isJsonNull();
28 | }
29 |
30 | @Override
31 | public TokenIdAndPublicURL read(JsonReader in) throws IOException {
32 | JsonObject access;
33 | try {
34 | access = new JsonParser().parse(in).getAsJsonObject().get("access").getAsJsonObject();
35 | } catch (JsonIOException e) {
36 | if (e.getCause() != null && e.getCause() instanceof IOException) {
37 | throw IOException.class.cast(e.getCause());
38 | }
39 | throw e;
40 | }
41 | JsonElement tokenField = access.get("token");
42 | if (isNull(tokenField)) {
43 | return null;
44 | }
45 | JsonElement idField = tokenField.getAsJsonObject().get("id");
46 | if (isNull(idField)) {
47 | return null;
48 | }
49 |
50 | TokenIdAndPublicURL tokenUrl = new TokenIdAndPublicURL();
51 | tokenUrl.tokenId = idField.getAsString();
52 |
53 | for (JsonElement s : access.get("serviceCatalog").getAsJsonArray()) {
54 | JsonObject service = s.getAsJsonObject();
55 | JsonElement typeField = service.get("type");
56 | JsonElement endpointsField = service.get("endpoints");
57 | if (!isNull(typeField) && !isNull(endpointsField) && type.equals(typeField.getAsString())) {
58 | for (JsonElement e : endpointsField.getAsJsonArray()) {
59 | JsonObject endpoint = e.getAsJsonObject();
60 | tokenUrl.publicURL = endpoint.get("publicURL").getAsString();
61 | }
62 | }
63 | }
64 | return tokenUrl;
65 | }
66 |
67 | @Override
68 | public String toString() {
69 | return "KeystoneAccessAdapter(" + type + ")";
70 | }
71 |
72 | @Override
73 | public void write(JsonWriter out, TokenIdAndPublicURL value) throws IOException {
74 | throw new UnsupportedOperationException();
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/dynect/src/main/java/denominator/dynect/DynECTZoneApi.java:
--------------------------------------------------------------------------------
1 | package denominator.dynect;
2 |
3 | import java.util.Iterator;
4 |
5 | import javax.inject.Inject;
6 |
7 | import denominator.dynect.DynECT.Record;
8 | import denominator.model.Zone;
9 | import denominator.model.rdata.SOAData;
10 |
11 | import static denominator.common.Preconditions.checkState;
12 | import static denominator.common.Util.singletonIterator;
13 |
14 | public final class DynECTZoneApi implements denominator.ZoneApi {
15 |
16 | private final DynECT api;
17 |
18 | @Inject
19 | DynECTZoneApi(DynECT api) {
20 | this.api = api;
21 | }
22 |
23 | @Override
24 | public Iterator iterator() {
25 | final Iterator delegate = api.zones().data.iterator();
26 | return new Iterator() {
27 | @Override
28 | public boolean hasNext() {
29 | return delegate.hasNext();
30 | }
31 |
32 | @Override
33 | public Zone next() {
34 | return fromSOA(delegate.next());
35 | }
36 |
37 | @Override
38 | public void remove() {
39 | throw new UnsupportedOperationException();
40 | }
41 | };
42 | }
43 |
44 | @Override
45 | public Iterator iterateByName(String name) {
46 | Zone zone = null;
47 | try {
48 | zone = fromSOA(name);
49 | } catch (DynECTException e) {
50 | if (e.getMessage().indexOf("No such zone") == -1) {
51 | throw e;
52 | }
53 | }
54 | return singletonIterator(zone);
55 | }
56 |
57 | @Override
58 | public String put(Zone zone) {
59 | try {
60 | api.createZone(zone.name(), zone.ttl(), zone.email());
61 | } catch (DynECTException e) {
62 | if (e.getMessage().indexOf("already exists") == -1) {
63 | throw e;
64 | }
65 | long soaId = getSOA(zone.name()).id;
66 | api.scheduleUpdateSOA(zone.name(), soaId, zone.ttl(), zone.email());
67 | }
68 | api.publish(zone.name());
69 | return zone.name();
70 | }
71 |
72 | @Override
73 | public void delete(String name) {
74 | try {
75 | api.deleteZone(name);
76 | } catch (DynECTException e) {
77 | if (e.getMessage().indexOf("No such zone") == -1) {
78 | throw e;
79 | }
80 | }
81 | }
82 |
83 | private Zone fromSOA(String name) {
84 | Record soa = getSOA(name);
85 | SOAData soaData = (SOAData) soa.rdata;
86 | return Zone.create(name, name, soa.ttl, soaData.rname());
87 | }
88 |
89 | private Record getSOA(String name) {
90 | Iterator soa = api.recordsInZoneByNameAndType(name, name, "SOA").data;
91 | checkState(soa.hasNext(), "SOA record for zone %s was not present", name);
92 | return soa.next();
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/ultradns/src/main/java/denominator/ultradns/UltraDNSRoundRobinPoolApi.java:
--------------------------------------------------------------------------------
1 | package denominator.ultradns;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import denominator.ultradns.UltraDNS.NameAndType;
7 |
8 | import static denominator.ResourceTypeToValue.lookup;
9 | import static denominator.common.Preconditions.checkNotNull;
10 | import static denominator.common.Preconditions.checkState;
11 |
12 | class UltraDNSRoundRobinPoolApi {
13 |
14 | private final UltraDNS api;
15 | private final String zoneName;
16 |
17 | UltraDNSRoundRobinPoolApi(UltraDNS api, String zoneName) {
18 | this.api = api;
19 | this.zoneName = zoneName;
20 | }
21 |
22 | boolean isPoolType(String type) {
23 | return type.equals("A") || type.equals("AAAA");
24 | }
25 |
26 | void add(String name, String type, int ttl, List> rdatas) {
27 | checkState(isPoolType(type), "%s not A or AAAA type", type);
28 | String poolId = reuseOrCreatePoolForNameAndType(name, type);
29 | for (Map rdata : rdatas) {
30 | String address = rdata.get("address").toString();
31 | int typeCode = lookup(type);
32 | api.addRecordToRRPool(typeCode, ttl, address, poolId, zoneName);
33 | }
34 | }
35 |
36 | private String reuseOrCreatePoolForNameAndType(String name, String type) {
37 | try {
38 | return api.addRRLBPool(zoneName, name, lookup(type));
39 | } catch (UltraDNSException e) {
40 | if (e.code() != UltraDNSException.POOL_ALREADY_EXISTS) {
41 | throw e;
42 | }
43 | return getPoolByNameAndType(name, type);
44 | }
45 | }
46 |
47 | String getPoolByNameAndType(String name, String type) {
48 | NameAndType nameAndType = new NameAndType();
49 | nameAndType.name = name;
50 | nameAndType.type = type;
51 | return api.getLoadBalancingPoolsByZone(zoneName).get(nameAndType);
52 | }
53 |
54 | void deletePool(String name, String type) {
55 | NameAndType nameAndType = new NameAndType();
56 | nameAndType.name = checkNotNull(name, "pool name was null");
57 | nameAndType.type = checkNotNull(type, "pool record type was null");
58 | String poolId = api.getLoadBalancingPoolsByZone(zoneName).get(nameAndType);
59 | if (poolId != null) {
60 | if (api.getRRPoolRecords(poolId).isEmpty()) {
61 | try {
62 | api.deleteLBPool(poolId);
63 | } catch (UltraDNSException e) {
64 | switch (e.code()) {
65 | // lost race
66 | case UltraDNSException.POOL_NOT_FOUND:
67 | case UltraDNSException.RESOURCE_RECORD_NOT_FOUND:
68 | return;
69 | }
70 | throw e;
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/core/src/main/java/denominator/config/OnlyBasicResourceRecordSets.java:
--------------------------------------------------------------------------------
1 | package denominator.config;
2 |
3 | import java.util.Collections;
4 | import java.util.Iterator;
5 |
6 | import javax.inject.Singleton;
7 |
8 | import dagger.Module;
9 | import dagger.Provides;
10 | import denominator.AllProfileResourceRecordSetApi;
11 | import denominator.DNSApiManager;
12 | import denominator.ResourceRecordSetApi;
13 | import denominator.model.ResourceRecordSet;
14 |
15 | /**
16 | * Used when the backend doesn't support any record types except basic ones.
17 | */
18 | @Module(injects = DNSApiManager.class, complete = false)
19 | public class OnlyBasicResourceRecordSets {
20 |
21 | @Provides
22 | @Singleton
23 | AllProfileResourceRecordSetApi.Factory provideAllProfileResourceRecordSetApi(
24 | final ResourceRecordSetApi.Factory factory) {
25 | return new AllProfileResourceRecordSetApi.Factory() {
26 |
27 | @Override
28 | public AllProfileResourceRecordSetApi create(String id) {
29 | return new OnlyBasicResourceRecordSetApi(factory.create(id));
30 | }
31 |
32 | };
33 | }
34 |
35 | private static class OnlyBasicResourceRecordSetApi implements AllProfileResourceRecordSetApi {
36 |
37 | private final ResourceRecordSetApi api;
38 |
39 | private OnlyBasicResourceRecordSetApi(ResourceRecordSetApi api) {
40 | this.api = api;
41 | }
42 |
43 | @Override
44 | public Iterator> iterator() {
45 | return api.iterator();
46 | }
47 |
48 | @Override
49 | public Iterator> iterateByName(String name) {
50 | return api.iterateByName(name);
51 | }
52 |
53 | @Override
54 | public Iterator> iterateByNameAndType(String name, String type) {
55 | ResourceRecordSet> rrs = api.getByNameAndType(name, type);
56 | if (rrs != null) {
57 | return Collections.>singleton(rrs).iterator();
58 | }
59 | return Collections.>emptySet().iterator();
60 | }
61 |
62 | @Override
63 | public ResourceRecordSet> getByNameTypeAndQualifier(String name, String type,
64 | String qualifier) {
65 | return null;
66 | }
67 |
68 | @Override
69 | public void put(ResourceRecordSet> rrset) {
70 | api.put(rrset);
71 | }
72 |
73 | @Override
74 | public void deleteByNameTypeAndQualifier(String name, String type, String qualifier) {
75 | api.deleteByNameAndType(name, type);
76 | }
77 |
78 | @Override
79 | public void deleteByNameAndType(String name, String type) {
80 | api.deleteByNameAndType(name, type);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/designate/src/main/java/denominator/designate/KeystoneV2AccessAdapter.java:
--------------------------------------------------------------------------------
1 | package denominator.designate;
2 |
3 | import com.google.gson.JsonElement;
4 | import com.google.gson.JsonIOException;
5 | import com.google.gson.JsonObject;
6 | import com.google.gson.JsonParser;
7 | import com.google.gson.TypeAdapter;
8 | import com.google.gson.stream.JsonReader;
9 | import com.google.gson.stream.JsonWriter;
10 |
11 | import java.io.IOException;
12 |
13 | import denominator.designate.KeystoneV2.TokenIdAndPublicURL;
14 |
15 | class KeystoneV2AccessAdapter extends TypeAdapter {
16 |
17 | private final String serviceTypeSuffix = ":dns";
18 |
19 | static boolean isNull(JsonElement element) {
20 | return element == null || element.isJsonNull();
21 | }
22 |
23 | @Override
24 | public TokenIdAndPublicURL read(JsonReader in) throws IOException {
25 | JsonObject access;
26 | try {
27 | access = new JsonParser().parse(in).getAsJsonObject().get("access").getAsJsonObject();
28 | } catch (JsonIOException e) {
29 | if (e.getCause() != null && e.getCause() instanceof IOException) {
30 | throw IOException.class.cast(e.getCause());
31 | }
32 | throw e;
33 | }
34 | JsonElement tokenField = access.get("token");
35 | if (isNull(tokenField)) {
36 | return null;
37 | }
38 | JsonElement idField = tokenField.getAsJsonObject().get("id");
39 | if (isNull(idField)) {
40 | return null;
41 | }
42 |
43 | TokenIdAndPublicURL tokenUrl = new TokenIdAndPublicURL();
44 | tokenUrl.tokenId = idField.getAsString();
45 |
46 | for (JsonElement s : access.get("serviceCatalog").getAsJsonArray()) {
47 | JsonObject service = s.getAsJsonObject();
48 | JsonElement typeField = service.get("type");
49 | JsonElement endpointsField = service.get("endpoints");
50 | if (!isNull(typeField) && !isNull(endpointsField) && typeField.getAsString()
51 | .endsWith(serviceTypeSuffix)) {
52 | for (JsonElement e : endpointsField.getAsJsonArray()) {
53 | JsonObject endpoint = e.getAsJsonObject();
54 | tokenUrl.publicURL = endpoint.get("publicURL").getAsString();
55 | if (tokenUrl.publicURL.endsWith("/")) {
56 | tokenUrl.publicURL = tokenUrl.publicURL.substring(0, tokenUrl.publicURL.length() - 1);
57 | }
58 | }
59 | }
60 | }
61 | return tokenUrl;
62 | }
63 |
64 | @Override
65 | public String toString() {
66 | return "KeystoneV2AccessAdapter(" + serviceTypeSuffix + ")";
67 | }
68 |
69 | @Override
70 | public void write(JsonWriter out, TokenIdAndPublicURL value) throws IOException {
71 | throw new UnsupportedOperationException();
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/core/src/main/java/denominator/ResourceRecordSetApi.java:
--------------------------------------------------------------------------------
1 | package denominator;
2 |
3 | import java.util.Iterator;
4 |
5 | import denominator.model.ResourceRecordSet;
6 |
7 | public interface ResourceRecordSetApi extends Iterable> {
8 |
9 | /**
10 | * Iterates across all basic record sets in the zone (those with no profile). Implementations are
11 | * lazy when possible.
12 | *
13 | * @return iterator which is lazy where possible
14 | * @throws IllegalArgumentException if the zone is not found.
15 | */
16 | @Override
17 | Iterator> iterator();
18 |
19 | /**
20 | * a listing of all resource record sets which have the specified name.
21 | *
22 | * @return iterator which is lazy where possible, empty if there are no records with that name.
23 | * @throws IllegalArgumentException if the zone is not found.
24 | * @since 1.3
25 | */
26 | Iterator> iterateByName(String name);
27 |
28 | /**
29 | * retrieve a resource record set by name and type.
30 | *
31 | * @param name {@link ResourceRecordSet#name() name} of the rrset
32 | * @param type {@link ResourceRecordSet#type() type} of the rrset
33 | * @return null unless a resource record exists with the same {@code name} and {@code type}
34 | * @throws IllegalArgumentException if the zone is not found.
35 | */
36 | ResourceRecordSet> getByNameAndType(String name, String type);
37 |
38 | /**
39 | * Idempotently replaces any existing records with {@link ResourceRecordSet#name() name} and
40 | * {@link ResourceRecordSet#type()} corresponding to {@code rrset}. If no records exists, they
41 | * will be added.
42 | *
43 | * Example of replacing the {@code A} record set for {@code www.denominator.io.}:
44 | *
45 | *
46 | * rrsApi.put(a("www.denominator.io.", "192.0.2.1"));
47 | *
48 | *
49 | * @param rrset contains the {@code rdata} elements ensure exist. If {@link
50 | * ResourceRecordSet#ttl() ttl} is not present, zone default is used.
51 | * @throws IllegalArgumentException if the zone is not found
52 | * @since 1.3
53 | */
54 | void put(ResourceRecordSet> rrset);
55 |
56 | /**
57 | * deletes a resource record set by name and type idempotently. This does not error if the record
58 | * set doesn't exist.
59 | *
60 | * @param name {@link ResourceRecordSet#name() name} of the rrset
61 | * @param type {@link ResourceRecordSet#type() type} of the rrset
62 | * @throws IllegalArgumentException if the zone is not found.
63 | */
64 | void deleteByNameAndType(String name, String type);
65 |
66 | interface Factory {
67 |
68 | ResourceRecordSetApi create(String id);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/clouddns/src/main/java/denominator/clouddns/CloudDNSFunctions.java:
--------------------------------------------------------------------------------
1 | package denominator.clouddns;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import denominator.clouddns.RackspaceApis.CloudDNS;
7 | import denominator.clouddns.RackspaceApis.Job;
8 | import denominator.clouddns.RackspaceApis.Record;
9 | import denominator.common.Util;
10 | import denominator.model.rdata.MXData;
11 | import denominator.model.rdata.SOAData;
12 | import denominator.model.rdata.SRVData;
13 | import denominator.model.rdata.TXTData;
14 | import feign.RetryableException;
15 | import feign.Retryer;
16 |
17 | import static denominator.common.Util.split;
18 | import static java.lang.String.format;
19 |
20 | final class CloudDNSFunctions {
21 |
22 | /**
23 | * Returns the ID of the object created or null.
24 | */
25 | static String awaitComplete(CloudDNS api, Job job) {
26 | RetryableException retryableException = new RetryableException(
27 | format("Job %s did not complete. Check your logs.", job.id), null);
28 | Retryer retryer = new Retryer.Default(500, 1000, 30);
29 |
30 | while (true) {
31 | job = api.getStatus(job.id);
32 |
33 | if ("COMPLETED".equals(job.status)) {
34 | return job.resultId;
35 | } else if ("ERROR".equals(job.status)) {
36 | throw new IllegalStateException(
37 | format("Job %s failed with error: %s", job.id, job.errorDetails));
38 | }
39 |
40 | retryer.continueOrPropagate(retryableException);
41 | }
42 | }
43 |
44 | /**
45 | * Special-cases priority field and the strange and incomplete SOA record.
46 | */
47 | static Map toRDataMap(Record record) {
48 | if ("MX".equals(record.type)) {
49 | return MXData.create(record.priority, record.data());
50 | } else if ("TXT".equals(record.type)) {
51 | return TXTData.create(record.data());
52 | } else if ("SRV".equals(record.type)) {
53 | List rdata = split(' ', record.data());
54 | return SRVData.builder()
55 | .priority(record.priority)
56 | .weight(Integer.valueOf(rdata.get(0)))
57 | .port(Integer.valueOf(rdata.get(1)))
58 | .target(rdata.get(2)).build();
59 | } else if ("SOA".equals(record.type)) {
60 | List threeParts = split(' ', record.data());
61 | return SOAData.builder()
62 | .mname(threeParts.get(0))
63 | .rname(threeParts.get(1))
64 | .serial(Integer.valueOf(threeParts.get(2)))
65 | .refresh(record.ttl)
66 | .retry(record.ttl)
67 | .expire(record.ttl).minimum(record.ttl).build();
68 | } else {
69 | return Util.toMap(record.type, record.data());
70 | }
71 | }
72 |
73 | private CloudDNSFunctions() {
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------