Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base argument class for conditionally reading objects. #989

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions api/src/main/java/io/minio/BaseArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -119,24 +120,34 @@ public B extraQueryParams(Map<String, String> queryParams) {
return (B) this;
}

/** Creates derived Args class with each attribute populated. */
@SuppressWarnings("unchecked") // safe as B will always be the builder of the current args class
public A build() throws IllegalArgumentException {
private A newInstance() {
try {
A args = (A) this.getClass().getEnclosingClass().getDeclaredConstructor().newInstance();
operations.forEach(operation -> operation.accept(args));
validate(args);
return args;
for (Constructor<?> constructor :
this.getClass().getEnclosingClass().getDeclaredConstructors()) {
if (constructor.getParameterCount() == 0) {
return (A) constructor.newInstance();
}
}

throw new RuntimeException(
this.getClass().getEnclosingClass() + " must have no argument constructor");
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException
| NoSuchMethodException
| SecurityException e) {
// This should never happen as we'll always have the
// Builder class as an enclosed class of the args class
e.printStackTrace();
return null;
// Args class must have no argument constructor with at least protected access.
throw new RuntimeException(e);
}
}

/** Creates derived Args class with each attribute populated. */
@SuppressWarnings("unchecked") // safe as B will always be the builder of the current args class
public A build() throws IllegalArgumentException {
A args = newInstance();
operations.forEach(operation -> operation.accept(args));
validate(args);
return args;
}
}
}
40 changes: 34 additions & 6 deletions api/src/main/java/io/minio/ComposeObjectArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,31 @@

package io.minio;

import java.util.LinkedList;
import java.util.List;
import okhttp3.HttpUrl;

