JCLOUDS-457: Clean up for the validators and the TreeHash class.

Now the validators use checkArgument instead of throwing an
exception by calling an static method.

There were some missing validators on the Async client, these
were fixed too.

Lastly, TreeHash inner class was removed. Now the factory methods
are in the TreeHash class.
This commit is contained in:
Roman C. Coedo 2014-06-20 01:26:48 +02:00 committed by Andrew Gaul
parent 248c855e48
commit 76ea768cb8
9 changed files with 100 additions and 127 deletions

View File

@ -79,7 +79,7 @@ public interface GlacierAsyncClient extends Closeable {
@Named("CreateVault")
@PUT
@Path("/-/vaults/{vault}")
ListenableFuture<URI> createVault(@PathParam("vault") @ParamValidators(VaultNameValidator.class) String vaultName);
ListenableFuture<URI> createVault(@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName);
/**
* @see GlacierClient#deleteVaultIfEmpty
@ -88,7 +88,7 @@ public interface GlacierAsyncClient extends Closeable {
@DELETE
@Path("/-/vaults/{vault}")
@Fallback(FalseOnIllegalArgumentException.class)
ListenableFuture<Boolean> deleteVault(@PathParam("vault") String vaultName);
ListenableFuture<Boolean> deleteVault(@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName);
/**
* @see GlacierClient#describeVault
@ -99,7 +99,7 @@ public interface GlacierAsyncClient extends Closeable {
@ResponseParser(ParseVaultMetadataFromHttpContent.class)
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<VaultMetadata> describeVault(
@PathParam("vault") @ParamValidators(VaultNameValidator.class) String vaultName);
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName);
/**
* @see GlacierClient#listVaults(PaginationOptions)
@ -127,7 +127,7 @@ public interface GlacierAsyncClient extends Closeable {
@Path("/-/vaults/{vault}/archives")
@ResponseParser(ParseArchiveIdHeader.class)
ListenableFuture<String> uploadArchive(
@PathParam("vault") String vaultName,
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName,
@ParamValidators(PayloadValidator.class) @BinderParam(BindHashesToHeaders.class) Payload payload,
@ParamValidators(DescriptionValidator.class) @BinderParam(BindDescriptionToHeaders.class) String description);
@ -139,7 +139,7 @@ public interface GlacierAsyncClient extends Closeable {
@Path("/-/vaults/{vault}/archives")
@ResponseParser(ParseArchiveIdHeader.class)
ListenableFuture<String> uploadArchive(
@PathParam("vault") String vaultName,
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName,
@ParamValidators(PayloadValidator.class) @BinderParam(BindHashesToHeaders.class) Payload payload);
/**
@ -149,7 +149,7 @@ public interface GlacierAsyncClient extends Closeable {
@DELETE
@Path("/-/vaults/{vault}/archives/{archive}")
ListenableFuture<Boolean> deleteArchive(
@PathParam("vault") String vaultName,
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName,
@PathParam("archive") String archiveId);
/**
@ -182,7 +182,8 @@ public interface GlacierAsyncClient extends Closeable {
@PUT
@Path("/-/vaults/{vault}/multipart-uploads/{uploadId}")
@ResponseParser(ParseMultipartUploadTreeHashHeader.class)
ListenableFuture<String> uploadPart(@PathParam("vault") String vaultName,
ListenableFuture<String> uploadPart(
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName,
@PathParam("uploadId") String uploadId,
@BinderParam(BindContentRangeToHeaders.class) ContentRange range,
@ParamValidators(PayloadValidator.class) @BinderParam(BindHashesToHeaders.class) Payload payload);

View File

@ -35,7 +35,7 @@ public class BindHashesToHeaders implements Binder {
private HttpRequest addChecksumHeaders(HttpRequest request, Payload payload) {
try {
TreeHash hash = TreeHash.Hasher.buildTreeHashFromPayload(payload);
TreeHash hash = TreeHash.buildTreeHashFromPayload(payload);
request = request.toBuilder()
.addHeader(GlacierHeaders.LINEAR_HASH, hash.getLinearHash().toString())
.addHeader(GlacierHeaders.TREE_HASH, hash.getTreeHash().toString())

View File

@ -16,6 +16,7 @@
*/
package org.jclouds.glacier.predicates.validators;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import org.jclouds.predicates.Validator;
@ -37,17 +38,10 @@ public final class DescriptionValidator extends Validator<String> {
public void validate(String description) {
if (isNullOrEmpty(description))
return;
if (description.length() > MAX_DESC_LENGTH)
throw exception("Description can't be longer than " + MAX_DESC_LENGTH + " characters" + " but was " + description.length());
if (!DESCRIPTION_ACCEPTABLE_RANGE.matchesAllOf(description))
throw exception("Description should have ASCII values between 32 and 126.");
}
protected static IllegalArgumentException exception(String reason) {
return new IllegalArgumentException(
String.format(
"Description doesn't match Glacier archive description rules. "
+ "Reason: %s. For more info, please refer to http://docs.aws.amazon.com/amazonglacier/latest/dev/api-archive-post.html.",
reason));
checkArgument(description.length() <= MAX_DESC_LENGTH,
"Description can't be longer than %d characters but was %d",
MAX_DESC_LENGTH, description.length());
checkArgument(DESCRIPTION_ACCEPTABLE_RANGE.matchesAllOf(description),
"Description should have ASCII values between 32 and 126.");
}
}

View File

@ -16,13 +16,17 @@
*/
package org.jclouds.glacier.predicates.validators;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.predicates.Validator;
import com.google.inject.Singleton;
/**
* Validates the part size parameter used when initiating multipart uploads.
*/
@Singleton
public final class PartSizeValidator extends Validator<Long> {
private static final int MIN_PART_SIZE = 1;
private static final int MAX_PART_SIZE = 4096;
@ -30,15 +34,7 @@ public final class PartSizeValidator extends Validator<Long> {
@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));
checkArgument(!(partSizeInMB < MIN_PART_SIZE || partSizeInMB > MAX_PART_SIZE || Long.bitCount(partSizeInMB) != 1),
"partSizeInMB must be a power of 2 between 1 and 4096.");
}
}

