diff --git a/gcloud-java-dns/pom.xml b/gcloud-java-dns/pom.xml
new file mode 100644
index 000000000000..5f04f261d500
--- /dev/null
+++ b/gcloud-java-dns/pom.xml
@@ -0,0 +1,56 @@
+
+
+ 4.0.0
+ com.google.gcloud
+ gcloud-java-dns
+ jar
+ GCloud Java DNS
+
+ Java idiomatic client for Google Cloud DNS.
+
+
+ com.google.gcloud
+ gcloud-java-pom
+ 0.1.3-SNAPSHOT
+
+
+ gcloud-java-dns
+
+
+
+ ${project.groupId}
+ gcloud-java-core
+ ${project.version}
+
+
+ com.google.apis
+ google-api-services-dns
+ v1-rev7-1.21.0
+ compile
+
+
+ com.google.guava
+ guava-jdk5
+
+
+ com.google.api-client
+ google-api-client
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.easymock
+ easymock
+ 3.3
+ test
+
+
+
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/AbstractOption.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/AbstractOption.java
new file mode 100644
index 000000000000..a148468d14b5
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/AbstractOption.java
@@ -0,0 +1,70 @@
+/*
+ * 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.common.base.MoreObjects;
+import com.google.gcloud.spi.DnsRpc;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * A base class for options.
+ */
+abstract class AbstractOption implements Serializable {
+
+ private static final long serialVersionUID = -5912727967831484228L;
+ private final Object value;
+ private final DnsRpc.Option rpcOption;
+
+ AbstractOption(DnsRpc.Option rpcOption, Object value) {
+ this.rpcOption = checkNotNull(rpcOption);
+ this.value = value;
+ }
+
+ Object value() {
+ return value;
+ }
+
+ DnsRpc.Option rpcOption() {
+ return rpcOption;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AbstractOption)) {
+ return false;
+ }
+ AbstractOption other = (AbstractOption) obj;
+ return Objects.equals(value, other.value) && Objects.equals(rpcOption, other.rpcOption);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, rpcOption);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("value", value)
+ .add("rpcOption", rpcOption)
+ .toString();
+ }
+}
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..76d231b704c4
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ChangeRequest.java
@@ -0,0 +1,317 @@
+/*
+ * 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.Change;
+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
+ * Zone}.
+ *
+ * @see Google Cloud DNS documentation
+ */
+public class ChangeRequest implements Serializable {
+
+ static final Function FROM_PB_FUNCTION =
+ new Function() {
+ @Override
+ public ChangeRequest apply(com.google.api.services.dns.model.Change pb) {
+ return ChangeRequest.fromPb(pb);
+ }
+ };
+ private static final long serialVersionUID = -9027378042756366333L;
+ 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(), DnsRecord.TO_PB_FUNCTION));
+ // set a list of deletions
+ pb.setDeletions(Lists.transform(deletions(), DnsRecord.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(), DnsRecord.FROM_PB_FUNCTION));
+ }
+ if (pb.getAdditions() != null) {
+ builder.additions(Lists.transform(pb.getAdditions(), DnsRecord.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/Dns.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/Dns.java
new file mode 100644
index 000000000000..3ad2094ec2e3
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/Dns.java
@@ -0,0 +1,533 @@
+/*
+ * 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 com.google.common.base.Joiner;
+import com.google.common.collect.Sets;
+import com.google.gcloud.Page;
+import com.google.gcloud.Service;
+import com.google.gcloud.spi.DnsRpc;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * An interface for the Google Cloud DNS service.
+ *
+ * @see Google Cloud DNS
+ */
+public interface Dns extends Service {
+
+ /**
+ * The fields of a project.
+ *
+ *
These values can be used to specify the fields to include in a partial response when calling
+ * {@link Dns#getProject(ProjectOption...)}. Project ID is always returned, even if not
+ * specified.
+ */
+ enum ProjectField {
+ PROJECT_ID("id"),
+ PROJECT_NUMBER("number"),
+ QUOTA("quota");
+
+ private final String selector;
+
+ ProjectField(String selector) {
+ this.selector = selector;
+ }
+
+ String selector() {
+ return selector;
+ }
+
+ static String selector(ProjectField... fields) {
+ Set fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1);
+ fieldStrings.add(PROJECT_ID.selector());
+ for (ProjectField field : fields) {
+ fieldStrings.add(field.selector());
+ }
+ return Joiner.on(',').join(fieldStrings);
+ }
+ }
+
+ /**
+ * The fields of a zone.
+ *
+ *
These values can be used to specify the fields to include in a partial response when calling
+ * {@link Dns#getZone(String, ZoneOption...)}. The name is always returned, even if not specified.
+ */
+ enum ZoneField {
+ CREATION_TIME("creationTime"),
+ DESCRIPTION("description"),
+ DNS_NAME("dnsName"),
+ ZONE_ID("id"),
+ NAME("name"),
+ NAME_SERVER_SET("nameServerSet"),
+ NAME_SERVERS("nameServers");
+
+ private final String selector;
+
+ ZoneField(String selector) {
+ this.selector = selector;
+ }
+
+ String selector() {
+ return selector;
+ }
+
+ static String selector(ZoneField... fields) {
+ Set fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1);
+ fieldStrings.add(NAME.selector());
+ for (ZoneField field : fields) {
+ fieldStrings.add(field.selector());
+ }
+ return Joiner.on(',').join(fieldStrings);
+ }
+ }
+
+ /**
+ * The fields of a DNS record.
+ *
+ *
These values can be used to specify the fields to include in a partial response when calling
+ * {@link Dns#listDnsRecords(String, DnsRecordListOption...)}. The name is always returned even if
+ * not selected.
+ */
+ enum DnsRecordField {
+ DNS_RECORDS("rrdatas"),
+ NAME("name"),
+ TTL("ttl"),
+ TYPE("type");
+
+ private final String selector;
+
+ DnsRecordField(String selector) {
+ this.selector = selector;
+ }
+
+ String selector() {
+ return selector;
+ }
+
+ static String selector(DnsRecordField... fields) {
+ Set fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1);
+ fieldStrings.add(NAME.selector());
+ for (DnsRecordField field : fields) {
+ fieldStrings.add(field.selector());
+ }
+ return Joiner.on(',').join(fieldStrings);
+ }
+ }
+
+ /**
+ * The fields of a change request.
+ *
+ *
These values can be used to specify the fields to include in a partial response when calling
+ * {@link Dns#applyChangeRequest(String, ChangeRequest, ChangeRequestOption...)} The ID is always
+ * returned even if not selected.
+ */
+ enum ChangeRequestField {
+ ID("id"),
+ START_TIME("startTime"),
+ STATUS("status"),
+ ADDITIONS("additions"),
+ DELETIONS("deletions");
+
+ private final String selector;
+
+ ChangeRequestField(String selector) {
+ this.selector = selector;
+ }
+
+ String selector() {
+ return selector;
+ }
+
+ static String selector(ChangeRequestField... fields) {
+ Set fieldStrings = Sets.newHashSetWithExpectedSize(fields.length + 1);
+ fieldStrings.add(ID.selector());
+ for (ChangeRequestField field : fields) {
+ fieldStrings.add(field.selector());
+ }
+ return Joiner.on(',').join(fieldStrings);
+ }
+ }
+
+ /**
+ * The sorting order for listing.
+ */
+ enum SortingOrder {
+ DESCENDING, ASCENDING;
+
+ public String selector() {
+ return name().toLowerCase();
+ }
+ }
+
+ /**
+ * Class that for specifying DNS record options.
+ */
+ class DnsRecordListOption extends AbstractOption implements Serializable {
+
+ private static final long serialVersionUID = 1009627025381096098L;
+
+ DnsRecordListOption(DnsRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify the DNS record's fields to be returned by the RPC call.
+ *
+ *
If this option is not provided all record fields are returned. {@code
+ * DnsRecordField.fields} can be used to specify only the fields of interest. The name of the
+ * DNS record always returned, even if not specified. {@link DnsRecordField} provides a list of
+ * fields that can be used.
+ */
+ public static DnsRecordListOption fields(DnsRecordField... fields) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("rrsets(").append(DnsRecordField.selector(fields)).append(')');
+ return new DnsRecordListOption(DnsRpc.Option.FIELDS, builder.toString());
+ }
+
+ /**
+ * Returns an option to specify a page token.
+ *
+ *
The page token (returned from a previous call to list) indicates from where listing should
+ * continue.
+ */
+ public static DnsRecordListOption pageToken(String pageToken) {
+ return new DnsRecordListOption(DnsRpc.Option.PAGE_TOKEN, pageToken);
+ }
+
+ /**
+ * The maximum number of DNS records to return per RPC.
+ *
+ *
The server can return fewer records than requested. When there are more results than the
+ * page size, the server will return a page token that can be used to fetch other results.
+ */
+ public static DnsRecordListOption pageSize(int pageSize) {
+ return new DnsRecordListOption(DnsRpc.Option.PAGE_SIZE, pageSize);
+ }
+
+ /**
+ * Restricts the list to only DNS records with this fully qualified domain name.
+ */
+ public static DnsRecordListOption dnsName(String dnsName) {
+ return new DnsRecordListOption(DnsRpc.Option.NAME, dnsName);
+ }
+
+ /**
+ * Restricts the list to return only records of this type. If present, {@link
+ * Dns.DnsRecordListOption#dnsName(String)} must also be present.
+ */
+ public static DnsRecordListOption type(DnsRecord.Type type) {
+ return new DnsRecordListOption(DnsRpc.Option.DNS_TYPE, type);
+ }
+ }
+
+ /**
+ * Class for specifying zone field options.
+ */
+ class ZoneOption extends AbstractOption implements Serializable {
+
+ private static final long serialVersionUID = -8065564464895945037L;
+
+ ZoneOption(DnsRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify the zones's fields to be returned by the RPC call.
+ *
+ *
If this option is not provided all zone fields are returned. {@code ZoneOption.fields} can
+ * be used to specify only the fields of interest. Zone ID is always returned, even if not
+ * specified. {@link ZoneField} provides a list of fields that can be used.
+ */
+ public static ZoneOption fields(ZoneField... fields) {
+ return new ZoneOption(DnsRpc.Option.FIELDS, ZoneField.selector(fields));
+ }
+ }
+
+ /**
+ * Class for specifying zone listing options.
+ */
+ class ZoneListOption extends AbstractOption implements Serializable {
+
+ private static final long serialVersionUID = -2830645032124504717L;
+
+ ZoneListOption(DnsRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify the zones's fields to be returned by the RPC call.
+ *
+ *
If this option is not provided all zone fields are returned. {@code ZoneOption.fields} can
+ * be used to specify only the fields of interest. Zone ID is always returned, even if not
+ * specified. {@link ZoneField} provides a list of fields that can be used.
+ */
+ public static ZoneListOption fields(ZoneField... fields) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("managedZones(").append(ZoneField.selector(fields)).append(')');
+ return new ZoneListOption(DnsRpc.Option.FIELDS, builder.toString());
+ }
+
+ /**
+ * Returns an option to specify a page token.
+ *
+ *
The page token (returned from a previous call to list) indicates from where listing should
+ * continue.
+ */
+ public static ZoneListOption pageToken(String pageToken) {
+ return new ZoneListOption(DnsRpc.Option.PAGE_TOKEN, pageToken);
+ }
+
+ /**
+ * Restricts the list to only zone with this fully qualified domain name.
+ */
+ public static ZoneListOption dnsName(String dnsName) {
+ StringBuilder builder = new StringBuilder();
+ return new ZoneListOption(DnsRpc.Option.DNS_NAME, dnsName);
+ }
+
+ /**
+ * The maximum number of zones to return per RPC.
+ *
+ *
The server can return fewer zones than requested. When there are more results than the
+ * page size, the server will return a page token that can be used to fetch other results.
+ */
+ public static ZoneListOption pageSize(int pageSize) {
+ return new ZoneListOption(DnsRpc.Option.PAGE_SIZE, pageSize);
+ }
+ }
+
+ /**
+ * Class for specifying project options.
+ */
+ class ProjectOption extends AbstractOption implements Serializable {
+
+ private static final long serialVersionUID = 6817937338218847748L;
+
+ ProjectOption(DnsRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify the project's fields to be returned by the RPC call.
+ *
+ *
If this option is not provided all project fields are returned. {@code
+ * ProjectOption.fields} can be used to specify only the fields of interest. Project ID is
+ * always returned, even if not specified. {@link ProjectField} provides a list of fields that
+ * can be used.
+ */
+ public static ProjectOption fields(ProjectField... fields) {
+ return new ProjectOption(DnsRpc.Option.FIELDS, ProjectField.selector(fields));
+ }
+ }
+
+ /**
+ * Class for specifying change request field options.
+ */
+ class ChangeRequestOption extends AbstractOption implements Serializable {
+
+ private static final long serialVersionUID = 1067273695061077782L;
+
+ ChangeRequestOption(DnsRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify which fields of {@link ChangeRequest} should be returned by the
+ * service.
+ *
+ *
If this option is not provided all change request fields are returned. {@code
+ * ChangeRequestOption.fields} can be used to specify only the fields of interest. The ID of the
+ * change request is always returned, even if not specified. {@link ChangeRequestField} provides
+ * a list of fields that can be used.
+ */
+ public static ChangeRequestOption fields(ChangeRequestField... fields) {
+ return new ChangeRequestOption(
+ DnsRpc.Option.FIELDS,
+ ChangeRequestField.selector(fields)
+ );
+ }
+ }
+
+ /**
+ * Class for specifying change request listing options.
+ */
+ class ChangeRequestListOption extends AbstractOption implements Serializable {
+
+ private static final long serialVersionUID = -900209143895376089L;
+
+ ChangeRequestListOption(DnsRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify which fields of{@link ChangeRequest} should be returned by the
+ * service.
+ *
+ *
If this option is not provided all change request fields are returned. {@code
+ * ChangeRequestOption.fields} can be used to specify only the fields of interest. The ID of the
+ * change request is always returned, even if not specified. {@link ChangeRequestField} provides
+ * a list of fields that can be used.
+ */
+ public static ChangeRequestListOption fields(ChangeRequestField... fields) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("changes(").append(ChangeRequestField.selector(fields)).append(')');
+ return new ChangeRequestListOption(DnsRpc.Option.FIELDS, builder.toString());
+ }
+
+ /**
+ * Returns an option to specify a page token.
+ *
+ *
The page token (returned from a previous call to list) indicates from where listing should
+ * continue.
+ */
+ public static ChangeRequestListOption pageToken(String pageToken) {
+ return new ChangeRequestListOption(DnsRpc.Option.PAGE_TOKEN, pageToken);
+ }
+
+ /**
+ * The maximum number of change requests to return per RPC.
+ *
+ *
The server can return fewer change requests than requested. When there are more results
+ * than the page size, the server will return a page token that can be used to fetch other
+ * results.
+ */
+ public static ChangeRequestListOption pageSize(int pageSize) {
+ return new ChangeRequestListOption(DnsRpc.Option.PAGE_SIZE, pageSize);
+ }
+
+ /**
+ * Returns an option to specify whether the the change requests should be listed in ascending
+ * (most-recent last) or descending (most-recent first) order with respect to when the change
+ * request was accepted by the server. If this option is not provided, the listing order is
+ * undefined.
+ */
+ public static ChangeRequestListOption sortOrder(SortingOrder order) {
+ return new ChangeRequestListOption(DnsRpc.Option.SORTING_ORDER, order.selector());
+ }
+ }
+
+ /**
+ * Creates a new zone.
+ *
+ *
Returns {@link Zone} object representing the new zone's information. In addition to the
+ * name, dns name and description (supplied by the user within the {@code zoneInfo} parameter),
+ * the returned object can include the following read-only fields supplied by the server: creation
+ * time, id, and list of name servers. The returned fields can be optionally restricted by
+ * specifying {@link ZoneOption}s.
+ *
+ * @throws DnsException upon failure
+ * @see Cloud DNS Managed Zones:
+ * create
+ */
+ Zone create(ZoneInfo zoneInfo, ZoneOption... options);
+
+ /**
+ * Returns the zone by the specified zone name. Returns {@code null} if the zone is not found. The
+ * returned fields can be optionally restricted by specifying {@link ZoneOption}s.
+ *
+ * @throws DnsException upon failure
+ * @see Cloud DNS Managed Zones:
+ * get
+ */
+ Zone getZone(String zoneName, ZoneOption... options);
+
+ /**
+ * Lists the zones inside the project.
+ *
+ *
This method returns zones in an unspecified order. New zones do not necessarily appear at
+ * the end of the list. Use {@link ZoneListOption} to restrict the listing to a domain name, set
+ * page size, and set page token.
+ *
+ * @return a page of zones
+ * @throws DnsException upon failure
+ * @see Cloud DNS Managed Zones:
+ * list
+ */
+ Page listZones(ZoneListOption... options);
+
+ /**
+ * Deletes an existing zone identified by name. Returns {@code true} if the zone was successfully
+ * deleted and {@code false} otherwise.
+ *
+ * @return {@code true} if zone was found and deleted and {@code false} otherwise
+ * @throws DnsException upon failure
+ * @see Cloud DNS Managed Zones:
+ * delete
+ */
+ boolean delete(String zoneName); // delete does not admit any options
+
+ /**
+ * Lists the DNS records in the zone identified by name.
+ *
+ *
The fields to be returned, page size and page tokens can be specified using {@link
+ * DnsRecordListOption}s.
+ *
+ * @throws DnsException upon failure or if the zone cannot be found
+ * @see Cloud DNS
+ * ResourceRecordSets: list
+ */
+ Page listDnsRecords(String zoneName, DnsRecordListOption... options);
+
+ /**
+ * Retrieves the information about the current project. The returned fields can be optionally
+ * restricted by specifying {@link ProjectOption}s.
+ *
+ * @throws DnsException upon failure
+ * @see Cloud DNS Projects: get
+ */
+ ProjectInfo getProject(ProjectOption... fields);
+
+ /**
+ * Submits a change request for the specified zone. The returned object contains the following
+ * read-only fields supplied by the server: id, start time and status. time, id, and list of name
+ * servers. The fields to be returned can be selected by {@link ChangeRequestOption}s.
+ *
+ * @return the new {@link ChangeRequest}
+ * @throws DnsException upon failure if zone is not found
+ * @see Cloud DNS Changes: create
+ */
+ ChangeRequest applyChangeRequest(String zoneName, ChangeRequest changeRequest,
+ ChangeRequestOption... options);
+
+ /**
+ * Retrieves updated information about a change request previously submitted for a zone identified
+ * by ID. Returns {@code null} if the request cannot be found and throws an exception if the zone
+ * does not exist. The fields to be returned using can be specified using {@link
+ * ChangeRequestOption}s.
+ *
+ * @throws DnsException upon failure or if the zone cannot be found
+ * @see Cloud DNS Chages: get
+ */
+ ChangeRequest getChangeRequest(String zoneName, String changeRequestId,
+ ChangeRequestOption... options);
+
+ /**
+ * Lists the change requests for the zone identified by name that were submitted to the service.
+ *
+ *
The sorting order for changes (based on when they were received by the server), fields to be
+ * returned, page size and page token can be specified using {@link ChangeRequestListOption}s.
+ *
+ * @return A page of change requests
+ * @throws DnsException upon failure or if the zone cannot be found
+ * @see Cloud DNS Chages: list
+ */
+ Page listChangeRequests(String zoneName, ChangeRequestListOption... options);
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsException.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsException.java
new file mode 100644
index 000000000000..25fafcb69df9
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsException.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.google.common.collect.ImmutableSet;
+import com.google.gcloud.BaseServiceException;
+import com.google.gcloud.RetryHelper.RetryHelperException;
+import com.google.gcloud.RetryHelper.RetryInterruptedException;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * DNS service exception.
+ */
+public class DnsException extends BaseServiceException {
+
+ // see: https://cloud.google.com/dns/troubleshooting
+ private static final Set RETRYABLE_ERRORS = ImmutableSet.of(
+ new Error(404, null),
+ new Error(409, null),
+ new Error(500, null),
+ new Error(502, null),
+ new Error(503, null));
+ private static final long serialVersionUID = 490302380416260252L;
+
+ public DnsException(IOException exception) {
+ super(exception, true);
+ }
+
+ public DnsException(int code, String message) {
+ super(code, message, null, true);
+ }
+
+ @Override
+ protected Set retryableErrors() {
+ return RETRYABLE_ERRORS;
+ }
+
+ /**
+ * Translate RetryHelperException to the DnsException that caused the error. This method will
+ * always throw an exception.
+ *
+ * @throws DnsException when {@code ex} was caused by a {@code DnsException}
+ * @throws RetryInterruptedException when {@code ex} is a {@code RetryInterruptedException}
+ */
+ static DnsException translateAndThrow(RetryHelperException ex) {
+ BaseServiceException.translateAndPropagateIfPossible(ex);
+ throw new DnsException(UNKNOWN_CODE, ex.getMessage());
+ }
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsFactory.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsFactory.java
new file mode 100644
index 000000000000..734652afb24d
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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 com.google.gcloud.ServiceFactory;
+
+/**
+ * An interface for Dns factories.
+ */
+public interface DnsFactory extends ServiceFactory {
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsImpl.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsImpl.java
new file mode 100644
index 000000000000..17521c13c625
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsImpl.java
@@ -0,0 +1,320 @@
+/*
+ * 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.checkArgument;
+import static com.google.gcloud.RetryHelper.RetryHelperException;
+import static com.google.gcloud.RetryHelper.runWithRetries;
+import static com.google.gcloud.dns.ChangeRequest.fromPb;
+
+import com.google.api.services.dns.model.Change;
+import com.google.api.services.dns.model.ManagedZone;
+import com.google.api.services.dns.model.ResourceRecordSet;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.gcloud.BaseService;
+import com.google.gcloud.Page;
+import com.google.gcloud.PageImpl;
+import com.google.gcloud.RetryHelper;
+import com.google.gcloud.spi.DnsRpc;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * A default implementation of Dns.
+ */
+final class DnsImpl extends BaseService implements Dns {
+
+ private final DnsRpc dnsRpc;
+
+ private static class ZonePageFetcher implements PageImpl.NextPageFetcher {
+
+ private static final long serialVersionUID = 2158209410430566961L;
+ private final Map requestOptions;
+ private final DnsOptions serviceOptions;
+
+ ZonePageFetcher(DnsOptions serviceOptions, String cursor,
+ Map optionMap) {
+ this.requestOptions =
+ PageImpl.nextRequestOptions(DnsRpc.Option.PAGE_TOKEN, cursor, optionMap);
+ this.serviceOptions = serviceOptions;
+ }
+
+ @Override
+ public Page nextPage() {
+ return listZones(serviceOptions, requestOptions);
+ }
+ }
+
+ private static class ChangeRequestPageFetcher implements PageImpl.NextPageFetcher {
+
+ private static final long serialVersionUID = 4473265130673029139L;
+ private final String zoneName;
+ private final Map requestOptions;
+ private final DnsOptions serviceOptions;
+
+ ChangeRequestPageFetcher(String zoneName, DnsOptions serviceOptions, String cursor,
+ Map optionMap) {
+ this.zoneName = zoneName;
+ this.requestOptions =
+ PageImpl.nextRequestOptions(DnsRpc.Option.PAGE_TOKEN, cursor, optionMap);
+ this.serviceOptions = serviceOptions;
+ }
+
+ @Override
+ public Page nextPage() {
+ return listChangeRequests(zoneName, serviceOptions, requestOptions);
+ }
+ }
+
+ private static class DnsRecordPageFetcher implements PageImpl.NextPageFetcher {
+
+ private static final long serialVersionUID = -6039369212511530846L;
+ private final Map requestOptions;
+ private final DnsOptions serviceOptions;
+ private final String zoneName;
+
+ DnsRecordPageFetcher(String zoneName, DnsOptions serviceOptions, String cursor,
+ Map optionMap) {
+ this.zoneName = zoneName;
+ this.requestOptions =
+ PageImpl.nextRequestOptions(DnsRpc.Option.PAGE_TOKEN, cursor, optionMap);
+ this.serviceOptions = serviceOptions;
+ }
+
+ @Override
+ public Page nextPage() {
+ return listDnsRecords(zoneName, serviceOptions, requestOptions);
+ }
+ }
+
+ DnsImpl(DnsOptions options) {
+ super(options);
+ dnsRpc = options.rpc();
+ }
+
+ @Override
+ public Page listZones(ZoneListOption... options) {
+ return listZones(options(), optionMap(options));
+ }
+
+ private static Page listZones(final DnsOptions serviceOptions,
+ final Map optionsMap) {
+ // define transformation function
+ // this differs from the other list operations since zone is functional and requires dns service
+ Function pbToZoneFunction = new Function() {
+ @Override
+ public Zone apply(
+ com.google.api.services.dns.model.ManagedZone zonePb) {
+ return Zone.fromPb(serviceOptions.service(), zonePb);
+ }
+ };
+ try {
+ // get a list of managed zones
+ final DnsRpc rpc = serviceOptions.rpc();
+ DnsRpc.ListResult result =
+ runWithRetries(new Callable>() {
+ @Override
+ public DnsRpc.ListResult call() {
+ return rpc.listZones(optionsMap);
+ }
+ }, serviceOptions.retryParams(), EXCEPTION_HANDLER);
+ String cursor = result.pageToken();
+ // transform that list into zone objects
+ Iterable zones = result.results() == null
+ ? ImmutableList.of() : Iterables.transform(result.results(), pbToZoneFunction);
+ return new PageImpl<>(new ZonePageFetcher(serviceOptions, cursor, optionsMap),
+ cursor, zones);
+ } catch (RetryHelperException e) {
+ throw DnsException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Page listChangeRequests(String zoneName,
+ ChangeRequestListOption... options) {
+ return listChangeRequests(zoneName, options(), optionMap(options));
+ }
+
+ private static Page listChangeRequests(final String zoneName,
+ final DnsOptions serviceOptions, final Map optionsMap) {
+ try {
+ // get a list of changes
+ final DnsRpc rpc = serviceOptions.rpc();
+ DnsRpc.ListResult result = runWithRetries(new Callable>() {
+ @Override
+ public DnsRpc.ListResult call() {
+ return rpc.listChangeRequests(zoneName, optionsMap);
+ }
+ }, serviceOptions.retryParams(), EXCEPTION_HANDLER);
+ String cursor = result.pageToken();
+ // transform that list into change request objects
+ Iterable changes = result.results() == null
+ ? ImmutableList.of()
+ : Iterables.transform(result.results(), ChangeRequest.FROM_PB_FUNCTION);
+ return new PageImpl<>(new ChangeRequestPageFetcher(zoneName, serviceOptions, cursor,
+ optionsMap), cursor, changes);
+ } catch (RetryHelperException e) {
+ throw DnsException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Page listDnsRecords(String zoneName, DnsRecordListOption... options) {
+ return listDnsRecords(zoneName, options(), optionMap(options));
+ }
+
+ private static Page listDnsRecords(final String zoneName,
+ final DnsOptions serviceOptions, final Map optionsMap) {
+ try {
+ // get a list of resource record sets
+ final DnsRpc rpc = serviceOptions.rpc();
+ DnsRpc.ListResult result = runWithRetries(
+ new Callable>() {
+ @Override
+ public DnsRpc.ListResult call() {
+ return rpc.listDnsRecords(zoneName, optionsMap);
+ }
+ }, serviceOptions.retryParams(), EXCEPTION_HANDLER);
+ String cursor = result.pageToken();
+ // transform that list into dns records
+ Iterable records = result.results() == null
+ ? ImmutableList.of()
+ : Iterables.transform(result.results(), DnsRecord.FROM_PB_FUNCTION);
+ return new PageImpl<>(new DnsRecordPageFetcher(zoneName, serviceOptions, cursor, optionsMap),
+ cursor, records);
+ } catch (RetryHelperException e) {
+ throw DnsException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Zone create(final ZoneInfo zoneInfo, Dns.ZoneOption... options) {
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.dns.model.ManagedZone answer = runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.dns.model.ManagedZone call() {
+ return dnsRpc.create(zoneInfo.toPb(), optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : Zone.fromPb(this, answer);
+ } catch (RetryHelper.RetryHelperException ex) {
+ throw DnsException.translateAndThrow(ex);
+ }
+ }
+
+ @Override
+ public Zone getZone(final String zoneName, Dns.ZoneOption... options) {
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.dns.model.ManagedZone answer = runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.dns.model.ManagedZone call() {
+ return dnsRpc.getZone(zoneName, optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : Zone.fromPb(this, answer);
+ } catch (RetryHelper.RetryHelperException ex) {
+ throw DnsException.translateAndThrow(ex);
+ }
+ }
+
+ @Override
+ public boolean delete(final String zoneName) {
+ try {
+ return runWithRetries(new Callable() {
+ @Override
+ public Boolean call() {
+ return dnsRpc.deleteZone(zoneName);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ } catch (RetryHelper.RetryHelperException ex) {
+ throw DnsException.translateAndThrow(ex);
+ }
+ }
+
+ @Override
+ public ProjectInfo getProject(Dns.ProjectOption... fields) {
+ final Map optionsMap = optionMap(fields);
+ try {
+ com.google.api.services.dns.model.Project answer = runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.dns.model.Project call() {
+ return dnsRpc.getProject(optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : ProjectInfo.fromPb(answer); // should never be null
+ } catch (RetryHelper.RetryHelperException ex) {
+ throw DnsException.translateAndThrow(ex);
+ }
+ }
+
+ @Override
+ public ChangeRequest applyChangeRequest(final String zoneName, final ChangeRequest changeRequest,
+ Dns.ChangeRequestOption... options) {
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.dns.model.Change answer =
+ runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.dns.model.Change call() {
+ return dnsRpc.applyChangeRequest(zoneName, changeRequest.toPb(), optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : fromPb(answer); // should never be null
+ } catch (RetryHelper.RetryHelperException ex) {
+ throw DnsException.translateAndThrow(ex);
+ }
+ }
+
+ @Override
+ public ChangeRequest getChangeRequest(final String zoneName, final String changeRequestId,
+ Dns.ChangeRequestOption... options) {
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.dns.model.Change answer =
+ runWithRetries(
+ new Callable() {
+ @Override
+ public com.google.api.services.dns.model.Change call() {
+ return dnsRpc.getChangeRequest(zoneName, changeRequestId, optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : fromPb(answer);
+ } catch (RetryHelper.RetryHelperException ex) {
+ throw DnsException.translateAndThrow(ex);
+ }
+ }
+
+ private Map optionMap(AbstractOption... options) {
+ Map temp = Maps.newEnumMap(DnsRpc.Option.class);
+ for (AbstractOption option : options) {
+ Object prev = temp.put(option.rpcOption(), option.value());
+ checkArgument(prev == null, "Duplicate option %s", option);
+ }
+ return ImmutableMap.copyOf(temp);
+ }
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsOptions.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsOptions.java
new file mode 100644
index 000000000000..d9317546cea0
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsOptions.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2015 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 com.google.common.collect.ImmutableSet;
+import com.google.gcloud.ServiceOptions;
+import com.google.gcloud.spi.DefaultDnsRpc;
+import com.google.gcloud.spi.DnsRpc;
+import com.google.gcloud.spi.DnsRpcFactory;
+
+import java.util.Set;
+
+public class DnsOptions extends ServiceOptions {
+
+ private static final long serialVersionUID = -519128051411747771L;
+ private static final String GC_DNS_RW = "https://www.googleapis.com/auth/ndev.clouddns.readwrite";
+ private static final Set SCOPES = ImmutableSet.of(GC_DNS_RW);
+
+ public static class DefaultDnsFactory implements DnsFactory {
+ private static final DnsFactory INSTANCE = new DefaultDnsFactory();
+
+ @Override
+ public Dns create(DnsOptions options) {
+ return new DnsImpl(options);
+ }
+ }
+
+ public static class DefaultDnsRpcFactory implements DnsRpcFactory {
+
+ private static final DnsRpcFactory INSTANCE = new DefaultDnsRpcFactory();
+
+ @Override
+ public DnsRpc create(DnsOptions options) {
+ return new DefaultDnsRpc(options);
+ }
+ }
+
+ public static class Builder extends ServiceOptions.Builder {
+
+ private Builder() {
+ }
+
+ private Builder(DnsOptions options) {
+ super(options);
+ }
+
+ @Override
+ public DnsOptions build() {
+ return new DnsOptions(this);
+ }
+ }
+
+ private DnsOptions(Builder builder) {
+ super(DnsFactory.class, DnsRpcFactory.class, builder);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected DnsFactory defaultServiceFactory() {
+ return DefaultDnsFactory.INSTANCE;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected DnsRpcFactory defaultRpcFactory() {
+ return DefaultDnsRpcFactory.INSTANCE;
+ }
+
+ @Override
+ protected Set scopes() {
+ return SCOPES;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof DnsOptions && baseEquals((DnsOptions) obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return baseHashCode();
+ }
+}
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
new file mode 100644
index 000000000000..c4e710bd0365
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsRecord.java
@@ -0,0 +1,318 @@
+/*
+ * 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.checkArgument;
+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 com.google.common.primitives.Ints;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class that represents a Google Cloud DNS record set.
+ *
+ *
A {@code DnsRecord} is the unit of data that will be returned by the DNS servers upon a DNS
+ * request for a specific domain. The {@code DnsRecord} holds the current state of the DNS records
+ * that make up a zone. You can read the records but you cannot modify them directly. Rather, you
+ * edit the records in a zone by creating a ChangeRequest.
+ *
+ * @see Google Cloud DNS
+ * documentation
+ */
+public class DnsRecord implements Serializable {
+
+ static final Function FROM_PB_FUNCTION =
+ new Function() {
+ @Override
+ public DnsRecord apply(com.google.api.services.dns.model.ResourceRecordSet pb) {
+ return DnsRecord.fromPb(pb);
+ }
+ };
+ 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 = 8148009870800115261L;
+ private final String name;
+ private final List rrdatas;
+ private final Integer ttl; // this is in seconds
+ private final Type type;
+
+ /**
+ * Enum for the DNS record types supported by Cloud DNS.
+ *
+ *
Google Cloud DNS currently supports records of type A, AAAA, CNAME, MX NAPTR, NS, PTR, SOA,
+ * SPF, SRV, TXT.
+ *
+ * @see Cloud DNS
+ * supported record types
+ */
+ public enum Type {
+ /**
+ * Address record, which is used to map host names to their IPv4 address.
+ */
+ A,
+ /**
+ * IPv6 Address record, which is used to map host names to their IPv6 address.
+ */
+ AAAA,
+ /**
+ * Canonical name record, which is used to alias names.
+ */
+ CNAME,
+ /**
+ * Mail exchange record, which is used in routing requests to mail servers.
+ */
+ MX,
+ /**
+ * Naming authority pointer record, defined by RFC3403.
+ */
+ NAPTR,
+ /**
+ * Name server record, which delegates a DNS zone to an authoritative server.
+ */
+ NS,
+ /**
+ * Pointer record, which is often used for reverse DNS lookups.
+ */
+ PTR,
+ /**
+ * Start of authority record, which specifies authoritative information about a DNS zone.
+ */
+ SOA,
+ /**
+ * Sender policy framework record, which is used in email validation systems.
+ */
+ SPF,
+ /**
+ * Service locator record, which is used by some voice over IP, instant messaging protocols and
+ * other applications.
+ */
+ SRV,
+ /**
+ * Text record, which can contain arbitrary text and can also be used to define machine readable
+ * data such as security or abuse prevention information.
+ */
+ TXT
+ }
+
+ /**
+ * A builder of {@link DnsRecord}.
+ */
+ public static class Builder {
+
+ private List rrdatas = new LinkedList<>();
+ private String name;
+ private Integer ttl;
+ private Type type;
+
+ private Builder(String name, Type type) {
+ this.name = checkNotNull(name);
+ this.type = checkNotNull(type);
+ }
+
+ /**
+ * Creates a builder and pre-populates attributes with the values from the provided {@code
+ * DnsRecord} instance.
+ */
+ private Builder(DnsRecord record) {
+ this.name = record.name;
+ this.ttl = record.ttl;
+ this.type = record.type;
+ this.rrdatas.addAll(record.rrdatas);
+ }
+
+ /**
+ * Adds a record to the record set. The records should be as defined in RFC 1035 (section 5) and
+ * RFC 1034 (section 3.6.1). Examples of records are available in Google DNS documentation.
+ *
+ * @see Google
+ * DNS documentation .
+ */
+ public Builder addRecord(String record) {
+ this.rrdatas.add(checkNotNull(record));
+ return this;
+ }
+
+ /**
+ * Removes a record from the set. An exact match is required.
+ */
+ public Builder removeRecord(String record) {
+ this.rrdatas.remove(checkNotNull(record));
+ return this;
+ }
+
+ /**
+ * Removes all the records.
+ */
+ public Builder clearRecords() {
+ this.rrdatas.clear();
+ return this;
+ }
+
+ /**
+ * Replaces the current records with the provided list of records.
+ */
+ public Builder records(List records) {
+ this.rrdatas = Lists.newLinkedList(checkNotNull(records));
+ return this;
+ }
+
+ /**
+ * Sets name for this DNS record set. For example, www.example.com.
+ */
+ public Builder name(String name) {
+ this.name = checkNotNull(name);
+ return this;
+ }
+
+ /**
+ * Sets the time that this record can be cached by resolvers. This number must be non-negative.
+ * The maximum duration must be equivalent to at most {@link Integer#MAX_VALUE} seconds.
+ *
+ * @param duration A non-negative number of time units
+ * @param unit The unit of the ttl parameter
+ */
+ public Builder ttl(int duration, TimeUnit unit) {
+ checkArgument(duration >= 0,
+ "Duration cannot be negative. The supplied value was %s.", duration);
+ checkNotNull(unit);
+ // we cannot have long because pb does not support it
+ long converted = unit.toSeconds(duration);
+ ttl = Ints.checkedCast(converted);
+ return this;
+ }
+
+ /**
+ * The identifier of a supported record type, for example, A, AAAA, MX, TXT, and so on.
+ */
+ public Builder type(Type type) {
+ this.type = checkNotNull(type);
+ return this;
+ }
+
+ /**
+ * Builds the DNS record.
+ */
+ public DnsRecord build() {
+ return new DnsRecord(this);
+ }
+ }
+
+ private DnsRecord(Builder builder) {
+ this.name = builder.name;
+ this.rrdatas = ImmutableList.copyOf(builder.rrdatas);
+ this.ttl = builder.ttl;
+ this.type = builder.type;
+ }
+
+ /**
+ * Creates a builder pre-populated with the attribute values of this instance.
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Creates a {@code DnsRecord} builder for the given {@code name} and {@code type}.
+ */
+ public static Builder builder(String name, Type type) {
+ return new Builder(name, type);
+ }
+
+ /**
+ * Returns the user-assigned name of this DNS record.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns a list of DNS record stored in this record set.
+ */
+ public List records() {
+ return rrdatas;
+ }
+
+ /**
+ * Returns the number of seconds that this DnsResource can be cached by resolvers.
+ */
+ public Integer ttl() {
+ return ttl;
+ }
+
+ /**
+ * Returns the type of this DNS record.
+ */
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, rrdatas, ttl, type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof DnsRecord) && Objects.equals(this.toPb(), ((DnsRecord) obj).toPb());
+ }
+
+ com.google.api.services.dns.model.ResourceRecordSet toPb() {
+ com.google.api.services.dns.model.ResourceRecordSet pb =
+ new com.google.api.services.dns.model.ResourceRecordSet();
+ pb.setName(this.name());
+ pb.setRrdatas(this.records());
+ pb.setTtl(this.ttl());
+ pb.setType(this.type().name());
+ return pb;
+ }
+
+ static DnsRecord fromPb(com.google.api.services.dns.model.ResourceRecordSet pb) {
+ Builder builder = builder(pb.getName(), Type.valueOf(pb.getType()));
+ if (pb.getRrdatas() != null) {
+ builder.records(pb.getRrdatas());
+ }
+ if (pb.getTtl() != null) {
+ builder.ttl(pb.getTtl(), TimeUnit.SECONDS);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("name", name())
+ .add("rrdatas", records())
+ .add("ttl", ttl())
+ .add("type", type())
+ .toString();
+ }
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ProjectInfo.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ProjectInfo.java
new file mode 100644
index 000000000000..f7b12b94bae8
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ProjectInfo.java
@@ -0,0 +1,287 @@
+/*
+ * 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.api.client.repackaged.com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.util.Objects;
+
+/**
+ * The class provides the Google Cloud DNS information associated with this project. A project is a
+ * top level container for resources including {@code Zone}s. Projects can be created only in
+ * the APIs console.
+ *
+ * @see Google Cloud DNS documentation
+ */
+public class ProjectInfo implements Serializable {
+
+ private static final long serialVersionUID = 8696578863323485036L;
+ private final String id;
+ private final BigInteger number;
+ private final Quota quota;
+
+ /**
+ * This class represents quotas assigned to the {@code ProjectInfo}.
+ *
+ * @see Google Cloud DNS
+ * documentation
+ */
+ public static class Quota implements Serializable {
+
+ private static final long serialVersionUID = 6854685970605363639L;
+ private final int zones;
+ private final int resourceRecordsPerRrset;
+ private final int rrsetAdditionsPerChange;
+ private final int rrsetDeletionsPerChange;
+ private final int rrsetsPerZone;
+ private final int totalRrdataSizePerChange;
+
+ /**
+ * Creates an instance of {@code Quota}.
+ *
+ *
This is the only way of creating an instance of {@code Quota}. As the service does not
+ * allow for specifying options, quota is an "all-or-nothing object" and we do not need a
+ * builder.
+ */
+ Quota(int zones,
+ int resourceRecordsPerRrset,
+ int rrsetAdditionsPerChange,
+ int rrsetDeletionsPerChange,
+ int rrsetsPerZone,
+ int totalRrdataSizePerChange) {
+ this.zones = zones;
+ this.resourceRecordsPerRrset = resourceRecordsPerRrset;
+ this.rrsetAdditionsPerChange = rrsetAdditionsPerChange;
+ this.rrsetDeletionsPerChange = rrsetDeletionsPerChange;
+ this.rrsetsPerZone = rrsetsPerZone;
+ this.totalRrdataSizePerChange = totalRrdataSizePerChange;
+ }
+
+ /**
+ * Returns the maximum allowed number of zones in the project.
+ */
+ public int zones() {
+ return zones;
+ }
+
+ /**
+ * Returns the maximum allowed number of records per {@link DnsRecord}.
+ */
+ public int resourceRecordsPerRrset() {
+ return resourceRecordsPerRrset;
+ }
+
+ /**
+ * Returns the maximum allowed number of {@link DnsRecord}s to add per {@link ChangeRequest}.
+ */
+ public int rrsetAdditionsPerChange() {
+ return rrsetAdditionsPerChange;
+ }
+
+ /**
+ * Returns the maximum allowed number of {@link DnsRecord}s to delete per {@link
+ * ChangeRequest}.
+ */
+ public int rrsetDeletionsPerChange() {
+ return rrsetDeletionsPerChange;
+ }
+
+ /**
+ * Returns the maximum allowed number of {@link DnsRecord}s per {@link ZoneInfo} in the
+ * project.
+ */
+ public int rrsetsPerZone() {
+ return rrsetsPerZone;
+ }
+
+ /**
+ * Returns the maximum allowed size for total records in one ChangesRequest in bytes.
+ */
+ public int totalRrdataSizePerChange() {
+ return totalRrdataSizePerChange;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof Quota) && this.toPb().equals(((Quota) other).toPb());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(zones, resourceRecordsPerRrset, rrsetAdditionsPerChange,
+ rrsetDeletionsPerChange, rrsetsPerZone, totalRrdataSizePerChange);
+ }
+
+ com.google.api.services.dns.model.Quota toPb() {
+ com.google.api.services.dns.model.Quota pb = new com.google.api.services.dns.model.Quota();
+ pb.setManagedZones(zones);
+ pb.setResourceRecordsPerRrset(resourceRecordsPerRrset);
+ pb.setRrsetAdditionsPerChange(rrsetAdditionsPerChange);
+ pb.setRrsetDeletionsPerChange(rrsetDeletionsPerChange);
+ pb.setRrsetsPerManagedZone(rrsetsPerZone);
+ pb.setTotalRrdataSizePerChange(totalRrdataSizePerChange);
+ return pb;
+ }
+
+ static Quota fromPb(com.google.api.services.dns.model.Quota pb) {
+ Quota quota = new Quota(pb.getManagedZones(),
+ pb.getResourceRecordsPerRrset(),
+ pb.getRrsetAdditionsPerChange(),
+ pb.getRrsetDeletionsPerChange(),
+ pb.getRrsetsPerManagedZone(),
+ pb.getTotalRrdataSizePerChange()
+ );
+ return quota;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("zones", zones)
+ .add("resourceRecordsPerRrset", resourceRecordsPerRrset)
+ .add("rrsetAdditionsPerChange", rrsetAdditionsPerChange)
+ .add("rrsetDeletionsPerChange", rrsetDeletionsPerChange)
+ .add("rrsetsPerZone", rrsetsPerZone)
+ .add("totalRrdataSizePerChange", totalRrdataSizePerChange)
+ .toString();
+ }
+ }
+
+ /**
+ * A builder for {@code ProjectInfo}.
+ */
+ static class Builder {
+ private String id;
+ private BigInteger number;
+ private Quota quota;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets an id of the project.
+ */
+ Builder id(String id) {
+ this.id = checkNotNull(id);
+ return this;
+ }
+
+ /**
+ * Sets a number of the project.
+ */
+ Builder number(BigInteger number) {
+ this.number = checkNotNull(number);
+ return this;
+ }
+
+ /**
+ * Sets quotas assigned to the project.
+ */
+ Builder quota(Quota quota) {
+ this.quota = checkNotNull(quota);
+ return this;
+ }
+
+ /**
+ * Builds an instance of the {@code ProjectInfo}.
+ */
+ ProjectInfo build() {
+ return new ProjectInfo(this);
+ }
+ }
+
+ private ProjectInfo(Builder builder) {
+ this.id = builder.id;
+ this.number = builder.number;
+ this.quota = builder.quota;
+ }
+
+ /**
+ * Returns a builder for {@code ProjectInfo}.
+ */
+ static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns the {@code Quota} object which contains quotas assigned to this project.
+ */
+ public Quota quota() {
+ return quota;
+ }
+
+ /**
+ * Returns project number. For internal use only.
+ */
+ BigInteger number() {
+ return number;
+ }
+
+ /**
+ * Returns project id. For internal use only.
+ */
+ String id() {
+ return id;
+ }
+
+ com.google.api.services.dns.model.Project toPb() {
+ com.google.api.services.dns.model.Project pb = new com.google.api.services.dns.model.Project();
+ pb.setId(id);
+ pb.setNumber(number);
+ if (this.quota != null) {
+ pb.setQuota(quota.toPb());
+ }
+ return pb;
+ }
+
+ static ProjectInfo fromPb(com.google.api.services.dns.model.Project pb) {
+ Builder builder = builder();
+ if (pb.getId() != null) {
+ builder.id(pb.getId());
+ }
+ if (pb.getNumber() != null) {
+ builder.number(pb.getNumber());
+ }
+ if (pb.getQuota() != null) {
+ builder.quota(Quota.fromPb(pb.getQuota()));
+ }
+ return builder.build();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof ProjectInfo) && toPb().equals(((ProjectInfo) other).toPb());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, number, quota);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("id", id)
+ .add("number", number)
+ .add("quota", quota)
+ .toString();
+ }
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/Zone.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/Zone.java
new file mode 100644
index 000000000000..41507647543a
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/Zone.java
@@ -0,0 +1,217 @@
+/*
+ * 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.gcloud.Page;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A Google Cloud DNS Zone object.
+ *
+ *
A zone is the container for all of your DNS records that share the same DNS name prefix, for
+ * example, example.com. Zones are automatically assigned a set of name servers when they are
+ * created to handle responding to DNS queries for that zone. A zone has quotas for the number of
+ * resource records that it can include.
+ *
+ * @see Google Cloud DNS managed zone
+ * documentation
+ */
+public class Zone extends ZoneInfo {
+
+ private static final long serialVersionUID = -5817771337847861598L;
+ private final DnsOptions options;
+ private transient Dns dns;
+
+ /**
+ * Builder for {@code Zone}.
+ */
+ public static class Builder extends ZoneInfo.Builder {
+ private final Dns dns;
+ private final ZoneInfo.BuilderImpl infoBuilder;
+
+ private Builder(Zone zone) {
+ this.dns = zone.dns;
+ this.infoBuilder = new ZoneInfo.BuilderImpl(zone);
+ }
+
+ @Override
+ public Builder name(String name) {
+ infoBuilder.name(name);
+ return this;
+ }
+
+ @Override
+ Builder id(String id) {
+ infoBuilder.id(id);
+ return this;
+ }
+
+ @Override
+ Builder creationTimeMillis(long creationTimeMillis) {
+ infoBuilder.creationTimeMillis(creationTimeMillis);
+ return this;
+ }
+
+ @Override
+ public Builder dnsName(String dnsName) {
+ infoBuilder.dnsName(dnsName);
+ return this;
+ }
+
+ @Override
+ public Builder description(String description) {
+ infoBuilder.description(description);
+ return this;
+ }
+
+ @Override
+ Builder nameServerSet(String nameServerSet) {
+ infoBuilder.nameServerSet(nameServerSet);
+ return this;
+ }
+
+ @Override
+ Builder nameServers(List nameServers) {
+ infoBuilder.nameServers(nameServers); // infoBuilder makes a copy
+ return this;
+ }
+
+ @Override
+ public Zone build() {
+ return new Zone(dns, infoBuilder);
+ }
+ }
+
+ Zone(Dns dns, ZoneInfo.BuilderImpl infoBuilder) {
+ super(infoBuilder);
+ this.dns = dns;
+ this.options = dns.options();
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Retrieves the latest information about the zone. The method retrieves the zone by name.
+ *
+ * @param options optional restriction on what fields should be fetched
+ * @return zone object containing updated information or {@code null} if not not found
+ * @throws DnsException upon failure
+ */
+ public Zone reload(Dns.ZoneOption... options) {
+ return dns.getZone(name(), options);
+ }
+
+ /**
+ * Deletes the zone. The method deletes the zone by name.
+ *
+ * @return {@code true} is zone was found and deleted and {@code false} otherwise
+ * @throws DnsException upon failure
+ */
+ public boolean delete() {
+ return dns.delete(name());
+ }
+
+ /**
+ * Lists all {@link DnsRecord}s associated with this zone. The method searches for zone by name.
+ *
+ * @param options optional restriction on listing and fields of {@link DnsRecord}s returned
+ * @return a page of DNS records
+ * @throws DnsException upon failure or if the zone is not found
+ */
+ public Page listDnsRecords(Dns.DnsRecordListOption... options) {
+ return dns.listDnsRecords(name(), options);
+ }
+
+ /**
+ * Submits {@link ChangeRequest} to the service for it to applied to this zone. The method
+ * searches for zone by name.
+ *
+ * @param options optional restriction on what fields of {@link ChangeRequest} should be returned
+ * @return ChangeRequest with server-assigned ID
+ * @throws DnsException upon failure or if the zone is not found
+ */
+ public ChangeRequest applyChangeRequest(ChangeRequest changeRequest,
+ Dns.ChangeRequestOption... options) {
+ checkNotNull(changeRequest);
+ return dns.applyChangeRequest(name(), changeRequest, options);
+ }
+
+ /**
+ * Retrieves an updated information about a change request previously submitted to be applied to
+ * this zone. Returns a {@link ChangeRequest} or {@code null} if the change request was not found.
+ * Throws {@link DnsException} if the zone is not found.
+ *
+ * @param options optional restriction on what fields of {@link ChangeRequest} should be returned
+ * @return updated ChangeRequest
+ * @throws DnsException upon failure or if the zone is not found
+ * @throws NullPointerException if {@code changeRequestId} is null
+ */
+ public ChangeRequest getChangeRequest(String changeRequestId,
+ Dns.ChangeRequestOption... options) {
+ checkNotNull(changeRequestId);
+ return dns.getChangeRequest(name(), changeRequestId, options);
+ }
+
+ /**
+ * Retrieves all change requests for this zone. The method searches for zone by name. Returns a
+ * page of {@link ChangeRequest}s.
+ *
+ * @param options optional restriction on listing and fields to be returned
+ * @return a page of change requests
+ * @throws DnsException upon failure or if the zone is not found
+ */
+ public Page listChangeRequests(Dns.ChangeRequestListOption... options) {
+ return dns.listChangeRequests(name(), options);
+ }
+
+ /**
+ * Returns the {@link Dns} service object associated with this zone.
+ */
+ public Dns dns() {
+ return this.dns;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Zone && Objects.equals(toPb(), ((Zone) obj).toPb())
+ && Objects.equals(options, ((Zone) obj).options);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), options);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ this.dns = options.service();
+ }
+
+ static Zone fromPb(Dns dns, com.google.api.services.dns.model.ManagedZone zone) {
+ ZoneInfo info = ZoneInfo.fromPb(zone);
+ return new Zone(dns, new ZoneInfo.BuilderImpl(info));
+ }
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ZoneInfo.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ZoneInfo.java
new file mode 100644
index 000000000000..7dffbcdd365c
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ZoneInfo.java
@@ -0,0 +1,317 @@
+/*
+ * 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.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.math.BigInteger;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@code Zone} represents a DNS zone hosted by the Google Cloud DNS service. A zone is a subtree
+ * of the DNS namespace under one administrative responsibility. See Google Cloud DNS documentation for
+ * more information.
+ */
+public class ZoneInfo implements Serializable {
+
+ private static final long serialVersionUID = 201601191647L;
+ private final String name;
+ private final String id;
+ private final Long creationTimeMillis;
+ private final String dnsName;
+ private final String description;
+ private final String nameServerSet;
+ private final List nameServers;
+
+ /**
+ * Builder for {@code ZoneInfo}.
+ */
+ public abstract static class Builder {
+ /**
+ * Sets a mandatory user-provided name for the zone. It must be unique within the project.
+ */
+ public abstract Builder name(String name);
+
+ /**
+ * Sets an id for the zone which is assigned to the zone by the server.
+ */
+ abstract Builder id(String id);
+
+ /**
+ * Sets the time when this zone was created.
+ */
+ abstract Builder creationTimeMillis(long creationTimeMillis);
+
+ /**
+ * Sets a mandatory DNS name of this zone, for instance "example.com.".
+ */
+ public abstract Builder dnsName(String dnsName);
+
+ /**
+ * Sets a mandatory description for this zone. The value is a string of at most 1024 characters
+ * which has no effect on the zone's function.
+ */
+ public abstract Builder description(String description);
+
+ /**
+ * Optionally specifies the NameServerSet for this zone. A NameServerSet is a set of DNS name
+ * servers that all host the same zones. Most users will not need to specify this value.
+ */
+ abstract Builder nameServerSet(String nameServerSet);
+ // this should not be included in tooling as per the service owners
+
+ /**
+ * Sets a list of servers that hold the information about the zone. This information is provided
+ * by Google Cloud DNS and is read only.
+ */
+ abstract Builder nameServers(List nameServers);
+
+ /**
+ * Builds the instance of {@code ZoneInfo} based on the information set by this builder.
+ */
+ public abstract ZoneInfo build();
+ }
+
+ static class BuilderImpl extends Builder {
+ private String name;
+ private String id;
+ private Long creationTimeMillis;
+ private String dnsName;
+ private String description;
+ private String nameServerSet;
+ private List nameServers;
+
+ private BuilderImpl(String name) {
+ this.name = checkNotNull(name);
+ }
+
+ /**
+ * Creates a builder from an existing ZoneInfo object.
+ */
+ BuilderImpl(ZoneInfo info) {
+ this.name = info.name;
+ this.id = info.id;
+ this.creationTimeMillis = info.creationTimeMillis;
+ this.dnsName = info.dnsName;
+ this.description = info.description;
+ this.nameServerSet = info.nameServerSet;
+ if (info.nameServers != null) {
+ this.nameServers = ImmutableList.copyOf(info.nameServers);
+ }
+ }
+
+ @Override
+ public Builder name(String name) {
+ this.name = checkNotNull(name);
+ return this;
+ }
+
+ @Override
+ Builder id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ @Override
+ Builder creationTimeMillis(long creationTimeMillis) {
+ this.creationTimeMillis = creationTimeMillis;
+ return this;
+ }
+
+ @Override
+ public Builder dnsName(String dnsName) {
+ this.dnsName = checkNotNull(dnsName);
+ return this;
+ }
+
+ @Override
+ public Builder description(String description) {
+ this.description = checkNotNull(description);
+ return this;
+ }
+
+ @Override
+ Builder nameServerSet(String nameServerSet) {
+ this.nameServerSet = checkNotNull(nameServerSet);
+ return this;
+ }
+
+ @Override
+ Builder nameServers(List nameServers) {
+ checkNotNull(nameServers);
+ this.nameServers = Lists.newLinkedList(nameServers);
+ return this;
+ }
+
+ @Override
+ public ZoneInfo build() {
+ return new ZoneInfo(this);
+ }
+ }
+
+ ZoneInfo(BuilderImpl builder) {
+ this.name = builder.name;
+ this.id = builder.id;
+ this.creationTimeMillis = builder.creationTimeMillis;
+ this.dnsName = builder.dnsName;
+ this.description = builder.description;
+ this.nameServerSet = builder.nameServerSet;
+ this.nameServers = builder.nameServers == null
+ ? null : ImmutableList.copyOf(builder.nameServers);
+ }
+
+ /**
+ * Returns a builder for {@code ZoneInfo} with an assigned {@code name}.
+ */
+ public static Builder builder(String name) {
+ return new BuilderImpl(name);
+ }
+
+ /**
+ * Returns the user-defined name of the zone.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the read-only zone id assigned by the server.
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the time when this zone was created on the server.
+ */
+ public Long creationTimeMillis() {
+ return creationTimeMillis;
+ }
+
+ /**
+ * Returns the DNS name of this zone, for instance "example.com.".
+ */
+ public String dnsName() {
+ return dnsName;
+ }
+
+ /**
+ * Returns the description of this zone.
+ */
+ public String description() {
+ return description;
+ }
+
+ /**
+ * Returns the optionally specified set of DNS name servers that all host this zone. This value is
+ * set only for specific use cases and is left empty for vast majority of users.
+ */
+ public String nameServerSet() {
+ return nameServerSet;
+ }
+
+ /**
+ * The nameservers that the zone should be delegated to. This is defined by the Google DNS cloud.
+ */
+ public List nameServers() {
+ return nameServers == null ? ImmutableList.of() : nameServers;
+ }
+
+ /**
+ * Returns a builder for {@code ZoneInfo} prepopulated with the metadata of this zone.
+ */
+ public Builder toBuilder() {
+ return new BuilderImpl(this);
+ }
+
+ com.google.api.services.dns.model.ManagedZone toPb() {
+ com.google.api.services.dns.model.ManagedZone pb =
+ new com.google.api.services.dns.model.ManagedZone();
+ pb.setDescription(this.description());
+ pb.setDnsName(this.dnsName());
+ if (this.id() != null) {
+ pb.setId(new BigInteger(this.id()));
+ }
+ pb.setName(this.name());
+ pb.setNameServers(this.nameServers); // do use real attribute value which may be null
+ pb.setNameServerSet(this.nameServerSet());
+ if (this.creationTimeMillis() != null) {
+ pb.setCreationTime(ISODateTimeFormat.dateTime()
+ .withZoneUTC()
+ .print(this.creationTimeMillis()));
+ }
+ return pb;
+ }
+
+ static ZoneInfo fromPb(com.google.api.services.dns.model.ManagedZone pb) {
+ Builder builder = new BuilderImpl(pb.getName());
+ if (pb.getDescription() != null) {
+ builder.description(pb.getDescription());
+ }
+ if (pb.getDnsName() != null) {
+ builder.dnsName(pb.getDnsName());
+ }
+ if (pb.getId() != null) {
+ builder.id(pb.getId().toString());
+ }
+ if (pb.getNameServers() != null) {
+ builder.nameServers(pb.getNameServers());
+ }
+ if (pb.getNameServerSet() != null) {
+ builder.nameServerSet(pb.getNameServerSet());
+ }
+ if (pb.getCreationTime() != null) {
+ builder.creationTimeMillis(DateTime.parse(pb.getCreationTime()).getMillis());
+ }
+ return builder.build();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj != null && obj.getClass().equals(ZoneInfo.class)
+ && Objects.equals(toPb(), ((ZoneInfo) obj).toPb());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, id, creationTimeMillis, dnsName,
+ description, nameServerSet, nameServers);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("name", name())
+ .add("id", id())
+ .add("description", description())
+ .add("dnsName", dnsName())
+ .add("nameServerSet", nameServerSet())
+ .add("nameServers", nameServers())
+ .add("creationTimeMillis", creationTimeMillis())
+ .toString();
+ }
+}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java b/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java
new file mode 100644
index 000000000000..6ed9c7e0f216
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/spi/DefaultDnsRpc.java
@@ -0,0 +1,194 @@
+package com.google.gcloud.spi;
+
+import static com.google.gcloud.spi.DnsRpc.ListResult.of;
+import static com.google.gcloud.spi.DnsRpc.Option.DNS_NAME;
+import static com.google.gcloud.spi.DnsRpc.Option.DNS_TYPE;
+import static com.google.gcloud.spi.DnsRpc.Option.FIELDS;
+import static com.google.gcloud.spi.DnsRpc.Option.NAME;
+import static com.google.gcloud.spi.DnsRpc.Option.PAGE_SIZE;
+import static com.google.gcloud.spi.DnsRpc.Option.PAGE_TOKEN;
+import static com.google.gcloud.spi.DnsRpc.Option.SORTING_ORDER;
+import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
+
+import com.google.api.client.http.HttpRequestInitializer;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.jackson.JacksonFactory;
+import com.google.api.services.dns.Dns;
+import com.google.api.services.dns.model.Change;
+import com.google.api.services.dns.model.ChangesListResponse;
+import com.google.api.services.dns.model.ManagedZone;
+import com.google.api.services.dns.model.ManagedZonesListResponse;
+import com.google.api.services.dns.model.Project;
+import com.google.api.services.dns.model.ResourceRecordSet;
+import com.google.api.services.dns.model.ResourceRecordSetsListResponse;
+import com.google.gcloud.dns.DnsException;
+import com.google.gcloud.dns.DnsOptions;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * A default implementation of the DnsRpc interface.
+ */
+public class DefaultDnsRpc implements DnsRpc {
+
+ private static final String SORT_BY = "changeSequence";
+ private final Dns dns;
+ private final DnsOptions options;
+
+ private static DnsException translate(IOException exception) {
+ return new DnsException(exception);
+ }
+
+ /**
+ * Constructs an instance of this rpc client with provided {@link DnsOptions}.
+ */
+ public DefaultDnsRpc(DnsOptions options) {
+ HttpTransport transport = options.httpTransportFactory().create();
+ HttpRequestInitializer initializer = options.httpRequestInitializer();
+ this.dns = new Dns.Builder(transport, new JacksonFactory(), initializer)
+ .setRootUrl(options.host())
+ .setApplicationName(options.applicationName())
+ .build();
+ this.options = options;
+ }
+
+ @Override
+ public ManagedZone create(ManagedZone zone, Map