JCLOUDS-457: Added initiateMultipartUpload and uploadPart operations.

Now the Glacier client supports initiateMultipartUpload and
uploadPart operations.
This commit is contained in:
Roman C. Coedo 2014-05-31 16:37:17 +02:00 committed by Andrew Gaul
parent 2efd8183ce
commit 248c855e48
12 changed files with 578 additions and 0 deletions

View File

@ -31,20 +31,26 @@ import javax.ws.rs.PathParam;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.blobstore.attr.BlobScope;
import org.jclouds.glacier.binders.BindContentRangeToHeaders;
import org.jclouds.glacier.binders.BindDescriptionToHeaders;
import org.jclouds.glacier.binders.BindHashesToHeaders;
import org.jclouds.glacier.binders.BindPartSizeToHeaders;
import org.jclouds.glacier.domain.PaginatedVaultCollection;
import org.jclouds.glacier.domain.VaultMetadata;
import org.jclouds.glacier.fallbacks.FalseOnIllegalArgumentException;
import org.jclouds.glacier.filters.RequestAuthorizeSignature;
import org.jclouds.glacier.functions.ParseArchiveIdHeader;
import org.jclouds.glacier.functions.ParseMultipartUploadIdHeader;
import org.jclouds.glacier.functions.ParseMultipartUploadTreeHashHeader;
import org.jclouds.glacier.functions.ParseVaultMetadataFromHttpContent;
import org.jclouds.glacier.functions.ParseVaultMetadataListFromHttpContent;
import org.jclouds.glacier.options.PaginationOptions;
import org.jclouds.glacier.predicates.validators.DescriptionValidator;
import org.jclouds.glacier.predicates.validators.PartSizeValidator;
import org.jclouds.glacier.predicates.validators.PayloadValidator;
import org.jclouds.glacier.predicates.validators.VaultNameValidator;
import org.jclouds.glacier.reference.GlacierHeaders;
import org.jclouds.glacier.util.ContentRange;
import org.jclouds.io.Payload;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Fallback;
@ -146,4 +152,38 @@ public interface GlacierAsyncClient extends Closeable {
@PathParam("vault") String vaultName,
@PathParam("archive") String archiveId);
/**
* @see GlacierClient#initiateMultipartUpload
*/
@Named("InitiateMultipartUpload")
@POST
@Path("/-/vaults/{vault}/multipart-uploads")
@ResponseParser(ParseMultipartUploadIdHeader.class)
ListenableFuture<String> initiateMultipartUpload(
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName,
@ParamValidators(PartSizeValidator.class) @BinderParam(BindPartSizeToHeaders.class) long partSizeInMB,
@ParamValidators(DescriptionValidator.class) @BinderParam(BindDescriptionToHeaders.class) String description);
/**
* @see GlacierClient#initiateMultipartUpload
*/
@Named("InitiateMultipartUpload")
@POST
@Path("/-/vaults/{vault}/multipart-uploads")
@ResponseParser(ParseMultipartUploadIdHeader.class)
ListenableFuture<String> initiateMultipartUpload(
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName,
@ParamValidators(PartSizeValidator.class) @BinderParam(BindPartSizeToHeaders.class) long partSizeInMB);
/**
* @see GlacierClient#uploadPart
*/
@Named("UploadPart")
@PUT
@Path("/-/vaults/{vault}/multipart-uploads/{uploadId}")
@ResponseParser(ParseMultipartUploadTreeHashHeader.class)
ListenableFuture<String> uploadPart(@PathParam("vault") String vaultName,
@PathParam("uploadId") String uploadId,
@BinderParam(BindContentRangeToHeaders.class) ContentRange range,
@ParamValidators(PayloadValidator.class) @BinderParam(BindHashesToHeaders.class) Payload payload);
}

View File