public class ComposeObjectArgs extends ObjectWriteArgs {
List<ComposeSource> sources;

protected ComposeObjectArgs() {}

public ComposeObjectArgs(CopyObjectArgs args) {
this.extraHeaders = args.extraHeaders;
this.extraQueryParams = args.extraQueryParams;
this.bucketName = args.bucketName;
this.region = args.region;
this.objectName = args.objectName;
this.headers = args.headers;
this.userMetadata = args.userMetadata;
this.sse = args.sse;
this.tags = args.tags;
this.retention = args.retention;
this.legalHold = args.legalHold;
this.sources = new LinkedList<>();
this.sources.add(new ComposeSource(args.source()));
}

public List<ComposeSource> sources() {
return sources;
}
Expand All @@ -29,7 +49,21 @@ public static Builder builder() {
return new Builder();
}

@Override
public void validateSse(HttpUrl url) {
super.validateSse(url);
for (ComposeSource source : sources) {
source.validateSsec(url);
}
}

public static final class Builder extends ObjectWriteArgs.Builder<Builder, ComposeObjectArgs> {
private void validateSources(List<ComposeSource> sources) {
if (sources == null || sources.isEmpty()) {
throw new IllegalArgumentException("compose sources cannot be empty");
}
}

@Override
protected void validate(ComposeObjectArgs args) {
super.validate(args);
Expand All @@ -41,11 +75,5 @@ public Builder sources(List<ComposeSource> sources) {
operations.add(args -> args.sources = sources);
return this;
}

private void validateSources(List<ComposeSource> sources) {
if (sources == null || sources.isEmpty()) {
throw new IllegalArgumentException("compose sources cannot be empty");
}
}
}
}
216 changes: 78 additions & 138 deletions api/src/main/java/io/minio/ComposeSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,84 +20,78 @@
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import java.time.ZonedDateTime;
import okhttp3.HttpUrl;

/** Source information to compose object. */
public class ComposeSource extends ObjectVersionArgs {
private Long offset;
private Long length;
private long objectSize;
private String matchETag;
private String notMatchETag;
private ZonedDateTime modifiedSince;
private ZonedDateTime unmodifiedSince;
private ServerSideEncryptionCustomerKey ssec;

private Multimap<String, String> headers;

public Long offset() {
return offset;
import io.minio.errors.InternalException;

/** A source object defintion for {@link ComposeObjectArgs}. */
public class ComposeSource extends ObjectConditionalReadArgs {
private Long objectSize = null;
private Multimap<String, String> headers = null;

protected ComposeSource() {}

public ComposeSource(ObjectConditionalReadArgs args) {
this.extraHeaders = args.extraHeaders;
this.extraQueryParams = args.extraQueryParams;
this.bucketName = args.bucketName;
this.region = args.region;
this.objectName = args.objectName;
this.versionId = args.versionId;
this.ssec = args.ssec;
this.offset = args.offset;
this.length = args.length;
this.matchETag = args.matchETag;
this.notMatchETag = args.notMatchETag;
this.modifiedSince = args.modifiedSince;
this.unmodifiedSince = args.unmodifiedSince;
}

public Long length() {
return length;
}

public long objectSize() {
return objectSize;
}

public String matchETag() {
return matchETag;
}

public String notMatchETag() {
return notMatchETag;
}
private void throwException(long objectsize, long arg, String argName) {
StringBuilder builder =
new StringBuilder().append("source ").append(bucketName).append("/").append(objectName);

public ZonedDateTime modifiedSince() {
return modifiedSince;
}
if (versionId != null) {
builder.append("?versionId=").append(versionId);
}

public ZonedDateTime unmodifiedSince() {
return unmodifiedSince;
}
builder
.append(": ")
.append(argName)
.append(" ")
.append(arg)
.append(" is beyond object size ")
.append(objectSize);

public Multimap<String, String> headers() {
return headers;
throw new IllegalArgumentException(builder.toString());
}

public ServerSideEncryptionCustomerKey ssec() {
return ssec;
}
private void validateSize(long objectSize) {
if (offset != null && offset >= objectSize) {
throwException(objectSize, offset, "offset");
}

public void validateSse(HttpUrl url) {
checkSse(ssec, url);
}
if (length != null) {
if (length > objectSize) {
throwException(objectSize, length, "length");
}

public static Builder builder() {
return new Builder();
if (offset + length > objectSize) {
throwException(objectSize, offset + length, "compose size");
}
}
}

/** Constructs header. */
public void buildHeaders(long objectSize, String etag) throws IllegalArgumentException {
public void buildHeaders(long objectSize, String etag) {
validateSize(objectSize);
Multimap<String, String> headers = HashMultimap.create();
headers.put("x-amz-copy-source", S3Escaper.encodePath(bucketName + "/" + objectName));
headers.put("x-amz-copy-source-if-match", etag);

if (extraHeaders() != null) {
headers.putAll(extraHeaders());
}
this.objectSize = Long.valueOf(objectSize);

if (matchETag != null) {
headers.put("x-amz-copy-source-if-match", matchETag);
String copySource = S3Escaper.encodePath(bucketName + "/" + objectName);
if (versionId != null) {
copySource += "?versionId=" + S3Escaper.encode(versionId);
}

if (ssec != null) {
headers.putAll(Multimaps.forMap(ssec.copySourceHeaders()));
}
Multimap<String, String> headers = HashMultimap.create();
headers.put("x-amz-copy-source", copySource);
headers.put("x-amz-copy-source-if-match", (matchETag != null) ? matchETag : etag);

if (notMatchETag != null) {
headers.put("x-amz-copy-source-if-none-match", notMatchETag);
Expand All @@ -115,90 +109,36 @@ public void buildHeaders(long objectSize, String etag) throws IllegalArgumentExc
unmodifiedSince.format(Time.HTTP_HEADER_DATE_FORMAT));
}

this.objectSize = objectSize;
this.headers = headers;
}

private void validateSize(long objectSize) throws IllegalArgumentException {
if (offset != null && offset >= objectSize) {
throw new IllegalArgumentException(
"source "
+ bucketName
+ "/"
+ objectName
+ ": offset "
+ offset
+ " is beyond object size "
+ objectSize);
if (ssec != null) {
headers.putAll(Multimaps.forMap(ssec.copySourceHeaders()));
}

if (length != null) {
if (length > objectSize) {
throw new IllegalArgumentException(
"source "
+ bucketName
+ "/"
+ objectName
+ ": length "
+ length
+ " is beyond object size "
+ objectSize);
}

if (offset + length > objectSize) {
throw new IllegalArgumentException(
"source "
+ bucketName
+ "/"
+ objectName
+ ": compose size "
+ (offset + length)
+ " is beyond object size "
+ objectSize);
}
}
this.headers = Multimaps.unmodifiableMultimap(headers);
}

/** Argument builder of {@link ComposeSource}. */
public static final class Builder extends ObjectVersionArgs.Builder<Builder, ComposeSource> {

public Builder offset(long offset) {
validateNullOrPositive(offset, "offset");
operations.add(args -> args.offset = offset);
return this;
}

public Builder length(long length) {
validateNullOrPositive(length, "length");
operations.add(args -> args.length = length);
return this;
public long objectSize() throws InternalException {
if (this.objectSize == null) {
throw new InternalException(
"buildHeaders(long objectSize, String etag) must be called prior to this method invocation");
}

public Builder ssec(ServerSideEncryptionCustomerKey ssec) {
operations.add(args -> args.ssec = ssec);
return this;
}

public Builder matchETag(String etag) {
validateNullOrNotEmptyString(etag, "etag");
operations.add(args -> args.matchETag = etag);
return this;
}
return this.objectSize;
}

public Builder notMatchETag(String etag) {
validateNullOrNotEmptyString(etag, "etag");
operations.add(args -> args.notMatchETag = etag);
return this;
public Multimap<String, String> headers() throws InternalException {
if (this.headers == null) {
throw new InternalException(
"buildHeaders(long objectSize, String etag) must be called prior to this method invocation");
}

public Builder modifiedSince(ZonedDateTime modifiedTime) {
operations.add(args -> args.modifiedSince = modifiedTime);
return this;
}
return this.headers;
}

public Builder unmodifiedSince(ZonedDateTime unmodifiedTime) {
operations.add(args -> args.unmodifiedSince = unmodifiedTime);
return this;
}
public static Builder builder() {
return new Builder();
}

/** Argument builder of {@link ComposeSource}. */
public static final class Builder
extends ObjectConditionalReadArgs.Builder<Builder, ComposeSource> {}
}
Loading