diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ChangeRequest.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ChangeRequest.java
new file mode 100644
index 000000000000..5ed03b1ea071
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ChangeRequest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud.dns;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.api.services.dns.model.ResourceRecordSet;
+import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import org.joda.time.DateTime;
+import org.joda.time.format.ISODateTimeFormat;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A class representing an atomic update to a collection of {@link DnsRecord}s within a {@code
+ * ManagedZone}.
+ *
+ * @see Google Cloud DNS documentation
+ */
+public class ChangeRequest implements Serializable {
+
+ private static final Function FROM_PB_FUNCTION =
+ new Function() {
+ @Override
+ public DnsRecord apply(com.google.api.services.dns.model.ResourceRecordSet pb) {
+ return DnsRecord.fromPb(pb);
+ }
+ };
+ private static final Function TO_PB_FUNCTION =
+ new Function() {
+ @Override
+ public com.google.api.services.dns.model.ResourceRecordSet apply(DnsRecord error) {
+ return error.toPb();
+ }
+ };
+ private static final long serialVersionUID = -8703939628990291682L;
+ private final List additions;
+ private final List deletions;
+ private final String id;
+ private final Long startTimeMillis;
+ private final Status status;
+
+ /**
+ * This enumerates the possible states of a {@code ChangeRequest}.
+ *
+ * @see Google Cloud DNS
+ * documentation
+ */
+ public enum Status {
+ PENDING,
+ DONE
+ }
+
+ /**
+ * A builder for {@code ChangeRequest}s.
+ */
+ public static class Builder {
+
+ private List additions = new LinkedList<>();
+ private List deletions = new LinkedList<>();
+ private String id;
+ private Long startTimeMillis;
+ private Status status;
+
+ private Builder(ChangeRequest cr) {
+ this.additions = Lists.newLinkedList(cr.additions());
+ this.deletions = Lists.newLinkedList(cr.deletions());
+ this.id = cr.id();
+ this.startTimeMillis = cr.startTimeMillis();
+ this.status = cr.status();
+ }
+
+ private Builder() {
+ }
+
+ /**
+ * Sets a collection of {@link DnsRecord}s which are to be added to the zone upon executing this
+ * {@code ChangeRequest}.
+ */
+ public Builder additions(List additions) {
+ this.additions = Lists.newLinkedList(checkNotNull(additions));
+ return this;
+ }
+
+ /**
+ * Sets a collection of {@link DnsRecord}s which are to be deleted from the zone upon executing
+ * this {@code ChangeRequest}.
+ */
+ public Builder deletions(List deletions) {
+ this.deletions = Lists.newLinkedList(checkNotNull(deletions));
+ return this;
+ }
+
+ /**
+ * Adds a {@link DnsRecord} to be added to the zone upon executing this {@code
+ * ChangeRequest}.
+ */
+ public Builder add(DnsRecord record) {
+ this.additions.add(checkNotNull(record));
+ return this;
+ }
+
+ /**
+ * Adds a {@link DnsRecord} to be deleted to the zone upon executing this
+ * {@code ChangeRequest}.
+ */
+ public Builder delete(DnsRecord record) {
+ this.deletions.add(checkNotNull(record));
+ return this;
+ }
+
+ /**
+ * Clears the collection of {@link DnsRecord}s which are to be added to the zone upon executing
+ * this {@code ChangeRequest}.
+ */
+ public Builder clearAdditions() {
+ this.additions.clear();
+ return this;
+ }
+
+ /**
+ * Clears the collection of {@link DnsRecord}s which are to be deleted from the zone upon
+ * executing this {@code ChangeRequest}.
+ */
+ public Builder clearDeletions() {
+ this.deletions.clear();
+ return this;
+ }
+
+ /**
+ * Removes a single {@link DnsRecord} from the collection of records to be
+ * added to the zone upon executing this {@code ChangeRequest}.
+ */
+ public Builder removeAddition(DnsRecord record) {
+ this.additions.remove(record);
+ return this;
+ }
+
+ /**
+ * Removes a single {@link DnsRecord} from the collection of records to be
+ * deleted from the zone upon executing this {@code ChangeRequest}.
+ */
+ public Builder removeDeletion(DnsRecord record) {
+ this.deletions.remove(record);
+ return this;
+ }
+
+ /**
+ * Associates a server-assigned id to this {@code ChangeRequest}.
+ */
+ Builder id(String id) {
+ this.id = checkNotNull(id);
+ return this;
+ }
+
+ /**
+ * Sets the time when this {@code ChangeRequest} was started by a server.
+ */
+ Builder startTimeMillis(long startTimeMillis) {
+ this.startTimeMillis = startTimeMillis;
+ return this;
+ }
+
+ /**
+ * Sets the current status of this {@code ChangeRequest}.
+ */
+ Builder status(Status status) {
+ this.status = checkNotNull(status);
+ return this;
+ }
+
+ /**
+ * Creates a {@code ChangeRequest} instance populated by the values associated with this
+ * builder.
+ */
+ public ChangeRequest build() {
+ return new ChangeRequest(this);
+ }
+ }
+
+ private ChangeRequest(Builder builder) {
+ this.additions = ImmutableList.copyOf(builder.additions);
+ this.deletions = ImmutableList.copyOf(builder.deletions);
+ this.id = builder.id;
+ this.startTimeMillis = builder.startTimeMillis;
+ this.status = builder.status;
+ }
+
+ /**
+ * Returns an empty builder for the {@code ChangeRequest} class.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Creates a builder populated with values of this {@code ChangeRequest}.
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Returns the list of {@link DnsRecord}s to be added to the zone upon submitting this {@code
+ * ChangeRequest}.
+ */
+ public List additions() {
+ return additions;
+ }
+
+ /**
+ * Returns the list of {@link DnsRecord}s to be deleted from the zone upon submitting this {@code
+ * ChangeRequest}.
+ */
+ public List deletions() {
+ return deletions;
+ }
+
+ /**
+ * Returns the id assigned to this {@code ChangeRequest} by the server.
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the time when this {@code ChangeRequest} was started by the server.
+ */
+ public Long startTimeMillis() {
+ return startTimeMillis;
+ }
+
+ /**
+ * Returns the status of this {@code ChangeRequest}.
+ */
+ public Status status() {
+ return status;
+ }
+
+ com.google.api.services.dns.model.Change toPb() {
+ com.google.api.services.dns.model.Change pb =
+ new com.google.api.services.dns.model.Change();
+ // set id
+ if (id() != null) {
+ pb.setId(id());
+ }
+ // set timestamp
+ if (startTimeMillis() != null) {
+ pb.setStartTime(ISODateTimeFormat.dateTime().withZoneUTC().print(startTimeMillis()));
+ }
+ // set status
+ if (status() != null) {
+ pb.setStatus(status().name().toLowerCase());
+ }
+ // set a list of additions
+ pb.setAdditions(Lists.transform(additions(), TO_PB_FUNCTION));
+ // set a list of deletions
+ pb.setDeletions(Lists.transform(deletions(), TO_PB_FUNCTION));
+ return pb;
+ }
+
+ static ChangeRequest fromPb(com.google.api.services.dns.model.Change pb) {
+ Builder builder = builder();
+ if (pb.getId() != null) {
+ builder.id(pb.getId());
+ }
+ if (pb.getStartTime() != null) {
+ builder.startTimeMillis(DateTime.parse(pb.getStartTime()).getMillis());
+ }
+ if (pb.getStatus() != null) {
+ // we are assuming that status indicated in pb is a lower case version of the enum name
+ builder.status(ChangeRequest.Status.valueOf(pb.getStatus().toUpperCase()));
+ }
+ if (pb.getDeletions() != null) {
+ builder.deletions(Lists.transform(pb.getDeletions(), FROM_PB_FUNCTION));
+ }
+ if (pb.getAdditions() != null) {
+ builder.additions(Lists.transform(pb.getAdditions(), FROM_PB_FUNCTION));
+ }
+ return builder.build();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof ChangeRequest) && toPb().equals(((ChangeRequest) other).toPb());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(additions, deletions, id, startTimeMillis, status);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("additions", additions)
+ .add("deletions", deletions)
+ .add("id", id)
+ .add("startTimeMillis", startTimeMillis)
+ .add("status", status)
+ .toString();
+ }
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsRecord.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsRecord.java
index c41e72a77400..4236d9c1c561 100644
--- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsRecord.java
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsRecord.java
@@ -273,14 +273,14 @@ com.google.api.services.dns.model.ResourceRecordSet toPb() {
}
static DnsRecord fromPb(com.google.api.services.dns.model.ResourceRecordSet pb) {
- Builder b = builder(pb.getName(), Type.valueOf(pb.getType()));
+ Builder builder = builder(pb.getName(), Type.valueOf(pb.getType()));
if (pb.getRrdatas() != null) {
- b.records(pb.getRrdatas());
+ builder.records(pb.getRrdatas());
}
if (pb.getTtl() != null) {
- b.ttl(pb.getTtl());
+ builder.ttl(pb.getTtl());
}
- return b.build();
+ return builder.build();
}
@Override
diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/ChangeRequestTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/ChangeRequestTest.java
new file mode 100644
index 000000000000..8b40a4dcff34
--- /dev/null
+++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/ChangeRequestTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud.dns;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class ChangeRequestTest {
+
+ private static final String ID = "cr-id-1";
+ private static final Long START_TIME_MILLIS = 12334567890L;
+ private static final ChangeRequest.Status STATUS = ChangeRequest.Status.PENDING;
+ private static final String NAME1 = "dns1";
+ private static final DnsRecord.Type TYPE1 = DnsRecord.Type.A;
+ private static final String NAME2 = "dns2";
+ private static final DnsRecord.Type TYPE2 = DnsRecord.Type.AAAA;
+ private static final String NAME3 = "dns3";
+ private static final DnsRecord.Type TYPE3 = DnsRecord.Type.MX;
+ private static final DnsRecord RECORD1 = DnsRecord.builder(NAME1, TYPE1).build();
+ private static final DnsRecord RECORD2 = DnsRecord.builder(NAME2, TYPE2).build();
+ private static final DnsRecord RECORD3 = DnsRecord.builder(NAME3, TYPE3).build();
+ private static final List ADDITIONS = ImmutableList.of(RECORD1, RECORD2);
+ private static final List DELETIONS = ImmutableList.of(RECORD3);
+ private static final ChangeRequest CHANGE = ChangeRequest.builder()
+ .add(RECORD1)
+ .add(RECORD2)
+ .delete(RECORD3)
+ .startTimeMillis(START_TIME_MILLIS)
+ .status(STATUS)
+ .id(ID)
+ .build();
+
+ @Test
+ public void testEmptyBuilder() {
+ ChangeRequest cr = ChangeRequest.builder().build();
+ assertNotNull(cr.deletions());
+ assertTrue(cr.deletions().isEmpty());
+ assertNotNull(cr.additions());
+ assertTrue(cr.additions().isEmpty());
+ }
+
+ @Test
+ public void testBuilder() {
+ assertEquals(ID, CHANGE.id());
+ assertEquals(STATUS, CHANGE.status());
+ assertEquals(START_TIME_MILLIS, CHANGE.startTimeMillis());
+ assertEquals(ADDITIONS, CHANGE.additions());
+ assertEquals(DELETIONS, CHANGE.deletions());
+ List recordList = ImmutableList.of(RECORD1);
+ ChangeRequest another = CHANGE.toBuilder().additions(recordList).build();
+ assertEquals(recordList, another.additions());
+ assertEquals(CHANGE.deletions(), another.deletions());
+ another = CHANGE.toBuilder().deletions(recordList).build();
+ assertEquals(recordList, another.deletions());
+ assertEquals(CHANGE.additions(), another.additions());
+ }
+
+ @Test
+ public void testEqualsAndNotEquals() {
+ ChangeRequest clone = CHANGE.toBuilder().build();
+ assertEquals(CHANGE, clone);
+ clone = ChangeRequest.fromPb(CHANGE.toPb());
+ assertEquals(CHANGE, clone);
+ clone = CHANGE.toBuilder().id("some-other-id").build();
+ assertNotEquals(CHANGE, clone);
+ clone = CHANGE.toBuilder().startTimeMillis(CHANGE.startTimeMillis() + 1).build();
+ assertNotEquals(CHANGE, clone);
+ clone = CHANGE.toBuilder().add(RECORD3).build();
+ assertNotEquals(CHANGE, clone);
+ clone = CHANGE.toBuilder().delete(RECORD1).build();
+ assertNotEquals(CHANGE, clone);
+ ChangeRequest empty = ChangeRequest.builder().build();
+ assertNotEquals(CHANGE, empty);
+ assertEquals(empty, ChangeRequest.builder().build());
+ }
+
+ @Test
+ public void testSameHashCodeOnEquals() {
+ ChangeRequest clone = CHANGE.toBuilder().build();
+ assertEquals(CHANGE, clone);
+ assertEquals(CHANGE.hashCode(), clone.hashCode());
+ ChangeRequest empty = ChangeRequest.builder().build();
+ assertEquals(empty.hashCode(), ChangeRequest.builder().build().hashCode());
+ }
+
+ @Test
+ public void testToAndFromPb() {
+ assertEquals(CHANGE, ChangeRequest.fromPb(CHANGE.toPb()));
+ ChangeRequest partial = ChangeRequest.builder().build();
+ assertEquals(partial, ChangeRequest.fromPb(partial.toPb()));
+ partial = ChangeRequest.builder().id(ID).build();
+ assertEquals(partial, ChangeRequest.fromPb(partial.toPb()));
+ partial = ChangeRequest.builder().add(RECORD1).build();
+ assertEquals(partial, ChangeRequest.fromPb(partial.toPb()));
+ partial = ChangeRequest.builder().delete(RECORD1).build();
+ assertEquals(partial, ChangeRequest.fromPb(partial.toPb()));
+ partial = ChangeRequest.builder().additions(ADDITIONS).build();
+ assertEquals(partial, ChangeRequest.fromPb(partial.toPb()));
+ partial = ChangeRequest.builder().deletions(DELETIONS).build();
+ assertEquals(partial, ChangeRequest.fromPb(partial.toPb()));
+ partial = ChangeRequest.builder().startTimeMillis(START_TIME_MILLIS).build();
+ assertEquals(partial, ChangeRequest.fromPb(partial.toPb()));
+ partial = ChangeRequest.builder().status(STATUS).build();
+ assertEquals(partial, ChangeRequest.fromPb(partial.toPb()));
+ }
+
+ @Test
+ public void testToBuilder() {
+ assertEquals(CHANGE, CHANGE.toBuilder().build());
+ ChangeRequest partial = ChangeRequest.builder().build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = ChangeRequest.builder().id(ID).build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = ChangeRequest.builder().add(RECORD1).build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = ChangeRequest.builder().delete(RECORD1).build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = ChangeRequest.builder().additions(ADDITIONS).build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = ChangeRequest.builder().deletions(DELETIONS).build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = ChangeRequest.builder().startTimeMillis(START_TIME_MILLIS).build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = ChangeRequest.builder().status(STATUS).build();
+ assertEquals(partial, partial.toBuilder().build());
+ }
+
+ @Test
+ public void testClearAdditions() {
+ ChangeRequest clone = CHANGE.toBuilder().clearAdditions().build();
+ assertTrue(clone.additions().isEmpty());
+ assertFalse(clone.deletions().isEmpty());
+ }
+
+ @Test
+ public void testAddAddition() {
+ try {
+ CHANGE.toBuilder().add(null);
+ fail("Should not be able to add null DnsRecord.");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ ChangeRequest clone = CHANGE.toBuilder().add(RECORD1).build();
+ assertEquals(CHANGE.additions().size() + 1, clone.additions().size());
+ }
+
+ @Test
+ public void testAddDeletion() {
+ try {
+ CHANGE.toBuilder().delete(null);
+ fail("Should not be able to delete null DnsRecord.");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ ChangeRequest clone = CHANGE.toBuilder().delete(RECORD1).build();
+ assertEquals(CHANGE.deletions().size() + 1, clone.deletions().size());
+ }
+
+ @Test
+ public void testClearDeletions() {
+ ChangeRequest clone = CHANGE.toBuilder().clearDeletions().build();
+ assertTrue(clone.deletions().isEmpty());
+ assertFalse(clone.additions().isEmpty());
+ }
+
+ @Test
+ public void testRemoveAddition() {
+ ChangeRequest clone = CHANGE.toBuilder().removeAddition(RECORD1).build();
+ assertTrue(clone.additions().contains(RECORD2));
+ assertFalse(clone.additions().contains(RECORD1));
+ assertTrue(clone.deletions().contains(RECORD3));
+ clone = CHANGE.toBuilder().removeAddition(RECORD2).removeAddition(RECORD1).build();
+ assertFalse(clone.additions().contains(RECORD2));
+ assertFalse(clone.additions().contains(RECORD1));
+ assertTrue(clone.additions().isEmpty());
+ assertTrue(clone.deletions().contains(RECORD3));
+ }
+
+ @Test
+ public void testRemoveDeletion() {
+ ChangeRequest clone = CHANGE.toBuilder().removeDeletion(RECORD3).build();
+ assertTrue(clone.deletions().isEmpty());
+ }
+
+ @Test
+ public void testDateParsing() {
+ String startTime = "2016-01-26T18:33:43.512Z"; // obtained from service
+ com.google.api.services.dns.model.Change change = CHANGE.toPb().setStartTime(startTime);
+ ChangeRequest converted = ChangeRequest.fromPb(change);
+ assertNotNull(converted.startTimeMillis());
+ assertEquals(change, converted.toPb());
+ assertEquals(change.getStartTime(), converted.toPb().getStartTime());
+ }
+}
diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/DnsRecordTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/DnsRecordTest.java
index 412aa2ce08ad..7a7daf8aac3c 100644
--- a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/DnsRecordTest.java
+++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/DnsRecordTest.java
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.google.gcloud.dns;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.junit.Assert.assertNotEquals;
import org.junit.Test;