diff --git a/src/main/java/com/aliyun/oss/OSS.java b/src/main/java/com/aliyun/oss/OSS.java index 241515fb..a497c120 100644 --- a/src/main/java/com/aliyun/oss/OSS.java +++ b/src/main/java/com/aliyun/oss/OSS.java @@ -4259,4 +4259,17 @@ public UdfApplicationLog getUdfApplicationLog(GetUdfApplicationLogRequest getUdf * @throws ClientException */ VoidResult deleteBucketTransferAcceleration(String bucketName) throws OSSException, ClientException; + + /** + * batch copy a batch of objects in the same bucket. + * + * @param copyObjectsRequest + * A {@link CopyObjectsRequest} instance that specifies bucket + * source key and target key。 + * @return A {@link CopyObjectResult} instance. + * @throws OSSException + * @throws ClientException + */ + public CopyObjectsResult copyObjects(CopyObjectsRequest copyObjectsRequest) throws OSSException, ClientException; + } diff --git a/src/main/java/com/aliyun/oss/OSSClient.java b/src/main/java/com/aliyun/oss/OSSClient.java index 5b3b69f9..b14c7350 100644 --- a/src/main/java/com/aliyun/oss/OSSClient.java +++ b/src/main/java/com/aliyun/oss/OSSClient.java @@ -1918,6 +1918,11 @@ public VoidResult deleteBucketTransferAcceleration(String bucketName) throws OSS return this.bucketOperation.deleteBucketTransferAcceleration(new GenericRequest(bucketName)); } + @Override + public CopyObjectsResult copyObjects(CopyObjectsRequest copyObjectsRequest) throws OSSException, ClientException { + return objectOperation.copyObjects(copyObjectsRequest); + } + @Override public void shutdown() { try { diff --git a/src/main/java/com/aliyun/oss/common/parser/RequestMarshallers.java b/src/main/java/com/aliyun/oss/common/parser/RequestMarshallers.java index 1b74016f..29c5f7b6 100644 --- a/src/main/java/com/aliyun/oss/common/parser/RequestMarshallers.java +++ b/src/main/java/com/aliyun/oss/common/parser/RequestMarshallers.java @@ -97,6 +97,7 @@ public final class RequestMarshallers { public static final SetBucketResourceGroupRequestMarshaller setBucketResourceGroupRequestMarshaller = new SetBucketResourceGroupRequestMarshaller(); public static final PutBucketTransferAccelerationRequestMarshaller putBucketTransferAccelerationRequestMarshaller = new PutBucketTransferAccelerationRequestMarshaller(); + public static final CopyObjectsRequestMarshaller copyObjectsRequestMarshaller = new CopyObjectsRequestMarshaller(); public interface RequestMarshaller extends Marshaller { @@ -1739,6 +1740,34 @@ public byte[] marshall(SetBucketTransferAccelerationRequest input) { } } + public static final class CopyObjectsRequestMarshaller implements RequestMarshaller2 { + + @Override + public byte[] marshall(CopyObjectsRequest input) { + StringBuffer xmlBody = new StringBuffer(); + + xmlBody.append(""); + if(input.getObjects() != null && !input.getObjects().isEmpty()){ + for (CopyObjects objs : input.getObjects()) { + xmlBody.append(""); + xmlBody.append("" + escapeKey(objs.getSourceKey()) + ""); + xmlBody.append("" + escapeKey(objs.getTargetKey()) + ""); + xmlBody.append(""); + } + } + xmlBody.append(""); + byte[] rawData = null; + try { + rawData = xmlBody.toString().getBytes(DEFAULT_CHARSET_NAME); + } catch (UnsupportedEncodingException e) { + throw new ClientException("Unsupported encoding " + e.getMessage(), e); + } + + return rawData; + } + + } + private static enum EscapedChar { // "\r" RETURN(" "), diff --git a/src/main/java/com/aliyun/oss/internal/OSSObjectOperation.java b/src/main/java/com/aliyun/oss/internal/OSSObjectOperation.java index ad2a66cb..e16c20f8 100644 --- a/src/main/java/com/aliyun/oss/internal/OSSObjectOperation.java +++ b/src/main/java/com/aliyun/oss/internal/OSSObjectOperation.java @@ -1055,6 +1055,36 @@ public VoidResult renameObject(RenameObjectRequest renameObjectRequest) throws O return doOperation(request, requestIdResponseParser, bucketName, destObject); } + public CopyObjectsResult copyObjects(CopyObjectsRequest copyObjectsRequest) throws OSSException, ClientException { + + assertParameterNotNull(copyObjectsRequest, "copyObjectsRequest"); + + String bucketName = copyObjectsRequest.getBucketName(); + + assertParameterNotNull(bucketName, "bucketName"); + ensureBucketNameValid(bucketName); + + if(copyObjectsRequest.getObjects() == null && copyObjectsRequest.getObjects().isEmpty()){ + throw new IllegalArgumentException("Batch copy object cannot be empty"); + } + + Map headers = new HashMap(); + populateRequestPayerHeader(headers, copyObjectsRequest.getRequestPayer()); + + Map params = new HashMap(); + params.put(RequestParameters.COPY, null); + + byte[] rawContent = copyObjectsRequestMarshaller.marshall(copyObjectsRequest); + + RequestMessage request = new OSSRequestMessageBuilder(getInnerClient()).setEndpoint(getEndpoint(copyObjectsRequest)) + .setMethod(HttpMethod.POST).setBucket(bucketName).setParameters(params).setHeaders(headers) + .setInputSize(rawContent.length).setInputStream(new ByteArrayInputStream(rawContent)) + .setOriginalRequest(copyObjectsRequest).build(); + + return doOperation(request, ResponseParsers.copyObjectsResponseParser, bucketName, null, true); + } + + private static enum MetadataDirective { /* Copy metadata from source object */ diff --git a/src/main/java/com/aliyun/oss/internal/RequestParameters.java b/src/main/java/com/aliyun/oss/internal/RequestParameters.java index 021f4b58..81c06430 100644 --- a/src/main/java/com/aliyun/oss/internal/RequestParameters.java +++ b/src/main/java/com/aliyun/oss/internal/RequestParameters.java @@ -149,4 +149,5 @@ public final class RequestParameters { public static final String START_AFTER = "start-after"; public static final String FETCH_OWNER = "fetch-owner"; public static final String SUBRESOURCE_TRANSFER_ACCELERATION = "transferAcceleration"; + public static final String COPY = "copy"; } diff --git a/src/main/java/com/aliyun/oss/internal/ResponseParsers.java b/src/main/java/com/aliyun/oss/internal/ResponseParsers.java index 34503ffa..0f0a3b27 100644 --- a/src/main/java/com/aliyun/oss/internal/ResponseParsers.java +++ b/src/main/java/com/aliyun/oss/internal/ResponseParsers.java @@ -137,6 +137,7 @@ public final class ResponseParsers { public static final GetSymbolicLinkResponseParser getSymbolicLinkResponseParser = new GetSymbolicLinkResponseParser(); public static final DeleteDirectoryResponseParser deleteDirectoryResponseParser = new DeleteDirectoryResponseParser(); + public static final CopyObjectsResponseParser copyObjectsResponseParser = new CopyObjectsResponseParser(); public static Long parseLongWithDefault(String defaultValue){ if(defaultValue == null || "".equals(defaultValue)){ @@ -3838,4 +3839,63 @@ private TransferAcceleration parseTransferAcceleration(InputStream inputStream) } } + public static final class CopyObjectsResponseParser implements ResponseParser { + @Override + public CopyObjectsResult parse(ResponseMessage response) throws ResponseParseException { + try { + CopyObjectsResult result = parseCopyObjects(response.getContent()); + result.setRequestId(response.getRequestId()); + result.setResponse(response); + return result; + } finally { + safeCloseResponse(response); + } + } + + private CopyObjectsResult parseCopyObjects(InputStream inputStream) throws ResponseParseException { + CopyObjectsResult result = new CopyObjectsResult(); + if (inputStream == null) { + return result; + } + + try { + Element root = getXmlRootElement(inputStream); + + if(root.getChild("Success") != null){ + List successObjects = new ArrayList(); + List successElements = root.getChild("Success").getChildren("Object"); + + for (Element e : successElements) { + CopyObjectsSuccessResult successResult = new CopyObjectsSuccessResult(); + successResult.setSourceKey(e.getChildText("SourceKey")); + successResult.setTargetKey(e.getChildText("TargetKey")); + successResult.setETag(e.getChildText("ETag")); + successObjects.add(successResult); + } + result.setSuccessObjects(successObjects); + } + + if(root.getChild("Failed") != null){ + List failedObjects = new ArrayList(); + List failedElements = root.getChild("Failed").getChildren("Object"); + + for (Element e : failedElements) { + CopyObjectsFailedResult failedResult = new CopyObjectsFailedResult(); + failedResult.setSourceKey(e.getChildText("SourceKey")); + failedResult.setTargetKey(e.getChildText("TargetKey")); + failedResult.setErrorStatus(e.getChildText("ErrorStatus")); + failedObjects.add(failedResult); + } + result.setFailedObjects(failedObjects); + } + return result; + } catch (JDOMParseException e) { + throw new ResponseParseException(e.getPartialDocument() + ": " + e.getMessage(), e); + } catch (Exception e) { + throw new ResponseParseException(e.getMessage(), e); + } + } + } + + } diff --git a/src/main/java/com/aliyun/oss/internal/SignParameters.java b/src/main/java/com/aliyun/oss/internal/SignParameters.java index 263bfec2..d09b6723 100644 --- a/src/main/java/com/aliyun/oss/internal/SignParameters.java +++ b/src/main/java/com/aliyun/oss/internal/SignParameters.java @@ -42,6 +42,6 @@ public class SignParameters { SUBRESOURCE_INVENTORY, SUBRESOURCE_INVENTORY_ID, SUBRESOURCE_CONTINUATION_TOKEN, SUBRESOURCE_WORM, SUBRESOURCE_WORM_ID, SUBRESOURCE_WORM_EXTEND, SUBRESOURCE_CALLBACK, SUBRESOURCE_CALLBACK_VAR, SUBRESOURCE_DIR, SUBRESOURCE_RENAME, SUBRESOURCE_DIR_DELETE, SUBRESOURCE_TRANSFER_ACCELERATION, - X_OSS_AC_SOURCE_IP, X_OSS_AC_SUBNET_MASK, X_OSS_AC_VPC_ID, X_OSS_AC_FORWARD_ALLOW}); + X_OSS_AC_SOURCE_IP, X_OSS_AC_SUBNET_MASK, X_OSS_AC_VPC_ID, X_OSS_AC_FORWARD_ALLOW, COPY}); } diff --git a/src/main/java/com/aliyun/oss/model/CopyObjects.java b/src/main/java/com/aliyun/oss/model/CopyObjects.java new file mode 100644 index 00000000..b4d8641d --- /dev/null +++ b/src/main/java/com/aliyun/oss/model/CopyObjects.java @@ -0,0 +1,34 @@ +package com.aliyun.oss.model; + +public class CopyObjects { + // Source object key. + private String sourceKey; + // Target object key. + private String targetKey; + + public String getSourceKey() { + return sourceKey; + } + + public void setSourceKey(String sourceKey) { + this.sourceKey = sourceKey; + } + + public CopyObjects withSourceKey(String sourceKey) { + this.sourceKey = sourceKey; + return this; + } + + public String getTargetKey() { + return targetKey; + } + + public void setTargetKey(String targetKey) { + this.targetKey = targetKey; + } + + public CopyObjects withTargetKey(String targetKey) { + this.targetKey = targetKey; + return this; + } +} diff --git a/src/main/java/com/aliyun/oss/model/CopyObjectsFailedResult.java b/src/main/java/com/aliyun/oss/model/CopyObjectsFailedResult.java new file mode 100644 index 00000000..27e5362d --- /dev/null +++ b/src/main/java/com/aliyun/oss/model/CopyObjectsFailedResult.java @@ -0,0 +1,14 @@ +package com.aliyun.oss.model; + +public class CopyObjectsFailedResult extends CopyObjects { + // Error state when copying objects. + private String errorStatus; + + public String getErrorStatus() { + return errorStatus; + } + + public void setErrorStatus(String errorStatus) { + this.errorStatus = errorStatus; + } +} diff --git a/src/main/java/com/aliyun/oss/model/CopyObjectsRequest.java b/src/main/java/com/aliyun/oss/model/CopyObjectsRequest.java new file mode 100644 index 00000000..697f3be6 --- /dev/null +++ b/src/main/java/com/aliyun/oss/model/CopyObjectsRequest.java @@ -0,0 +1,15 @@ +package com.aliyun.oss.model; + +import java.util.List; + +public class CopyObjectsRequest extends GenericRequest { + private List objects; + + public List getObjects() { + return objects; + } + + public void setObjects(List objects) { + this.objects = objects; + } +} diff --git a/src/main/java/com/aliyun/oss/model/CopyObjectsResult.java b/src/main/java/com/aliyun/oss/model/CopyObjectsResult.java new file mode 100644 index 00000000..21602439 --- /dev/null +++ b/src/main/java/com/aliyun/oss/model/CopyObjectsResult.java @@ -0,0 +1,30 @@ +package com.aliyun.oss.model; + +import java.util.List; + +public class CopyObjectsResult extends GenericResult { + /** + * Successfully copied objects collection. + */ + private List successObjects; + /** + * Copy failed objects collection. + */ + private List failedObjects; + + public List getSuccessObjects() { + return successObjects; + } + + public void setSuccessObjects(List successObjects) { + this.successObjects = successObjects; + } + + public List getFailedObjects() { + return failedObjects; + } + + public void setFailedObjects(List failedObjects) { + this.failedObjects = failedObjects; + } +} diff --git a/src/main/java/com/aliyun/oss/model/CopyObjectsSuccessResult.java b/src/main/java/com/aliyun/oss/model/CopyObjectsSuccessResult.java new file mode 100644 index 00000000..9a313e64 --- /dev/null +++ b/src/main/java/com/aliyun/oss/model/CopyObjectsSuccessResult.java @@ -0,0 +1,14 @@ +package com.aliyun.oss.model; + +public class CopyObjectsSuccessResult extends CopyObjects { + // Target object's ETag + private String eTag; + + public String getETag() { + return eTag; + } + + public void setETag(String eTag) { + this.eTag = eTag; + } +} diff --git a/src/samples/BatchCopyObjectsSample.java b/src/samples/BatchCopyObjectsSample.java new file mode 100644 index 00000000..3c5cf690 --- /dev/null +++ b/src/samples/BatchCopyObjectsSample.java @@ -0,0 +1,50 @@ +import com.aliyun.oss.ClientException; +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; +import com.aliyun.oss.OSSException; +import com.aliyun.oss.model.*; +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.List; + +public class BatchCopyObjectsSample { + + private static String endpoint = "*** Provide OSS endpoint ***"; + private static String accessKeyId = "*** Provide your AccessKeyId ***"; + private static String accessKeySecret = "*** Provide your AccessKeySecret ***"; + private static String bucketName = "*** Provide bucket name ***"; + private static String key = "*** Provide object name ***"; + private static String targetKey = "*** Provide target object name ***"; + + public static void main(String[] args) { + // Create an OSSClient instance. + OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + try { + // batch copy a batch of objects in the same bucket. + byte[] content = { 'A', 'l', 'i', 'y', 'u', 'n' }; + ossClient.putObject(bucketName, key, new ByteArrayInputStream(content)); + + CopyObjectsRequest copyObjectsRequest = new CopyObjectsRequest(); + List objects = new ArrayList(); + CopyObjects obj1 = new CopyObjects().withSourceKey(key).withTargetKey(targetKey); + objects.add(obj1); + copyObjectsRequest.setObjects(objects); + copyObjectsRequest.setBucketName(bucketName); + CopyObjectsResult copyObjectsResult = ossClient.copyObjects(copyObjectsRequest); + System.out.println(copyObjectsResult.getSuccessObjects().get(0).getETag()); + } catch (OSSException oe) { + System.out.println("Error Message: " + oe.getErrorMessage()); + System.out.println("Error Code: " + oe.getErrorCode()); + System.out.println("Request ID: " + oe.getRequestId()); + System.out.println("Host ID: " + oe.getHostId()); + } catch (ClientException ce) { + System.out.println("Error Message: " + ce.getMessage()); + } finally { + /* + * Do not forget to shut down the client finally to release all allocated resources. + */ + ossClient.shutdown(); + } + } +} diff --git a/src/test/java/com/aliyun/oss/common/parser/RequestMarshallersTest.java b/src/test/java/com/aliyun/oss/common/parser/RequestMarshallersTest.java index e4589b49..d43954ab 100644 --- a/src/test/java/com/aliyun/oss/common/parser/RequestMarshallersTest.java +++ b/src/test/java/com/aliyun/oss/common/parser/RequestMarshallersTest.java @@ -1334,4 +1334,51 @@ public void testPutBucketTransferAccelerationRequestMarshaller() { Assert.assertEquals("true", status); } + @Test + public void testCopyObjectsRequestMarshaller() { + + CopyObjectsRequest copyObjectsRequest = new CopyObjectsRequest(); + + byte[] data = copyObjectsRequestMarshaller.marshall(copyObjectsRequest); + ByteArrayInputStream is = new ByteArrayInputStream(data); + + SAXBuilder builder = new SAXBuilder(); + Document doc = null; + try { + doc = builder.build(is); + } catch (JDOMException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + Element root = doc.getRootElement(); + Assert.assertEquals(root.getChildren("Object").size(), 0); + + + + copyObjectsRequest = new CopyObjectsRequest(); + List objects = new ArrayList(); + CopyObjects copyObjects = new CopyObjects(); + copyObjects.setSourceKey("Thread-1"); + copyObjects.setTargetKey("Thread-12"); + objects.add(copyObjects); + copyObjectsRequest.setObjects(objects); + + data = copyObjectsRequestMarshaller.marshall(copyObjectsRequest); + is = new ByteArrayInputStream(data); + + try { + doc = builder.build(is); + } catch (JDOMException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + root = doc.getRootElement(); + Assert.assertEquals(root.getChildren("Object").get(0).getChildText("SourceKey"), "Thread-1"); + Assert.assertEquals(root.getChildren("Object").get(0).getChildText("TargetKey"), "Thread-12"); + + } } diff --git a/src/test/java/com/aliyun/oss/common/parser/ResponseParsersTest.java b/src/test/java/com/aliyun/oss/common/parser/ResponseParsersTest.java index 2f238c37..f6150846 100644 --- a/src/test/java/com/aliyun/oss/common/parser/ResponseParsersTest.java +++ b/src/test/java/com/aliyun/oss/common/parser/ResponseParsersTest.java @@ -3,12 +3,8 @@ import com.aliyun.oss.common.comm.ResponseMessage; import com.aliyun.oss.common.utils.DateUtil; import com.aliyun.oss.internal.ResponseParsers; -import com.aliyun.oss.internal.model.OSSErrorResult; import com.aliyun.oss.model.*; import junit.framework.Assert; -import org.jdom2.Document; -import org.jdom2.Element; -import org.jdom2.input.SAXBuilder; import org.junit.Test; import java.io.ByteArrayInputStream; @@ -4455,4 +4451,80 @@ public void testGetBucketTransferAccelerationResponseParser() { } Assert.assertEquals(false, result.isEnabled()); } + + @Test + public void testCopyObjectsResponseParser() { + String respBody = "" + + "\n" + + "\t\n" + + " \n" + + " copy-source1.data\n" + + " copy1.data\n" + + " F2064A169EE92E9775EE5324D0B1****\n" + + " \n" + + " \n" + + " copy-source2.data\n" + + " copy2.data\n" + + " 5B3C1A2E053D763E1B002CC607C5****\n" + + " \n" + + " \n" + + " \n" + + " \t\n" + + " copy-source3.data\n" + + " copy3.data\n" + + " AccessDenied\n" + + " \n" + + " \n" + + ""; + + InputStream instream = null; + try { + instream = new ByteArrayInputStream(respBody.getBytes("utf-8")); + } catch (UnsupportedEncodingException e) { + Assert.fail("UnsupportedEncodingException"); + } + + CopyObjectsResult result = null; + try { + ResponseMessage response = new ResponseMessage(null); + response.setContent(instream); + ResponseParsers.CopyObjectsResponseParser parser = new ResponseParsers.CopyObjectsResponseParser(); + result = parser.parse(response); + } catch (ResponseParseException e) { + Assert.fail("parse delete directory response body fail!"); + } + + Assert.assertEquals("copy-source1.data", result.getSuccessObjects().get(0).getSourceKey()); + Assert.assertEquals("copy1.data", result.getSuccessObjects().get(0).getTargetKey()); + Assert.assertEquals("F2064A169EE92E9775EE5324D0B1****", result.getSuccessObjects().get(0).getETag()); + Assert.assertEquals("copy-source2.data", result.getSuccessObjects().get(1).getSourceKey()); + Assert.assertEquals("copy2.data", result.getSuccessObjects().get(1).getTargetKey()); + Assert.assertEquals("5B3C1A2E053D763E1B002CC607C5****", result.getSuccessObjects().get(1).getETag()); + Assert.assertEquals("copy-source3.data", result.getFailedObjects().get(0).getSourceKey()); + Assert.assertEquals("copy3.data", result.getFailedObjects().get(0).getTargetKey()); + Assert.assertEquals("AccessDenied", result.getFailedObjects().get(0).getErrorStatus()); + + + + respBody = "" + + "\n" + + ""; + + try { + instream = new ByteArrayInputStream(respBody.getBytes("utf-8")); + } catch (UnsupportedEncodingException e) { + Assert.fail("UnsupportedEncodingException"); + } + + result = null; + try { + ResponseMessage response = new ResponseMessage(null); + response.setContent(instream); + ResponseParsers.CopyObjectsResponseParser parser = new ResponseParsers.CopyObjectsResponseParser(); + result = parser.parse(response); + } catch (ResponseParseException e) { + Assert.fail("parse delete directory response body fail!"); + } + Assert.assertEquals(null, result.getSuccessObjects()); + } } diff --git a/src/test/java/com/aliyun/oss/integrationtests/CopyObjectsTest.java b/src/test/java/com/aliyun/oss/integrationtests/CopyObjectsTest.java new file mode 100644 index 00000000..c1787b5d --- /dev/null +++ b/src/test/java/com/aliyun/oss/integrationtests/CopyObjectsTest.java @@ -0,0 +1,150 @@ +package com.aliyun.oss.integrationtests; + +import com.aliyun.oss.model.*; +import junit.framework.Assert; +import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.List; +import static com.aliyun.oss.integrationtests.TestUtils.waitForCacheExpiration; +import static com.aliyun.oss.internal.OSSConstants.DEFAULT_OBJECT_CONTENT_TYPE; + +public class CopyObjectsTest extends TestBase { + + @Test + public void testBatchCopyExistingObjects() { + String sourceBucket = "copy-existing-object-source-bucket"; + String sourceKey = "copy-existing-object-source-object"; + String sourceKey2 = "copy-existing-object-source-object2"; + String targetKey = "copy-existing-object-target-object"; + String targetKey2 = "copy-existing-object-target-object2"; + String userMetaKey0 = "user"; + String userMetaValue0 = "aliy"; + + try { + ossClient.createBucket(sourceBucket); + + byte[] content = { 'A', 'l', 'i', 'y', 'u', 'n' }; + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(content.length); + metadata.setContentType(DEFAULT_OBJECT_CONTENT_TYPE); + metadata.addUserMetadata(userMetaKey0, userMetaValue0); + + PutObjectResult putObjectResult = ossClient.putObject(sourceBucket, sourceKey, + new ByteArrayInputStream(content), metadata); + PutObjectResult putObjectResult2 = ossClient.putObject(sourceBucket, sourceKey2, + new ByteArrayInputStream(content), metadata); + + CopyObjectsRequest copyObjectsRequest = new CopyObjectsRequest(); + List objects = new ArrayList(); + CopyObjects obj1 = new CopyObjects().withSourceKey(sourceKey).withTargetKey(targetKey); + objects.add(obj1); + CopyObjects obj2 = new CopyObjects().withSourceKey(sourceKey2).withTargetKey(targetKey2); + objects.add(obj2); + copyObjectsRequest.setObjects(objects); + copyObjectsRequest.setBucketName(sourceBucket); + CopyObjectsResult copyObjectsResult = ossClient.copyObjects(copyObjectsRequest); + + String sourceETag = putObjectResult.getETag(); + String targetETag = copyObjectsResult.getSuccessObjects().get(0).getETag(); + Assert.assertEquals(sourceETag, targetETag); + Assert.assertEquals(putObjectResult.getRequestId().length(), REQUEST_ID_LEN); + String sourceETag2 = putObjectResult2.getETag(); + String targetETag2 = copyObjectsResult.getSuccessObjects().get(1).getETag(); + Assert.assertEquals(sourceETag2, targetETag2); + + OSSObject ossObject = ossClient.getObject(sourceBucket, targetKey); + ObjectMetadata newObjectMetadata = ossObject.getObjectMetadata(); + Assert.assertEquals(DEFAULT_OBJECT_CONTENT_TYPE, newObjectMetadata.getContentType()); + Assert.assertEquals(userMetaValue0, newObjectMetadata.getUserMetadata().get(userMetaKey0)); + Assert.assertEquals(ossObject.getRequestId().length(), REQUEST_ID_LEN); + + OSSObject ossObject2 = ossClient.getObject(sourceBucket, targetKey2); + ObjectMetadata newObjectMetadata2 = ossObject2.getObjectMetadata(); + Assert.assertEquals(DEFAULT_OBJECT_CONTENT_TYPE, newObjectMetadata2.getContentType()); + Assert.assertEquals(userMetaValue0, newObjectMetadata2.getUserMetadata().get(userMetaKey0)); + Assert.assertEquals(ossObject2.getRequestId().length(), REQUEST_ID_LEN); + + } catch (Exception e) { + Assert.fail(e.getMessage()); + } finally { + waitForCacheExpiration(5); + deleteBucketWithObjects(ossClient, sourceBucket); + } + } + + @Test + public void testBatchCopyNonexistentObject() { + String existingSourceBucket = "copy-nonexistent-object-existing-source-bucket"; + String existingSourceKey = "copy-nonexistent-object-existing-source-object"; + String nonexistentSourceKey = "copy-nonexistent-object-nonexistent-source-object"; + String targetKey = "copy-nonexistent-object-target"; + + try { + ossClient.createBucket(existingSourceBucket); + + // Try to copy object under non-existent source bucket + CopyObjectsRequest copyObjectsRequest = new CopyObjectsRequest(); + List objects = new ArrayList(); + CopyObjects obj1 = new CopyObjects().withSourceKey(nonexistentSourceKey).withTargetKey(targetKey); + objects.add(obj1); + copyObjectsRequest.setObjects(objects); + copyObjectsRequest.setBucketName(existingSourceBucket); + CopyObjectsResult copyObjectsResult = ossClient.copyObjects(copyObjectsRequest); + + String resultSourceKey = copyObjectsResult.getFailedObjects().get(0).getSourceKey(); + String resultTargetKey = copyObjectsResult.getFailedObjects().get(0).getTargetKey(); + String errorStatus = copyObjectsResult.getFailedObjects().get(0).getErrorStatus(); + Assert.assertEquals(nonexistentSourceKey, resultSourceKey); + Assert.assertEquals(targetKey, resultTargetKey); + Assert.assertEquals("NoSuchKey", errorStatus); + + } catch (Exception e) { + Assert.fail(e.getMessage()); + } finally { + deleteBucketWithObjects(ossClient, existingSourceBucket); + } + } + + @Test + public void testBatchCopyObjectWithSpecialChars() { + String sourceBucket = "copy-existing-object-source-bucket"; + String sourceKey = "测\\r试-中.~,+\"'*&¥#@%!(文)+字符|?/.zip"; + String targetKey = "测\\r试-中.~,+\"'*&¥#@%!(文)+字符|?/-2.zip"; + String userMetaKey0 = "user"; + String userMetaValue0 = "阿里人"; + + try { + ossClient.createBucket(sourceBucket); + + byte[] content = { 'A', 'l', 'i', 'y', 'u', 'n' }; + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(content.length); + metadata.setContentType(DEFAULT_OBJECT_CONTENT_TYPE); + metadata.addUserMetadata(userMetaKey0, userMetaValue0); + + PutObjectResult putObjectResult = ossClient.putObject(sourceBucket, sourceKey, + new ByteArrayInputStream(content), metadata); + CopyObjectsRequest copyObjectsRequest = new CopyObjectsRequest(); + List objects = new ArrayList(); + CopyObjects obj1 = new CopyObjects().withSourceKey(sourceKey).withTargetKey(targetKey); + objects.add(obj1); + copyObjectsRequest.setObjects(objects); + copyObjectsRequest.setBucketName(sourceBucket); + CopyObjectsResult copyObjectsResult = ossClient.copyObjects(copyObjectsRequest); + String sourceETag = putObjectResult.getETag(); + String targetETag = copyObjectsResult.getSuccessObjects().get(0).getETag(); + Assert.assertEquals(sourceETag, targetETag); + + OSSObject ossObject = ossClient.getObject(sourceBucket, targetKey); + ObjectMetadata newObjectMetadata = ossObject.getObjectMetadata(); + Assert.assertEquals(DEFAULT_OBJECT_CONTENT_TYPE, newObjectMetadata.getContentType()); + Assert.assertEquals(userMetaValue0, newObjectMetadata.getUserMetadata().get(userMetaKey0)); + + } catch (Exception e) { + Assert.fail(e.getMessage()); + } finally { + deleteBucketWithObjects(ossClient, sourceBucket); + } + } +}