@ -22,6 +22,7 @@ import java.net.URI;
import org.jclouds.glacier.domain.PaginatedVaultCollection;
import org.jclouds.glacier.domain.VaultMetadata;
import org.jclouds.glacier.options.PaginationOptions;
import org.jclouds.glacier.util.ContentRange;
import org.jclouds.io.Payload;
/**
@ -112,4 +113,40 @@ public interface GlacierClient extends Closeable {
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/api-archive-delete.html" />
*/
boolean deleteArchive(String vaultName, String archiveId);
/**
* Starts a new multipart upload.
*
* @param vaultName
* Name of the Vault where the archive is going to be stored.
* @param partSizeInMB
* Content size for each part.
* @param description
* The archive description.
* @return The Multipart Upload Id.
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/api-multipart-initiate-upload.html" />
*/
String initiateMultipartUpload(String vaultName, long partSizeInMB, String description);
/**
* Starts a new multipart upload.
*/
String initiateMultipartUpload(String vaultName, long partSizeInMB);
/**
* Uploads one of the multipart upload parts.
*
* @param vaultName
* Name of the Vault where the archive is going to be stored.
* @param uploadId
* Multipart upload identifier.
* @param range
* The content range that this part is uploading.
* @param payload
* Content for this part.
* @return Tree-hash of the payload calculated by Amazon. This hash needs to be stored to complete the multipart
* upload.
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/api-upload-part.html" />
*/
String uploadPart(String vaultName, String uploadId, ContentRange range, Payload payload);
}

View File

