diff --git a/src/main/java/com/google/gcloud/datastore/DatastoreServiceFactory.java b/src/main/java/com/google/gcloud/datastore/DatastoreServiceFactory.java index b2cd7c8eec11..ce0887792f98 100644 --- a/src/main/java/com/google/gcloud/datastore/DatastoreServiceFactory.java +++ b/src/main/java/com/google/gcloud/datastore/DatastoreServiceFactory.java @@ -17,7 +17,9 @@ package com.google.gcloud.datastore; - +/** + * A base class for DatasoreService factories. + */ public abstract class DatastoreServiceFactory { private static final DatastoreServiceFactory INSTANCE = new DatastoreServiceFactory() { diff --git a/src/main/java/com/google/gcloud/examples/DatastoreExample.java b/src/main/java/com/google/gcloud/examples/DatastoreExample.java index e153bb5da85a..3d7feae66e8c 100644 --- a/src/main/java/com/google/gcloud/examples/DatastoreExample.java +++ b/src/main/java/com/google/gcloud/examples/DatastoreExample.java @@ -46,7 +46,7 @@ *
  • compile using maven - {@code mvn compile}
  • *
  • run using maven - {@code mvn exec:java * -Dexec.mainClass="com.google.gcloud.examples.DatastoreExample" - * -Dexec.args="projectId [user] [delete|display|add comment]"}
  • + * -Dexec.args="[projectId] [user] [delete|display|add comment]"} * */ public class DatastoreExample { @@ -174,25 +174,23 @@ public static void main(String... args) { DatastoreAction action = null; DatastoreService datastore = null; Key key = null; - if (args.length > 0) { - String projectId = args[0]; - // If you want to access a local Datastore running via the gcd sdk, do -// DatastoreServiceOptions options = DatastoreServiceOptions.builder() -// .projectId(projectId) -// .namespace(NAMESPACE) -// .host("http://localhost:8080") -// .build(); - DatastoreServiceOptions options = DatastoreServiceOptions.builder() - .projectId(projectId) - .namespace(NAMESPACE) - .build(); - String name = args.length > 1 ? args[1] : System.getProperty("user.name"); - datastore = DatastoreServiceFactory.instance().get(options); - KeyFactory keyFactory = datastore.newKeyFactory().kind(USER_KIND); - key = keyFactory.newKey(name); - String actionName = args.length > 2 ? args[2].toLowerCase() : DEFAULT_ACTION; - action = ACTIONS.get(actionName); - } + String projectId = args.length > 0 ? args[0] : null; + // If you want to access a local Datastore running via the gcd sdk, do + // DatastoreServiceOptions options = DatastoreServiceOptions.builder() + // .projectId(projectId) + // .namespace(NAMESPACE) + // .host("http://localhost:8080") + // .build(); + DatastoreServiceOptions options = DatastoreServiceOptions.builder() + .projectId(projectId) + .namespace(NAMESPACE) + .build(); + String name = args.length > 1 ? args[1] : System.getProperty("user.name"); + datastore = DatastoreServiceFactory.instance().get(options); + KeyFactory keyFactory = datastore.newKeyFactory().kind(USER_KIND); + key = keyFactory.newKey(name); + String actionName = args.length > 2 ? args[2].toLowerCase() : DEFAULT_ACTION; + action = ACTIONS.get(actionName); if (action == null) { StringBuilder actionAndParams = new StringBuilder(); for (Map.Entry entry : ACTIONS.entrySet()) { @@ -204,7 +202,7 @@ public static void main(String... args) { actionAndParams.append('|'); } actionAndParams.setLength(actionAndParams.length() - 1); - System.out.printf("Usage: %s projectId [user] [%s]%n", + System.out.printf("Usage: %s [projectId] [user] [%s]%n", DatastoreExample.class.getSimpleName(), actionAndParams); return; } diff --git a/src/main/java/com/google/gcloud/examples/StorageExample.java b/src/main/java/com/google/gcloud/examples/StorageExample.java index 89b5c971e915..442e3b84d13b 100644 --- a/src/main/java/com/google/gcloud/examples/StorageExample.java +++ b/src/main/java/com/google/gcloud/examples/StorageExample.java @@ -270,14 +270,15 @@ Tuple parse(String... args) { if (args.length < 2 || args.length > 3) { throw new IllegalArgumentException(); } - Path path = null; + Path path; if (args.length > 2) { path = Paths.get(args[2]); if (Files.isDirectory(path)) { path = path.resolve(Paths.get(args[1]).getFileName()); } + } else { + path = null; } - String blob = args.length < 3 ? path.getFileName().toString() : args[2]; return Tuple.of(Blob.of(args[0], args[1]), path); } diff --git a/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java b/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java index ce107815c435..7ebbb1340d0f 100644 --- a/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java +++ b/src/main/java/com/google/gcloud/spi/DefaultStorageRpc.java @@ -1 +1 @@ -/* * 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.spi; import static com.google.gcloud.spi.StorageRpc.Option.DELIMITER; import static com.google.gcloud.spi.StorageRpc.Option.IF_GENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_GENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_METAGENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_METAGENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_GENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_GENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_METAGENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_METAGENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.MAX_RESULTS; import static com.google.gcloud.spi.StorageRpc.Option.PAGE_TOKEN; import static com.google.gcloud.spi.StorageRpc.Option.PREDEFINED_ACL; import static com.google.gcloud.spi.StorageRpc.Option.PREDEFINED_DEFAULT_OBJECT_ACL; import static com.google.gcloud.spi.StorageRpc.Option.PREFIX; import static com.google.gcloud.spi.StorageRpc.Option.VERSIONS; import com.google.api.client.googleapis.batch.json.JsonBatchCallback; import com.google.api.client.googleapis.json.GoogleJsonError; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.googleapis.media.MediaHttpDownloader; import com.google.api.client.http.AbstractInputStreamContent; import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.EmptyContent; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.jackson.JacksonFactory; import com.google.api.services.storage.Storage; import com.google.api.services.storage.Storage.Objects.Get; import com.google.api.services.storage.Storage.Objects.Insert; import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.Buckets; import com.google.api.services.storage.model.ComposeRequest; import com.google.api.services.storage.model.ComposeRequest.SourceObjects.ObjectPreconditions; import com.google.api.services.storage.model.Objects; import com.google.api.services.storage.model.StorageObject; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.gcloud.storage.StorageServiceException; import com.google.gcloud.storage.StorageServiceOptions; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; public class DefaultStorageRpc implements StorageRpc { public static final String DEFAULT_PROJECTION = "full"; private final StorageServiceOptions options; private final Storage storage; // see: https://cloud.google.com/storage/docs/concepts-techniques#practices private static final Set RETRYABLE_CODES = ImmutableSet.of(504, 503, 502, 500, 408); public DefaultStorageRpc(StorageServiceOptions options) { HttpTransport transport = options.httpTransportFactory().create(); HttpRequestInitializer initializer = options.httpRequestInitializer(); this.options = options; storage = new Storage.Builder(transport, new JacksonFactory(), initializer) .setApplicationName("gcloud-java") .build(); // Todo: make sure nulls are being used as Data.asNull() // TOdo: consider options } private static StorageServiceException translate(IOException exception) { StorageServiceException translated; if (exception instanceof GoogleJsonResponseException) { translated = translate(((GoogleJsonResponseException) exception).getDetails()); } else { translated = new StorageServiceException(0, exception.getMessage(), false); } translated.initCause(exception); return translated; } private static StorageServiceException translate(GoogleJsonError exception) { boolean retryable = RETRYABLE_CODES.contains(exception.getCode()) || "InternalError".equals(exception.getMessage()); return new StorageServiceException(exception.getCode(), exception.getMessage(), retryable); } @Override public Bucket create(Bucket bucket, Map options) throws StorageServiceException { try { return storage.buckets() .insert(this.options.projectId(), bucket) .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(PREDEFINED_ACL.getString(options)) .setPredefinedDefaultObjectAcl(PREDEFINED_DEFAULT_OBJECT_ACL.getString(options)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public StorageObject create(StorageObject storageObject, final byte[] content, Map options) throws StorageServiceException { try { return storage.objects() .insert(storageObject.getBucket(), storageObject, new AbstractInputStreamContent(storageObject.getContentType()) { @Override public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(content); } @Override public long getLength() throws IOException { return content.length; } @Override public boolean retrySupported() { return true; } }) .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(PREDEFINED_ACL.getString(options)) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public Tuple> list(Map options) { try { Buckets buckets = storage.buckets() .list(this.options.projectId()) .setProjection(DEFAULT_PROJECTION) .setPrefix(PREFIX.getString(options)) .setMaxResults(MAX_RESULTS.getLong(options)) .setPageToken(PAGE_TOKEN.getString(options)) .execute(); return Tuple.>of(buckets.getNextPageToken(), buckets.getItems()); } catch (IOException ex) { throw translate(ex); } } @Override public Tuple> list(String bucket, Map options) { try { Objects objects = storage.objects() .list(bucket) .setProjection(DEFAULT_PROJECTION) .setVersions(VERSIONS.getBoolean(options)) .setDelimiter(DELIMITER.getString(options)) .setPrefix(PREFIX.getString(options)) .setMaxResults(MAX_RESULTS.getLong(options)) .setPageToken(PAGE_TOKEN.getString(options)) .execute(); return Tuple.>of( objects.getNextPageToken(), objects.getItems()); } catch (IOException ex) { throw translate(ex); } } @Override public Bucket get(Bucket bucket, Map options) { try { return storage.buckets() .get(bucket.getName()) .setProjection(DEFAULT_PROJECTION) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public StorageObject get(StorageObject object, Map options) { try { return getRequest(object, options).execute(); } catch (IOException ex) { throw translate(ex); } } private Storage.Objects.Get getRequest(StorageObject object, Map options) throws IOException { return storage.objects() .get(object.getBucket(), object.getName()) .setProjection(DEFAULT_PROJECTION) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); } @Override public Bucket patch(Bucket bucket, Map options) { try { return storage.buckets() .patch(bucket.getName(), bucket) .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(PREDEFINED_ACL.getString(options)) .setPredefinedDefaultObjectAcl(PREDEFINED_DEFAULT_OBJECT_ACL.getString(options)) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public StorageObject patch(StorageObject storageObject, Map options) { try { return patchRequest(storageObject, options).execute(); } catch (IOException ex) { throw translate(ex); } } private Storage.Objects.Patch patchRequest(StorageObject storageObject, Map options) throws IOException { return storage.objects() .patch(storageObject.getBucket(), storageObject.getName(), storageObject) .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(PREDEFINED_ACL.getString(options)) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); } @Override public boolean delete(Bucket bucket, Map options) { try { storage.buckets() .delete(bucket.getName()) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .execute(); return true; } catch (IOException ex) { StorageServiceException serviceException = translate(ex); if (serviceException.code() == 404) { return false; } throw serviceException; } } @Override public boolean delete(StorageObject blob, Map options) { try { deleteRequest(blob, options).execute(); return true; } catch (IOException ex) { StorageServiceException serviceException = translate(ex); if (serviceException.code() == 404) { return false; } throw serviceException; } } private Storage.Objects.Delete deleteRequest(StorageObject blob, Map options) throws IOException { return storage.objects() .delete(blob.getBucket(), blob.getName()) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationMatch(100L) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); } @Override public StorageObject compose(Iterable sources, StorageObject target, Map targetOptions) throws StorageServiceException { ComposeRequest request = new ComposeRequest(); if (target.getContentType() == null) { // todo: remove once this is no longer requirement (b/20681287). target.setContentType("application/octet-stream"); } request.setDestination(target); List sourceObjects = new ArrayList<>(); for (StorageObject source : sources) { ComposeRequest.SourceObjects sourceObject = new ComposeRequest.SourceObjects(); sourceObject.setName(source.getName()); Long generation = source.getGeneration(); if (generation != null) { sourceObject.setGeneration(generation); sourceObject.setObjectPreconditions( new ObjectPreconditions().setIfGenerationMatch(generation)); } sourceObjects.add(sourceObject); } request.setSourceObjects(sourceObjects); try { // todo: missing setProjection (b/20659000) return storage.objects() .compose(target.getBucket(), target.getName(), request) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(targetOptions)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(targetOptions)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public StorageObject copy(StorageObject source, Map sourceOptions, StorageObject target, Map targetOptions) throws StorageServiceException { try { return storage .objects() .copy(source.getBucket(), source.getName(), target.getBucket(), target.getName(), target.getContentType() != null ? target : null) .setProjection(DEFAULT_PROJECTION) .setIfMetagenerationMatch(IF_SOURCE_METAGENERATION_MATCH.getLong(sourceOptions)) .setIfMetagenerationNotMatch(IF_SOURCE_METAGENERATION_NOT_MATCH.getLong(sourceOptions)) .setIfGenerationMatch(IF_SOURCE_GENERATION_MATCH.getLong(sourceOptions)) .setIfGenerationNotMatch(IF_SOURCE_GENERATION_NOT_MATCH.getLong(sourceOptions)) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(targetOptions)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(targetOptions)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(targetOptions)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(targetOptions)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public byte[] load(StorageObject from, Map options) throws StorageServiceException { try { Storage.Objects.Get getRequest = storage.objects() .get(from.getBucket(), from.getName()) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); ByteArrayOutputStream out = new ByteArrayOutputStream(); getRequest.getMediaHttpDownloader().setDirectDownloadEnabled(true); getRequest.executeMediaAndDownloadTo(out); return out.toByteArray(); } catch (IOException ex) { throw translate(ex); } } @Override public BatchResponse batch(BatchRequest request) throws StorageServiceException { com.google.api.client.googleapis.batch.BatchRequest batch = storage.batch(); final Map> deletes = Maps.newConcurrentMap(); final Map> updates = Maps.newConcurrentMap(); final Map> gets = Maps.newConcurrentMap(); try { for (final Tuple> tuple : request.toDelete) { deleteRequest(tuple.x(), tuple.y()).queue(batch, new JsonBatchCallback() { @Override public void onSuccess(Void ignore, HttpHeaders responseHeaders) { deletes.put(tuple.x(), Tuple.of(Boolean.TRUE, null)); } @Override public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) { deletes.put(tuple.x(), Tuple.of(null, translate(e))); } }); } for (final Tuple> tuple : request.toUpdate) { patchRequest(tuple.x(), tuple.y()).queue(batch, new JsonBatchCallback() { @Override public void onSuccess(StorageObject storageObject, HttpHeaders responseHeaders) { updates.put(tuple.x(), Tuple.of(storageObject, null)); } @Override public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) { updates.put(tuple.x(), Tuple.of(null, translate(e))); } }); } for (final Tuple> tuple : request.toGet) { getRequest(tuple.x(), tuple.y()).queue(batch, new JsonBatchCallback() { @Override public void onSuccess(StorageObject storageObject, HttpHeaders responseHeaders) { gets.put(tuple.x(), Tuple.of(storageObject, null)); } @Override public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) { gets.put(tuple.x(), Tuple.of(null, translate(e))); } }); } batch.execute(); } catch (IOException ex) { throw translate(ex); } return new BatchResponse(deletes, updates, gets); } @Override public byte[] read(StorageObject from, Map options, long position, int bytes) throws StorageServiceException { try { Get req = storage.objects().get(from.getBucket(), from.getName()); req.setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); MediaHttpDownloader downloader = req.getMediaHttpDownloader(); // todo: Fix int casting (https://github.com/google/google-api-java-client/issues/937) downloader.setContentRange(position, (int) position + bytes); downloader.setDirectDownloadEnabled(true); ByteArrayOutputStream output = new ByteArrayOutputStream(); req.executeMediaAndDownloadTo(output); return output.toByteArray(); } catch (IOException ex) { throw translate(ex); } } @Override public void write(String uploadId, byte[] toWrite, int toWriteOffset, StorageObject dest, long destOffset, int length, boolean last) throws StorageServiceException { try { GenericUrl url = new GenericUrl(uploadId); HttpRequest httpRequest = storage.getRequestFactory().buildPostRequest(url, new ByteArrayContent(null, toWrite, toWriteOffset, length)); long limit = destOffset + length; StringBuilder range = new StringBuilder("bytes "); range.append(destOffset).append('-').append(limit - 1).append('/'); if (last) { range.append(limit); } else { range.append('*'); } httpRequest.getHeaders().setContentRange(range.toString()); int code; String message; IOException exception = null; try { HttpResponse response = httpRequest.execute(); code = response.getStatusCode(); message = response.getStatusMessage(); } catch (HttpResponseException ex) { exception = ex; code = ex.getStatusCode(); message = ex.getStatusMessage(); } if (!last && code != 308 || last && !(code == 200 || code == 201)) { if (exception != null) { throw exception; } GoogleJsonError error = new GoogleJsonError(); error.setCode(code); error.setMessage(message); throw translate(error); } } catch (IOException ex) { throw translate(ex); } } @Override public String open(StorageObject object, Map options) throws StorageServiceException { try { Insert req = storage.objects().insert(object.getBucket(), object); GenericUrl url = req.buildHttpRequest().getUrl(); String scheme = url.getScheme(); String host = url.getHost(); String path = "/upload" + url.getRawPath(); url = new GenericUrl(scheme + "://" + host + path); url.set("uploadType", "resumable"); url.set("name", object.getName()); for (Option option : options.keySet()) { Object value = option.get(options); if (value != null) { url.set(option.name(), value.toString()); } } HttpRequest httpRequest = storage.getRequestFactory().buildPostRequest(url, new EmptyContent()); httpRequest.getHeaders().set("X-Upload-Content-Type", MoreObjects.firstNonNull(object.getContentType(), "application/octet-stream")); HttpResponse response = httpRequest.execute(); if (response.getStatusCode() != 200) { GoogleJsonError error = new GoogleJsonError(); error.setCode(response.getStatusCode()); error.setMessage(response.getStatusMessage()); throw translate(error); } return response.getHeaders().getLocation(); } catch (IOException ex) { throw translate(ex); } } } \ No newline at end of file +/* * 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.spi; import static com.google.gcloud.spi.StorageRpc.Option.DELIMITER; import static com.google.gcloud.spi.StorageRpc.Option.IF_GENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_GENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_METAGENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_METAGENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_GENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_GENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_METAGENERATION_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.IF_SOURCE_METAGENERATION_NOT_MATCH; import static com.google.gcloud.spi.StorageRpc.Option.MAX_RESULTS; import static com.google.gcloud.spi.StorageRpc.Option.PAGE_TOKEN; import static com.google.gcloud.spi.StorageRpc.Option.PREDEFINED_ACL; import static com.google.gcloud.spi.StorageRpc.Option.PREDEFINED_DEFAULT_OBJECT_ACL; import static com.google.gcloud.spi.StorageRpc.Option.PREFIX; import static com.google.gcloud.spi.StorageRpc.Option.VERSIONS; import com.google.api.client.googleapis.batch.json.JsonBatchCallback; import com.google.api.client.googleapis.json.GoogleJsonError; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.client.googleapis.media.MediaHttpDownloader; import com.google.api.client.http.AbstractInputStreamContent; import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.json.JsonHttpContent; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson.JacksonFactory; import com.google.api.services.storage.Storage; import com.google.api.services.storage.Storage.Objects.Get; import com.google.api.services.storage.Storage.Objects.Insert; import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.Buckets; import com.google.api.services.storage.model.ComposeRequest; import com.google.api.services.storage.model.ComposeRequest.SourceObjects.ObjectPreconditions; import com.google.api.services.storage.model.Objects; import com.google.api.services.storage.model.StorageObject; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.gcloud.storage.StorageServiceException; import com.google.gcloud.storage.StorageServiceOptions; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; public class DefaultStorageRpc implements StorageRpc { public static final String DEFAULT_PROJECTION = "full"; private final StorageServiceOptions options; private final Storage storage; // see: https://cloud.google.com/storage/docs/concepts-techniques#practices private static final Set RETRYABLE_CODES = ImmutableSet.of(504, 503, 502, 500, 408); public DefaultStorageRpc(StorageServiceOptions options) { HttpTransport transport = options.httpTransportFactory().create(); HttpRequestInitializer initializer = options.httpRequestInitializer(); this.options = options; storage = new Storage.Builder(transport, new JacksonFactory(), initializer) .setApplicationName("gcloud-java") .build(); // Todo: make sure nulls are being used as Data.asNull() } private static StorageServiceException translate(IOException exception) { StorageServiceException translated; if (exception instanceof GoogleJsonResponseException) { translated = translate(((GoogleJsonResponseException) exception).getDetails()); } else { translated = new StorageServiceException(0, exception.getMessage(), false); } translated.initCause(exception); return translated; } private static StorageServiceException translate(GoogleJsonError exception) { boolean retryable = RETRYABLE_CODES.contains(exception.getCode()) || "InternalError".equals(exception.getMessage()); return new StorageServiceException(exception.getCode(), exception.getMessage(), retryable); } @Override public Bucket create(Bucket bucket, Map options) throws StorageServiceException { try { return storage.buckets() .insert(this.options.projectId(), bucket) .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(PREDEFINED_ACL.getString(options)) .setPredefinedDefaultObjectAcl(PREDEFINED_DEFAULT_OBJECT_ACL.getString(options)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public StorageObject create(StorageObject storageObject, final byte[] content, Map options) throws StorageServiceException { try { return storage.objects() .insert(storageObject.getBucket(), storageObject, new AbstractInputStreamContent(storageObject.getContentType()) { @Override public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(content); } @Override public long getLength() throws IOException { return content.length; } @Override public boolean retrySupported() { return true; } }) .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(PREDEFINED_ACL.getString(options)) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public Tuple> list(Map options) { try { Buckets buckets = storage.buckets() .list(this.options.projectId()) .setProjection(DEFAULT_PROJECTION) .setPrefix(PREFIX.getString(options)) .setMaxResults(MAX_RESULTS.getLong(options)) .setPageToken(PAGE_TOKEN.getString(options)) .execute(); return Tuple.>of(buckets.getNextPageToken(), buckets.getItems()); } catch (IOException ex) { throw translate(ex); } } @Override public Tuple> list(String bucket, Map options) { try { Objects objects = storage.objects() .list(bucket) .setProjection(DEFAULT_PROJECTION) .setVersions(VERSIONS.getBoolean(options)) .setDelimiter(DELIMITER.getString(options)) .setPrefix(PREFIX.getString(options)) .setMaxResults(MAX_RESULTS.getLong(options)) .setPageToken(PAGE_TOKEN.getString(options)) .execute(); return Tuple.>of( objects.getNextPageToken(), objects.getItems()); } catch (IOException ex) { throw translate(ex); } } @Override public Bucket get(Bucket bucket, Map options) { try { return storage.buckets() .get(bucket.getName()) .setProjection(DEFAULT_PROJECTION) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public StorageObject get(StorageObject object, Map options) { try { return getRequest(object, options).execute(); } catch (IOException ex) { throw translate(ex); } } private Storage.Objects.Get getRequest(StorageObject object, Map options) throws IOException { return storage.objects() .get(object.getBucket(), object.getName()) .setProjection(DEFAULT_PROJECTION) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); } @Override public Bucket patch(Bucket bucket, Map options) { try { return storage.buckets() .patch(bucket.getName(), bucket) .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(PREDEFINED_ACL.getString(options)) .setPredefinedDefaultObjectAcl(PREDEFINED_DEFAULT_OBJECT_ACL.getString(options)) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public StorageObject patch(StorageObject storageObject, Map options) { try { return patchRequest(storageObject, options).execute(); } catch (IOException ex) { throw translate(ex); } } private Storage.Objects.Patch patchRequest(StorageObject storageObject, Map options) throws IOException { return storage.objects() .patch(storageObject.getBucket(), storageObject.getName(), storageObject) .setProjection(DEFAULT_PROJECTION) .setPredefinedAcl(PREDEFINED_ACL.getString(options)) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); } @Override public boolean delete(Bucket bucket, Map options) { try { storage.buckets() .delete(bucket.getName()) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .execute(); return true; } catch (IOException ex) { StorageServiceException serviceException = translate(ex); if (serviceException.code() == 404) { return false; } throw serviceException; } } @Override public boolean delete(StorageObject blob, Map options) { try { deleteRequest(blob, options).execute(); return true; } catch (IOException ex) { StorageServiceException serviceException = translate(ex); if (serviceException.code() == 404) { return false; } throw serviceException; } } private Storage.Objects.Delete deleteRequest(StorageObject blob, Map options) throws IOException { return storage.objects() .delete(blob.getBucket(), blob.getName()) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationMatch(100L) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); } @Override public StorageObject compose(Iterable sources, StorageObject target, Map targetOptions) throws StorageServiceException { ComposeRequest request = new ComposeRequest(); if (target.getContentType() == null) { // todo: remove once this is no longer requirement (b/20681287). target.setContentType("application/octet-stream"); } request.setDestination(target); List sourceObjects = new ArrayList<>(); for (StorageObject source : sources) { ComposeRequest.SourceObjects sourceObject = new ComposeRequest.SourceObjects(); sourceObject.setName(source.getName()); Long generation = source.getGeneration(); if (generation != null) { sourceObject.setGeneration(generation); sourceObject.setObjectPreconditions( new ObjectPreconditions().setIfGenerationMatch(generation)); } sourceObjects.add(sourceObject); } request.setSourceObjects(sourceObjects); try { // todo: missing setProjection (b/20659000) return storage.objects() .compose(target.getBucket(), target.getName(), request) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(targetOptions)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(targetOptions)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public StorageObject copy(StorageObject source, Map sourceOptions, StorageObject target, Map targetOptions) throws StorageServiceException { try { return storage .objects() .copy(source.getBucket(), source.getName(), target.getBucket(), target.getName(), target.getContentType() != null ? target : null) .setProjection(DEFAULT_PROJECTION) .setIfMetagenerationMatch(IF_SOURCE_METAGENERATION_MATCH.getLong(sourceOptions)) .setIfMetagenerationNotMatch(IF_SOURCE_METAGENERATION_NOT_MATCH.getLong(sourceOptions)) .setIfGenerationMatch(IF_SOURCE_GENERATION_MATCH.getLong(sourceOptions)) .setIfGenerationNotMatch(IF_SOURCE_GENERATION_NOT_MATCH.getLong(sourceOptions)) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(targetOptions)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(targetOptions)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(targetOptions)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(targetOptions)) .execute(); } catch (IOException ex) { throw translate(ex); } } @Override public byte[] load(StorageObject from, Map options) throws StorageServiceException { try { Storage.Objects.Get getRequest = storage.objects() .get(from.getBucket(), from.getName()) .setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); ByteArrayOutputStream out = new ByteArrayOutputStream(); getRequest.getMediaHttpDownloader().setDirectDownloadEnabled(true); getRequest.executeMediaAndDownloadTo(out); return out.toByteArray(); } catch (IOException ex) { throw translate(ex); } } @Override public BatchResponse batch(BatchRequest request) throws StorageServiceException { com.google.api.client.googleapis.batch.BatchRequest batch = storage.batch(); final Map> deletes = Maps.newConcurrentMap(); final Map> updates = Maps.newConcurrentMap(); final Map> gets = Maps.newConcurrentMap(); try { for (final Tuple> tuple : request.toDelete) { deleteRequest(tuple.x(), tuple.y()).queue(batch, new JsonBatchCallback() { @Override public void onSuccess(Void ignore, HttpHeaders responseHeaders) { deletes.put(tuple.x(), Tuple.of(Boolean.TRUE, null)); } @Override public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) { deletes.put(tuple.x(), Tuple.of(null, translate(e))); } }); } for (final Tuple> tuple : request.toUpdate) { patchRequest(tuple.x(), tuple.y()).queue(batch, new JsonBatchCallback() { @Override public void onSuccess(StorageObject storageObject, HttpHeaders responseHeaders) { updates.put(tuple.x(), Tuple.of(storageObject, null)); } @Override public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) { updates.put(tuple.x(), Tuple.of(null, translate(e))); } }); } for (final Tuple> tuple : request.toGet) { getRequest(tuple.x(), tuple.y()).queue(batch, new JsonBatchCallback() { @Override public void onSuccess(StorageObject storageObject, HttpHeaders responseHeaders) { gets.put(tuple.x(), Tuple.of(storageObject, null)); } @Override public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) { gets.put(tuple.x(), Tuple.of(null, translate(e))); } }); } batch.execute(); } catch (IOException ex) { throw translate(ex); } return new BatchResponse(deletes, updates, gets); } @Override public byte[] read(StorageObject from, Map options, long position, int bytes) throws StorageServiceException { try { Get req = storage.objects().get(from.getBucket(), from.getName()); req.setIfMetagenerationMatch(IF_METAGENERATION_MATCH.getLong(options)) .setIfMetagenerationNotMatch(IF_METAGENERATION_NOT_MATCH.getLong(options)) .setIfGenerationMatch(IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(IF_GENERATION_NOT_MATCH.getLong(options)); MediaHttpDownloader downloader = req.getMediaHttpDownloader(); // todo: Fix int casting (https://github.com/google/google-api-java-client/issues/937) downloader.setContentRange(position, (int) position + bytes); downloader.setDirectDownloadEnabled(true); ByteArrayOutputStream output = new ByteArrayOutputStream(); req.executeMediaAndDownloadTo(output); return output.toByteArray(); } catch (IOException ex) { throw translate(ex); } } @Override public void write(String uploadId, byte[] toWrite, int toWriteOffset, StorageObject dest, long destOffset, int length, boolean last) throws StorageServiceException { try { GenericUrl url = new GenericUrl(uploadId); HttpRequest httpRequest = storage.getRequestFactory().buildPostRequest(url, new ByteArrayContent(null, toWrite, toWriteOffset, length)); long limit = destOffset + length; StringBuilder range = new StringBuilder("bytes "); range.append(destOffset).append('-').append(limit - 1).append('/'); if (last) { range.append(limit); } else { range.append('*'); } httpRequest.getHeaders().setContentRange(range.toString()); int code; String message; IOException exception = null; try { HttpResponse response = httpRequest.execute(); code = response.getStatusCode(); message = response.getStatusMessage(); } catch (HttpResponseException ex) { exception = ex; code = ex.getStatusCode(); message = ex.getStatusMessage(); } if (!last && code != 308 || last && !(code == 200 || code == 201)) { if (exception != null) { throw exception; } GoogleJsonError error = new GoogleJsonError(); error.setCode(code); error.setMessage(message); throw translate(error); } } catch (IOException ex) { throw translate(ex); } } @Override public String open(StorageObject object, Map options) throws StorageServiceException { try { Insert req = storage.objects().insert(object.getBucket(), object); GenericUrl url = req.buildHttpRequest().getUrl(); String scheme = url.getScheme(); String host = url.getHost(); String path = "/upload" + url.getRawPath(); url = new GenericUrl(scheme + "://" + host + path); url.set("uploadType", "resumable"); url.set("name", object.getName()); for (Option option : options.keySet()) { Object content = option.get(options); if (content != null) { url.set(option.value(), content.toString()); } } JsonFactory jsonFactory = storage.getJsonFactory(); HttpRequestFactory requestFactory = storage.getRequestFactory(); HttpRequest httpRequest = requestFactory.buildPostRequest(url, new JsonHttpContent(jsonFactory, object)); httpRequest.getHeaders().set("X-Upload-Content-Type", MoreObjects.firstNonNull(object.getContentType(), "application/octet-stream")); HttpResponse response = httpRequest.execute(); if (response.getStatusCode() != 200) { GoogleJsonError error = new GoogleJsonError(); error.setCode(response.getStatusCode()); error.setMessage(response.getStatusMessage()); throw translate(error); } return response.getHeaders().getLocation(); } catch (IOException ex) { throw translate(ex); } } } \ No newline at end of file diff --git a/src/main/java/com/google/gcloud/storage/Acl.java b/src/main/java/com/google/gcloud/storage/Acl.java index 10673b5aeae0..138ba4f3fa3a 100644 --- a/src/main/java/com/google/gcloud/storage/Acl.java +++ b/src/main/java/com/google/gcloud/storage/Acl.java @@ -21,6 +21,9 @@ import java.io.Serializable; +/** + * Access Control List on for buckets or blobs. + */ public final class Acl implements Serializable { private static final long serialVersionUID = 6435575339887912222L; diff --git a/src/main/java/com/google/gcloud/storage/BatchRequest.java b/src/main/java/com/google/gcloud/storage/BatchRequest.java index 4f27abd89547..84122a83d4ff 100644 --- a/src/main/java/com/google/gcloud/storage/BatchRequest.java +++ b/src/main/java/com/google/gcloud/storage/BatchRequest.java @@ -43,14 +43,23 @@ public static class Builder { private Builder() {} + /** + * Delete the given blob. + */ public void delete(Blob blob, BlobSourceOption... options) { toDelete.put(blob, options); } + /** + * Update the given blob. + */ public void update(Blob blob, BlobTargetOption... options) { toUpdate.put(blob, options); } + /** + * Retrieve metadata for the given blob. + */ public void get(Blob blob, BlobSourceOption... options) { toGet.put(blob, options); } @@ -66,15 +75,15 @@ private BatchRequest(Builder builder) { toGet = ImmutableMap.copyOf(builder.toGet); } - public Map toDelete() { + Map toDelete() { return toDelete; } - public Map toUpdate() { + Map toUpdate() { return toUpdate; } - public Map toGet() { + Map toGet() { return toGet; } diff --git a/src/main/java/com/google/gcloud/storage/Blob.java b/src/main/java/com/google/gcloud/storage/Blob.java index 6cb8e0eb8ae6..49d763b0f65a 100644 --- a/src/main/java/com/google/gcloud/storage/Blob.java +++ b/src/main/java/com/google/gcloud/storage/Blob.java @@ -33,6 +33,11 @@ import java.util.List; import java.util.Map; +/** + * A Google Storage object. + * + * @see Concepts and Terminology + */ public class Blob implements Serializable { private static final long serialVersionUID = 2228487739943277159L; @@ -148,7 +153,7 @@ public Builder cacheControl(String cacheControl) { } public Builder acl(List acl) { - this.acl = ImmutableList.copyOf(acl); + this.acl = acl != null ? ImmutableList.copyOf(acl) : null; return this; } @@ -188,7 +193,7 @@ public Builder mediaLink(String mediaLink) { } public Builder metadata(Map metadata) { - this.metadata = ImmutableMap.copyOf(metadata); + this.metadata = metadata != null ? ImmutableMap.copyOf(metadata) : null; return this; } diff --git a/src/main/java/com/google/gcloud/storage/Bucket.java b/src/main/java/com/google/gcloud/storage/Bucket.java index 981dc7f788c0..f6be9a78b2c6 100644 --- a/src/main/java/com/google/gcloud/storage/Bucket.java +++ b/src/main/java/com/google/gcloud/storage/Bucket.java @@ -40,6 +40,11 @@ import java.io.Serializable; import java.util.List; +/** + * A Google Storage bucket. + * + * @see Concepts and Terminology + */ public final class Bucket implements Serializable { private static final long serialVersionUID = -3946094202176916586L; @@ -431,17 +436,17 @@ Builder metageneration(Long metageneration) { } public Builder cors(Iterable cors) { - this.cors = ImmutableList.copyOf(cors); + this.cors = cors != null ? ImmutableList.copyOf(cors) : null; return this; } public Builder acl(Iterable acl) { - this.acl = ImmutableList.copyOf(acl); + this.acl = acl != null ? ImmutableList.copyOf(acl) : null; return this; } public Builder defaultAcl(Iterable acl) { - this.defaultAcl = ImmutableList.copyOf(acl); + this.defaultAcl = acl != null ? ImmutableList.copyOf(acl) : null; return this; } diff --git a/src/main/java/com/google/gcloud/storage/Cors.java b/src/main/java/com/google/gcloud/storage/Cors.java index ccbe2413042e..366c8d9223bd 100644 --- a/src/main/java/com/google/gcloud/storage/Cors.java +++ b/src/main/java/com/google/gcloud/storage/Cors.java @@ -30,6 +30,9 @@ import java.net.URISyntaxException; import java.util.List; +/** + * Cross-Origin Resource Sharing (CORS) configuration for a bucket. + */ public final class Cors implements Serializable { private static final long serialVersionUID = -8637770919343335655L; diff --git a/src/main/java/com/google/gcloud/storage/StorageService.java b/src/main/java/com/google/gcloud/storage/StorageService.java index f52e4d667d01..a97b20b7253a 100644 --- a/src/main/java/com/google/gcloud/storage/StorageService.java +++ b/src/main/java/com/google/gcloud/storage/StorageService.java @@ -30,11 +30,13 @@ import java.util.List; import java.util.Set; +/** + * An interface for Google Cloud Storage. + * + * @see Google Cloud Storage + */ public interface StorageService extends Service { - // todo: provide way for construct signed URLs - - // https://cloud.google.com/storage/docs/access-control#Signed-URLs - enum PredefinedAcl { AUTHENTICATED_READ("authenticatedRead"), ALL_AUTHENTICATED_USERS("allAuthenticatedUsers"), @@ -347,17 +349,26 @@ public static Builder builder() { } } + /** + * Create a new bucket. + * + * @return a complete bucket information. * @throws StorageServiceException upon failure */ Bucket create(Bucket bucket, BucketTargetOption... options); /** + * Create a new blob. + * + * @return a complete blob information. * @throws StorageServiceException upon failure */ Blob create(Blob blob, byte[] content, BlobTargetOption... options); /** + * Returns a complete bucket information. + * * @throws StorageServiceException upon failure */ Bucket get(Bucket bucket, BucketSourceOption... options); @@ -426,5 +437,4 @@ public static Builder builder() { */ BlobWriteChannel writer(Blob blob, BlobTargetOption... options); - } diff --git a/src/main/java/com/google/gcloud/storage/StorageServiceFactory.java b/src/main/java/com/google/gcloud/storage/StorageServiceFactory.java index a801622082ef..51c7c1812e6f 100644 --- a/src/main/java/com/google/gcloud/storage/StorageServiceFactory.java +++ b/src/main/java/com/google/gcloud/storage/StorageServiceFactory.java @@ -17,7 +17,9 @@ package com.google.gcloud.storage; - +/** + * A base class for StorageService factories. + */ public abstract class StorageServiceFactory { private static final StorageServiceFactory INSTANCE = new StorageServiceFactory() { @@ -27,9 +29,15 @@ public StorageService get(StorageServiceOptions options) { } }; + /** + * Returns the default factory instance. + */ public static StorageServiceFactory instance() { return INSTANCE; } + /** + * Returns a {@code StorageService} for the given options. + */ public abstract StorageService get(StorageServiceOptions options); }