View File

@ -16,6 +16,9 @@
*/
package org.jclouds.glacier.predicates.validators;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.io.Payload;
import org.jclouds.predicates.Validator;
@ -31,19 +34,9 @@ public final class PayloadValidator extends Validator<Payload> {
@Override
public void validate(Payload payload) {
if (payload == null)
throw exception(payload, "Archive must have a payload.");
if (payload.getContentMetadata().getContentLength() == null)
throw exception(payload, "Content length must be set.");
if (payload.getContentMetadata().getContentLength() > MAX_CONTENT_SIZE)
throw exception(payload, "Max content size is 4gb" + " but was " + payload.getContentMetadata().getContentLength());
}
protected static IllegalArgumentException exception(Payload payload, String reason) {
return new IllegalArgumentException(
String.format(
"Payload '%s' doesn't match Glacier archive upload rules. "
+ "Reason: %s. For more info, please refer to http://docs.aws.amazon.com/amazonglacier/latest/dev/api-archive-post.html.",
payload, reason));
checkNotNull(payload, "Archive must have a payload.");
checkNotNull(payload.getContentMetadata().getContentLength(), "Content length must be set.");
checkArgument(payload.getContentMetadata().getContentLength() <= MAX_CONTENT_SIZE,
"Max content size is 4gb but was %d", payload.getContentMetadata().getContentLength());
}
}

View File

@ -16,6 +16,7 @@
*/
package org.jclouds.glacier.predicates.validators;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import org.jclouds.predicates.Validator;
@ -34,26 +35,16 @@ public final class VaultNameValidator extends Validator<String> {
private static final int MIN_LENGTH = 1;
private static final int MAX_LENGTH = 255;
private static final CharMatcher VAULT_NAME_ACCEPTABLE_RANGE = CharMatcher.inRange('a', 'z')
.or(CharMatcher.inRange('A', 'Z'))
.or(CharMatcher.inRange('0', '9'))
.or(CharMatcher.anyOf("-_."));
@Override
public void validate(String vaultName) {
if (isNullOrEmpty(vaultName) || vaultName.length() > MAX_LENGTH)
throw exception(vaultName, "Can't be null or empty. Length must be " + MIN_LENGTH + " to " + MAX_LENGTH
+ " symbols.");
CharMatcher range = getAcceptableRange();
if (!range.matchesAllOf(vaultName))
throw exception(vaultName, "Should have ASCII letters and numbers, underscores, hyphens, or periods.");
}
private static CharMatcher getAcceptableRange() {
return CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.inRange('0', '9'))
.or(CharMatcher.anyOf("-_."));
}
protected static IllegalArgumentException exception(String vaultName, String reason) {
return new IllegalArgumentException(
String.format(
"Object '%s' doesn't match AWS Vault naming convention. "
+ "Reason: %s. For more info, please refer to http://docs.aws.amazon.com/amazonglacier/latest/dev/api-vault-put.html.",
vaultName, reason));
checkArgument(!isNullOrEmpty(vaultName) && vaultName.length() <= MAX_LENGTH,
"Can't be null or empty. Length must be %d to %d symbols.", MIN_LENGTH, MAX_LENGTH);
checkArgument(VAULT_NAME_ACCEPTABLE_RANGE.matchesAllOf(vaultName),
"Should contain only ASCII letters and numbers, underscores, hyphens, or periods.");
}
}