@ -0,0 +1,40 @@
/*
* 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.glacier.binders;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.glacier.util.ContentRange;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.Binder;
import com.google.common.net.HttpHeaders;
/**
* Binds the ContentRange to the request headers.
*/
public class BindContentRangeToHeaders implements Binder {
@SuppressWarnings("unchecked")
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
checkArgument(checkNotNull(input, "input") instanceof ContentRange, "This binder is only valid for Payload");
checkNotNull(request, "request");
ContentRange range = ContentRange.class.cast(input);
return (R) request.toBuilder().addHeader(HttpHeaders.CONTENT_RANGE, range.buildHeader()).build();
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.glacier.binders;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.glacier.reference.GlacierHeaders;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.Binder;
/**
* Binds the Part size to the request headers.
*/
public class BindPartSizeToHeaders implements Binder {
@SuppressWarnings("unchecked")
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
checkArgument(checkNotNull(input, "input") instanceof Long, "This binder is only valid for long");
checkNotNull(request, "request");
Long partSizeInMB = Long.class.cast(input);
return (R) request.toBuilder()
.replaceHeader(GlacierHeaders.PART_SIZE, Long.toString(partSizeInMB << 20))
.build();
}
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foimport org.jclouds.http.HttpException;
import org.jclouds.http.HttpResponse;
import com.google.common.base.Function;
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.glacier.functions;
import org.jclouds.glacier.reference.GlacierHeaders;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpResponse;
import com.google.common.base.Function;
/**
* Parses the multipart upload id from the HttpResponse.
*/
public class ParseMultipartUploadIdHeader implements Function<HttpResponse, String> {
@Override
public String apply(HttpResponse from) {
String id = from.getFirstHeaderOrNull(GlacierHeaders.MULTIPART_UPLOAD_ID);
if (id == null)
throw new HttpException("Did not receive Multipart upload Id");
return id;
}
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foimport org.jclouds.http.HttpException;
import org.jclouds.http.HttpResponse;
import com.google.common.base.Function;
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.glacier.functions;
import org.jclouds.glacier.reference.GlacierHeaders;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpResponse;
import com.google.common.base.Function;
/**
* Parses the tree hash header from the HttpResponse.
*/
public class ParseMultipartUploadTreeHashHeader implements Function<HttpResponse, String> {
@Override
public String apply(HttpResponse from) {
String id = from.getFirstHeaderOrNull(GlacierHeaders.TREE_HASH);
if (id == null)
throw new HttpException("Did not receive Tree hash");
return id;
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.glacier.predicates.validators;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.predicates.Validator;
/**
* Validates the part size parameter used when initiating multipart uploads.
*/
public final class PartSizeValidator extends Validator<Long> {
private static final int MIN_PART_SIZE = 1;
private static final int MAX_PART_SIZE = 4096;
@Override
public void validate(Long partSizeInMB) throws IllegalArgumentException {
checkNotNull(partSizeInMB, "partSizeInMB");
if (partSizeInMB < MIN_PART_SIZE || partSizeInMB > MAX_PART_SIZE || (partSizeInMB & (partSizeInMB - 1)) != 0)
throw exception(partSizeInMB, "partSizeInMB must be a power of 2 between 1 and 4096.");
}
protected static IllegalArgumentException exception(Long size, String reason) {
return new IllegalArgumentException(
String.format(
"Part size '%s' doesn't match Glacier Multipart upload rules. "
+ "Reason: %s. For more info, please refer to http://http://docs.aws.amazon.com/amazonglacier/latest/dev/api-multipart-initiate-upload.html.",
size, reason));
}
}

View File

@ -30,6 +30,8 @@ public final class GlacierHeaders {
public static final String LINEAR_HASH = HEADER_PREFIX + "content-sha256";
public static final String TREE_HASH = HEADER_PREFIX + "sha256-tree-hash";
public static final String ARCHIVE_ID = HEADER_PREFIX + "archive-id";
public static final String MULTIPART_UPLOAD_ID = HEADER_PREFIX + "multipart-upload-id";
public static final String PART_SIZE = HEADER_PREFIX + "part-size";
private GlacierHeaders() {
}

View File

@ -0,0 +1,90 @@
/*
* 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.glacier.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.common.base.Objects;
/**
* This class represents a range of bytes.
*/
public final class ContentRange {
private final long from;
private final long to;
private ContentRange(long from, long to) {
checkArgument(from < to, "\"from\" should be lower than \"to\"");
checkArgument(from >= 0 && to > 0, "\"from\" cannot be negative and \"to\" has to be positive");
this.from = from;
this.to = to;
}
public static ContentRange fromString(String contentRangeString) {
checkArgument(!isNullOrEmpty(contentRangeString) && contentRangeString.matches("[0-9]+-[0-9]+"),
"The string should be two numbers separated by a hyphen (from-to)");
String[] strings = contentRangeString.split("-", 2);
long from = Long.parseLong(strings[0]);
long to = Long.parseLong(strings[1]);
return new ContentRange(from, to);
}
public static ContentRange fromPartNumber(long partNumber, long partSizeInMB) {
checkArgument(partNumber >= 0, "The part number cannot be negative");
checkArgument(partSizeInMB > 0, "The part size has to be positive");
long from = partNumber * (partSizeInMB << 20);
long to = from + (partSizeInMB << 20) - 1;
return new ContentRange(from, to);
}
public static ContentRange build(long from, long to) {
return new ContentRange(from, to);
}
public long getFrom() {
return from;
}
public long getTo() {
return to;
}
public String buildHeader() {
return "bytes " + from + "-" + to + "/*";
}
@Override
public int hashCode() {
return Objects.hashCode(this.from, this.to);
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ContentRange other = (ContentRange) obj;
return Objects.equal(this.from, other.to) && Objects.equal(this.from, other.to);
}
@Override
public String toString() {
return from + "-" + to;
}
}

View File

@ -20,6 +20,7 @@ import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
import static org.jclouds.Constants.PROPERTY_SO_TIMEOUT;
import static org.jclouds.glacier.util.TestUtils.MiB;
import static org.jclouds.glacier.util.TestUtils.buildPayload;
import static org.jclouds.util.Strings2.urlEncode;
import static org.testng.Assert.assertEquals;
@ -40,6 +41,10 @@ import org.jclouds.glacier.domain.PaginatedVaultCollection;
import org.jclouds.glacier.domain.VaultMetadata;
import org.jclouds.glacier.options.PaginationOptions;
import org.jclouds.glacier.reference.GlacierHeaders;
import org.jclouds.glacier.util.ContentRange;
import org.jclouds.http.HttpResponseException;
import org.jclouds.io.Payload;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
@ -76,6 +81,8 @@ public class GlacierClientMockTest {
private static final String ARCHIVE_LOCATION = VAULT_LOCATION + "/archives/" + ARCHIVE_ID;
private static final String TREEHASH = "beb0fe31a1c7ca8c6c04d574ea906e3f97b31fdca7571defb5b44dca89b5af60";
private static final String DESCRIPTION = "test description";
private static final String MULTIPART_UPLOAD_LOCATION = VAULT_LOCATION + "/multipart-uploads/" + ARCHIVE_ID;
private static final String MULTIPART_UPLOAD_ID = "OW2fM5iVylEpFEMM9_HpKowRapC3vn5sSL39_396UW9zLFUWVrnRHaPjUJddQ5OxSHVXjYtrN47NBZ-khxOjyEXAMPLE";
private static final Set<Module> modules = ImmutableSet.<Module> of(new ExecutorServiceModule(sameThreadExecutor(),
sameThreadExecutor()));
@ -106,6 +113,7 @@ public class GlacierClientMockTest {
@BeforeTest
private void initServer() throws IOException {
server = new MockWebServer();
server.setBodyLimit(0);
server.play();
client = getGlacierClient(server.getUrl("/"));
}
@ -208,4 +216,59 @@ public class GlacierClientMockTest {
assertTrue(client.deleteArchive(VAULT_NAME, ARCHIVE_ID));
assertEquals(server.takeRequest().getRequestLine(), "DELETE /-/vaults/" + VAULT_NAME + "/archives/" + ARCHIVE_ID + " " + HTTP);
}
@Test
public void testInitiateMultipartUpload() throws InterruptedException {
MockResponse mr = buildBaseResponse(201);
mr.addHeader(HttpHeaders.LOCATION, MULTIPART_UPLOAD_LOCATION);
mr.addHeader(GlacierHeaders.MULTIPART_UPLOAD_ID, MULTIPART_UPLOAD_ID);
server.enqueue(mr);
assertEquals(client.initiateMultipartUpload(VAULT_NAME, 4, DESCRIPTION), MULTIPART_UPLOAD_ID);
RecordedRequest request = server.takeRequest();
assertEquals(request.getRequestLine(), "POST /-/vaults/" + VAULT_NAME + "/multipart-uploads " + HTTP);
assertEquals(request.getHeader(GlacierHeaders.PART_SIZE), "4194304");
assertEquals(request.getHeader(GlacierHeaders.ARCHIVE_DESCRIPTION), DESCRIPTION);
}
@Test
public void testUploadPart() throws InterruptedException {
MockResponse mr = buildBaseResponse(204);
mr.addHeader(GlacierHeaders.TREE_HASH, TREEHASH);
server.enqueue(mr);
assertEquals(
client.uploadPart(VAULT_NAME, MULTIPART_UPLOAD_ID, ContentRange.fromPartNumber(0, 4), buildPayload(4 * MiB)),
TREEHASH);
RecordedRequest request = server.takeRequest();
assertEquals(request.getRequestLine(),
"PUT /-/vaults/" + VAULT_NAME + "/multipart-uploads/" + MULTIPART_UPLOAD_ID + " " + HTTP);
assertEquals(request.getHeader(HttpHeaders.CONTENT_RANGE), "bytes 0-4194303/*");
assertEquals(request.getHeader(HttpHeaders.CONTENT_LENGTH), "4194304");
}
// TODO: Change size to 4096 when moving to JDK 7
@Test
public void testUploadPartMaxSize() throws InterruptedException {
MockResponse mr = buildBaseResponse(204);
mr.addHeader(GlacierHeaders.TREE_HASH, TREEHASH);
server.enqueue(mr);
long size = 1024;
ContentRange range = ContentRange.fromPartNumber(0, size);
Payload payload = buildPayload(1);
payload.getContentMetadata().setContentLength(size * MiB);
try {
/* The client.uploadPart call should throw an HttpResponseException since the payload is smaller than expected.
* This trick makes the test way faster.
*/
client.uploadPart(VAULT_NAME, MULTIPART_UPLOAD_ID, range, payload);
Assert.fail();
} catch (HttpResponseException e) {
}
RecordedRequest request = server.takeRequest();
assertEquals(request.getRequestLine(), "PUT /-/vaults/" + VAULT_NAME + "/multipart-uploads/" + MULTIPART_UPLOAD_ID + " " + HTTP);
assertEquals(request.getHeader(HttpHeaders.CONTENT_RANGE), range.buildHeader());
assertEquals(request.getHeader(HttpHeaders.CONTENT_LENGTH), payload.getContentMetadata().getContentLength().toString());
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.glacier.predicates.validators;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "PartSizeValidatorTest")
public class PartSizeValidatorTest {
private static final PartSizeValidator VALIDATOR = new PartSizeValidator();
public void testValidate() {
VALIDATOR.validate(1L);
VALIDATOR.validate(2L);
VALIDATOR.validate(4L);
VALIDATOR.validate(32L);
VALIDATOR.validate(4096L);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testZero() {
VALIDATOR.validate(0L);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testTooBig() {
VALIDATOR.validate(8192L);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNotPowerOfTwo() {
VALIDATOR.validate(25L);
}
}

View File

@ -0,0 +1,99 @@
/*
* 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.glacier.util;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "ContentRangeTest")
public class ContentRangeTest {
@Test
public void testContentRangeFromString() {
ContentRange range = ContentRange.fromString("0-10");
assertEquals(range.getFrom(), 0);
assertEquals(range.getTo(), 10);
range = ContentRange.fromString("1000-2000");
assertEquals(range.getFrom(), 1000);
assertEquals(range.getTo(), 2000);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testContentRangeFromStringWithoutTo() {
ContentRange.fromString("-10");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testContentRangeFromStringWithoutFrom() {
ContentRange.fromString("10-");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testContentRangeFromStringWithEmptyString() {
ContentRange.fromString("");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testContentRangeFromStringWithNullString() {
ContentRange.fromString(null);
}
@Test
public void testContentRangeFromPartNumber() {
ContentRange range = ContentRange.fromPartNumber(0, 4096);
assertEquals(range.getFrom(), 0);
assertEquals(range.getTo(), (4096L << 20) - 1);
range = ContentRange.fromPartNumber(1, 4096);
assertEquals(range.getFrom(), 4096L << 20);
assertEquals(range.getTo(), 2 * (4096L << 20) - 1);
range = ContentRange.fromPartNumber(2, 4096);
assertEquals(range.getFrom(), 2 * (4096L << 20));
assertEquals(range.getTo(), 3 * (4096L << 20) - 1);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testContentRangeFromPartNumberWithNegativePartNumber() {
ContentRange.fromPartNumber(-1, 4096);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testContentRangeFromPartNumberWithZeroPartSize() {
ContentRange.fromPartNumber(0, 0);
}
@Test
public void testBuildContentRange() {
ContentRange range = ContentRange.build(0, 4096);
assertEquals(range.getFrom(), 0);
assertEquals(range.getTo(), 4096);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBuildContentRangeWithTransposedValues() {
ContentRange.build(50, 10);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBuildContentRangeWithNegatives() {
ContentRange.build(-100, -50);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBuildContentRangeWithZeroTo() {
ContentRange.build(0, 0);
}
}