diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/BatchResult.java b/gcloud-java-core/src/main/java/com/google/gcloud/BatchResult.java new file mode 100644 index 000000000000..8ba86d362ea8 --- /dev/null +++ b/gcloud-java-core/src/main/java/com/google/gcloud/BatchResult.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * The class that holds a single result of a batch call. + */ +public interface BatchResult { + + /** + * Returns {@code true} if the batch has been submitted and the result is available, and {@code + * false} otherwise + */ + boolean submitted(); + + /** + * Returns result of this call. + * + * @throws IllegalArgumentException if the batch has not been submitted yet + * @throws E if an error occured when processing this request + */ + T get() throws E; +} 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..7b2912b0d42d 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,12 @@ ChangeRequest getChangeRequest(String zoneName, String changeRequestId, * @see Cloud DNS Chages: list */ Page listChangeRequests(String zoneName, ChangeRequestListOption... options); + + /** + * 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(); + + void submitBatch(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..73e6233e9116 --- /dev/null +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsBatch.java @@ -0,0 +1,205 @@ +/* + * 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.LinkedList; +import java.util.List; + +/** + * A batch of operations to be submitted to Google Cloud DNS using a single HTTP request. + */ +public class DnsBatch { + + private List requests = new LinkedList<>(); + private Dns dns; + + /** + * 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 final DnsBatchResult result; + + 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; + this.result = builder.result; + } + + private static RequestBuilder builder(Operation operation, DnsBatchResult result, + AbstractOption... options) { + return new RequestBuilder(operation, result, 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; + } + + DnsBatchResult result() { + return result; + } + } + + static class RequestBuilder { + private final AbstractOption[] options; + private String zoneName; + private String changeId; + private ChangeRequest changeRequest; + private ZoneInfo zoneInfo; + private final Operation operation; + private final DnsBatchResult result; + + RequestBuilder(Operation operation, DnsBatchResult result, AbstractOption... options) { + this.operation = operation; + this.options = options; + this.result = result; + } + + 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; + } + + List requests() { + return requests; + } + + /** + * Adds a {@code DnsBatch.Request} representing 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 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 DnsBatchResult> listZones(Dns.ZoneListOption... options) { + DnsBatchResult> result = new DnsBatchResult<>(); + Request request = Request.builder(Operation.LIST_ZONES, result, options).build(); + requests.add(request); + return result; + } + + // todo(mderka) add the rest of the operations + + /** + * Submits this batch for processing using a single HTTP request. + */ + public void submit() { + dns.submitBatch(this); + } +} \ No newline at end of file diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsBatchResult.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsBatchResult.java new file mode 100644 index 000000000000..7eb929f0235e --- /dev/null +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsBatchResult.java @@ -0,0 +1,44 @@ +package com.google.gcloud.dns; + +import com.google.gcloud.BatchResult; + +/** + * This class holds a single result of a batch call to the Cloud DNS. + */ +public class DnsBatchResult implements BatchResult { + + private T result; + private boolean submitted = false; + private DnsException error; + + DnsBatchResult() { + } + + @Override + public boolean submitted() { + return submitted; + } + + @Override + public T get() throws DnsException { + if(!submitted()) { + throw new IllegalStateException("Batch has not been submitted yet"); + } + if(error != null) { + throw error; + } + return result; + } + + void result(T result) { + this.result = result; + } + + void error(DnsException error) { + this.error = error; + } + + void submit() { + this.submitted = true; + } +} 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..bdb7f14feeca 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; @@ -39,6 +40,10 @@ public class DnsException extends BaseServiceException { new Error(null, "rateLimitExceeded")); private static final long serialVersionUID = 490302380416260252L; + public DnsException(GoogleJsonError error) { + super(error, true); + } + public DnsException(IOException exception) { super(exception, 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..67bcdb7406e8 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,8 @@ import com.google.gcloud.RetryHelper; import com.google.gcloud.dns.spi.DnsRpc; +import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.concurrent.Callable; @@ -111,6 +118,16 @@ public Page nextPage() { dnsRpc = options.rpc(); } + private static Function pbToZoneFunction(final DnsOptions options) { + return new Function() { + @Override + public Zone apply( + com.google.api.services.dns.model.ManagedZone zonePb) { + return Zone.fromPb(options.service(), zonePb); + } + }; + } + @Override public Page listZones(ZoneListOption... options) { return listZones(options(), optionMap(options)); @@ -118,15 +135,6 @@ public Page listZones(ZoneListOption... 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(); @@ -139,8 +147,8 @@ public DnsRpc.ListResult call() { }, 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); + Iterable zones = result.results() == null ? ImmutableList.of() + : Iterables.transform(result.results(), pbToZoneFunction(serviceOptions)); return new PageImpl<>(new ZonePageFetcher(serviceOptions, cursor, optionsMap), cursor, zones); } catch (RetryHelperException e) { @@ -309,6 +317,77 @@ public com.google.api.services.dns.model.Change call() { } } + @Override + public DnsBatch batch() { + return new DnsBatch(this); + } + + @Override + public void submitBatch(DnsBatch batch) { + BatchRequest batchRequest = dnsRpc.createBatch(); + for (final DnsBatch.Request request : batch.requests()) { + final Map optionMap = optionMap(request.options()); + JsonBatchCallback callback; + switch (request.operation()) { + case LIST_ZONES: + callback = listZonesCallback(this.options(), request, optionMap); + batchRequest = dnsRpc.prepareListZones(batchRequest, callback, optionMap); + break; + case CREATE_ZONE: + callback = createZoneCallback(this.options(), request, optionMap); + batchRequest = dnsRpc.prepareCreateZone(request.zoneInfo().toPb(), batchRequest, callback, + optionMap); + break; + case DELETE_ZONE: + callback = createZoneCallback(this.options(), request, optionMap); + batchRequest = dnsRpc.prepareDeleteZone(request.zoneName(), batchRequest, callback); + break; + // todo(mderka) implement the rest + } + } + dnsRpc.submitBatch(batchRequest); + } + + // todo(mderka) make methods to prepare other callbacks + private JsonBatchCallback listZonesCallback(final DnsOptions serviceOptions, + final DnsBatch.Request request, final Map optionMap) { + JsonBatchCallback callback = new JsonBatchCallback() { + @Override + public void onSuccess(ManagedZonesListResponse response, HttpHeaders httpHeaders) + throws IOException { + DnsBatchResult result = request.result(); + List zones = response.getManagedZones(); + Page zonePage = new PageImpl<>( + new ZonePageFetcher(options(), response.getNextPageToken(), optionMap), + response.getNextPageToken(), zones == null ? ImmutableList.of() + : Iterables.transform(zones, pbToZoneFunction(serviceOptions))); + result.result(zonePage); + result.submit(); + } + + @Override + public void onFailure(GoogleJsonError googleJsonError, HttpHeaders httpHeaders) + throws IOException { + DnsBatchResult result = request.result(); + result.error(new DnsException(googleJsonError)); + result.submit(); + } + }; + return callback; + } + + // todo(mderka) make methods to prepare other callbacks + private JsonBatchCallback createZoneCallback(final DnsOptions serviceOptions, + final DnsBatch.Request request, final Map optionMap) { + throw new UnsupportedOperationException("not implemented yet"); + } + + // todo(mderka) make methods to prepare other callbacks + private JsonBatchCallback deleteZoneCallback(final DnsOptions serviceOptions, + final DnsBatch.Request request, final Map optionMap) { + throw new UnsupportedOperationException("not implemented yet"); + } + private Map optionMap(AbstractOption... options) { Map temp = Maps.newEnumMap(DnsRpc.Option.class); for (AbstractOption option : options) { 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..909b095c5eee 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 @@ -1,3 +1,19 @@ +/* + * 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.spi; import static com.google.gcloud.dns.spi.DnsRpc.ListResult.of; @@ -10,6 +26,8 @@ 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.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.jackson.JacksonFactory; @@ -85,12 +103,7 @@ public ManagedZone getZone(String zoneName, Map options) throws DnsEx public ListResult listZones(Map options) throws DnsException { // fields, page token, page size try { - ManagedZonesListResponse zoneList = dns.managedZones().list(this.options.projectId()) - .setFields(FIELDS.getString(options)) - .setMaxResults(PAGE_SIZE.getInt(options)) - .setDnsName(DNS_NAME.getString(options)) - .setPageToken(PAGE_TOKEN.getString(options)) - .execute(); + ManagedZonesListResponse zoneList = zoneListRequest(options).execute(); return of(zoneList.getNextPageToken(), zoneList.getManagedZones()); } catch (IOException ex) { throw translate(ex); @@ -193,4 +206,58 @@ public ListResult listChangeRequests(String zoneName, Map opt throw translate(ex); } } + + @Override + public BatchRequest createBatch() { + return dns.batch(); + } + + @Override + public BatchRequest prepareListZones(BatchRequest batch, JsonBatchCallback callback, + Map options) { + try { + zoneListRequest(options).queue(batch, callback); + return batch; + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public BatchRequest prepareCreateZone(ManagedZone zone, BatchRequest batch, + JsonBatchCallback callback, Map options) { + // todo(mderka) implement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public BatchRequest prepareGetZone(String zoneName, BatchRequest batch, + JsonBatchCallback callback, Map options) { + // todo(mderka) implement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public BatchRequest prepareDeleteZone(String zoneName, BatchRequest batch, + JsonBatchCallback callback) { + // todo(mderka) implement + throw new UnsupportedOperationException("not implemented yet"); + } + + @Override + public void submitBatch(BatchRequest batchRequest) { + try { + batchRequest.execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + + private Dns.ManagedZones.List zoneListRequest(Map options) throws IOException { + return dns.managedZones().list(this.options.projectId()) + .setFields(FIELDS.getString(options)) + .setMaxResults(PAGE_SIZE.getInt(options)) + .setDnsName(DNS_NAME.getString(options)) + .setPageToken(PAGE_TOKEN.getString(options)); + } } diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/spi/DnsRpc.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/spi/DnsRpc.java index bde93b99bfdd..4e0b672cc9c0 100644 --- a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/spi/DnsRpc.java +++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/spi/DnsRpc.java @@ -16,6 +16,8 @@ package com.google.gcloud.dns.spi; +import com.google.api.client.googleapis.batch.BatchRequest; +import com.google.api.client.googleapis.batch.json.JsonBatchCallback; import com.google.api.services.dns.model.Change; import com.google.api.services.dns.model.ManagedZone; import com.google.api.services.dns.model.Project; @@ -23,6 +25,7 @@ import com.google.common.collect.ImmutableList; import com.google.gcloud.dns.DnsException; +import java.io.IOException; import java.util.Map; public interface DnsRpc { @@ -171,4 +174,51 @@ Change getChangeRequest(String zoneName, String changeRequestId, Map */ ListResult listChangeRequests(String zoneName, Map options) throws DnsException; + + /** + * Initializes an empty batch. + */ + BatchRequest createBatch(); + + /** + * Prepares a call to "list zones" and adds it to the batch with the provided {@code callback} and + * {@code options}. + * + * @return updated batch + */ + BatchRequest prepareListZones(BatchRequest batch, JsonBatchCallback callback, + Map options); + + /** + * Prepares a call to "create zone" and adds it to the batch with the provided {@code callback} + * and {@code options}. + * + * @return updated batch + */ + BatchRequest prepareCreateZone(ManagedZone zone, BatchRequest batch, JsonBatchCallback callback, + Map options); + + /** + * Prepares a call to "get zone" and adds it to the batch with the provided {@code callback} and + * {@code options}. + * + * @return updated batch + */ + BatchRequest prepareGetZone(String zoneName, BatchRequest batch, JsonBatchCallback callback, + Map options); + + /** + * Prepares a call to "delete zone" and adds it to the batch with the provided {@code callback} + * and {@code options}. + * + * @return updated batch + */ + BatchRequest prepareDeleteZone(String zoneName, BatchRequest batch, JsonBatchCallback callback); + + // todo(mderka) add prepare for every single opration + + /** + * Submits a batch of requests for processing using a single HTTP request to Cloud DNS. + */ + void submitBatch(BatchRequest requests); } diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/it/ITDnsTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/it/ITDnsTest.java index bfea46dfe039..23ca8037a61a 100644 --- a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/it/ITDnsTest.java +++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/it/ITDnsTest.java @@ -25,8 +25,10 @@ import com.google.common.collect.ImmutableList; import com.google.gcloud.Page; +import com.google.gcloud.dns.DnsBatchResult; import com.google.gcloud.dns.ChangeRequest; import com.google.gcloud.dns.Dns; +import com.google.gcloud.dns.DnsBatch; import com.google.gcloud.dns.DnsException; import com.google.gcloud.dns.DnsOptions; import com.google.gcloud.dns.DnsRecord; @@ -952,4 +954,24 @@ public void testListDnsRecords() { clear(); } } + + @Test + public void testListZoneBatch() { + DNS.create(ZONE1); + DNS.create(ZONE_EMPTY_DESCRIPTION); + try { + DnsBatch batch = DNS.batch(); + DnsBatchResult> batchResult = batch.listZones(); + batch.submit(); + assertTrue(batchResult.submitted()); + Iterator iteratorBatch = batchResult.get().iterateAll(); + Iterator iteratorList = DNS.listZones().iterateAll(); + while(iteratorBatch.hasNext()) { + assertEquals(iteratorList.next(), iteratorBatch.next()); + } + } finally { + DNS.delete(ZONE1.name()); + DNS.delete(ZONE_EMPTY_DESCRIPTION.name()); + } + } }