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 missing xml prolog and namespaces #1778

Merged
merged 7 commits into from
Apr 11, 2024
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
2 changes: 1 addition & 1 deletion .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
34 changes: 32 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
* [PLANNED - 4.x - RELEASE TBD ~ late 2023 / early 2024](#planned---4x---release-tbd--late-2023--early-2024)
* [Planned changes](#planned-changes)
* [CURRENT - 3.x - THIS VERSION IS UNDER ACTIVE DEVELOPMENT](#current---3x---this-version-is-under-active-development)
* [3.6.0 - PLANNED](#360---planned)
* [3.7.0 - PLANNED](#370---planned)
* [3.6.0](#360)
* [3.5.2](#352)
* [3.5.1](#351)
* [3.5.0](#350)
* [3.4.0](#340)
Expand Down Expand Up @@ -117,7 +119,7 @@ Version 3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav

**The current major version 3 will receive new features, dependency updates and bug fixes on a continuous basis.**

## 3.6.0 - PLANNED
## 3.7.0 - PLANNED
3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration.

* Features and fixes
Expand All @@ -128,6 +130,34 @@ Version 3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Jav
* Version updates
* TBD

## 3.6.0
3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration.

* Features and fixes
* Return XML prolog and namespace in all responses (fixes #1754)
* Refactorings
* Removal of JAX-B for AccessControlPolicy requests/responses.
* Jackson-databind-xml 2.17.0 adds polymorphic (de-)serializiation through "xsi:type"
* Jackson-annotation cleanup in POJOs
* Version updates
* Bump spring-boot.version from 3.2.3 to 3.2.4
* Bump aws-v2.version from 2.24.9 to 2.25.28
* Bump com.amazonaws:aws-java-sdk-s3 from 1.12.665 to 1.12.698
* Bump commons-io:commons-io from 2.15.1 to 2.16.1
* Bump org.testng:testng from 7.9.0 to 7.10.1
* Bump org.mockito.kotlin:mockito-kotlin from 5.2.1 to 5.3.1
* Bump com.puppycrawl.tools:checkstyle from 10.14.0 to 10.15.0
* Bump org.apache.maven.plugins:maven-gpg-plugin from 3.1.0 to 3.2.2
* Bump org.apache.maven.plugins:maven-compiler-plugin from 3.12.1 to 3.13.0
* Bump org.apache.maven.plugins:maven-source-plugin from 3.3.0 to 3.3.1
* Bump com.github.ekryd.sortpom:sortpom-maven-plugin from 3.4.0 to 3.4.1
* Bump org.jacoco:jacoco-maven-plugin from 0.8.11 to 0.8.12
* Bump actions/checkout from 4.1.1 to 4.1.2
* Bump actions/setup-java from 4.1.0 to 4.2.1
* Bump github/codeql-action from 3.24.6 to 3.24.10
* Bump actions/dependency-review-action from 4.1.3 to 4.2.5
* Bump maven from 3.8.5 to 3.9.6

## 3.5.2
3.x is JDK17 LTS bytecode compatible, with Docker and JUnit / direct Java integration.

Expand Down
2 changes: 1 addition & 1 deletion build-config/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>com.adobe.testing</groupId>
<artifactId>s3mock-parent</artifactId>
<version>3.5.3-SNAPSHOT</version>
<version>3.6.0-SNAPSHOT</version>
</parent>

<artifactId>s3mock-build-config</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion docker/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>com.adobe.testing</groupId>
<artifactId>s3mock-parent</artifactId>
<version>3.5.3-SNAPSHOT</version>
<version>3.6.0-SNAPSHOT</version>
</parent>

<artifactId>s3mock-docker</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>com.adobe.testing</groupId>
<artifactId>s3mock-parent</artifactId>
<version>3.5.3-SNAPSHOT</version>
<version>3.6.0-SNAPSHOT</version>
</parent>

<artifactId>s3mock-integration-tests</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() {
overrides.contentDisposition = "contentDisposition"
overrides.contentEncoding = "contentEncoding"
overrides.contentLanguage = "contentLanguage"
overrides.contentType = "contentType"
overrides.contentType = "my/contentType"
overrides.expires = "expires"
presignedUrlRequest.withResponseHeaders(overrides)
val resourceUrl = s3Client.generatePresignedUrl(presignedUrlRequest)
Expand All @@ -414,7 +414,7 @@ internal class GetPutDeleteObjectV1IT : S3TestBase() {
assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_DISPOSITION).value).isEqualTo("contentDisposition")
assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_ENCODING).value).isEqualTo("contentEncoding")
assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_LANGUAGE).value).isEqualTo("contentLanguage")
assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_TYPE).value).isEqualTo("contentType")
assertThat(getObjectResponse.getFirstHeader(Headers.CONTENT_TYPE).value).isEqualTo("my/contentType")
assertThat(getObjectResponse.getFirstHeader(Headers.EXPIRES).value).isEqualTo("expires")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ internal class PlainHttpIT : S3TestBase() {
.readLines()
.stream()
.collect(Collectors.joining()))
.isEqualTo("<Error><Code>InvalidBucketName</Code>" +
"<Message>The specified bucket is not valid.</Message><Resource/><RequestId/></Error>")
.isEqualTo("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Error><Code>InvalidBucketName</Code>" +
"<Message>The specified bucket is not valid.</Message></Error>")
}

@Test
Expand Down
13 changes: 11 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<groupId>com.adobe.testing</groupId>
<artifactId>s3mock-parent</artifactId>
<version>3.5.3-SNAPSHOT</version>
<version>3.6.0-SNAPSHOT</version>
<packaging>pom</packaging>

<name>S3Mock - Parent</name>
Expand Down Expand Up @@ -99,6 +99,8 @@
<docker-maven-plugin.version>0.44.0</docker-maven-plugin.version>

<docker.image.name>adobe/s3mock</docker.image.name>
<!-- need Jackson 2.17.0+ for XML with xsi:type (de-)serialization -->
<jackson-bom.version>2.17.0</jackson-bom.version>
<java.version>17</java.version>
<junit-jupiter.version>5.7.2</junit-jupiter.version>
<junit.version>4.13.2</junit.version>
Expand All @@ -124,7 +126,7 @@
<!-- Run Docker build by default -->
<skipDocker>false</skipDocker>
<sortpom-maven-plugin.version>3.4.1</sortpom-maven-plugin.version>
<spring-boot.version>3.2.3</spring-boot.version>
<spring-boot.version>3.2.4</spring-boot.version>
<testcontainers.version>1.19.7</testcontainers.version>
<testng.version>7.10.1</testng.version>
<xmlunit-assertj3.version>2.9.1</xmlunit-assertj3.version>
Expand Down Expand Up @@ -191,6 +193,13 @@
<artifactId>mockito-kotlin</artifactId>
<version>${mockito-kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
Expand Down
6 changes: 1 addition & 5 deletions server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>com.adobe.testing</groupId>
<artifactId>s3mock-parent</artifactId>
<version>3.5.3-SNAPSHOT</version>
<version>3.6.0-SNAPSHOT</version>
</parent>

<artifactId>s3mock</artifactId>
Expand Down Expand Up @@ -76,10 +76,6 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,13 @@
import com.adobe.testing.s3mock.store.S3ObjectMetadata;
import com.adobe.testing.s3mock.util.AwsHttpHeaders.MetadataDirective;
import com.adobe.testing.s3mock.util.CannedAclUtil;
import com.adobe.testing.s3mock.util.XmlUtil;
import jakarta.xml.bind.JAXBException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLStreamException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream;
import org.springframework.http.HttpHeaders;
Expand Down Expand Up @@ -312,12 +309,12 @@ public ResponseEntity<StreamingResponseBody> getObject(@PathVariable String buck
public ResponseEntity<Void> putObjectAcl(@PathVariable final String bucketName,
@PathVariable ObjectKey key,
@RequestHeader(value = X_AMZ_ACL, required = false) ObjectCannedACL cannedAcl,
@RequestBody(required = false) String body) throws XMLStreamException, JAXBException {
@RequestBody(required = false) AccessControlPolicy body) {
bucketService.verifyBucketExists(bucketName);
objectService.verifyObjectExists(bucketName, key.key());
AccessControlPolicy policy;
if (body != null) {
policy = XmlUtil.deserializeJaxb(body);
policy = body;
} else if (cannedAcl != null) {
policy = CannedAclUtil.policyForCannedAcl(cannedAcl);
} else {
Expand Down Expand Up @@ -349,12 +346,12 @@ public ResponseEntity<Void> putObjectAcl(@PathVariable final String bucketName,
},
produces = APPLICATION_XML_VALUE
)
public ResponseEntity<String> getObjectAcl(@PathVariable final String bucketName,
@PathVariable ObjectKey key) throws JAXBException {
public ResponseEntity<AccessControlPolicy> getObjectAcl(@PathVariable final String bucketName,
@PathVariable ObjectKey key) {
bucketService.verifyBucketExists(bucketName);
objectService.verifyObjectExists(bucketName, key.key());
var acl = objectService.getAcl(bucketName, key.key());
return ResponseEntity.ok(XmlUtil.serializeJaxb(acl));
return ResponseEntity.ok(acl);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
import com.adobe.testing.s3mock.service.MultipartService;
import com.adobe.testing.s3mock.service.ObjectService;
import com.adobe.testing.s3mock.store.KmsKeyStore;
import com.ctc.wstx.api.WstxOutputProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
Expand Down Expand Up @@ -139,6 +144,13 @@ MappingJackson2XmlHttpMessageConverter messageConverter() {

var xmlConverter = new MappingJackson2XmlHttpMessageConverter();
xmlConverter.setSupportedMediaTypes(mediaTypes);
XmlMapper xmlMapper = (XmlMapper) xmlConverter.getObjectMapper();
xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
xmlMapper.enable(ToXmlGenerator.Feature.AUTO_DETECT_XSI_TYPE);
xmlMapper.enable(FromXmlParser.Feature.AUTO_DETECT_XSI_TYPE);
xmlMapper.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION);
xmlMapper.getFactory().getXMLOutputFactory()
.setProperty(WstxOutputProperties.P_USE_DOUBLE_QUOTES_IN_XML_DECL, true);

return xmlConverter;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 Adobe.
* Copyright 2017-2024 Adobe.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,12 +17,10 @@
package com.adobe.testing.s3mock.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;

/**
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortIncompleteMultipartUpload.html">API Reference</a>.
*/
@JsonRootName("ListPartsResult")
public record AbortIncompleteMultipartUpload(
@JsonProperty("DaysAfterInitiation")
Integer daysAfterInitiation
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2023 Adobe.
* Copyright 2017-2024 Adobe.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,37 +16,46 @@

package com.adobe.testing.s3mock.dto;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import java.util.List;
import java.util.Objects;

/**
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_AccessControlPolicy.html">API Reference</a>.
* This POJO uses JAX-B annotations instead of Jackson annotations because AWS decided to use
* xsi:type annotations in the XML representation, which are not supported by Jackson.
* JAX-B currently does not support Java records, see https://github.com/jakartaee/jaxb-api/issues/183
* This class is a POJO instead of a record because jackson-databind-xml as of now does not support
* record classes with @JacksonXmlElementWrapper:
* https://github.com/FasterXML/jackson-dataformat-xml/issues/517
*/
@XmlRootElement(name = "AccessControlPolicy")
@XmlAccessorType(XmlAccessType.FIELD)
@JsonRootName("AccessControlPolicy")
public class AccessControlPolicy {
@XmlElement(name = "Owner")
private Owner owner;
@JsonProperty("Owner")
Owner owner;

@XmlElement(name = "Grant")
@XmlElementWrapper(name = "AccessControlList")
private List<Grant> accessControlList;
@JsonProperty("Grant")
@JacksonXmlElementWrapper(localName = "AccessControlList")
List<Grant> accessControlList;

//workaround for adding xmlns attribute to root element only.
@JacksonXmlProperty(isAttribute = true, localName = "xmlns")
String xmlns;

public AccessControlPolicy() {
// Jackson needs the default constructor for deserialization.
//needed by Jackson
}

public AccessControlPolicy(Owner owner, List<Grant> accessControlList) {
public AccessControlPolicy(Owner owner, List<Grant> accessControlList, String xmlns) {
this.owner = owner;
this.accessControlList = accessControlList;
this.xmlns = xmlns;
}

@JsonCreator(mode = JsonCreator.Mode.DISABLED)
public AccessControlPolicy(Owner owner, List<Grant> accessControlList) {
this(owner, accessControlList, "http://s3.amazonaws.com/doc/2006-03-01/");
}

public Owner getOwner() {
Expand All @@ -73,13 +82,22 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
AccessControlPolicy policy = (AccessControlPolicy) o;
return Objects.equals(owner, policy.owner) && Objects.equals(
accessControlList, policy.accessControlList);
AccessControlPolicy that = (AccessControlPolicy) o;
return Objects.equals(owner, that.owner)
&& Objects.equals(accessControlList, that.accessControlList);
}

@Override
public int hashCode() {
return Objects.hash(owner, accessControlList);
}

@Override
public String toString() {
return "AccessControlPolicy{"
+ "owner=" + owner
+ ", accessControlList=" + accessControlList
+ ", xmlns='" + xmlns + '\''
+ '}';
}
}
Loading