Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stax.StAXSource;
Expand Down Expand Up @@ -705,7 +706,26 @@ public void marshal(Object graph, Result result) throws XmlMappingException {
@Override
public void marshal(Object graph, Result result, @Nullable MimeContainer mimeContainer) throws XmlMappingException {
try {
Marshaller marshaller = createMarshaller();
// XOP packaging replaces base64Binary simple content with xop:Include children.
// Schema validation must therefore run against the logical (non-XOP) infoset first,
// then MTOM marshalling proceeds without a Schema so validation does not see XOP.
// See spring-projects/spring-ws#1030 / SWS-958.
if (this.mtomEnabled && mimeContainer != null && this.schema != null) {
Marshaller validatingMarshaller = createMarshaller(true);
// Non-XOP marshaller keeps base64 as simple content and supports @XmlAttachmentRef.
validatingMarshaller.setAttachmentMarshaller(LogicalAttachmentMarshaller.INSTANCE);
validatingMarshaller.marshal(graph, new DOMResult());
Marshaller mtomMarshaller = createMarshaller(false);
mtomMarshaller.setAttachmentMarshaller(new Jaxb2AttachmentMarshaller(mimeContainer));
if (StaxUtils.isStaxResult(result)) {
marshalStaxResult(mtomMarshaller, graph, result);
}
else {
mtomMarshaller.marshal(graph, result);
}
return;
}
Marshaller marshaller = createMarshaller(true);
if (this.mtomEnabled && mimeContainer != null) {
marshaller.setAttachmentMarshaller(new Jaxb2AttachmentMarshaller(mimeContainer));
}
Expand All @@ -729,9 +749,13 @@ public void marshal(Object graph, Result result, @Nullable MimeContainer mimeCon
* @see #createUnmarshaller()
*/
public Marshaller createMarshaller() {
return createMarshaller(true);
}

private Marshaller createMarshaller(boolean applySchema) {
try {
Marshaller marshaller = getJaxbContext().createMarshaller();
initJaxbMarshaller(marshaller);
initJaxbMarshaller(marshaller, applySchema);
return marshaller;
}
catch (JAXBException ex) {
Expand Down Expand Up @@ -766,6 +790,10 @@ private void marshalStaxResult(Marshaller jaxbMarshaller, Object graph, Result s
* and {@link #setAdapters adapters}.
*/
protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException {
initJaxbMarshaller(marshaller, true);
}

private void initJaxbMarshaller(Marshaller marshaller, boolean applySchema) throws JAXBException {
if (this.marshallerProperties != null) {
for (Map.Entry<String, ?> entry : this.marshallerProperties.entrySet()) {
marshaller.setProperty(entry.getKey(), entry.getValue());
Expand All @@ -782,7 +810,7 @@ protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException {
marshaller.setAdapter(adapter);
}
}
if (this.schema != null) {
if (applySchema && this.schema != null) {
marshaller.setSchema(this.schema);
}
}
Expand Down Expand Up @@ -983,6 +1011,38 @@ else if (ex instanceof UnmarshalException) {
}


/**
* Attachment marshaller used only while validating the logical infoset (no XOP).
* {@link #isXOPPackage()} is {@code false} so {@code xs:base64Binary} stays simple
* content; SWA/{@code @XmlAttachmentRef} fields still get a placeholder content id.
*/
private static final class LogicalAttachmentMarshaller extends AttachmentMarshaller {

static final LogicalAttachmentMarshaller INSTANCE = new LogicalAttachmentMarshaller();

@Override
public boolean isXOPPackage() {
return false;
}

@Override
public String addMtomAttachment(byte[] data, int offset, int length, String mimeType,
String elementNamespace, String elementLocalName) {
return "cid:validation";
}

@Override
public String addMtomAttachment(DataHandler dataHandler, String elementNamespace, String elementLocalName) {
return "cid:validation";
}

@Override
public String addSwaRefAttachment(DataHandler dataHandler) {
return "cid:validation";
}
}


private static class Jaxb2AttachmentMarshaller extends AttachmentMarshaller {

private final MimeContainer mimeContainer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.xmlunit.diff.DifferenceEvaluator;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.testfixture.xml.XmlContent;
import org.springframework.oxm.AbstractMarshallerTests;
Expand Down Expand Up @@ -291,6 +292,50 @@ void marshalAttachments() throws Exception {
verify(mimeContainer, times(3)).addAttachment(isA(String.class), isA(DataHandler.class));
}

@Test // spring-ws#1030 / SWS-958: mtomEnabled + schema must validate logical infoset then XOP-encode
void marshalAttachmentsWithSchema() throws Exception {
marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(BinaryObject.class);
marshaller.setMtomEnabled(true);
marshaller.setSchema(new ClassPathResource("binary-object.xsd", getClass()));
marshaller.afterPropertiesSet();
MimeContainer mimeContainer = mock();

Resource logo = new ClassPathResource("spring-ws.png", getClass());
DataHandler dataHandler = new DataHandler(new FileDataSource(logo.getFile()));

given(mimeContainer.convertToXopPackage()).willReturn(true);
byte[] bytes = FileCopyUtils.copyToByteArray(logo.getInputStream());
BinaryObject object = new BinaryObject(bytes, dataHandler);
StringWriter writer = new StringWriter();
marshaller.marshal(object, new StreamResult(writer), mimeContainer);
String xml = writer.toString();
assertThat(xml).as("No XML written").isNotEmpty();
assertThat(xml).contains("Include");
verify(mimeContainer, times(3)).addAttachment(isA(String.class), isA(DataHandler.class));
}

@Test // spring-ws#1030: schema still rejects content that does not match when MTOM is enabled
void marshalAttachmentsWithSchemaRejectsInvalidGraph() throws Exception {
marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(BinaryObject.class);
marshaller.setMtomEnabled(true);
// flight.xsd does not describe BinaryObject — logical validation must still fail before XOP
marshaller.setSchema(new FileSystemResource("src/test/schema/flight.xsd"));
marshaller.afterPropertiesSet();
MimeContainer mimeContainer = mock();

Resource logo = new ClassPathResource("spring-ws.png", getClass());
DataHandler dataHandler = new DataHandler(new FileDataSource(logo.getFile()));
given(mimeContainer.convertToXopPackage()).willReturn(true);
byte[] bytes = FileCopyUtils.copyToByteArray(logo.getInputStream());
BinaryObject object = new BinaryObject(bytes, dataHandler);

assertThatExceptionOfType(XmlMappingException.class).isThrownBy(() ->
marshaller.marshal(object, new StreamResult(new StringWriter()), mimeContainer));
verify(mimeContainer, times(0)).addAttachment(isA(String.class), isA(DataHandler.class));
}

@Test // SPR-10714
void marshalAWrappedObjectHoldingAnXmlElementDeclElement() throws Exception {
marshaller = new Jaxb2Marshaller();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://springframework.org/spring-ws"
xmlns:tns="http://springframework.org/spring-ws"
elementFormDefault="qualified">

<xs:element name="binaryObject" type="tns:binaryObjectType"/>

<xs:complexType name="binaryObjectType">
<xs:sequence>
<xs:element name="bytes" type="xs:base64Binary" minOccurs="0"/>
<xs:element name="dataHandler" type="xs:base64Binary" minOccurs="0"/>
<!-- swaRef is a simple URI type; treat as string for validation -->
<xs:element name="swaDataHandler" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>