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
index 6ce6b4c19994..7d7138111ae1 100644
--- 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
@@ -533,4 +533,19 @@ ChangeRequest getChangeRequest(String zoneName, String changeRequestId,
* @see Cloud DNS Chages: list
*/
Page listChangeRequests(String zoneName, ChangeRequestListOption... options);
+
+ /**
+ * Submits a {@code batch} for processing to the Google Cloud DNS. The operations in the batch
+ * will be called using a single HTTP request. For each successfully executed operation, its
+ * {@link DnsBatch.Callback#success(Object, DnsBatch.Request)} callback method will be invoked.
+ * For each operation which returned an error, its {@link DnsBatch.Callback#error(DnsException,
+ * DnsBatch.Request)} callback method will be invoked.
+ */
+ void submitBatch(DnsBatch batch);
+
+ /**
+ * Initiates a new empty batch ready to be populated with service calls, which will use this
+ * {@code Dns} instance when submitted for processing to Google Cloud DNS.
+ */
+ DnsBatch batch();
}
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsBatch.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsBatch.java
new file mode 100644
index 000000000000..6c2adf3b867d
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsBatch.java
@@ -0,0 +1,231 @@
+/*
+ * 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.Page;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A batch of operations to be submitted to Google Cloud DNS using a single HTTP request.
+ */
+public class DnsBatch {
+
+ private Map requests = new LinkedHashMap<>();
+ private Dns dns;
+
+ /**
+ * An interface for the callback which will be invoked when the operation has been executed. The
+ * parameter {@code } represents the type of the result of the operation and thus depends on
+ * the {@link DnsBatch.Request} that this call back belongs to and it should be as follows:
+ *
+ *
+ *
{@link Zone} for creating and getting a zone
+ *
{@link Boolean} for deleting a zone
+ *
{@link ChangeRequest} for creating and getting a change request
+ *
{@link ProjectInfo} for getting a project
+ *
{@code Page} for listing zones
+ *
{@code Page} for listing {@link DnsRecord}s inside a zone
+ *
{@code Page} for listing {@link ChangeRequest}s for a zone
+ *
+ */
+ public interface Callback {
+ /**
+ * A method which will be called if the {@link DnsBatch.Request} succeeds. See the {@link
+ * Callback} documentation for details on type {@code T}.
+ *
+ * @param output the result of the operation
+ * @param request the request which succeeded
+ */
+ void success(T output, DnsBatch.Request request);
+
+ /**
+ * A method which will be called if the {@link DnsBatch.Request} fails.
+ *
+ * @param ex the error
+ * @param request the request which failed
+ */
+ void error(DnsException ex, DnsBatch.Request request);
+ }
+
+ /**
+ * An operation to be submitted to Google Cloud DNS within this batch. Only an subset of the class
+ * attributes appropriate for the represented operation is initialized. Refer to the class method
+ * and attribute documentation for the specific fields.
+ */
+ public static class Request {
+
+ private final String zoneName;
+ private final String changeId;
+ private final ChangeRequest changeRequest;
+ private final ZoneInfo zoneInfo;
+ private final Operation operation;
+ private final AbstractOption[] options;
+
+ private Request(RequestBuilder builder) {
+ this.zoneName = builder.zoneName;
+ this.changeId = builder.changeId;
+ this.changeRequest = builder.changeRequest;
+ this.zoneInfo = builder.zoneInfo;
+ this.operation = builder.operation;
+ this.options = builder.options;
+ }
+
+ private static RequestBuilder builder(Operation operation, AbstractOption... options) {
+ return new RequestBuilder(operation, options);
+ }
+
+ /**
+ * Returns the name of the zone to which the operation is applied. This field is initialized for
+ * zone create, get and delete operation, and listing DNS records and changes within a zone.
+ * Returns {@code null} in other cases.
+ */
+ public String zoneName() {
+ return zoneName;
+ }
+
+ /**
+ * Returns the id of the change request which is being retrieved. Getting a change request is
+ * the only operation when this attribute is initialized. The method returns {@code null} in the
+ * remaining cases.
+ */
+ public String changeId() {
+ return changeId;
+ }
+
+ /**
+ * Returns the change request which is being created. Creating a change request is the only
+ * operation when this attribute is initialized. The method returns {@code null} in the
+ * remaining cases.
+ */
+ public ChangeRequest changeRequest() {
+ return changeRequest;
+ }
+
+ /**
+ * Returns the zone which is being created. Creating a zone is the only operation when this
+ * attribute is initialized. The method returns {@code null} in the remaining cases.
+ */
+ public ZoneInfo zoneInfo() {
+ return zoneInfo;
+ }
+
+ /**
+ * Returns the type of the operation represented by this {@link DnsBatch.Request}. This field is
+ * always initialized.
+ */
+ public Operation operation() {
+ return operation;
+ }
+
+ /**
+ * Returns options provided to the operation. Returns an empty array if no options were
+ * provided.
+ */
+ public AbstractOption[] options() {
+ return options == null ? new AbstractOption[0] : options;
+ }
+ }
+
+ static class RequestBuilder {
+ private final AbstractOption[] options;
+ private String zoneName;
+ private String changeId;
+ private ChangeRequest changeRequest;
+ private ZoneInfo zoneInfo;
+ private final Operation operation;
+
+ RequestBuilder(Operation operation, AbstractOption... options) {
+ this.operation = operation;
+ this.options = options;
+ }
+
+ RequestBuilder zoneName(String zoneName) {
+ this.zoneName = zoneName;
+ return this;
+ }
+
+ RequestBuilder changeId(String changeId) {
+ this.changeId = changeId;
+ return this;
+ }
+
+ RequestBuilder changeRequest(ChangeRequest changeRequest) {
+ this.changeRequest = changeRequest;
+ return this;
+ }
+
+ RequestBuilder zoneInfo(ZoneInfo zoneInfo) {
+ this.zoneInfo = zoneInfo;
+ return this;
+ }
+
+ Request build() {
+ return new Request(this);
+ }
+ }
+
+ /**
+ * Represents the type of the batch operation.
+ */
+ public enum Operation {
+ CREATE_ZONE,
+ DELETE_ZONE,
+ GET_ZONE,
+ LIST_ZONES,
+ APPLY_CHANGE_REQUEST,
+ GET_CHANGE_REQUEST,
+ LIST_CHANGES_REQUESTS,
+ LIST_DNS_RECORDS
+ }
+
+ DnsBatch(Dns dns) {
+ this.dns = dns;
+ }
+
+ public Dns service() {
+ return dns;
+ }
+
+ Map requests() {
+ return requests;
+ }
+
+ /**
+ * Adds a {@code DnsBatch.Request} represeting the list zones operation to this batch. The request
+ * will not have initialized any fields except for the operation type and options (if provided).
+ * The {@code callback} will receive a page of {@link Zone}s upon success of the request. The
+ * {@code options} can be used to restrict the fields returned or provide page size limits in the
+ * same way as for {@link Dns#listZones(Dns.ZoneListOption...)}.
+ */
+ public DnsBatch listZones(Callback> callback, Dns.ZoneListOption... options) {
+ Request request = Request.builder(Operation.LIST_ZONES, options).build();
+ requests.put(request, callback);
+ return this;
+ }
+
+ // todo(mderka) add the rest of the operations
+
+ /**
+ * Submits this batch for processing using a single HTTP request. This will invoke all callbacks
+ * for the invidual {@link DnsBatch.Request}s contained in this batch.
+ */
+ public void submit() {
+ dns.submitBatch(this);
+ }
+}
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
index 1ecb98a3fdc6..21df5038a647 100644
--- 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
@@ -16,6 +16,7 @@
package com.google.gcloud.dns;
+import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.common.collect.ImmutableSet;
import com.google.gcloud.BaseServiceException;
import com.google.gcloud.RetryHelper.RetryHelperException;
@@ -43,6 +44,10 @@ public DnsException(IOException exception) {
super(exception, true);
}
+ public DnsException(GoogleJsonError error) {
+ super(error, true);
+ }
+
private DnsException(int code, String message) {
super(code, message, null, true);
}
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
index a60cfd9151da..61888a89de25 100644
--- 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
@@ -21,8 +21,13 @@
import static com.google.gcloud.RetryHelper.runWithRetries;
import static com.google.gcloud.dns.ChangeRequest.fromPb;
+import com.google.api.client.googleapis.batch.BatchRequest;
+import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
+import com.google.api.client.googleapis.json.GoogleJsonError;
+import com.google.api.client.http.HttpHeaders;
import com.google.api.services.dns.model.Change;
import com.google.api.services.dns.model.ManagedZone;
+import com.google.api.services.dns.model.ManagedZonesListResponse;
import com.google.api.services.dns.model.ResourceRecordSet;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
@@ -35,6 +40,7 @@
import com.google.gcloud.RetryHelper;
import com.google.gcloud.dns.spi.DnsRpc;
+import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -309,11 +315,86 @@ public com.google.api.services.dns.model.Change call() {
}
}
+ @Override
+ public void submitBatch(DnsBatch toSubmit) {
+ try {
+ BatchRequest batchRequest = prepareBatch(toSubmit);
+ batchRequest.execute();
+ } catch (IOException ex) {
+ throw new DnsException(ex);
+ }
+ }
+
+ /**
+ * Since {@code BatchRequest} is a final class, it cannot be mocked with easy mock and the call of
+ * {@code execute()} cannot be tested. Thus, most of the functionality of {@link
+ * #submitBatch(DnsBatch)} is extracted to this method which does not make the call so it does not
+ * communicate with the service.
+ */
+ BatchRequest prepareBatch(DnsBatch toSubmit) throws IOException {
+ BatchRequest batch = null;
+ for (Map.Entry entry : toSubmit.requests().entrySet()) {
+ DnsBatch.Request request = entry.getKey();
+ DnsBatch.Callback callback = entry.getValue();
+ switch (request.operation()) {
+ case LIST_ZONES:
+ JsonBatchCallback rpcCallback = listZonesCallback(callback, request);
+ batch =
+ dnsRpc.enqueueListZones(batch, request, rpcCallback, optionMap(request.options()));
+ break;
+ default:
+ // todo(mderka) implement the rest of the operations
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+ }
+ return batch;
+ }
+
+ @Override
+ public DnsBatch batch() {
+ return new DnsBatch(this);
+ }
+
+ private JsonBatchCallback listZonesCallback(final DnsBatch.Callback callback,
+ final DnsBatch.Request request) {
+ return new JsonBatchCallback() {
+ @Override
+ public void onFailure(GoogleJsonError error, HttpHeaders httpHeaders) {
+ if (callback != null) {
+ callback.error(new DnsException(error), request);
+ }
+ }
+
+ @Override
+ public void onSuccess(ManagedZonesListResponse zoneList, HttpHeaders httpHeaders) {
+ if (callback != null) {
+ DnsRpc.ListResult listResult =
+ DnsRpc.ListResult.of(zoneList.getNextPageToken(), zoneList.getManagedZones());
+ String cursor = listResult.pageToken();
+ Iterable zones = listResult.results() == null ? ImmutableList.of()
+ : Iterables.transform(listResult.results(), new Function() {
+ @Override
+ public Zone apply(ManagedZone managedZone) {
+ return new Zone(options().service(),
+ new ZoneInfo.BuilderImpl(ZoneInfo.fromPb(managedZone)));
+ }
+ }
+ );
+ Page page = new PageImpl<>(
+ new ZonePageFetcher(options(), cursor, optionMap(request.options())), cursor, zones);
+ callback.success(page, request);
+ }
+ }
+ };
+ }
+
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);
+ if (options != null) {
+ 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/ZoneInfo.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/ZoneInfo.java
index 38a88b67777e..4d29a6aee4eb 100644
--- 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
@@ -18,6 +18,8 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.api.services.dns.model.ManagedZone;
+import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/spi/DefaultDnsRpc.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/spi/DefaultDnsRpc.java
index f8b8adb87ada..d7f0087732d6 100644
--- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/spi/DefaultDnsRpc.java
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/spi/DefaultDnsRpc.java
@@ -10,6 +10,9 @@
import static com.google.gcloud.dns.spi.DnsRpc.Option.SORTING_ORDER;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
+import com.google.api.client.googleapis.batch.BatchRequest;
+import com.google.api.client.googleapis.batch.json.JsonBatchCallback;
+import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
@@ -21,6 +24,7 @@
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.DnsBatch;
import com.google.gcloud.dns.DnsException;
import com.google.gcloud.dns.DnsOptions;
@@ -40,6 +44,10 @@ private static DnsException translate(IOException exception) {
return new DnsException(exception);
}
+ private static DnsException translate(GoogleJsonError exception) {
+ return new DnsException(exception);
+ }
+
/**
* Constructs an instance of this rpc client with provided {@link DnsOptions}.
*/
@@ -53,6 +61,26 @@ public DefaultDnsRpc(DnsOptions options) {
this.options = options;
}
+ @Override
+ public BatchRequest enqueueListZones(BatchRequest batch, final DnsBatch.Request request,
+ JsonBatchCallback batchCallback, Map