From bd6a495a04959e3dca157c1cb1c87943f86021bc Mon Sep 17 00:00:00 2001 From: Andrew Gaul Date: Fri, 20 May 2016 16:24:19 -0700 Subject: [PATCH] JCLOUDS-1005: Backblaze B2 object operations --- .../src/main/java/org/jclouds/b2/B2Api.java | 4 + .../jclouds/b2/binders/UploadFileBinder.java | 51 ++ .../java/org/jclouds/b2/domain/Action.java | 33 ++ .../java/org/jclouds/b2/domain/B2Object.java | 55 ++ .../org/jclouds/b2/domain/B2ObjectList.java | 52 ++ .../jclouds/b2/domain/DeleteFileResponse.java | 32 + .../jclouds/b2/domain/HideFileResponse.java | 37 ++ .../jclouds/b2/domain/UploadFileResponse.java | 41 ++ .../jclouds/b2/domain/UploadUrlResponse.java | 35 ++ .../org/jclouds/b2/features/ObjectApi.java | 150 +++++ .../filters/RequestAuthorizationDownload.java | 59 ++ .../functions/ParseB2ObjectFromResponse.java | 58 ++ .../handlers/ParseB2ErrorFromJsonContent.java | 8 + .../org/jclouds/b2/reference/B2Headers.java | 36 ++ .../b2/features/ObjectApiLiveTest.java | 284 +++++++++ .../b2/features/ObjectApiMockTest.java | 545 ++++++++++++++++++ ...file_version_already_deleted_response.json | 5 + .../test/resources/delete_object_request.json | 4 + .../resources/delete_object_response.json | 4 + .../get_file_info_deleted_file_response.json | 5 + .../test/resources/get_file_info_request.json | 3 + .../resources/get_file_info_response.json | 12 + ...et_upload_url_deleted_bucket_response.json | 5 + .../resources/get_upload_url_request.json | 3 + .../resources/get_upload_url_response.json | 5 + .../src/test/resources/hide_file_request.json | 4 + .../test/resources/hide_file_response.json | 6 + .../resources/list_file_names_request.json | 3 + .../resources/list_file_names_response.json | 19 + .../resources/list_file_versions_request.json | 3 + .../list_file_versions_response.json | 27 + .../test/resources/upload_file_response.json | 13 + 32 files changed, 1601 insertions(+) create mode 100644 providers/b2/src/main/java/org/jclouds/b2/binders/UploadFileBinder.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/domain/Action.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/domain/B2Object.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/domain/B2ObjectList.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/domain/DeleteFileResponse.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/domain/HideFileResponse.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/domain/UploadFileResponse.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/domain/UploadUrlResponse.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/features/ObjectApi.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/filters/RequestAuthorizationDownload.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/functions/ParseB2ObjectFromResponse.java create mode 100644 providers/b2/src/main/java/org/jclouds/b2/reference/B2Headers.java create mode 100644 providers/b2/src/test/java/org/jclouds/b2/features/ObjectApiLiveTest.java create mode 100644 providers/b2/src/test/java/org/jclouds/b2/features/ObjectApiMockTest.java create mode 100644 providers/b2/src/test/resources/delete_file_version_already_deleted_response.json create mode 100644 providers/b2/src/test/resources/delete_object_request.json create mode 100644 providers/b2/src/test/resources/delete_object_response.json create mode 100644 providers/b2/src/test/resources/get_file_info_deleted_file_response.json create mode 100644 providers/b2/src/test/resources/get_file_info_request.json create mode 100644 providers/b2/src/test/resources/get_file_info_response.json create mode 100644 providers/b2/src/test/resources/get_upload_url_deleted_bucket_response.json create mode 100644 providers/b2/src/test/resources/get_upload_url_request.json create mode 100644 providers/b2/src/test/resources/get_upload_url_response.json create mode 100644 providers/b2/src/test/resources/hide_file_request.json create mode 100644 providers/b2/src/test/resources/hide_file_response.json create mode 100644 providers/b2/src/test/resources/list_file_names_request.json create mode 100644 providers/b2/src/test/resources/list_file_names_response.json create mode 100644 providers/b2/src/test/resources/list_file_versions_request.json create mode 100644 providers/b2/src/test/resources/list_file_versions_response.json create mode 100644 providers/b2/src/test/resources/upload_file_response.json diff --git a/providers/b2/src/main/java/org/jclouds/b2/B2Api.java b/providers/b2/src/main/java/org/jclouds/b2/B2Api.java index 19419b5210..bbb64044d0 100644 --- a/providers/b2/src/main/java/org/jclouds/b2/B2Api.java +++ b/providers/b2/src/main/java/org/jclouds/b2/B2Api.java @@ -20,6 +20,7 @@ import java.io.Closeable; import org.jclouds.b2.features.AuthorizationApi; import org.jclouds.b2.features.BucketApi; +import org.jclouds.b2.features.ObjectApi; import org.jclouds.rest.annotations.Delegate; /** Provides access to Backblaze B2 resources via their REST API. */ @@ -29,4 +30,7 @@ public interface B2Api extends Closeable { @Delegate BucketApi getBucketApi(); + + @Delegate + ObjectApi getObjectApi(); } diff --git a/providers/b2/src/main/java/org/jclouds/b2/binders/UploadFileBinder.java b/providers/b2/src/main/java/org/jclouds/b2/binders/UploadFileBinder.java new file mode 100644 index 0000000000..9384033a5c --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/binders/UploadFileBinder.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.binders; + +import java.util.Map; + +import org.jclouds.http.HttpRequest; +import org.jclouds.b2.domain.UploadUrlResponse; +import org.jclouds.b2.reference.B2Headers; +import org.jclouds.rest.MapBinder; + +import com.google.common.net.HttpHeaders; +import com.google.common.net.PercentEscaper; + +public final class UploadFileBinder implements MapBinder { + private static final PercentEscaper escaper = new PercentEscaper("._-/~!$'()*;=:@", false); + + @Override + public R bindToRequest(R request, Map postParams) { + UploadUrlResponse uploadUrl = (UploadUrlResponse) postParams.get("uploadUrl"); + String fileName = (String) postParams.get("fileName"); + Map fileInfo = (Map) postParams.get("fileInfo"); + HttpRequest.Builder builder = request.toBuilder() + .endpoint(uploadUrl.uploadUrl()) + .replaceHeader(HttpHeaders.AUTHORIZATION, uploadUrl.authorizationToken()) + .replaceHeader(B2Headers.FILE_NAME, escaper.escape(fileName)); + for (Map.Entry entry : fileInfo.entrySet()) { + builder.replaceHeader(B2Headers.FILE_INFO_PREFIX + entry.getKey(), entry.getValue()); + } + return (R) builder.build(); + } + + @Override + public R bindToRequest(R request, Object input) { + throw new UnsupportedOperationException(); + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/domain/Action.java b/providers/b2/src/main/java/org/jclouds/b2/domain/Action.java new file mode 100644 index 0000000000..bd6c852b1d --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/domain/Action.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import com.google.common.base.CaseFormat; + +public enum Action { + UPLOAD, + HIDE; + + public static Action fromValue(String symbol) { + return Action.valueOf(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, symbol)); + } + + @Override + public String toString() { + return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name()); + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/domain/B2Object.java b/providers/b2/src/main/java/org/jclouds/b2/domain/B2Object.java new file mode 100644 index 0000000000..8b99a8edcd --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/domain/B2Object.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.util.Date; +import java.util.Map; + +import org.jclouds.io.Payload; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; + +@AutoValue +public abstract class B2Object { + public abstract String fileId(); + public abstract String fileName(); + @Nullable public abstract String contentSha1(); + @Nullable public abstract Map fileInfo(); + @Nullable public abstract Payload payload(); + @Nullable public abstract Date uploadTimestamp(); + @Nullable public abstract Action action(); + @Nullable public abstract String accountId(); + @Nullable public abstract String bucketId(); + @Nullable public abstract Long contentLength(); + @Nullable public abstract String contentType(); + + @SerializedNames({"fileId", "fileName", "accountId", "bucketId", "contentLength", "contentSha1", "contentType", "fileInfo", "action", "uploadTimestamp", "payload"}) + public static B2Object create(String fileId, String fileName, @Nullable String accountId, @Nullable String bucketId, @Nullable Long contentLength, @Nullable String contentSha1, @Nullable String contentType, @Nullable Map fileInfo, @Nullable Action action, @Nullable Long uploadTimestamp, @Nullable Payload payload) { + if ("none".equals(contentSha1)) { + // large files may have "none" sha1 + contentSha1 = null; + } + if (fileInfo != null) { + fileInfo = ImmutableMap.copyOf(fileInfo); + } + Date date = uploadTimestamp == null ? null : new Date(uploadTimestamp); + return new AutoValue_B2Object(fileId, fileName, contentSha1, fileInfo, payload, date, action, accountId, bucketId, contentLength, contentType); + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/domain/B2ObjectList.java b/providers/b2/src/main/java/org/jclouds/b2/domain/B2ObjectList.java new file mode 100644 index 0000000000..780ab75f77 --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/domain/B2ObjectList.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.util.Date; +import java.util.List; + +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; + +@AutoValue +public abstract class B2ObjectList { + public abstract List files(); + @Nullable public abstract String nextFileId(); + @Nullable public abstract String nextFileName(); + + @SerializedNames({"files", "nextFileId", "nextFileName"}) + public static B2ObjectList create(List files, @Nullable String nextFileId, @Nullable String nextFileName) { + return new AutoValue_B2ObjectList(ImmutableList.copyOf(files), nextFileId, nextFileName); + } + + @AutoValue + public abstract static class Entry { + public abstract Action action(); + public abstract String fileId(); + public abstract String fileName(); + public abstract long size(); + public abstract Date uploadTimestamp(); + + @SerializedNames({"action", "fileId", "fileName", "size", "uploadTimestamp"}) + public static Entry create(Action action, String fileId, String fileName, long size, long uploadTimestamp) { + return new AutoValue_B2ObjectList_Entry(action, fileId, fileName, size, new Date(uploadTimestamp)); + } + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/domain/DeleteFileResponse.java b/providers/b2/src/main/java/org/jclouds/b2/domain/DeleteFileResponse.java new file mode 100644 index 0000000000..21e54700c4 --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/domain/DeleteFileResponse.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class DeleteFileResponse { + public abstract String fileName(); + public abstract String fileId(); + + @SerializedNames({"fileName", "fileId"}) + public static DeleteFileResponse create(String fileName, String fileId) { + return new AutoValue_DeleteFileResponse(fileName, fileId); + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/domain/HideFileResponse.java b/providers/b2/src/main/java/org/jclouds/b2/domain/HideFileResponse.java new file mode 100644 index 0000000000..d7e5e115b2 --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/domain/HideFileResponse.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.util.Date; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class HideFileResponse { + /** Always "hide". */ + public abstract Action action(); + public abstract String fileId(); + public abstract String fileName(); + public abstract Date uploadTimestamp(); + + @SerializedNames({"action", "fileId", "fileName", "uploadTimestamp"}) + public static HideFileResponse create(Action action, String fileId, String fileName, long uploadTimestamp) { + return new AutoValue_HideFileResponse(action, fileId, fileName, new Date(uploadTimestamp)); + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/domain/UploadFileResponse.java b/providers/b2/src/main/java/org/jclouds/b2/domain/UploadFileResponse.java new file mode 100644 index 0000000000..dc235dbcf6 --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/domain/UploadFileResponse.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.util.Map; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; + +@AutoValue +public abstract class UploadFileResponse { + public abstract String fileId(); + public abstract String fileName(); + public abstract String accountId(); + public abstract String bucketId(); + public abstract long contentLength(); + public abstract String contentSha1(); + public abstract String contentType(); + public abstract Map fileInfo(); + + @SerializedNames({"fileId", "fileName", "accountId", "bucketId", "contentLength", "contentSha1", "contentType", "fileInfo"}) + public static UploadFileResponse create(String fileId, String fileName, String accountId, String bucketId, long contentLength, String contentSha1, String contentType, Map fileInfo) { + return new AutoValue_UploadFileResponse(fileId, fileName, accountId, bucketId, contentLength, contentSha1, contentType, ImmutableMap.copyOf(fileInfo)); + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/domain/UploadUrlResponse.java b/providers/b2/src/main/java/org/jclouds/b2/domain/UploadUrlResponse.java new file mode 100644 index 0000000000..36712cabde --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/domain/UploadUrlResponse.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.domain; + +import java.net.URI; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class UploadUrlResponse { + public abstract String bucketId(); + public abstract URI uploadUrl(); + public abstract String authorizationToken(); + + @SerializedNames({"bucketId", "uploadUrl", "authorizationToken"}) + public static UploadUrlResponse create(String bucketId, URI uploadUrl, String authorizationToken) { + return new AutoValue_UploadUrlResponse(bucketId, uploadUrl, authorizationToken); + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/features/ObjectApi.java b/providers/b2/src/main/java/org/jclouds/b2/features/ObjectApi.java new file mode 100644 index 0000000000..4864864f3f --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/features/ObjectApi.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.features; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +import static org.jclouds.blobstore.attr.BlobScopes.CONTAINER; + +import java.util.Map; +import javax.inject.Named; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; + +import org.jclouds.Fallbacks.NullOnNotFoundOr404; +import org.jclouds.blobstore.attr.BlobScope; +import org.jclouds.http.options.GetOptions; +import org.jclouds.io.Payload; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.b2.binders.UploadFileBinder; +import org.jclouds.b2.domain.B2Object; +import org.jclouds.b2.domain.B2ObjectList; +import org.jclouds.b2.domain.DeleteFileResponse; +import org.jclouds.b2.domain.HideFileResponse; +import org.jclouds.b2.domain.UploadFileResponse; +import org.jclouds.b2.domain.UploadUrlResponse; +import org.jclouds.b2.filters.RequestAuthorization; +import org.jclouds.b2.filters.RequestAuthorizationDownload; +import org.jclouds.b2.functions.ParseB2ObjectFromResponse; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; +import org.jclouds.rest.binders.BindToJsonPayload; + +@BlobScope(CONTAINER) +public interface ObjectApi { + @Named("b2_get_upload_url") + @POST + @Path("/b2api/v1/b2_get_upload_url") + @RequestFilters(RequestAuthorization.class) + @MapBinder(BindToJsonPayload.class) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + UploadUrlResponse getUploadUrl(@PayloadParam("bucketId") String bucketId); + + @Named("b2_upload_file") + @POST + @MapBinder(UploadFileBinder.class) + @Consumes(APPLICATION_JSON) + UploadFileResponse uploadFile(@PayloadParam("uploadUrl") UploadUrlResponse uploadUrl, @PayloadParam("fileName") String fileName, @HeaderParam("X-Bz-Content-Sha1") String contentSha1, @PayloadParam("fileInfo") Map fileInfo, Payload payload); + + @Named("b2_delete_file_version") + @POST + @Path("/b2api/v1/b2_delete_file_version") + @MapBinder(BindToJsonPayload.class) + @RequestFilters(RequestAuthorization.class) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + DeleteFileResponse deleteFileVersion(@PayloadParam("fileName") String fileName, @PayloadParam("fileId") String fileId); + + @Named("b2_get_file_info") + @POST + @Path("/b2api/v1/b2_get_file_info") + @MapBinder(BindToJsonPayload.class) + @RequestFilters(RequestAuthorization.class) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + @Fallback(NullOnNotFoundOr404.class) + B2Object getFileInfo(@PayloadParam("fileId") String fileId); + + @Named("b2_download_file_by_id") + @GET + @Path("/b2api/v1/b2_download_file_by_id") + @RequestFilters(RequestAuthorizationDownload.class) + @ResponseParser(ParseB2ObjectFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + B2Object downloadFileById(@QueryParam("fileId") String fileId); + + @Named("b2_download_file_by_id") + @GET + @Path("/b2api/v1/b2_download_file_by_id") + @RequestFilters(RequestAuthorizationDownload.class) + @ResponseParser(ParseB2ObjectFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + B2Object downloadFileById(@QueryParam("fileId") String fileId, GetOptions options); + + @Named("b2_download_file_by_name") + @GET + @Path("/file/{bucketName}/{fileName}") + @RequestFilters(RequestAuthorizationDownload.class) + @ResponseParser(ParseB2ObjectFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + B2Object downloadFileByName(@PathParam("bucketName") String bucketName, @PathParam("fileName") String fileName); + + @Named("b2_download_file_by_name") + @GET + @Path("/file/{bucketName}/{fileName}") + @RequestFilters(RequestAuthorizationDownload.class) + @ResponseParser(ParseB2ObjectFromResponse.class) + @Fallback(NullOnNotFoundOr404.class) + B2Object downloadFileByName(@PathParam("bucketName") String bucketName, @PathParam("fileName") String fileName, GetOptions options); + + @Named("b2_list_file_names") + @GET + @Path("/b2api/v1/b2_list_file_names") + @MapBinder(BindToJsonPayload.class) + @RequestFilters(RequestAuthorization.class) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + B2ObjectList listFileNames(@PayloadParam("bucketId") String bucketId, @PayloadParam("startFileName") @Nullable String startFileName, @PayloadParam("maxFileCount") @Nullable Integer maxFileCount); + + @Named("b2_list_file_versions") + @GET + @Path("/b2api/v1/b2_list_file_versions") + @MapBinder(BindToJsonPayload.class) + @RequestFilters(RequestAuthorization.class) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + B2ObjectList listFileVersions(@PayloadParam("bucketId") String bucketId, @PayloadParam("startFileId") @Nullable String startFileId, @PayloadParam("startFileName") @Nullable String startFileName, @PayloadParam("maxFileCount") @Nullable Integer maxFileCount); + + @Named("b2_hide_file") + @POST + @Path("/b2api/v1/b2_hide_file") + @MapBinder(BindToJsonPayload.class) + @RequestFilters(RequestAuthorization.class) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + HideFileResponse hideFile(@PayloadParam("bucketId") String bucketId, @PayloadParam("fileName") String fileName); +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/filters/RequestAuthorizationDownload.java b/providers/b2/src/main/java/org/jclouds/b2/filters/RequestAuthorizationDownload.java new file mode 100644 index 0000000000..e5f01ed40a --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/filters/RequestAuthorizationDownload.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.filters; + +import java.net.URI; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.collect.Memoized; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.b2.domain.Authorization; + +import com.google.common.base.Supplier; +import com.google.common.net.HttpHeaders; + +@Singleton +public final class RequestAuthorizationDownload implements HttpRequestFilter { + private final Supplier auth; + + @Inject + RequestAuthorizationDownload(@Memoized Supplier auth) { + this.auth = auth; + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + Authorization auth = this.auth.get(); + + // Replace with download URL + URI endpoint = request.getEndpoint(); + endpoint = URI.create(auth.downloadUrl() + + (endpoint.getPort() == -1 ? "" : ":" + endpoint.getPort()) + + endpoint.getRawPath() + + (endpoint.getQuery() == null ? "" : "?" + endpoint.getQuery())); + + request = request.toBuilder() + .endpoint(endpoint) + .replaceHeader(HttpHeaders.AUTHORIZATION, auth.authorizationToken()) + .build(); + return request; + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/functions/ParseB2ObjectFromResponse.java b/providers/b2/src/main/java/org/jclouds/b2/functions/ParseB2ObjectFromResponse.java new file mode 100644 index 0000000000..35fcab99a2 --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/functions/ParseB2ObjectFromResponse.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.functions; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Date; +import java.util.Map; + +import org.jclouds.http.HttpResponse; +import org.jclouds.io.MutableContentMetadata; +import org.jclouds.io.Payload; +import org.jclouds.b2.domain.B2Object; +import org.jclouds.b2.reference.B2Headers; + +import com.google.common.base.Function; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; + +public final class ParseB2ObjectFromResponse implements Function { + @Override + public B2Object apply(HttpResponse from) { + Payload payload = from.getPayload(); + MutableContentMetadata contentMeta = payload.getContentMetadata(); + + String fileId = from.getFirstHeaderOrNull(B2Headers.FILE_ID); + String fileName; + try { + fileName = URLDecoder.decode(from.getFirstHeaderOrNull(B2Headers.FILE_NAME), "UTF-8"); + } catch (UnsupportedEncodingException uee) { + throw Throwables.propagate(uee); + } + String contentSha1 = from.getFirstHeaderOrNull(B2Headers.CONTENT_SHA1); + ImmutableMap.Builder fileInfo = ImmutableMap.builder(); + for (Map.Entry entry : from.getHeaders().entries()) { + if (entry.getKey().regionMatches(true, 0, B2Headers.FILE_INFO_PREFIX, 0, B2Headers.FILE_INFO_PREFIX.length())) { + fileInfo.put(entry.getKey().substring(B2Headers.FILE_INFO_PREFIX.length()), entry.getValue()); + } + } + Date uploadTimestamp = new Date(Long.parseLong(from.getFirstHeaderOrNull(B2Headers.UPLOAD_TIMESTAMP))); + + return B2Object.create(fileId, fileName, null, null, contentMeta.getContentLength(), contentSha1, contentMeta.getContentType(), fileInfo.build(), null, uploadTimestamp.getTime(), payload); + } +} diff --git a/providers/b2/src/main/java/org/jclouds/b2/handlers/ParseB2ErrorFromJsonContent.java b/providers/b2/src/main/java/org/jclouds/b2/handlers/ParseB2ErrorFromJsonContent.java index b48760ccc9..6442e28267 100644 --- a/providers/b2/src/main/java/org/jclouds/b2/handlers/ParseB2ErrorFromJsonContent.java +++ b/providers/b2/src/main/java/org/jclouds/b2/handlers/ParseB2ErrorFromJsonContent.java @@ -17,6 +17,7 @@ package org.jclouds.b2.handlers; import org.jclouds.blobstore.ContainerNotFoundException; +import org.jclouds.blobstore.KeyNotFoundException; import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpErrorHandler; import org.jclouds.http.HttpResponse; @@ -24,6 +25,7 @@ import org.jclouds.http.functions.ParseJson; import org.jclouds.json.Json; import org.jclouds.b2.B2ResponseException; import org.jclouds.b2.domain.B2Error; +import org.jclouds.rest.ResourceNotFoundException; import com.google.inject.Inject; import com.google.inject.TypeLiteral; @@ -39,6 +41,12 @@ public final class ParseB2ErrorFromJsonContent extends ParseJson implem return new ContainerNotFoundException(exception); } else if ("bad_json".equals(error.code())) { return new IllegalArgumentException(error.message(), exception); + } else if ("bad_request".equals(error.code())) { + return new IllegalArgumentException(error.message(), exception); + } else if ("file_not_present".equals(error.code())) { + return new KeyNotFoundException(exception); + } else if ("not_found".equals(error.code())) { + return new ResourceNotFoundException(error.message(), exception); } else { return exception; } diff --git a/providers/b2/src/main/java/org/jclouds/b2/reference/B2Headers.java b/providers/b2/src/main/java/org/jclouds/b2/reference/B2Headers.java new file mode 100644 index 0000000000..4937c1ba63 --- /dev/null +++ b/providers/b2/src/main/java/org/jclouds/b2/reference/B2Headers.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.reference; + +public final class B2Headers { + public static final String CONTENT_SHA1 = "X-Bz-Content-Sha1"; + public static final String FILE_ID = "X-Bz-File-Id"; + public static final String FILE_NAME = "X-Bz-File-Name"; + public static final String UPLOAD_TIMESTAMP = "X-Bz-Upload-Timestamp"; + /** + * Recommended user metadata for last-modified. The value should be a base 10 number which represents a UTC time + * when the original source file was last modified. It is a base 10 number of milliseconds since midnight, January + * 1, 1970 UTC. + */ + public static final String LAST_MODIFIED = "X-Bz-Info-src_last_modified_millis"; + + public static final String FILE_INFO_PREFIX = "X-Bz-Info-"; + + private B2Headers() { + throw new AssertionError("intentionally unimplemented"); + } +} diff --git a/providers/b2/src/test/java/org/jclouds/b2/features/ObjectApiLiveTest.java b/providers/b2/src/test/java/org/jclouds/b2/features/ObjectApiLiveTest.java new file mode 100644 index 0000000000..3de3cdb0f4 --- /dev/null +++ b/providers/b2/src/test/java/org/jclouds/b2/features/ObjectApiLiveTest.java @@ -0,0 +1,284 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.features; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Random; + +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.b2.domain.Action; +import org.jclouds.b2.domain.B2Object; +import org.jclouds.b2.domain.B2ObjectList; +import org.jclouds.b2.domain.Bucket; +import org.jclouds.b2.domain.BucketType; +import org.jclouds.b2.domain.HideFileResponse; +import org.jclouds.b2.domain.UploadFileResponse; +import org.jclouds.b2.domain.UploadUrlResponse; +import org.jclouds.b2.internal.BaseB2ApiLiveTest; +import org.jclouds.util.Closeables2; +import org.jclouds.utils.TestUtils; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteSource; + +public final class ObjectApiLiveTest extends BaseB2ApiLiveTest { + private static final Random random = new Random(); + + @Test(groups = "live") + public void testGetFileInfo() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + + ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024); + Payload payload = Payloads.newByteSourcePayload(byteSource); + payload.getContentMetadata().setContentLength(byteSource.size()); + String fileName = "file-name"; + String contentSha1 = byteSource.hash(Hashing.sha1()).toString(); + String contentType = "text/plain"; + payload.getContentMetadata().setContentType(contentType); + Map fileInfo = ImmutableMap.of("author", "unknown"); + + Bucket response = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + UploadFileResponse uploadFile = null; + try { + UploadUrlResponse uploadUrl = objectApi.getUploadUrl(response.bucketId()); + + uploadFile = objectApi.uploadFile(uploadUrl, fileName, contentSha1, fileInfo, payload); + + B2Object b2Object = objectApi.getFileInfo(uploadFile.fileId()); + assertThat(b2Object.fileId()).isEqualTo(uploadFile.fileId()); + assertThat(b2Object.fileName()).isEqualTo(fileName); + assertThat(b2Object.accountId()).isEqualTo(response.accountId()); + assertThat(b2Object.bucketId()).isEqualTo(response.bucketId()); + assertThat(b2Object.contentLength()).isEqualTo(byteSource.size()); + assertThat(b2Object.contentSha1()).isEqualTo(contentSha1); + assertThat(b2Object.contentType()).isEqualTo(contentType); + assertThat(b2Object.fileInfo()).isEqualTo(fileInfo); + assertThat(b2Object.action()).isEqualTo(Action.UPLOAD); + assertThat(b2Object.uploadTimestamp()).isAfterYear(2015); + assertThat(b2Object.payload()).isNull(); + } finally { + if (uploadFile != null) { + objectApi.deleteFileVersion(uploadFile.fileName(), uploadFile.fileId()); + } + bucketApi.deleteBucket(response.bucketId()); + } + } + + @Test(groups = "live") + public void testDownloadFileById() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + + ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024); + Payload payload = Payloads.newByteSourcePayload(byteSource); + payload.getContentMetadata().setContentLength(byteSource.size()); + String fileName = "file-name"; + String contentSha1 = byteSource.hash(Hashing.sha1()).toString(); + String contentType = "text/plain"; + payload.getContentMetadata().setContentType(contentType); + Map fileInfo = ImmutableMap.of("author", "unknown"); + + Bucket response = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + UploadFileResponse uploadFile = null; + try { + UploadUrlResponse uploadUrl = objectApi.getUploadUrl(response.bucketId()); + + uploadFile = objectApi.uploadFile(uploadUrl, fileName, contentSha1, fileInfo, payload); + + B2Object b2Object = objectApi.downloadFileById(uploadFile.fileId()); + payload = b2Object.payload(); + assertThat(b2Object.fileName()).isEqualTo(fileName); + assertThat(b2Object.contentSha1()).isEqualTo(contentSha1); + assertThat(b2Object.fileInfo()).isEqualTo(fileInfo); + assertThat(b2Object.uploadTimestamp()).isAfterYear(2015); + assertThat(payload.getContentMetadata().getContentType()).isEqualTo(contentType); + + InputStream actual = null; + InputStream expected = null; + try { + actual = payload.openStream(); + expected = byteSource.openStream(); + assertThat(actual).hasContentEqualTo(expected); + } finally { + Closeables2.closeQuietly(expected); + Closeables2.closeQuietly(actual); + } + } finally { + if (uploadFile != null) { + objectApi.deleteFileVersion(uploadFile.fileName(), uploadFile.fileId()); + } + bucketApi.deleteBucket(response.bucketId()); + } + } + + @Test(groups = "live") + public void testDownloadFileByName() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + + String bucketName = getBucketName(); + ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024); + Payload payload = Payloads.newByteSourcePayload(byteSource); + payload.getContentMetadata().setContentLength(byteSource.size()); + String fileName = "file name"; // intentionally using spaces in file name + String contentSha1 = byteSource.hash(Hashing.sha1()).toString(); + String contentType = "text/plain"; + payload.getContentMetadata().setContentType(contentType); + Map fileInfo = ImmutableMap.of("author", "unknown"); + + Bucket response = bucketApi.createBucket(bucketName, BucketType.ALL_PRIVATE); + UploadFileResponse uploadFile = null; + try { + UploadUrlResponse uploadUrl = objectApi.getUploadUrl(response.bucketId()); + + uploadFile = objectApi.uploadFile(uploadUrl, fileName, contentSha1, fileInfo, payload); + + B2Object b2Object = objectApi.downloadFileByName(bucketName, fileName); + payload = b2Object.payload(); + assertThat(b2Object.fileName()).isEqualTo(fileName); + assertThat(b2Object.contentSha1()).isEqualTo(contentSha1); + assertThat(b2Object.fileInfo()).isEqualTo(fileInfo); + assertThat(b2Object.uploadTimestamp()).isAfterYear(2015); + assertThat(payload.getContentMetadata().getContentType()).isEqualTo(contentType); + + InputStream actual = null; + InputStream expected = null; + try { + actual = payload.openStream(); + expected = byteSource.openStream(); + assertThat(actual).hasContentEqualTo(expected); + } finally { + Closeables2.closeQuietly(expected); + Closeables2.closeQuietly(actual); + } + } finally { + if (uploadFile != null) { + objectApi.deleteFileVersion(uploadFile.fileName(), uploadFile.fileId()); + } + bucketApi.deleteBucket(response.bucketId()); + } + } + + @Test(groups = "live") + public void testListFileNames() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + + Bucket response = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + int numFiles = 3; + ImmutableList.Builder uploadFiles = ImmutableList.builder(); + try { + for (int i = 0; i < numFiles; ++i) { + uploadFiles.add(createFile(objectApi, response.bucketId(), "file" + i)); + } + + B2ObjectList list = objectApi.listFileNames(response.bucketId(), null, null); + assertThat(list.files()).hasSize(numFiles); + } finally { + for (UploadFileResponse uploadFile : uploadFiles.build()) { + objectApi.deleteFileVersion(uploadFile.fileName(), uploadFile.fileId()); + } + bucketApi.deleteBucket(response.bucketId()); + } + } + + @Test(groups = "live") + public void testListFileVersions() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + + Bucket response = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + int numFiles = 3; + ImmutableList.Builder uploadFiles = ImmutableList.builder(); + try { + for (int i = 0; i < numFiles; ++i) { + uploadFiles.add(createFile(objectApi, response.bucketId(), "file")); + } + + B2ObjectList list = objectApi.listFileNames(response.bucketId(), null, null); + assertThat(list.files()).hasSize(1); + + list = objectApi.listFileVersions(response.bucketId(), null, null, null); + assertThat(list.files()).hasSize(numFiles); + } finally { + for (UploadFileResponse uploadFile : uploadFiles.build()) { + objectApi.deleteFileVersion(uploadFile.fileName(), uploadFile.fileId()); + } + bucketApi.deleteBucket(response.bucketId()); + } + } + + @Test(groups = "live") + public void testHideFile() throws Exception { + BucketApi bucketApi = api.getBucketApi(); + ObjectApi objectApi = api.getObjectApi(); + String fileName = "file-name"; + + Bucket response = bucketApi.createBucket(getBucketName(), BucketType.ALL_PRIVATE); + UploadFileResponse uploadFile = null; + HideFileResponse hideFile = null; + try { + uploadFile = createFile(objectApi, response.bucketId(), fileName); + + B2ObjectList list = objectApi.listFileNames(response.bucketId(), null, null); + assertThat(list.files()).hasSize(1); + + hideFile = objectApi.hideFile(response.bucketId(), fileName); + + list = objectApi.listFileNames(response.bucketId(), null, null); + assertThat(list.files()).isEmpty(); + + list = objectApi.listFileVersions(response.bucketId(), null, null, null); + assertThat(list.files()).hasSize(2); + } finally { + if (hideFile != null) { + objectApi.deleteFileVersion(hideFile.fileName(), hideFile.fileId()); + } + if (uploadFile != null) { + objectApi.deleteFileVersion(uploadFile.fileName(), uploadFile.fileId()); + } + bucketApi.deleteBucket(response.bucketId()); + } + } + + private static String getBucketName() { + return "jcloudstestbucket-" + random.nextInt(Integer.MAX_VALUE); + } + + private static UploadFileResponse createFile(ObjectApi objectApi, String bucketId, String fileName) throws IOException { + ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024); + Payload payload = Payloads.newByteSourcePayload(byteSource); + payload.getContentMetadata().setContentLength(byteSource.size()); + String contentSha1 = byteSource.hash(Hashing.sha1()).toString(); + String contentType = "text/plain"; + payload.getContentMetadata().setContentType(contentType); + Map fileInfo = ImmutableMap.of("author", "unknown"); + + UploadUrlResponse uploadUrl = objectApi.getUploadUrl(bucketId); + + return objectApi.uploadFile(uploadUrl, fileName, contentSha1, fileInfo, payload); + } +} diff --git a/providers/b2/src/test/java/org/jclouds/b2/features/ObjectApiMockTest.java b/providers/b2/src/test/java/org/jclouds/b2/features/ObjectApiMockTest.java new file mode 100644 index 0000000000..100f909af8 --- /dev/null +++ b/providers/b2/src/test/java/org/jclouds/b2/features/ObjectApiMockTest.java @@ -0,0 +1,545 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.jclouds.b2.features; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.Date; +import java.util.Map; +import java.util.Set; +import java.util.Properties; + +import org.jclouds.ContextBuilder; +import org.jclouds.blobstore.ContainerNotFoundException; +import org.jclouds.blobstore.KeyNotFoundException; +import org.jclouds.concurrent.config.ExecutorServiceModule; +import org.jclouds.http.options.GetOptions; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.b2.B2Api; +import org.jclouds.b2.domain.Action; +import org.jclouds.b2.domain.B2Object; +import org.jclouds.b2.domain.B2ObjectList; +import org.jclouds.b2.domain.DeleteFileResponse; +import org.jclouds.b2.domain.HideFileResponse; +import org.jclouds.b2.domain.UploadFileResponse; +import org.jclouds.b2.domain.UploadUrlResponse; +import org.jclouds.b2.reference.B2Headers; +import org.jclouds.util.Strings2; +import org.testng.annotations.Test; + +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.net.HttpHeaders; +import com.google.common.reflect.TypeToken; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.inject.Module; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; + +@Test(groups = "unit", testName = "ObjectApiMockTest") +public final class ObjectApiMockTest { + private final Set modules = ImmutableSet. of( + new ExecutorServiceModule(MoreExecutors.sameThreadExecutor())); + + private static final String BUCKET_NAME = "BUCKET_NAME"; + private static final String BUCKET_ID = "4a48fe8875c6214145260818"; + private static final String FILE_ID = "4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104"; + private static final String FILE_NAME = "typing_test.txt"; + private static final String CONTENT_TYPE = "text/plain"; + private static final String SHA1 = "bae5ed658ab3546aee12f23f36392f35dba1ebdd"; + private static final String PAYLOAD = "The quick brown fox jumped over the lazy dog.\n"; + private static final Map FILE_INFO = ImmutableMap.of("author", "unknown"); + + public void testGetUploadUrl() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/get_upload_url_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + UploadUrlResponse response = api.getUploadUrl(BUCKET_ID); + assertThat(response.bucketId()).isEqualTo(BUCKET_ID); + assertThat(response.uploadUrl()).isEqualTo(URI.create("https://pod-000-1005-03.backblaze.com/b2api/v1/b2_upload_file?cvt=c001_v0001005_t0027&bucket=4a48fe8875c6214145260818")); + assertThat(response.authorizationToken()).isEqualTo("2_20151009170037_f504a0f39a0f4e657337e624_9754dde94359bd7b8f1445c8f4cc1a231a33f714_upld"); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_get_upload_url", "/get_upload_url_request.json"); + } finally { + server.shutdown(); + } + } + + public void testGetUploadUrlDeletedBucket() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setResponseCode(400).setBody(stringFromResource("/get_upload_url_deleted_bucket_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + try { + api.getUploadUrl(BUCKET_ID); + failBecauseExceptionWasNotThrown(ContainerNotFoundException.class); + } catch (ContainerNotFoundException cnfe) { + // expected + } + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_get_upload_url", "/get_upload_url_request.json"); + } finally { + server.shutdown(); + } + } + + public void testUploadFile() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/upload_file_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + String accountId = "d522aa47a10f"; + + UploadUrlResponse uploadUrl = UploadUrlResponse.create(BUCKET_ID, server.getUrl("/b2api/v1/b2_upload_file/4a48fe8875c6214145260818/c001_v0001007_t0042").toURI(), "FAKE-AUTHORIZATION-TOKEN"); + Payload payload = Payloads.newStringPayload(PAYLOAD); + payload.getContentMetadata().setContentType(CONTENT_TYPE); + UploadFileResponse response = api.uploadFile(uploadUrl, FILE_NAME, SHA1, FILE_INFO, payload); + + assertThat(response.fileId()).isEqualTo(FILE_ID); + assertThat(response.fileName()).isEqualTo(FILE_NAME); + assertThat(response.accountId()).isEqualTo(accountId); + assertThat(response.bucketId()).isEqualTo(BUCKET_ID); + assertThat(response.contentLength()).isEqualTo(PAYLOAD.length()); + assertThat(response.contentSha1()).isEqualTo(SHA1); + assertThat(response.contentType()).isEqualTo(CONTENT_TYPE); + assertThat(response.fileInfo()).isEqualTo(FILE_INFO); + + assertThat(server.getRequestCount()).isEqualTo(1); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_upload_file/4a48fe8875c6214145260818/c001_v0001007_t0042"); + } finally { + server.shutdown(); + } + } + + public void testDeleteFileVersion() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/delete_object_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + DeleteFileResponse response = api.deleteFileVersion(FILE_NAME, FILE_ID); + assertThat(response.fileName()).isEqualTo(FILE_NAME); + assertThat(response.fileId()).isEqualTo(FILE_ID); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_delete_file_version", "/delete_object_request.json"); + } finally { + server.shutdown(); + } + } + + public void testDeleteAlreadyDeletedFileVersion() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setResponseCode(400).setBody(stringFromResource("/delete_file_version_already_deleted_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + try { + api.deleteFileVersion(FILE_NAME, FILE_ID); + failBecauseExceptionWasNotThrown(KeyNotFoundException.class); + } catch (KeyNotFoundException knfe) { + // expected + } + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_delete_file_version", "/delete_object_request.json"); + } finally { + server.shutdown(); + } + } + + public void testGetFileInfo() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/get_file_info_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + B2Object b2Object = api.getFileInfo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001"); + assertThat(b2Object.fileId()).isEqualTo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001"); + assertThat(b2Object.fileName()).isEqualTo("akitty.jpg"); + assertThat(b2Object.accountId()).isEqualTo("7eecc42b9675"); + assertThat(b2Object.bucketId()).isEqualTo("e73ede9c9c8412db49f60715"); + assertThat(b2Object.contentLength()).isEqualTo(122573); + assertThat(b2Object.contentSha1()).isEqualTo("a01a21253a07fb08a354acd30f3a6f32abb76821"); + assertThat(b2Object.contentType()).isEqualTo("image/jpeg"); + assertThat(b2Object.fileInfo()).isEqualTo(ImmutableMap.of()); + assertThat(b2Object.action()).isEqualTo(Action.UPLOAD); + assertThat(b2Object.uploadTimestamp()).isAfterYear(2014); + assertThat(b2Object.payload()).isNull(); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_get_file_info", "/get_file_info_request.json"); + } finally { + server.shutdown(); + } + } + + public void testGetFileInfoDeletedFileVersion() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setResponseCode(404).setBody(stringFromResource("/get_file_info_deleted_file_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + B2Object b2Object = api.getFileInfo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001"); + assertThat(b2Object).isNull(); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_get_file_info", "/get_file_info_request.json"); + } finally { + server.shutdown(); + } + } + + public void testDownloadFileById() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + + server.enqueue(new MockResponse() + .addHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE) + .addHeader(B2Headers.FILE_ID, FILE_ID) + .addHeader(B2Headers.FILE_NAME, FILE_NAME) + .addHeader(B2Headers.CONTENT_SHA1, SHA1) + .addHeader(B2Headers.UPLOAD_TIMESTAMP, String.valueOf(1500000000000L)) + .addHeader(B2Headers.FILE_INFO_PREFIX + FILE_INFO.entrySet().iterator().next().getKey(), FILE_INFO.entrySet().iterator().next().getValue()) + .setBody(PAYLOAD)); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + + B2Object b2Object = api.downloadFileById(FILE_ID); + + assertThat(b2Object.fileId()).isEqualTo(FILE_ID); + assertThat(b2Object.fileName()).isEqualTo(FILE_NAME); + assertThat(b2Object.contentSha1()).isEqualTo(SHA1); + assertThat(b2Object.fileInfo()).isEqualTo(FILE_INFO); + assertThat(b2Object.uploadTimestamp()).isAfterYear(2015); + assertThat(b2Object.payload().getContentMetadata().getContentLength()).isEqualTo(PAYLOAD.length()); + assertThat(b2Object.payload().getContentMetadata().getContentType()).isEqualTo(CONTENT_TYPE); + + assertThat(server.getRequestCount()).isEqualTo(2); + + RecordedRequest request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/b2api/v1/b2_authorize_account"); + + request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/b2api/v1/b2_download_file_by_id?fileId=4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104"); + } finally { + server.shutdown(); + } + } + + public void testDownloadFileByIdOptions() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + + server.enqueue(new MockResponse() + .addHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE) + .addHeader(B2Headers.FILE_ID, FILE_ID) + .addHeader(B2Headers.FILE_NAME, FILE_NAME) + .addHeader(B2Headers.CONTENT_SHA1, SHA1) + .addHeader(B2Headers.UPLOAD_TIMESTAMP, String.valueOf(1500000000000L)) + .addHeader(B2Headers.FILE_INFO_PREFIX + FILE_INFO.entrySet().iterator().next().getKey(), FILE_INFO.entrySet().iterator().next().getValue()) + .setBody(PAYLOAD)); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + + B2Object b2Object = api.downloadFileById(FILE_ID, new GetOptions().range(42, 69)); + + assertThat(server.getRequestCount()).isEqualTo(2); + + RecordedRequest request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/b2api/v1/b2_authorize_account"); + + request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/b2api/v1/b2_download_file_by_id?fileId=4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104"); + assertThat(request.getHeaders()).contains("Range: bytes=42-69"); + } finally { + server.shutdown(); + } + } + + public void testDownloadFileByName() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + + server.enqueue(new MockResponse() + .addHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE) + .addHeader(B2Headers.FILE_ID, FILE_ID) + .addHeader(B2Headers.FILE_NAME, FILE_NAME) + .addHeader(B2Headers.CONTENT_SHA1, SHA1) + .addHeader(B2Headers.UPLOAD_TIMESTAMP, String.valueOf(1500000000000L)) + .addHeader(B2Headers.FILE_INFO_PREFIX + FILE_INFO.entrySet().iterator().next().getKey(), FILE_INFO.entrySet().iterator().next().getValue()) + .setBody(PAYLOAD)); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + + B2Object b2Object = api.downloadFileByName(BUCKET_NAME, FILE_NAME); + + assertThat(b2Object.fileId()).isEqualTo(FILE_ID); + assertThat(b2Object.fileName()).isEqualTo(FILE_NAME); + assertThat(b2Object.contentSha1()).isEqualTo(SHA1); + assertThat(b2Object.fileInfo()).isEqualTo(FILE_INFO); + assertThat(b2Object.uploadTimestamp()).isAfterYear(2015); + assertThat(b2Object.payload().getContentMetadata().getContentLength()).isEqualTo(PAYLOAD.length()); + assertThat(b2Object.payload().getContentMetadata().getContentType()).isEqualTo(CONTENT_TYPE); + + assertThat(server.getRequestCount()).isEqualTo(2); + + RecordedRequest request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/b2api/v1/b2_authorize_account"); + + request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).isEqualTo("/file/BUCKET_NAME/typing_test.txt"); + } finally { + server.shutdown(); + } + } + + public void testListFileNames() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/list_file_names_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + String accountId = "d522aa47a10f"; + + B2ObjectList list = api.listFileNames(BUCKET_ID, null, null); + + assertThat(list.nextFileName()).isNull(); + assertThat(list.files()).hasSize(2); + + B2ObjectList.Entry object = list.files().get(0); + assertThat(object.action()).isEqualTo(Action.UPLOAD); + assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f1004ba650fe24e6b_d20150809_m012853_c100_v0009990_t0000"); + assertThat(object.fileName()).isEqualTo("files/hello.txt"); + assertThat(object.size()).isEqualTo(6); + assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439083733000L)); + + object = list.files().get(1); + assertThat(object.action()).isEqualTo(Action.UPLOAD); + assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f1004ba650fe24e6c_d20150809_m012854_c100_v0009990_t0000"); + assertThat(object.fileName()).isEqualTo("files/world.txt"); + assertThat(object.size()).isEqualTo(6); + assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439083734000L)); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_list_file_names", "/list_file_names_request.json"); + } finally { + server.shutdown(); + } + } + + public void testListFileVersions() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/list_file_versions_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + String accountId = "d522aa47a10f"; + + B2ObjectList list = api.listFileVersions(BUCKET_ID, null, null, null); + + assertThat(list.nextFileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f100920ddab886247_d20150809_m232316_c100_v0009990_t0003"); + assertThat(list.nextFileName()).isEqualTo("files/world.txt"); + assertThat(list.files()).hasSize(3); + + B2ObjectList.Entry object = list.files().get(0); + assertThat(object.action()).isEqualTo(Action.UPLOAD); + assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f100920ddab886245_d20150809_m232316_c100_v0009990_t0003"); + assertThat(object.fileName()).isEqualTo("files/hello.txt"); + assertThat(object.size()).isEqualTo(6); + assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439162596000L)); + + object = list.files().get(1); + assertThat(object.action()).isEqualTo(Action.HIDE); + assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f100920ddab886247_d20150809_m232323_c100_v0009990_t0005"); + assertThat(object.fileName()).isEqualTo("files/world.txt"); + assertThat(object.size()).isEqualTo(0); + assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439162603000L)); + + object = list.files().get(2); + assertThat(object.action()).isEqualTo(Action.UPLOAD); + assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f100920ddab886246_d20150809_m232316_c100_v0009990_t0003"); + assertThat(object.fileName()).isEqualTo("files/world.txt"); + assertThat(object.size()).isEqualTo(6); + assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439162596000L)); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_list_file_versions", "/list_file_versions_request.json"); + } finally { + server.shutdown(); + } + } + + public void testHideFile() throws Exception { + MockWebServer server = createMockWebServer(); + server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json"))); + server.enqueue(new MockResponse().setBody(stringFromResource("/hide_file_response.json"))); + + try { + ObjectApi api = api(server.getUrl("/").toString(), "b2").getObjectApi(); + String accountId = "d522aa47a10f"; + + HideFileResponse response = api.hideFile(BUCKET_ID, FILE_NAME); + assertThat(response.action()).isEqualTo(Action.HIDE); + assertThat(response.fileId()).isEqualTo("4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104"); + assertThat(response.fileName()).isEqualTo(FILE_NAME); + assertThat(response.uploadTimestamp()).isEqualTo(new Date(1437815673000L)); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertAuthentication(server); + assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_hide_file", "/hide_file_request.json"); + } finally { + server.shutdown(); + } + } + + public B2Api api(String uri, String provider, Properties overrides) { + return ContextBuilder.newBuilder(provider) + .credentials("ACCOUNT_ID", "APPLICATION_KEY") + .endpoint(uri) + .overrides(overrides) + .modules(modules) + .buildApi(new TypeToken(getClass()) {}); + } + + public B2Api api(String uri, String provider) { + return api(uri, provider, new Properties()); + } + + public static MockWebServer createMockWebServer() throws IOException { + MockWebServer server = new MockWebServer(); + server.play(); + URL url = server.getUrl(""); + return server; + } + + public void assertAuthentication(MockWebServer server) { + assertThat(server.getRequestCount()).isGreaterThanOrEqualTo(1); + try { + assertThat(server.takeRequest().getRequestLine()).isEqualTo("GET /b2api/v1/b2_authorize_account HTTP/1.1"); + } catch (InterruptedException e) { + throw Throwables.propagate(e); + } + } + + /** + * Ensures the request has a json header for the proper REST methods. + * + * @param request + * @param method + * The request method (such as GET). + * @param path + * The path requested for this REST call. + * @see RecordedRequest + */ + public void assertRequest(RecordedRequest request, String method, String path) { + assertThat(request.getMethod()).isEqualTo(method); + assertThat(request.getPath()).isEqualTo(path); + } + + /** + * Ensures the request is json and has the same contents as the resource + * file provided. + * + * @param request + * @param method + * The request method (such as GET). + * @param resourceLocation + * The location of the resource file. Contents will be compared to + * the request body as JSON. + * @see RecordedRequest + */ + public void assertRequest(RecordedRequest request, String method, String path, String resourceLocation) { + assertRequest(request, method, path); + assertContentTypeIsJson(request); + JsonParser parser = new JsonParser(); + JsonElement requestJson; + try { + requestJson = parser.parse(new String(request.getBody(), Charsets.UTF_8)); + } catch (Exception e) { + throw Throwables.propagate(e); + } + JsonElement resourceJson = parser.parse(stringFromResource(resourceLocation)); + assertThat(requestJson).isEqualTo(resourceJson); + } + + /** + * Ensures the request has a json header. + * + * @param request + * @see RecordedRequest + */ + private void assertContentTypeIsJson(RecordedRequest request) { + assertThat(request.getHeaders()).contains("Content-Type: application/json"); + } + + /** + * Get a string from a resource + * + * @param resourceName + * The name of the resource. + * @return The content of the resource + */ + public String stringFromResource(String resourceName) { + try { + return Strings2.toStringAndClose(getClass().getResourceAsStream(resourceName)); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } +} diff --git a/providers/b2/src/test/resources/delete_file_version_already_deleted_response.json b/providers/b2/src/test/resources/delete_file_version_already_deleted_response.json new file mode 100644 index 0000000000..43dd03d795 --- /dev/null +++ b/providers/b2/src/test/resources/delete_file_version_already_deleted_response.json @@ -0,0 +1,5 @@ +{ + "status" : 400, + "code" : "file_not_present", + "message" : "File not present: file-name 4_za7acecf18b053f3258580715_f1036e7f957cafbe1_d20160609_m045216_c001_v0001011_t0035" +} diff --git a/providers/b2/src/test/resources/delete_object_request.json b/providers/b2/src/test/resources/delete_object_request.json new file mode 100644 index 0000000000..faccfa84c1 --- /dev/null +++ b/providers/b2/src/test/resources/delete_object_request.json @@ -0,0 +1,4 @@ +{ + "fileName": "typing_test.txt", + "fileId": "4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104" +} diff --git a/providers/b2/src/test/resources/delete_object_response.json b/providers/b2/src/test/resources/delete_object_response.json new file mode 100644 index 0000000000..768ce19c70 --- /dev/null +++ b/providers/b2/src/test/resources/delete_object_response.json @@ -0,0 +1,4 @@ +{ + "fileId" : "4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104", + "fileName" : "typing_test.txt" +} diff --git a/providers/b2/src/test/resources/get_file_info_deleted_file_response.json b/providers/b2/src/test/resources/get_file_info_deleted_file_response.json new file mode 100644 index 0000000000..5cf4eab441 --- /dev/null +++ b/providers/b2/src/test/resources/get_file_info_deleted_file_response.json @@ -0,0 +1,5 @@ +{ + "status" : 404, + "code" : "not_found", + "message" : "file_state_deleted" +} diff --git a/providers/b2/src/test/resources/get_file_info_request.json b/providers/b2/src/test/resources/get_file_info_request.json new file mode 100644 index 0000000000..5ab3a636fe --- /dev/null +++ b/providers/b2/src/test/resources/get_file_info_request.json @@ -0,0 +1,3 @@ +{ + "fileId": "4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001" +} diff --git a/providers/b2/src/test/resources/get_file_info_response.json b/providers/b2/src/test/resources/get_file_info_response.json new file mode 100644 index 0000000000..b322d86eb6 --- /dev/null +++ b/providers/b2/src/test/resources/get_file_info_response.json @@ -0,0 +1,12 @@ +{ + "accountId": "7eecc42b9675", + "bucketId": "e73ede9c9c8412db49f60715", + "contentLength": 122573, + "contentSha1": "a01a21253a07fb08a354acd30f3a6f32abb76821", + "contentType": "image/jpeg", + "fileId": "4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001", + "fileInfo": {}, + "fileName": "akitty.jpg", + "action": "upload", + "uploadTimestamp": 1439083733000 +} diff --git a/providers/b2/src/test/resources/get_upload_url_deleted_bucket_response.json b/providers/b2/src/test/resources/get_upload_url_deleted_bucket_response.json new file mode 100644 index 0000000000..007a1eb98a --- /dev/null +++ b/providers/b2/src/test/resources/get_upload_url_deleted_bucket_response.json @@ -0,0 +1,5 @@ +{ + "status" : 400, + "code" : "bad_bucket_id", + "message" : "Bucket b7ecac119bd53f3258580715 does not exist" +} diff --git a/providers/b2/src/test/resources/get_upload_url_request.json b/providers/b2/src/test/resources/get_upload_url_request.json new file mode 100644 index 0000000000..80cb5ba491 --- /dev/null +++ b/providers/b2/src/test/resources/get_upload_url_request.json @@ -0,0 +1,3 @@ +{ + "bucketId" : "4a48fe8875c6214145260818" +} diff --git a/providers/b2/src/test/resources/get_upload_url_response.json b/providers/b2/src/test/resources/get_upload_url_response.json new file mode 100644 index 0000000000..0be7f61192 --- /dev/null +++ b/providers/b2/src/test/resources/get_upload_url_response.json @@ -0,0 +1,5 @@ +{ + "bucketId" : "4a48fe8875c6214145260818", + "uploadUrl" : "https://pod-000-1005-03.backblaze.com/b2api/v1/b2_upload_file?cvt=c001_v0001005_t0027&bucket=4a48fe8875c6214145260818", + "authorizationToken" : "2_20151009170037_f504a0f39a0f4e657337e624_9754dde94359bd7b8f1445c8f4cc1a231a33f714_upld" +} diff --git a/providers/b2/src/test/resources/hide_file_request.json b/providers/b2/src/test/resources/hide_file_request.json new file mode 100644 index 0000000000..10e05e0849 --- /dev/null +++ b/providers/b2/src/test/resources/hide_file_request.json @@ -0,0 +1,4 @@ +{ + "bucketId": "4a48fe8875c6214145260818", + "fileName": "typing_test.txt" +} diff --git a/providers/b2/src/test/resources/hide_file_response.json b/providers/b2/src/test/resources/hide_file_response.json new file mode 100644 index 0000000000..85c6853a40 --- /dev/null +++ b/providers/b2/src/test/resources/hide_file_response.json @@ -0,0 +1,6 @@ +{ + "action" : "hide", + "fileId" : "4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104", + "fileName" : "typing_test.txt", + "uploadTimestamp" : 1437815673000 +} diff --git a/providers/b2/src/test/resources/list_file_names_request.json b/providers/b2/src/test/resources/list_file_names_request.json new file mode 100644 index 0000000000..32b805ce69 --- /dev/null +++ b/providers/b2/src/test/resources/list_file_names_request.json @@ -0,0 +1,3 @@ +{ + "bucketId": "4a48fe8875c6214145260818" +} diff --git a/providers/b2/src/test/resources/list_file_names_response.json b/providers/b2/src/test/resources/list_file_names_response.json new file mode 100644 index 0000000000..51d95ba96e --- /dev/null +++ b/providers/b2/src/test/resources/list_file_names_response.json @@ -0,0 +1,19 @@ +{ + "files": [ + { + "action": "upload", + "fileId": "4_z27c88f1d182b150646ff0b16_f1004ba650fe24e6b_d20150809_m012853_c100_v0009990_t0000", + "fileName": "files/hello.txt", + "size": 6, + "uploadTimestamp": 1439083733000 + }, + { + "action": "upload", + "fileId": "4_z27c88f1d182b150646ff0b16_f1004ba650fe24e6c_d20150809_m012854_c100_v0009990_t0000", + "fileName": "files/world.txt", + "size": 6, + "uploadTimestamp": 1439083734000 + } + ], + "nextFileName": null +} diff --git a/providers/b2/src/test/resources/list_file_versions_request.json b/providers/b2/src/test/resources/list_file_versions_request.json new file mode 100644 index 0000000000..b083b91355 --- /dev/null +++ b/providers/b2/src/test/resources/list_file_versions_request.json @@ -0,0 +1,3 @@ +{ + "bucketId": "4a48fe8875c6214145260818" +} diff --git a/providers/b2/src/test/resources/list_file_versions_response.json b/providers/b2/src/test/resources/list_file_versions_response.json new file mode 100644 index 0000000000..e7aaf48e5c --- /dev/null +++ b/providers/b2/src/test/resources/list_file_versions_response.json @@ -0,0 +1,27 @@ +{ + "files": [ + { + "action": "upload", + "fileId": "4_z27c88f1d182b150646ff0b16_f100920ddab886245_d20150809_m232316_c100_v0009990_t0003", + "fileName": "files/hello.txt", + "size": 6, + "uploadTimestamp": 1439162596000 + }, + { + "action": "hide", + "fileId": "4_z27c88f1d182b150646ff0b16_f100920ddab886247_d20150809_m232323_c100_v0009990_t0005", + "fileName": "files/world.txt", + "size": 0, + "uploadTimestamp": 1439162603000 + }, + { + "action": "upload", + "fileId": "4_z27c88f1d182b150646ff0b16_f100920ddab886246_d20150809_m232316_c100_v0009990_t0003", + "fileName": "files/world.txt", + "size": 6, + "uploadTimestamp": 1439162596000 + } + ], + "nextFileId": "4_z27c88f1d182b150646ff0b16_f100920ddab886247_d20150809_m232316_c100_v0009990_t0003", + "nextFileName": "files/world.txt" +} diff --git a/providers/b2/src/test/resources/upload_file_response.json b/providers/b2/src/test/resources/upload_file_response.json new file mode 100644 index 0000000000..7833124998 --- /dev/null +++ b/providers/b2/src/test/resources/upload_file_response.json @@ -0,0 +1,13 @@ +{ + "fileId" : "4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104", + "fileName" : "typing_test.txt", + "accountId" : "d522aa47a10f", + "bucketId" : "4a48fe8875c6214145260818", + "contentLength" : 46, + "contentSha1" : "bae5ed658ab3546aee12f23f36392f35dba1ebdd", + "contentType" : "text/plain", + "uploadTimestamp" : 0, + "fileInfo" : { + "author" : "unknown" + } +}