View File

@ -38,6 +38,8 @@ import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
public final class TreeHash {
private static final int CHUNK_SIZE = 1024 * 1024;
private final HashCode treeHash;
private final HashCode linearHash;
@ -74,68 +76,64 @@ public final class TreeHash {
return "TreeHash [treeHash=" + treeHash + ", linearHash=" + linearHash + "]";
}
public static class Hasher {
private static final int CHUNK_SIZE = 1024 * 1024;
private static HashCode hashList(Collection<HashCode> hashList) {
Builder<HashCode> result = ImmutableList.builder();
while (hashList.size() > 1) {
//Hash pairs of values and add them to the result list.
for (Iterator<HashCode> it = hashList.iterator(); it.hasNext();) {
HashCode hc1 = it.next();
if (it.hasNext()) {
HashCode hc2 = it.next();
result.add(Hashing.sha256().newHasher()
.putBytes(hc1.asBytes())
.putBytes(hc2.asBytes())
.hash());
} else {
result.add(hc1);
}
private static HashCode hashList(Collection<HashCode> hashList) {
Builder<HashCode> result = ImmutableList.builder();
while (hashList.size() > 1) {
//Hash pairs of values and add them to the result list.
for (Iterator<HashCode> it = hashList.iterator(); it.hasNext();) {
HashCode hc1 = it.next();
if (it.hasNext()) {
HashCode hc2 = it.next();
result.add(Hashing.sha256().newHasher()
.putBytes(hc1.asBytes())
.putBytes(hc2.asBytes())
.hash());
} else {
result.add(hc1);
}
hashList = result.build();
result = ImmutableList.builder();
}
return hashList.iterator().next();
hashList = result.build();
result = ImmutableList.builder();
}
return hashList.iterator().next();
}
/**
* Builds the Hash and the TreeHash values of the payload.
*
* @return The calculated TreeHash.
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/checksum-calculations.html" />
*/
public static TreeHash buildTreeHashFromPayload(Payload payload) throws IOException {
InputStream is = null;
try {
is = checkNotNull(payload, "payload").openStream();
Builder<HashCode> list = ImmutableList.builder();
HashingInputStream linearHis = new HashingInputStream(Hashing.sha256(), is);
while (true) {
HashingInputStream chunkedHis = new HashingInputStream(
Hashing.sha256(), ByteStreams.limit(linearHis, CHUNK_SIZE));
long count = ByteStreams.copy(chunkedHis, ByteStreams.nullOutputStream());
if (count == 0) {
break;
}
list.add(chunkedHis.hash());
}
//The result list contains exactly one element now.
return new TreeHash(hashList(list.build()), linearHis.hash());
} finally {
Closeables.closeQuietly(is);
/**
* Builds the Hash and the TreeHash values of the payload.
*
* @return The calculated TreeHash.
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/checksum-calculations.html" />
*/
public static TreeHash buildTreeHashFromPayload(Payload payload) throws IOException {
InputStream is = null;
try {
is = checkNotNull(payload, "payload").openStream();
Builder<HashCode> list = ImmutableList.builder();
HashingInputStream linearHis = new HashingInputStream(Hashing.sha256(), is);
while (true) {
HashingInputStream chunkedHis = new HashingInputStream(
Hashing.sha256(), ByteStreams.limit(linearHis, CHUNK_SIZE));
long count = ByteStreams.copy(chunkedHis, ByteStreams.nullOutputStream());
if (count == 0) {
break;
}
list.add(chunkedHis.hash());
}
}
/**
* Builds a TreeHash based on a map of hashed chunks.
*
* @return The calculated TreeHash.
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/checksum-calculations.html" />
*/
public static HashCode buildTreeHashFromMap(Map<Integer, HashCode> map) {
checkArgument(!map.isEmpty(), "The map cannot be empty.");
return hashList(ImmutableSortedMap.copyOf(map).values());
//The result list contains exactly one element now.
return new TreeHash(hashList(list.build()), linearHis.hash());
} finally {
Closeables.closeQuietly(is);
}
}
/**
* Builds a TreeHash based on a map of hashed chunks.
*
* @return The calculated TreeHash.
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/checksum-calculations.html" />
*/
public static HashCode buildTreeHashFromMap(Map<Integer, HashCode> map) {
checkArgument(!map.isEmpty(), "The map cannot be empty.");
return hashList(ImmutableSortedMap.copyOf(map).values());
}
}

View File

@ -31,14 +31,14 @@ public class PayloadValidatorTest {
VALIDATOR.validate(buildPayload(10));
}
@Test(expectedExceptions = IllegalArgumentException.class)
@Test(expectedExceptions = NullPointerException.class)
public void testNoContentLength() {
Payload payload = buildPayload(10);
payload.getContentMetadata().setContentLength(null);
VALIDATOR.validate(payload);
}
@Test(expectedExceptions = IllegalArgumentException.class)
@Test(expectedExceptions = NullPointerException.class)
public void testNullPayload() {
VALIDATOR.validate(null);
}

View File

@ -34,7 +34,7 @@ public class TreeHashTest {
@Test
public void testTreeHasherWith1MBPayload() throws IOException {
TreeHash th = TreeHash.Hasher.buildTreeHashFromPayload(new ByteSourcePayload(buildData(1 * MiB)));
TreeHash th = TreeHash.buildTreeHashFromPayload(new ByteSourcePayload(buildData(1 * MiB)));
assertEquals(th.getLinearHash(),
HashCode.fromString("9bc1b2a288b26af7257a36277ae3816a7d4f16e89c1e7e77d0a5c48bad62b360"));
assertEquals(th.getTreeHash(),
@ -43,7 +43,7 @@ public class TreeHashTest {
@Test
public void testTreeHasherWith2MBPayload() throws IOException {
TreeHash th = TreeHash.Hasher.buildTreeHashFromPayload(new ByteSourcePayload(buildData(2 * MiB)));
TreeHash th = TreeHash.buildTreeHashFromPayload(new ByteSourcePayload(buildData(2 * MiB)));
assertEquals(th.getLinearHash(),
HashCode.fromString("5256ec18f11624025905d057d6befb03d77b243511ac5f77ed5e0221ce6d84b5"));
assertEquals(th.getTreeHash(),
@ -52,7 +52,7 @@ public class TreeHashTest {
@Test
public void testTreeHasherWith3MBPayload() throws IOException {
TreeHash th = TreeHash.Hasher.buildTreeHashFromPayload(new ByteSourcePayload(buildData(3 * MiB)));
TreeHash th = TreeHash.buildTreeHashFromPayload(new ByteSourcePayload(buildData(3 * MiB)));
assertEquals(th.getLinearHash(),
HashCode.fromString("6f850bc94ae6f7de14297c01616c36d712d22864497b28a63b81d776b035e656"));
assertEquals(th.getTreeHash(),
@ -61,7 +61,7 @@ public class TreeHashTest {
@Test
public void testTreeHasherWithMoreThan3MBPayload() throws IOException {
TreeHash th = TreeHash.Hasher.buildTreeHashFromPayload(new ByteSourcePayload(buildData(3 * MiB + 512 * 1024)));
TreeHash th = TreeHash.buildTreeHashFromPayload(new ByteSourcePayload(buildData(3 * MiB + 512 * 1024)));
assertEquals(th.getLinearHash(),
HashCode.fromString("34c8bdd269f89a091cf17d5d23503940e0abf61c4b6544e42854b9af437f31bb"));
assertEquals(th.getTreeHash(),
@ -73,7 +73,7 @@ public class TreeHashTest {
Builder<Integer, HashCode> map = ImmutableMap.<Integer, HashCode>builder();
map.put(2, HashCode.fromString("9bc1b2a288b26af7257a36277ae3816a7d4f16e89c1e7e77d0a5c48bad62b360"));
map.put(1, HashCode.fromString("9bc1b2a288b26af7257a36277ae3816a7d4f16e89c1e7e77d0a5c48bad62b360"));
HashCode treehash = TreeHash.Hasher.buildTreeHashFromMap(map.build());
HashCode treehash = TreeHash.buildTreeHashFromMap(map.build());
assertEquals(treehash, HashCode.fromString("560c2c9333c719cb00cfdffee3ba293db17f58743cdd1f7e4055373ae6300afa"));
}
}