diff --git a/spring-ws-core/build.gradle b/spring-ws-core/build.gradle index 0539d210..abb0b9b4 100644 --- a/spring-ws-core/build.gradle +++ b/spring-ws-core/build.gradle @@ -23,6 +23,15 @@ dependencies { exclude(group: "commons-logging", module: "commons-logging") } optional("org.apache.httpcomponents.client5:httpclient5") + optional("org.apache.ws.commons.axiom:axiom-impl") { + exclude(group: "commons-logging", module: "commons-logging") + } + optional("org.apache.ws.commons.axiom:axiom-compat") { + exclude(group: "commons-logging", module: "commons-logging") + } + optional("org.apache.ws.commons.axiom:axiom-legacy-attachments") { + exclude(group: "commons-logging", module: "commons-logging") + } optional("org.apache.ws.xmlschema:xmlschema-core") optional("org.dom4j:dom4j") optional("org.jdom:jdom2") diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AbstractPayload.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AbstractPayload.java new file mode 100644 index 00000000..0834911a --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AbstractPayload.java @@ -0,0 +1,90 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPFactory; + +import org.springframework.util.Assert; +import org.springframework.util.xml.StaxUtils; +import org.springframework.ws.soap.axiom.support.AxiomUtils; + +/** + * Abstract base class for {@link Payload} implementations. + * + * @author Arjen Poutsma + * @since 2.0 + */ +abstract class AbstractPayload extends Payload { + + private final SOAPBody axiomBody; + + private final SOAPFactory axiomFactory; + + protected AbstractPayload(SOAPBody axiomBody, SOAPFactory axiomFactory) { + Assert.notNull(axiomBody, "'axiomBody' must not be null"); + Assert.notNull(axiomFactory, "'axiomFactory' must not be null"); + this.axiomBody = axiomBody; + this.axiomFactory = axiomFactory; + } + + @Override + public final Source getSource() { + try { + OMElement payloadElement = getPayloadElement(); + if (payloadElement != null) { + XMLStreamReader streamReader = getStreamReader(payloadElement); + return StaxUtils.createCustomStaxSource(streamReader); + } + else { + return null; + } + } + catch (OMException ex) { + throw new AxiomSoapBodyException(ex); + } + } + + protected abstract XMLStreamReader getStreamReader(OMElement payloadElement); + + @Override + public final Result getResult() { + AxiomUtils.removeContents(getAxiomBody()); + return getResultInternal(); + } + + protected abstract Result getResultInternal(); + + public SOAPFactory getAxiomFactory() { + return this.axiomFactory; + } + + protected SOAPBody getAxiomBody() { + return this.axiomBody; + } + + protected OMElement getPayloadElement() throws OMException { + return getAxiomBody().getFirstElement(); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachment.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachment.java new file mode 100644 index 00000000..b4c941ca --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachment.java @@ -0,0 +1,72 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.activation.DataHandler; + +import org.springframework.util.Assert; +import org.springframework.ws.mime.Attachment; + +/** + * Axiom-specific implementation of {@link org.springframework.ws.mime.Attachment}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomAttachment implements Attachment { + + private final DataHandler dataHandler; + + private final String contentId; + + AxiomAttachment(String contentId, DataHandler dataHandler) { + Assert.notNull(contentId, "contentId must not be null"); + Assert.notNull(dataHandler, "dataHandler must not be null"); + this.contentId = contentId; + this.dataHandler = dataHandler; + } + + @Override + public String getContentId() { + return this.contentId; + } + + @Override + public String getContentType() { + return this.dataHandler.getContentType(); + } + + @Override + public InputStream getInputStream() throws IOException { + return this.dataHandler.getInputStream(); + } + + @Override + public long getSize() { + // Axiom does not support getting the size of attachments. + return -1; + } + + @Override + public DataHandler getDataHandler() { + return this.dataHandler; + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachmentException.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachmentException.java new file mode 100644 index 00000000..5f609294 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachmentException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.mime.AttachmentException; + +/** + * Exception thrown when a {@link AxiomAttachment} could not be accessed. + * + * @author Arjen Poutsma + */ +@SuppressWarnings("serial") +public class AxiomAttachmentException extends AttachmentException { + + public AxiomAttachmentException(String msg) { + super(msg); + } + + public AxiomAttachmentException(String msg, Throwable ex) { + super(msg, ex); + } + + public AxiomAttachmentException(Throwable ex) { + super(ex); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Body.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Body.java new file mode 100644 index 00000000..11877674 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Body.java @@ -0,0 +1,159 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.Locale; + +import javax.xml.namespace.QName; + +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.soap.SOAP11Constants; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPFault; +import org.apache.axiom.soap.SOAPFaultCode; +import org.apache.axiom.soap.SOAPFaultReason; +import org.apache.axiom.soap.SOAPProcessingException; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.soap11.Soap11Body; +import org.springframework.ws.soap.soap11.Soap11Fault; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.Soap11Body}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoap11Body extends AxiomSoapBody implements Soap11Body { + + private final boolean langAttributeOnSoap11FaultString; + + AxiomSoap11Body(SOAPBody axiomBody, SOAPFactory axiomFactory, boolean payloadCaching, + boolean langAttributeOnSoap11FaultString) { + super(axiomBody, axiomFactory, payloadCaching); + this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString; + } + + @Override + public Soap11Fault addMustUnderstandFault(String faultString, Locale locale) { + SOAPFault fault = addStandardFault(SOAP11Constants.FAULT_CODE_MUST_UNDERSTAND, faultString, locale); + return new AxiomSoap11Fault(fault, getAxiomFactory()); + } + + @Override + public Soap11Fault addClientOrSenderFault(String faultString, Locale locale) { + SOAPFault fault = addStandardFault(SOAP11Constants.FAULT_CODE_SENDER, faultString, locale); + return new AxiomSoap11Fault(fault, getAxiomFactory()); + } + + @Override + public Soap11Fault addServerOrReceiverFault(String faultString, Locale locale) { + SOAPFault fault = addStandardFault(SOAP11Constants.FAULT_CODE_RECEIVER, faultString, locale); + return new AxiomSoap11Fault(fault, getAxiomFactory()); + } + + @Override + public Soap11Fault addVersionMismatchFault(String faultString, Locale locale) { + SOAPFault fault = addStandardFault(SOAP11Constants.FAULT_CODE_VERSION_MISMATCH, faultString, locale); + return new AxiomSoap11Fault(fault, getAxiomFactory()); + } + + @Override + public Soap11Fault addFault(QName code, String faultString, Locale faultStringLocale) { + Assert.notNull(code, "No faultCode given"); + Assert.hasLength(faultString, "faultString cannot be empty"); + if (!StringUtils.hasLength(code.getNamespaceURI())) { + throw new IllegalArgumentException( + "A fault code with namespace and local part must be specific for a custom fault code"); + } + if (!this.langAttributeOnSoap11FaultString) { + faultStringLocale = null; + } + try { + AxiomUtils.removeContents(getAxiomBody()); + SOAPFault fault = getAxiomFactory().createSOAPFault(getAxiomBody()); + SOAPFaultCode faultCode = getAxiomFactory().createSOAPFaultCode(fault); + setValueText(code, fault, faultCode); + SOAPFaultReason faultReason = getAxiomFactory().createSOAPFaultReason(fault); + if (faultStringLocale != null) { + addLangAttribute(faultStringLocale, faultReason); + } + faultReason.setText(faultString); + return new AxiomSoap11Fault(fault, getAxiomFactory()); + + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + } + + private void setValueText(QName code, SOAPFault fault, SOAPFaultCode faultCode) { + String prefix = code.getPrefix(); + if (StringUtils.hasLength(code.getNamespaceURI()) && StringUtils.hasLength(prefix)) { + OMNamespace namespace = fault.findNamespaceURI(prefix); + if (namespace == null) { + fault.declareNamespace(code.getNamespaceURI(), prefix); + } + } + else if (StringUtils.hasLength(code.getNamespaceURI())) { + OMNamespace namespace = fault.findNamespace(code.getNamespaceURI(), null); + if (namespace == null) { + namespace = fault.declareNamespace(code.getNamespaceURI(), ""); + } + code = new QName(code.getNamespaceURI(), code.getLocalPart(), namespace.getPrefix()); + } + faultCode.setText(code); + } + + private SOAPFault addStandardFault(String localName, String faultString, Locale locale) { + Assert.notNull(faultString, "No faultString given"); + try { + AxiomUtils.removeContents(getAxiomBody()); + SOAPFault fault = getAxiomFactory().createSOAPFault(getAxiomBody()); + SOAPFaultCode faultCode = getAxiomFactory().createSOAPFaultCode(fault); + faultCode.setText( + new QName(fault.getNamespace().getNamespaceURI(), localName, fault.getNamespace().getPrefix())); + SOAPFaultReason faultReason = getAxiomFactory().createSOAPFaultReason(fault); + if (locale != null) { + addLangAttribute(locale, faultReason); + } + faultReason.setText(faultString); + return fault; + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + } + + private void addLangAttribute(Locale locale, SOAPFaultReason faultReason) { + OMNamespace xmlNamespace = getAxiomFactory().createOMNamespace("http://www.w3.org/XML/1998/namespace", "xml"); + OMAttribute langAttribute = getAxiomFactory().createOMAttribute("lang", xmlNamespace, + AxiomUtils.toLanguage(locale)); + faultReason.addAttribute(langAttribute); + } + + @Override + public Soap11Fault getFault() { + SOAPFault axiomFault = getAxiomBody().getFault(); + return (axiomFault != null) ? new AxiomSoap11Fault(axiomFault, getAxiomFactory()) : null; + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Fault.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Fault.java new file mode 100644 index 00000000..3fbd4bce --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Fault.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.Locale; + +import javax.xml.namespace.QName; + +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPFault; + +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.soap11.Soap11Fault; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.Soap11Fault}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoap11Fault extends AxiomSoapFault implements Soap11Fault { + + AxiomSoap11Fault(SOAPFault axiomFault, SOAPFactory axiomFactory) { + super(axiomFault, axiomFactory); + } + + @Override + public QName getFaultCode() { + return getAxiomFault().getCode().getTextAsQName(); + } + + @Override + public String getFaultStringOrReason() { + if (getAxiomFault().getReason() != null) { + return getAxiomFault().getReason().getText(); + } + return null; + } + + @Override + public Locale getFaultStringLocale() { + if (getAxiomFault().getReason() != null) { + OMAttribute langAttribute = getAxiomFault().getReason() + .getAttribute(new QName("http://www.w3.org/XML/1998/namespace", "lang")); + if (langAttribute != null) { + String xmlLangString = langAttribute.getAttributeValue(); + if (xmlLangString != null) { + return AxiomUtils.toLocale(xmlLangString); + } + + } + } + return null; + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Header.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Header.java new file mode 100644 index 00000000..349b3d3a --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Header.java @@ -0,0 +1,64 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.apache.axiom.soap.RolePlayer; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPHeader; +import org.apache.axiom.soap.SOAPHeaderBlock; + +import org.springframework.util.ObjectUtils; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.soap11.Soap11Header; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.Soap11Header}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoap11Header extends AxiomSoapHeader implements Soap11Header { + + AxiomSoap11Header(SOAPHeader axiomHeader, SOAPFactory axiomFactory) { + super(axiomHeader, axiomFactory); + } + + @Override + @SuppressWarnings("unchecked") + public Iterator examineHeaderElementsToProcess(final String[] actors) { + RolePlayer rolePlayer = null; + if (!ObjectUtils.isEmpty(actors)) { + rolePlayer = new RolePlayer() { + + public List getRoles() { + return Arrays.asList(actors); + } + + public boolean isUltimateDestination() { + return false; + } + }; + } + Iterator result = (Iterator) getAxiomHeader().getHeadersToProcess(rolePlayer); + return new AxiomSoapHeaderElementIterator(result); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Body.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Body.java new file mode 100644 index 00000000..90e9824f --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Body.java @@ -0,0 +1,112 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.Locale; + +import javax.xml.namespace.QName; + +import org.apache.axiom.soap.SOAP12Constants; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPFault; +import org.apache.axiom.soap.SOAPFaultCode; +import org.apache.axiom.soap.SOAPFaultReason; +import org.apache.axiom.soap.SOAPFaultText; +import org.apache.axiom.soap.SOAPFaultValue; +import org.apache.axiom.soap.SOAPProcessingException; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.soap12.Soap12Body; +import org.springframework.ws.soap.soap12.Soap12Fault; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.Soap12Body}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoap12Body extends AxiomSoapBody implements Soap12Body { + + AxiomSoap12Body(SOAPBody axiomBody, SOAPFactory axiomFactory, boolean payloadCaching) { + super(axiomBody, axiomFactory, payloadCaching); + } + + @Override + public Soap12Fault addMustUnderstandFault(String reason, Locale locale) { + Assert.notNull(locale, "No locale given"); + SOAPFault fault = addStandardFault(SOAP12Constants.FAULT_CODE_MUST_UNDERSTAND, reason, locale); + return new AxiomSoap12Fault(fault, getAxiomFactory()); + } + + @Override + public Soap12Fault addClientOrSenderFault(String reason, Locale locale) { + Assert.notNull(locale, "No locale given"); + SOAPFault fault = addStandardFault(SOAP12Constants.FAULT_CODE_SENDER, reason, locale); + return new AxiomSoap12Fault(fault, getAxiomFactory()); + } + + @Override + public Soap12Fault addServerOrReceiverFault(String reason, Locale locale) { + Assert.notNull(locale, "No locale given"); + SOAPFault fault = addStandardFault(SOAP12Constants.FAULT_CODE_RECEIVER, reason, locale); + return new AxiomSoap12Fault(fault, getAxiomFactory()); + } + + @Override + public Soap12Fault addVersionMismatchFault(String reason, Locale locale) { + Assert.notNull(locale, "No locale given"); + SOAPFault fault = addStandardFault(SOAP12Constants.FAULT_CODE_VERSION_MISMATCH, reason, locale); + return new AxiomSoap12Fault(fault, getAxiomFactory()); + } + + @Override + public Soap12Fault addDataEncodingUnknownFault(QName[] subcodes, String reason, Locale locale) { + Assert.notNull(locale, "No locale given"); + SOAPFault fault = addStandardFault(SOAP12Constants.FAULT_CODE_DATA_ENCODING_UNKNOWN, reason, locale); + return new AxiomSoap12Fault(fault, getAxiomFactory()); + } + + private SOAPFault addStandardFault(String localName, String faultStringOrReason, Locale locale) { + Assert.notNull(faultStringOrReason, "No faultStringOrReason given"); + try { + AxiomUtils.removeContents(getAxiomBody()); + SOAPFault fault = getAxiomFactory().createSOAPFault(getAxiomBody()); + SOAPFaultCode code = getAxiomFactory().createSOAPFaultCode(fault); + SOAPFaultValue value = getAxiomFactory().createSOAPFaultValue(code); + value.setText(fault.getNamespace().getPrefix() + ":" + localName); + SOAPFaultReason reason = getAxiomFactory().createSOAPFaultReason(fault); + SOAPFaultText text = getAxiomFactory().createSOAPFaultText(reason); + if (locale != null) { + text.setLang(AxiomUtils.toLanguage(locale)); + } + text.setText(faultStringOrReason); + return fault; + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + } + + @Override + public Soap12Fault getFault() { + SOAPFault axiomFault = getAxiomBody().getFault(); + return (axiomFault != null) ? new AxiomSoap12Fault(axiomFault, getAxiomFactory()) : null; + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Fault.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Fault.java new file mode 100644 index 00000000..0d12b808 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Fault.java @@ -0,0 +1,159 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import javax.xml.namespace.QName; + +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPFault; +import org.apache.axiom.soap.SOAPFaultCode; +import org.apache.axiom.soap.SOAPFaultNode; +import org.apache.axiom.soap.SOAPFaultReason; +import org.apache.axiom.soap.SOAPFaultSubCode; +import org.apache.axiom.soap.SOAPFaultText; +import org.apache.axiom.soap.SOAPFaultValue; +import org.apache.axiom.soap.SOAPProcessingException; + +import org.springframework.util.StringUtils; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.soap12.Soap12Fault; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.Soap12Fault}. + * + * @author Arjen Poutsma + */ +class AxiomSoap12Fault extends AxiomSoapFault implements Soap12Fault { + + AxiomSoap12Fault(SOAPFault axiomFault, SOAPFactory axiomFactory) { + super(axiomFault, axiomFactory); + } + + @Override + public QName getFaultCode() { + return getAxiomFault().getCode().getValue().getTextAsQName(); + } + + @Override + public Iterator getFaultSubcodes() { + List subcodes = new ArrayList(); + SOAPFaultSubCode subcode = getAxiomFault().getCode().getSubCode(); + while (subcode != null) { + subcodes.add(subcode.getValue().getTextAsQName()); + subcode = subcode.getSubCode(); + } + return subcodes.iterator(); + } + + @Override + public void addFaultSubcode(QName subcode) { + SOAPFaultCode faultCode = getAxiomFault().getCode(); + SOAPFaultSubCode faultSubCode = null; + if (faultCode.getSubCode() == null) { + faultSubCode = getAxiomFactory().createSOAPFaultSubCode(faultCode); + } + else { + faultSubCode = faultCode.getSubCode(); + while (true) { + if (faultSubCode.getSubCode() != null) { + faultSubCode = faultSubCode.getSubCode(); + } + else { + faultSubCode = getAxiomFactory().createSOAPFaultSubCode(faultSubCode); + break; + } + } + } + SOAPFaultValue faultValue = getAxiomFactory().createSOAPFaultValue(faultSubCode); + setValueText(subcode, faultValue); + } + + private void setValueText(QName code, SOAPFaultValue faultValue) { + String prefix = code.getPrefix(); + if (StringUtils.hasLength(code.getNamespaceURI()) && StringUtils.hasLength(prefix)) { + OMNamespace namespace = getAxiomFault().findNamespaceURI(prefix); + if (namespace == null) { + getAxiomFault().declareNamespace(code.getNamespaceURI(), prefix); + } + } + else if (StringUtils.hasLength(code.getNamespaceURI())) { + OMNamespace namespace = getAxiomFault().findNamespace(code.getNamespaceURI(), null); + if (namespace == null) { + throw new IllegalArgumentException("Could not resolve namespace of code [" + code + "]"); + } + code = new QName(code.getNamespaceURI(), code.getLocalPart(), namespace.getPrefix()); + } + faultValue.setText(prefix + ":" + code.getLocalPart()); + } + + @Override + public String getFaultNode() { + SOAPFaultNode faultNode = getAxiomFault().getNode(); + if (faultNode == null) { + return null; + } + else { + return faultNode.getFaultNodeValue(); + } + } + + @Override + public void setFaultNode(String uri) { + try { + SOAPFaultNode faultNode = getAxiomFactory().createSOAPFaultNode(getAxiomFault()); + faultNode.setFaultNodeValue(uri); + getAxiomFault().setNode(faultNode); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + } + + @Override + public String getFaultStringOrReason() { + return getFaultReasonText(Locale.getDefault()); + } + + @Override + public String getFaultReasonText(Locale locale) { + SOAPFaultReason faultReason = getAxiomFault().getReason(); + String language = AxiomUtils.toLanguage(locale); + SOAPFaultText faultText = faultReason.getSOAPFaultText(language); + return (faultText != null) ? faultText.getText() : null; + } + + @Override + public void setFaultReasonText(Locale locale, String text) { + SOAPFaultReason faultReason = getAxiomFault().getReason(); + String language = AxiomUtils.toLanguage(locale); + try { + SOAPFaultText faultText = getAxiomFactory().createSOAPFaultText(faultReason); + faultText.setLang(language); + faultText.setText(text); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Header.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Header.java new file mode 100644 index 00000000..3cc97ac9 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Header.java @@ -0,0 +1,104 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import javax.xml.namespace.QName; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.soap.RolePlayer; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPHeader; +import org.apache.axiom.soap.SOAPHeaderBlock; +import org.apache.axiom.soap.SOAPProcessingException; + +import org.springframework.util.ObjectUtils; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapHeaderException; +import org.springframework.ws.soap.soap12.Soap12Header; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.Soap12Header}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoap12Header extends AxiomSoapHeader implements Soap12Header { + + AxiomSoap12Header(SOAPHeader axiomHeader, SOAPFactory axiomFactory) { + super(axiomHeader, axiomFactory); + } + + @Override + public SoapHeaderElement addNotUnderstoodHeaderElement(QName headerName) { + try { + SOAPHeaderBlock notUnderstood = getAxiomHeader().addHeaderBlock("NotUnderstood", + getAxiomHeader().getNamespace()); + OMNamespace headerNamespace = notUnderstood.declareNamespace(headerName.getNamespaceURI(), + headerName.getPrefix()); + notUnderstood.addAttribute("qname", headerNamespace.getPrefix() + ":" + headerName.getLocalPart(), null); + return new AxiomSoapHeaderElement(notUnderstood, getAxiomFactory()); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + @Override + public SoapHeaderElement addUpgradeHeaderElement(String[] supportedSoapUris) { + try { + SOAPHeaderBlock upgrade = getAxiomHeader().addHeaderBlock("Upgrade", getAxiomHeader().getNamespace()); + for (String supportedSoapUri : supportedSoapUris) { + OMElement supportedEnvelope = getAxiomFactory().createOMElement("SupportedEnvelope", + getAxiomHeader().getNamespace(), upgrade); + OMNamespace namespace = supportedEnvelope.declareNamespace(supportedSoapUri, ""); + supportedEnvelope.addAttribute("qname", namespace.getPrefix() + ":Envelope", null); + } + return new AxiomSoapHeaderElement(upgrade, getAxiomFactory()); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + @Override + @SuppressWarnings("unchecked") + public Iterator examineHeaderElementsToProcess(final String[] roles, + final boolean isUltimateDestination) throws SoapHeaderException { + RolePlayer rolePlayer = null; + if (!ObjectUtils.isEmpty(roles)) { + rolePlayer = new RolePlayer() { + + public List getRoles() { + return Arrays.asList(roles); + } + + public boolean isUltimateDestination() { + return isUltimateDestination; + } + }; + } + Iterator result = (Iterator) getAxiomHeader().getHeadersToProcess(rolePlayer); + return new AxiomSoapHeaderElementIterator(result); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBody.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBody.java new file mode 100644 index 00000000..4e9f6b53 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBody.java @@ -0,0 +1,86 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import javax.xml.namespace.QName; +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +import org.apache.axiom.om.OMDataSource; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPFactory; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.stream.StreamingPayload; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.Soap11Body}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +abstract class AxiomSoapBody extends AxiomSoapElement implements SoapBody { + + private final Payload payload; + + protected AxiomSoapBody(SOAPBody axiomBody, SOAPFactory axiomFactory, boolean payloadCaching) { + super(axiomBody, axiomFactory); + if (payloadCaching) { + this.payload = new CachingPayload(axiomBody, axiomFactory); + } + else { + this.payload = new NonCachingPayload(axiomBody, axiomFactory); + } + } + + @Override + public Source getPayloadSource() { + return this.payload.getSource(); + } + + @Override + public Result getPayloadResult() { + return this.payload.getResult(); + } + + @Override + public boolean hasFault() { + return getAxiomBody().hasFault(); + } + + protected final SOAPBody getAxiomBody() { + return (SOAPBody) getAxiomElement(); + } + + public void setStreamingPayload(StreamingPayload payload) { + Assert.notNull(payload, "'payload' must not be null"); + OMDataSource dataSource = new StreamingOMDataSource(payload); + SOAPFactory factory = getAxiomFactory(); + QName name = payload.getName(); + // Ignore the prefix; only the namespace URI and local name are significant + OMElement payloadElement = factory.createOMElement(dataSource, name.getLocalPart(), + factory.createOMNamespace(name.getNamespaceURI(), null)); + + SOAPBody soapBody = getAxiomBody(); + AxiomUtils.removeContents(soapBody); + soapBody.addChild(payloadElement); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBodyException.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBodyException.java new file mode 100644 index 00000000..f4395d49 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBodyException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapEnvelopeException; + +/** + * Exception thrown when an Axiom SOAP body could not be accessed. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +@SuppressWarnings("serial") +public class AxiomSoapBodyException extends SoapEnvelopeException { + + public AxiomSoapBodyException(String msg) { + super(msg); + } + + public AxiomSoapBodyException(String msg, Throwable ex) { + super(msg, ex); + } + + public AxiomSoapBodyException(Throwable ex) { + super(ex); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapElement.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapElement.java new file mode 100644 index 00000000..f61f35c5 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapElement.java @@ -0,0 +1,155 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.transform.Source; + +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.soap.SOAPFactory; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.StaxUtils; +import org.springframework.ws.soap.SoapElement; + +/** + * Axiom-specific version of {@link SoapElement}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoapElement implements SoapElement { + + private final OMElement axiomElement; + + private final SOAPFactory axiomFactory; + + protected AxiomSoapElement(OMElement axiomElement, SOAPFactory axiomFactory) { + Assert.notNull(axiomElement, "axiomElement must not be null"); + Assert.notNull(axiomFactory, "axiomFactory must not be null"); + this.axiomElement = axiomElement; + this.axiomFactory = axiomFactory; + } + + @Override + public final QName getName() { + try { + return this.axiomElement.getQName(); + } + catch (OMException ex) { + throw new AxiomSoapElementException(ex); + } + } + + @Override + public final Source getSource() { + try { + return StaxUtils.createCustomStaxSource(this.axiomElement.getXMLStreamReader()); + } + catch (OMException ex) { + throw new AxiomSoapElementException(ex); + } + } + + @Override + public final void addAttribute(QName name, String value) { + try { + String namespaceUri = name.getNamespaceURI(); + String prefix = name.getPrefix(); + if (StringUtils.hasLength(namespaceUri) && !StringUtils.hasLength(prefix)) { + prefix = null; + } + OMNamespace namespace = getAxiomFactory().createOMNamespace(namespaceUri, prefix); + OMAttribute attribute = getAxiomFactory().createOMAttribute(name.getLocalPart(), namespace, value); + getAxiomElement().addAttribute(attribute); + } + catch (OMException ex) { + throw new AxiomSoapElementException(ex); + } + } + + @Override + public void removeAttribute(QName name) { + try { + OMAttribute attribute = getAxiomElement().getAttribute(name); + if (attribute != null) { + getAxiomElement().removeAttribute(attribute); + } + } + catch (OMException ex) { + throw new AxiomSoapElementException(ex); + } + } + + @Override + public final String getAttributeValue(QName name) { + try { + return getAxiomElement().getAttributeValue(name); + } + catch (OMException ex) { + throw new AxiomSoapElementException(ex); + } + } + + @Override + public final Iterator getAllAttributes() { + try { + List results = new ArrayList(); + for (Iterator iterator = getAxiomElement().getAllAttributes(); iterator.hasNext();) { + OMAttribute attribute = (OMAttribute) iterator.next(); + results.add(attribute.getQName()); + } + return results.iterator(); + + } + catch (OMException ex) { + throw new AxiomSoapElementException(ex); + } + } + + @Override + public void addNamespaceDeclaration(String prefix, String namespaceUri) { + try { + if (StringUtils.hasLength(prefix)) { + getAxiomElement().declareNamespace(namespaceUri, prefix); + } + else { + getAxiomElement().declareDefaultNamespace(namespaceUri); + } + } + catch (OMException ex) { + throw new AxiomSoapElementException(ex); + } + } + + protected final OMElement getAxiomElement() { + return this.axiomElement; + } + + protected final SOAPFactory getAxiomFactory() { + return this.axiomFactory; + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapElementException.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapElementException.java new file mode 100644 index 00000000..84e98a31 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapElementException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapElementException; + +/** + * Axiom-specific {@link SoapElementException}. + * + * @author Arjen Poutsma + */ +@SuppressWarnings("serial") +public class AxiomSoapElementException extends SoapElementException { + + public AxiomSoapElementException(String msg) { + super(msg); + } + + public AxiomSoapElementException(String msg, Throwable ex) { + super(msg, ex); + } + + public AxiomSoapElementException(Throwable ex) { + super(ex); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelope.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelope.java new file mode 100644 index 00000000..22d8da6f --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelope.java @@ -0,0 +1,105 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMException; +import org.apache.axiom.soap.SOAP11Constants; +import org.apache.axiom.soap.SOAP12Constants; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPEnvelope; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPHeader; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.SoapHeader; + +/** + * Axiom-Specific version of {@code org.springframework.ws.soap.SoapEnvelope}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoapEnvelope extends AxiomSoapElement implements SoapEnvelope { + + boolean payloadCaching; + + private AxiomSoapBody body; + + private final boolean langAttributeOnSoap11FaultString; + + AxiomSoapEnvelope(SOAPEnvelope axiomEnvelope, SOAPFactory axiomFactory, boolean payloadCaching, + boolean langAttributeOnSoap11FaultString) { + super(axiomEnvelope, axiomFactory); + this.payloadCaching = payloadCaching; + this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString; + } + + @Override + public SoapHeader getHeader() { + try { + if (getAxiomEnvelope().getHeader() == null) { + return null; + } + else { + SOAPHeader axiomHeader = getAxiomEnvelope().getHeader(); + String namespaceURI = getAxiomEnvelope().getNamespace().getNamespaceURI(); + if (SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(namespaceURI)) { + return new AxiomSoap11Header(axiomHeader, getAxiomFactory()); + } + else if (SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(namespaceURI)) { + return new AxiomSoap12Header(axiomHeader, getAxiomFactory()); + } + else { + throw new AxiomSoapEnvelopeException("Unknown SOAP namespace \"" + namespaceURI + "\""); + } + } + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + @Override + public SoapBody getBody() { + if (this.body == null) { + try { + SOAPBody axiomBody = getAxiomEnvelope().getBody(); + String namespaceURI = getAxiomEnvelope().getNamespace().getNamespaceURI(); + if (SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(namespaceURI)) { + this.body = new AxiomSoap11Body(axiomBody, getAxiomFactory(), this.payloadCaching, + this.langAttributeOnSoap11FaultString); + } + else if (SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(namespaceURI)) { + this.body = new AxiomSoap12Body(axiomBody, getAxiomFactory(), this.payloadCaching); + } + else { + throw new AxiomSoapEnvelopeException("Unknown SOAP namespace \"" + namespaceURI + "\""); + } + } + catch (OMException ex) { + throw new AxiomSoapBodyException(ex); + } + } + return this.body; + } + + protected SOAPEnvelope getAxiomEnvelope() { + return (SOAPEnvelope) getAxiomElement(); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelopeException.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelopeException.java new file mode 100644 index 00000000..a9dda8bb --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelopeException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapEnvelopeException; + +/** + * Axiom-specific {@link SoapEnvelopeException}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +@SuppressWarnings("serial") +public class AxiomSoapEnvelopeException extends SoapEnvelopeException { + + public AxiomSoapEnvelopeException(String msg) { + super(msg); + } + + public AxiomSoapEnvelopeException(String msg, Throwable ex) { + super(msg, ex); + } + + public AxiomSoapEnvelopeException(Throwable ex) { + super(ex); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFault.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFault.java new file mode 100644 index 00000000..7a86d2d6 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFault.java @@ -0,0 +1,86 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMException; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPFault; +import org.apache.axiom.soap.SOAPFaultDetail; +import org.apache.axiom.soap.SOAPFaultRole; +import org.apache.axiom.soap.SOAPProcessingException; + +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.SoapFaultDetail; + +/** + * Axiom implementation of {@link SoapFault}. + * + * @author Arjen Poutsma + */ +abstract class AxiomSoapFault extends AxiomSoapElement implements SoapFault { + + protected AxiomSoapFault(SOAPFault axiomFault, SOAPFactory axiomFactory) { + super(axiomFault, axiomFactory); + } + + @Override + public String getFaultActorOrRole() { + SOAPFaultRole faultRole = getAxiomFault().getRole(); + return (faultRole != null) ? faultRole.getRoleValue() : null; + } + + @Override + public void setFaultActorOrRole(String actor) { + try { + SOAPFaultRole axiomFaultRole = getAxiomFactory().createSOAPFaultRole(getAxiomFault()); + axiomFaultRole.setRoleValue(actor); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + @Override + public SoapFaultDetail getFaultDetail() { + try { + SOAPFaultDetail axiomFaultDetail = getAxiomFault().getDetail(); + return (axiomFaultDetail != null) ? new AxiomSoapFaultDetail(axiomFaultDetail, getAxiomFactory()) : null; + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + @Override + public SoapFaultDetail addFaultDetail() { + try { + SOAPFaultDetail axiomFaultDetail = getAxiomFactory().createSOAPFaultDetail(getAxiomFault()); + return new AxiomSoapFaultDetail(axiomFaultDetail, getAxiomFactory()); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + protected SOAPFault getAxiomFault() { + return (SOAPFault) getAxiomElement(); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetail.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetail.java new file mode 100644 index 00000000..7938a573 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetail.java @@ -0,0 +1,103 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.Iterator; + +import javax.xml.namespace.QName; +import javax.xml.transform.Result; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPFaultDetail; + +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.SoapFaultDetailElement; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.SoapFaultDetail}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoapFaultDetail extends AxiomSoapElement implements SoapFaultDetail { + + AxiomSoapFaultDetail(SOAPFaultDetail axiomFaultDetail, SOAPFactory axiomFactory) { + super(axiomFaultDetail, axiomFactory); + } + + @Override + public SoapFaultDetailElement addFaultDetailElement(QName name) { + try { + OMElement element = getAxiomFactory().createOMElement(name, getAxiomFaultDetail()); + return new AxiomSoapFaultDetailElement(element, getAxiomFactory()); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + @Override + @SuppressWarnings("unchecked") + public Iterator getDetailEntries() { + return new AxiomSoapFaultDetailElementIterator(getAxiomFaultDetail().getChildElements()); + } + + @Override + public Result getResult() { + return getAxiomFaultDetail().getSAXResult(); + } + + protected SOAPFaultDetail getAxiomFaultDetail() { + return (SOAPFaultDetail) getAxiomElement(); + } + + private final class AxiomSoapFaultDetailElementIterator implements Iterator { + + private final Iterator axiomIterator; + + private AxiomSoapFaultDetailElementIterator(Iterator axiomIterator) { + this.axiomIterator = axiomIterator; + } + + @Override + public boolean hasNext() { + return this.axiomIterator.hasNext(); + } + + @Override + public SoapFaultDetailElement next() { + try { + OMElement axiomElement = this.axiomIterator.next(); + return new AxiomSoapFaultDetailElement(axiomElement, getAxiomFactory()); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + @Override + public void remove() { + this.axiomIterator.remove(); + } + + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailElement.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailElement.java new file mode 100644 index 00000000..b24cac28 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailElement.java @@ -0,0 +1,60 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import javax.xml.transform.Result; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.soap.SOAPFactory; + +import org.springframework.ws.soap.SoapFaultDetailElement; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.SoapFaultDetailElement}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +class AxiomSoapFaultDetailElement extends AxiomSoapElement implements SoapFaultDetailElement { + + AxiomSoapFaultDetailElement(OMElement axiomElement, SOAPFactory soapFactory) { + super(axiomElement, soapFactory); + } + + @Override + public Result getResult() { + try { + return getAxiomElement().getSAXResult(); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + @Override + public void addText(String text) { + try { + getAxiomElement().setText(text); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultException.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultException.java new file mode 100644 index 00000000..c85a4266 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapFaultException; + +/** + * Axiom-specific {@link SoapFaultException}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +@SuppressWarnings("serial") +public class AxiomSoapFaultException extends SoapFaultException { + + public AxiomSoapFaultException(String msg) { + super(msg); + } + + public AxiomSoapFaultException(String msg, Throwable ex) { + super(msg, ex); + } + + public AxiomSoapFaultException(Throwable ex) { + super(ex); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeader.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeader.java new file mode 100644 index 00000000..51da6f3f --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeader.java @@ -0,0 +1,145 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.util.Iterator; + +import javax.xml.namespace.QName; +import javax.xml.transform.Result; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPHeader; +import org.apache.axiom.soap.SOAPHeaderBlock; + +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapHeaderException; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.SoapHeader}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +abstract class AxiomSoapHeader extends AxiomSoapElement implements SoapHeader { + + AxiomSoapHeader(SOAPHeader axiomHeader, SOAPFactory axiomFactory) { + super(axiomHeader, axiomFactory); + } + + @Override + public Result getResult() { + return getAxiomHeader().getSAXResult(); + } + + @Override + public SoapHeaderElement addHeaderElement(QName name) { + try { + OMNamespace namespace = getAxiomFactory().createOMNamespace(name.getNamespaceURI(), name.getPrefix()); + SOAPHeaderBlock axiomHeaderBlock = getAxiomHeader().addHeaderBlock(name.getLocalPart(), namespace); + return new AxiomSoapHeaderElement(axiomHeaderBlock, getAxiomFactory()); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + @Override + public void removeHeaderElement(QName name) throws SoapHeaderException { + try { + OMElement element = getAxiomHeader().getFirstChildWithName(name); + if (element != null) { + element.detach(); + } + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + @Override + @SuppressWarnings("unchecked") + public Iterator examineMustUnderstandHeaderElements(String role) { + try { + return new AxiomSoapHeaderElementIterator(getAxiomHeader().examineMustUnderstandHeaderBlocks(role)); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + @Override + @SuppressWarnings("unchecked") + public Iterator examineAllHeaderElements() { + try { + return new AxiomSoapHeaderElementIterator(getAxiomHeader().examineAllHeaderBlocks()); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + @Override + @SuppressWarnings("unchecked") + public Iterator examineHeaderElements(QName name) throws SoapHeaderException { + try { + return new AxiomSoapHeaderElementIterator(getAxiomHeader().getChildrenWithName(name)); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + protected SOAPHeader getAxiomHeader() { + return (SOAPHeader) getAxiomElement(); + } + + protected class AxiomSoapHeaderElementIterator implements Iterator { + + private final Iterator axiomIterator; + + protected AxiomSoapHeaderElementIterator(Iterator axiomIterator) { + this.axiomIterator = axiomIterator; + } + + @Override + public boolean hasNext() { + return this.axiomIterator.hasNext(); + } + + @Override + public SoapHeaderElement next() { + try { + OMElement axiomHeaderBlock = this.axiomIterator.next(); + return new AxiomSoapHeaderElement(axiomHeaderBlock, getAxiomFactory()); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + @Override + public void remove() { + this.axiomIterator.remove(); + } + + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderElement.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderElement.java new file mode 100644 index 00000000..d7912e82 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderElement.java @@ -0,0 +1,84 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import javax.xml.transform.Result; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPHeaderBlock; + +import org.springframework.ws.soap.SoapHeaderElement; + +/** + * Axiom-specific version of {@code org.springframework.ws.soap.SoapHeaderHeaderElement}. + * + * @author Arjen Poutsma + */ +class AxiomSoapHeaderElement extends AxiomSoapElement implements SoapHeaderElement { + + AxiomSoapHeaderElement(OMElement axiomHeaderBlock, SOAPFactory axiomFactory) { + super(axiomHeaderBlock, axiomFactory); + } + + @Override + public String getActorOrRole() { + return getAxiomHeaderBlock().getRole(); + } + + @Override + public void setActorOrRole(String role) { + getAxiomHeaderBlock().setRole(role); + } + + @Override + public boolean getMustUnderstand() { + return getAxiomHeaderBlock().getMustUnderstand(); + } + + @Override + public void setMustUnderstand(boolean mustUnderstand) { + getAxiomHeaderBlock().setMustUnderstand(mustUnderstand); + } + + @Override + public Result getResult() { + try { + return getAxiomHeaderBlock().getSAXResult(); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + + } + + @Override + public String getText() { + return getAxiomHeaderBlock().getText(); + } + + @Override + public void setText(String content) { + getAxiomHeaderBlock().setText(content); + } + + protected SOAPHeaderBlock getAxiomHeaderBlock() { + return (SOAPHeaderBlock) getAxiomElement(); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderException.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderException.java new file mode 100644 index 00000000..7b82bc57 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapHeaderException; + +/** + * Axiom-specific {@link SoapHeaderException}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +@SuppressWarnings("serial") +public class AxiomSoapHeaderException extends SoapHeaderException { + + public AxiomSoapHeaderException(String msg) { + super(msg); + } + + public AxiomSoapHeaderException(String msg, Throwable ex) { + super(msg, ex); + } + + public AxiomSoapHeaderException(Throwable ex) { + super(ex); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java new file mode 100644 index 00000000..07988386 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java @@ -0,0 +1,407 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.Iterator; + +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.dom.DOMSource; + +import jakarta.activation.DataHandler; +import org.apache.axiom.attachments.Attachments; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.om.OMXMLBuilderFactory; +import org.apache.axiom.om.impl.MTOMConstants; +import org.apache.axiom.om.impl.OMMultipartWriter; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPEnvelope; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPMessage; +import org.apache.axiom.soap.SOAPProcessingException; +import org.w3c.dom.Document; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.mime.Attachment; +import org.springframework.ws.soap.AbstractSoapMessage; +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.support.SoapUtils; +import org.springframework.ws.stream.StreamingPayload; +import org.springframework.ws.stream.StreamingWebServiceMessage; +import org.springframework.ws.transport.TransportConstants; +import org.springframework.ws.transport.TransportOutputStream; + +/** + * AXIOM-specific implementation of the {@link SoapMessage} interface. Created via the + * {@link AxiomSoapMessageFactory}, wraps a {@link SOAPMessage}. + * + * @author Arjen Poutsma + * @since 1.0.0 + * @see SOAPMessage + */ +public class AxiomSoapMessage extends AbstractSoapMessage implements StreamingWebServiceMessage { + + private static final String EMPTY_SOAP_ACTION = "\"\""; + + private SOAPMessage axiomMessage; + + private final SOAPFactory axiomFactory; + + private final Attachments attachments; + + private final boolean payloadCaching; + + private AxiomSoapEnvelope envelope; + + private String soapAction; + + private final boolean langAttributeOnSoap11FaultString; + + private OMOutputFormat outputFormat; + + /** + * Create a new, empty {@code AxiomSoapMessage}. + * @param soapFactory the AXIOM SOAPFactory + */ + public AxiomSoapMessage(SOAPFactory soapFactory) { + this(soapFactory, true, true); + } + + /** + * Create a new, empty {@code AxiomSoapMessage}. + * @param soapFactory the AXIOM SOAPFactory + */ + public AxiomSoapMessage(SOAPFactory soapFactory, boolean payloadCaching, boolean langAttributeOnSoap11FaultString) { + SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope(); + this.axiomFactory = soapFactory; + this.axiomMessage = this.axiomFactory.createSOAPMessage(); + this.axiomMessage.setSOAPEnvelope(soapEnvelope); + this.attachments = new Attachments(); + this.payloadCaching = payloadCaching; + this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString; + this.soapAction = EMPTY_SOAP_ACTION; + } + + /** + * Create a new {@code AxiomSoapMessage} based on the given AXIOM {@code SOAPMessage}. + * @param soapMessage the AXIOM SOAPMessage + * @param soapAction the value of the SOAP Action header + * @param payloadCaching whether the contents of the SOAP body should be cached or not + */ + public AxiomSoapMessage(SOAPMessage soapMessage, String soapAction, boolean payloadCaching, + boolean langAttributeOnSoap11FaultString) { + this(soapMessage, new Attachments(), soapAction, payloadCaching, langAttributeOnSoap11FaultString); + } + + /** + * Create a new {@code AxiomSoapMessage} based on the given AXIOM {@code SOAPMessage} + * and attachments. + * @param soapMessage the AXIOM SOAPMessage + * @param attachments the attachments + * @param soapAction the value of the SOAP Action header + * @param payloadCaching whether the contents of the SOAP body should be cached or not + */ + public AxiomSoapMessage(SOAPMessage soapMessage, Attachments attachments, String soapAction, boolean payloadCaching, + boolean langAttributeOnSoap11FaultString) { + Assert.notNull(soapMessage, "'soapMessage' must not be null"); + Assert.notNull(attachments, "'attachments' must not be null"); + this.axiomMessage = soapMessage; + this.axiomFactory = (SOAPFactory) soapMessage.getSOAPEnvelope().getOMFactory(); + this.attachments = attachments; + if (!StringUtils.hasLength(soapAction)) { + soapAction = EMPTY_SOAP_ACTION; + } + this.soapAction = soapAction; + this.payloadCaching = payloadCaching; + this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString; + } + + /** + * Return the AXIOM {@code SOAPMessage} that this {@code AxiomSoapMessage} is based + * on. + */ + public final SOAPMessage getAxiomMessage() { + return this.axiomMessage; + } + + /** + * Sets the AXIOM {@code SOAPMessage} that this {@code AxiomSoapMessage} is based on. + *

+ * Calling this method also clears the SOAP Action property. + */ + public final void setAxiomMessage(SOAPMessage axiomMessage) { + Assert.notNull(axiomMessage, "'axiomMessage' must not be null"); + this.axiomMessage = axiomMessage; + this.envelope = null; + this.soapAction = EMPTY_SOAP_ACTION; + } + + /** + * Sets the {@link OMOutputFormat} to be used when writing the message. + * @see #writeTo(java.io.OutputStream) + */ + public void setOutputFormat(OMOutputFormat outputFormat) { + this.outputFormat = outputFormat; + } + + @Override + public void setStreamingPayload(StreamingPayload payload) { + AxiomSoapBody soapBody = (AxiomSoapBody) getSoapBody(); + soapBody.setStreamingPayload(payload); + } + + @Override + public SoapEnvelope getEnvelope() { + if (this.envelope == null) { + try { + this.envelope = new AxiomSoapEnvelope(this.axiomMessage.getSOAPEnvelope(), this.axiomFactory, + this.payloadCaching, this.langAttributeOnSoap11FaultString); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapEnvelopeException(ex); + } + } + return this.envelope; + } + + @Override + public String getSoapAction() { + return this.soapAction; + } + + @Override + public void setSoapAction(String soapAction) { + soapAction = SoapUtils.escapeAction(soapAction); + this.soapAction = soapAction; + } + + @Override + public Document getDocument() { + return AxiomUtils.toDocument(this.axiomMessage.getSOAPEnvelope()); + } + + @Override + public void setDocument(Document document) { + // save the Soap Action + String soapAction = getSoapAction(); + // replace the Axiom message + setAxiomMessage( + OMXMLBuilderFactory.createSOAPModelBuilder(this.axiomFactory.getMetaFactory(), new DOMSource(document)) + .getSOAPMessage()); + // restore the Soap Action + setSoapAction(soapAction); + } + + @Override + public boolean isXopPackage() { + try { + return MTOMConstants.MTOM_TYPE.equals(this.attachments.getAttachmentSpecType()); + } + catch (OMException ex) { + return false; + } + catch (NullPointerException ex) { + // gotta love Axis2 + return false; + } + } + + @Override + public boolean convertToXopPackage() { + return false; + } + + @Override + public Attachment getAttachment(String contentId) { + Assert.hasLength(contentId, "contentId must not be empty"); + if (contentId.startsWith("<") && contentId.endsWith(">")) { + contentId = contentId.substring(1, contentId.length() - 1); + } + DataHandler dataHandler = this.attachments.getDataHandler(contentId); + return (dataHandler != null) ? new AxiomAttachment(contentId, dataHandler) : null; + } + + @Override + public Iterator getAttachments() { + return new AxiomAttachmentIterator(); + } + + @Override + public Attachment addAttachment(String contentId, DataHandler dataHandler) { + Assert.hasLength(contentId, "contentId must not be empty"); + Assert.notNull(dataHandler, "dataHandler must not be null"); + this.attachments.addDataHandler(contentId, dataHandler); + return new AxiomAttachment(contentId, dataHandler); + } + + @Override + public void writeTo(OutputStream outputStream) throws IOException { + try { + + OMOutputFormat outputFormat = getOutputFormat(); + if (outputStream instanceof TransportOutputStream) { + TransportOutputStream transportOutputStream = (TransportOutputStream) outputStream; + String contentType = outputFormat.getContentType(); + if (!(outputFormat.isDoingSWA() || outputFormat.isOptimized())) { + String charsetEncoding = this.axiomMessage.getCharsetEncoding(); + contentType += "; charset=" + charsetEncoding; + } + SoapVersion version = getVersion(); + if (SoapVersion.SOAP_11 == version) { + transportOutputStream.addHeader(TransportConstants.HEADER_SOAP_ACTION, this.soapAction); + transportOutputStream.addHeader(TransportConstants.HEADER_ACCEPT, version.getContentType()); + } + else if (SoapVersion.SOAP_12 == version) { + contentType += "; action=" + this.soapAction; + transportOutputStream.addHeader(TransportConstants.HEADER_ACCEPT, version.getContentType()); + } + transportOutputStream.addHeader(TransportConstants.HEADER_CONTENT_TYPE, contentType); + + } + if (!(outputFormat.isOptimized()) & outputFormat.isDoingSWA()) { + writeSwAMessage(outputStream, outputFormat); + } + else { + if (this.payloadCaching) { + this.axiomMessage.serialize(outputStream, outputFormat); + } + else { + this.axiomMessage.serializeAndConsume(outputStream, outputFormat); + } + } + outputStream.flush(); + } + catch (XMLStreamException ex) { + throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex); + } + catch (OMException ex) { + throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex); + } + } + + private OMOutputFormat getOutputFormat() { + if (this.outputFormat != null) { + return this.outputFormat; + } + else { + String charsetEncoding = this.axiomMessage.getCharsetEncoding(); + + OMOutputFormat outputFormat = new OMOutputFormat(); + outputFormat.setCharSetEncoding(charsetEncoding); + outputFormat.setSOAP11(getVersion() == SoapVersion.SOAP_11); + if (isXopPackage()) { + outputFormat.setDoOptimize(true); + } + else if (!this.attachments.getContentIDSet().isEmpty()) { + outputFormat.setDoingSWA(true); + } + return outputFormat; + } + } + + private void writeSwAMessage(OutputStream outputStream, OMOutputFormat format) + throws XMLStreamException, UnsupportedEncodingException { + StringWriter writer = new StringWriter(); + SOAPEnvelope envelope = this.axiomMessage.getSOAPEnvelope(); + if (this.payloadCaching) { + envelope.serialize(writer, format); + } + else { + envelope.serializeAndConsume(writer, format); + } + + try { + OMMultipartWriter mpw = new OMMultipartWriter(outputStream, format); + + Writer rootPartWriter = new OutputStreamWriter(mpw.writeRootPart(), format.getCharSetEncoding()); + rootPartWriter.write(writer.toString()); + rootPartWriter.close(); + + // Get the collection of ids associated with the attachments + for (String id : this.attachments.getAllContentIDs()) { + mpw.writePart(this.attachments.getBlob(id), id); + } + + mpw.complete(); + } + catch (IOException ex) { + throw new OMException("Error writing SwA message", ex); + } + } + + public String toString() { + StringBuilder builder = new StringBuilder("AxiomSoapMessage"); + if (this.payloadCaching) { + try { + SOAPEnvelope envelope = this.axiomMessage.getSOAPEnvelope(); + if (envelope != null) { + SOAPBody body = envelope.getBody(); + if (body != null) { + OMElement bodyElement = body.getFirstElement(); + if (bodyElement != null) { + builder.append(' '); + builder.append(bodyElement.getQName()); + } + } + } + } + catch (OMException ex) { + // ignore + } + } + return builder.toString(); + } + + private final class AxiomAttachmentIterator implements Iterator { + + private final Iterator iterator; + + private AxiomAttachmentIterator() { + this.iterator = AxiomSoapMessage.this.attachments.getContentIDSet().iterator(); + } + + @Override + public boolean hasNext() { + return this.iterator.hasNext(); + } + + @Override + public Attachment next() { + String contentId = this.iterator.next(); + DataHandler dataHandler = AxiomSoapMessage.this.attachments.getDataHandler(contentId); + return new AxiomAttachment(contentId, dataHandler); + } + + @Override + public void remove() { + this.iterator.remove(); + } + + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageCreationException.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageCreationException.java new file mode 100644 index 00000000..c2a68866 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageCreationException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapMessageCreationException; + +/** + * Axiom-specific {@link SoapMessageCreationException}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +@SuppressWarnings("serial") +public class AxiomSoapMessageCreationException extends SoapMessageCreationException { + + public AxiomSoapMessageCreationException(String msg) { + super(msg); + } + + public AxiomSoapMessageCreationException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageException.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageException.java new file mode 100644 index 00000000..19e9011e --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapMessageException; + +/** + * Axiom-specific {@link SoapMessageException}. + * + * @author Arjen Poutsma + * @since 1.0.0 + */ +@SuppressWarnings("serial") +public class AxiomSoapMessageException extends SoapMessageException { + + public AxiomSoapMessageException(String msg) { + super(msg); + } + + public AxiomSoapMessageException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageFactory.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageFactory.java new file mode 100644 index 00000000..82b6352b --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageFactory.java @@ -0,0 +1,407 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.Locale; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.axiom.attachments.Attachments; +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMException; +import org.apache.axiom.om.impl.MTOMConstants; +import org.apache.axiom.soap.SOAP11Constants; +import org.apache.axiom.soap.SOAP11Version; +import org.apache.axiom.soap.SOAP12Constants; +import org.apache.axiom.soap.SOAP12Version; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPMessage; +import org.apache.axiom.soap.SOAPModelBuilder; +import org.apache.axiom.soap.impl.builder.MTOMStAXSOAPModelBuilder; +import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor; +import org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping; +import org.springframework.ws.soap.SoapMessageFactory; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor; +import org.springframework.ws.soap.server.endpoint.mapping.SoapActionAnnotationMethodEndpointMapping; +import org.springframework.ws.soap.support.SoapUtils; +import org.springframework.ws.transport.TransportConstants; +import org.springframework.ws.transport.TransportInputStream; +import org.springframework.xml.XMLInputFactoryUtils; + +/** + * Axiom-specific implementation of the + * {@link org.springframework.ws.WebServiceMessageFactory WebServiceMessageFactory} + * interface. Creates {@link org.springframework.ws.soap.axiom.AxiomSoapMessage + * AxiomSoapMessages}. + *

+ * To increase reading performance on the SOAP request created by this message factory, + * you can set the {@link #setPayloadCaching(boolean) payloadCaching} property to + * {@code false} (default is {@code true}). This this will read the contents of the body + * directly from the stream. However, when this setting is enabled, the payload + * can only be read once. This means that any endpoint mappings or interceptors + * which are based on the message payload (such as the + * {@link PayloadRootAnnotationMethodEndpointMapping}, the + * {@link PayloadValidatingInterceptor}, or the {@link PayloadLoggingInterceptor}) cannot + * be used. Instead, use an endpoint mapping that does not consume the payload (i.e. the + * {@link SoapActionAnnotationMethodEndpointMapping}). + *

+ * Additionally, this message factory can cache large attachments to disk by setting the + * {@link #setAttachmentCaching(boolean) attachmentCaching} property to {@code true} + * (default is {@code false}). Optionally, the location where attachments are stored can + * be defined via the {@link #setAttachmentCacheDir(File) attachmentCacheDir} property + * (defaults to the system temp file path). + *

+ * Mostly derived from {@code org.apache.axis2.transport.http.HTTPTransportUtils} and + * {@code org.apache.axis2.transport.TransportUtils}, which we cannot use since they are + * not part of the Axiom distribution. + * + * @author Arjen Poutsma + * @author Andreas Veithen + * @since 1.0.0 + * @see AxiomSoapMessage + * @see #setPayloadCaching(boolean) + */ +public class AxiomSoapMessageFactory implements SoapMessageFactory, InitializingBean { + + private static final String CHARSET_PARAMETER = "charset"; + + private static final String DEFAULT_CHARSET_ENCODING = "UTF-8"; + + private static final String MULTI_PART_RELATED_CONTENT_TYPE = "multipart/related"; + + private static final Log logger = LogFactory.getLog(AxiomSoapMessageFactory.class); + + private XMLInputFactory inputFactory; + + private boolean payloadCaching = true; + + private boolean attachmentCaching = false; + + private File attachmentCacheDir; + + private int attachmentCacheThreshold = 4096; + + // use SOAP 1.1 by default + private SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory(); + + private boolean langAttributeOnSoap11FaultString = true; + + private boolean replacingEntityReferences = false; + + private boolean supportingExternalEntities = false; + + /** + * Indicates whether the SOAP Body payload should be cached or not. Default is + * {@code true}. + *

+ * Setting this to {@code false} will increase performance, but also result in the + * fact that the message payload can only be read once. + */ + public void setPayloadCaching(boolean payloadCaching) { + this.payloadCaching = payloadCaching; + } + + /** + * Indicates whether SOAP attachments should be cached or not. Default is + * {@code false}. + *

+ * Setting this to {@code true} will cause Axiom to store larger attachments on disk, + * rather than in memory. This decreases memory consumption, but decreases + * performance. + */ + public void setAttachmentCaching(boolean attachmentCaching) { + this.attachmentCaching = attachmentCaching; + } + + /** + * Sets the directory where SOAP attachments will be stored. Only used when + * {@link #setAttachmentCaching(boolean) attachmentCaching} is set to {@code true}. + *

+ * The parameter should be an existing, writable directory. This property defaults to + * the temporary directory of the operating system (i.e. the value of the + * {@code java.io.tmpdir} system property). + */ + public void setAttachmentCacheDir(File attachmentCacheDir) { + Assert.notNull(attachmentCacheDir, "'attachmentCacheDir' must not be null"); + Assert.isTrue(attachmentCacheDir.isDirectory(), "'attachmentCacheDir' must be a directory"); + Assert.isTrue(attachmentCacheDir.canWrite(), "'attachmentCacheDir' must be writable"); + this.attachmentCacheDir = attachmentCacheDir; + } + + /** + * Sets the threshold for attachments caching, in bytes. Attachments larger than this + * threshold will be cached in the {@link #setAttachmentCacheDir(File) attachment + * cache directory}. Only used when {@link #setAttachmentCaching(boolean) + * attachmentCaching} is set to {@code true}. + *

+ * Defaults to 4096 bytes (i.e. 4 kilobytes). + */ + public void setAttachmentCacheThreshold(int attachmentCacheThreshold) { + Assert.isTrue(attachmentCacheThreshold > 0, "'attachmentCacheThreshold' must be larger than 0"); + this.attachmentCacheThreshold = attachmentCacheThreshold; + } + + @Override + public void setSoapVersion(SoapVersion version) { + if (SoapVersion.SOAP_11 == version) { + this.soapFactory = OMAbstractFactory.getSOAP11Factory(); + } + else if (SoapVersion.SOAP_12 == version) { + this.soapFactory = OMAbstractFactory.getSOAP12Factory(); + } + else { + throw new IllegalArgumentException( + "Invalid version [" + version + "]. " + "Expected the SOAP_11 or SOAP_12 constant"); + } + } + + /** + * Defines whether a {@code xml:lang} attribute should be set on SOAP 1.1 + * {@code } elements. + *

+ * The default is {@code true}, to comply with WS-I, but this flag can be set to + * {@code false} to the older W3C SOAP 1.1 specification. + * @see WS-I Basic + * Profile 1.1 + */ + public void setLangAttributeOnSoap11FaultString(boolean langAttributeOnSoap11FaultString) { + this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString; + } + + /** + * Sets whether internal entity references should be replaced with their replacement + * text and report them as characters. + * @see XMLInputFactory#IS_REPLACING_ENTITY_REFERENCES + */ + public void setReplacingEntityReferences(boolean replacingEntityReferences) { + this.replacingEntityReferences = replacingEntityReferences; + } + + /** + * Sets whether external parsed entities should be resolved. + * @see XMLInputFactory#IS_SUPPORTING_EXTERNAL_ENTITIES + */ + public void setSupportingExternalEntities(boolean supportingExternalEntities) { + this.supportingExternalEntities = supportingExternalEntities; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (logger.isInfoEnabled()) { + logger.info(this.payloadCaching ? "Enabled payload caching" : "Disabled payload caching"); + } + if (this.attachmentCacheDir == null) { + String tempDir = System.getProperty("java.io.tmpdir"); + setAttachmentCacheDir(new File(tempDir)); + } + this.inputFactory = createXmlInputFactory(); + } + + @Override + public AxiomSoapMessage createWebServiceMessage() { + return new AxiomSoapMessage(this.soapFactory, this.payloadCaching, this.langAttributeOnSoap11FaultString); + } + + @Override + public AxiomSoapMessage createWebServiceMessage(InputStream inputStream) throws IOException { + Assert.isInstanceOf(TransportInputStream.class, inputStream, + "AxiomSoapMessageFactory requires a TransportInputStream"); + if (this.inputFactory == null) { + this.inputFactory = createXmlInputFactory(); + } + TransportInputStream transportInputStream = (TransportInputStream) inputStream; + String contentType = getHeaderValue(transportInputStream, TransportConstants.HEADER_CONTENT_TYPE); + if (!StringUtils.hasLength(contentType)) { + if (logger.isDebugEnabled()) { + logger.debug("TransportInputStream has no Content-Type header; defaulting to \"" + + this.soapFactory.getSOAPVersion().getMediaType() + "\""); + } + contentType = this.soapFactory.getSOAPVersion().getMediaType().toString(); + } + String soapAction = getHeaderValue(transportInputStream, TransportConstants.HEADER_SOAP_ACTION); + if (!StringUtils.hasLength(soapAction)) { + soapAction = SoapUtils.extractActionFromContentType(contentType); + } + try { + if (isMultiPartRelated(contentType)) { + return createMultiPartAxiomSoapMessage(inputStream, contentType, soapAction); + } + else { + return createAxiomSoapMessage(inputStream, contentType, soapAction); + } + } + catch (XMLStreamException ex) { + throw new AxiomSoapMessageCreationException("Could not parse request: " + ex.getMessage(), ex); + } + catch (OMException ex) { + throw new AxiomSoapMessageCreationException("Could not create message: " + ex.getMessage(), ex); + } + } + + private String getHeaderValue(TransportInputStream transportInputStream, String header) throws IOException { + String contentType = null; + Iterator iterator = transportInputStream.getHeaders(header); + if (iterator.hasNext()) { + contentType = iterator.next(); + } + return contentType; + } + + private boolean isMultiPartRelated(String contentType) { + contentType = contentType.toLowerCase(Locale.ENGLISH); + return contentType.contains(MULTI_PART_RELATED_CONTENT_TYPE); + } + + @SuppressWarnings("deprecation") + /** Creates an AxiomSoapMessage without attachments. */ + private AxiomSoapMessage createAxiomSoapMessage(InputStream inputStream, String contentType, String soapAction) + throws XMLStreamException { + XMLStreamReader reader = this.inputFactory.createXMLStreamReader(inputStream, getCharSetEncoding(contentType)); + String envelopeNamespace = getSoapEnvelopeNamespace(contentType); + SOAPModelBuilder builder = new StAXSOAPModelBuilder(reader, this.soapFactory, envelopeNamespace); + SOAPMessage soapMessage = builder.getSOAPMessage(); + return new AxiomSoapMessage(soapMessage, soapAction, this.payloadCaching, + this.langAttributeOnSoap11FaultString); + } + + @SuppressWarnings("deprecation") + /** Creates an AxiomSoapMessage with attachments. */ + private AxiomSoapMessage createMultiPartAxiomSoapMessage(InputStream inputStream, String contentType, + String soapAction) throws XMLStreamException { + Attachments attachments = new Attachments(inputStream, contentType, this.attachmentCaching, + this.attachmentCacheDir.getAbsolutePath(), Integer.toString(this.attachmentCacheThreshold)); + XMLStreamReader reader = this.inputFactory.createXMLStreamReader(attachments.getRootPartInputStream(), + getCharSetEncoding(attachments.getRootPartContentType())); + SOAPModelBuilder builder; + String envelopeNamespace = getSoapEnvelopeNamespace(contentType); + if (MTOMConstants.SWA_TYPE.equals(attachments.getAttachmentSpecType()) + || MTOMConstants.SWA_TYPE_12.equals(attachments.getAttachmentSpecType())) { + builder = new StAXSOAPModelBuilder(reader, this.soapFactory, envelopeNamespace); + } + else if (MTOMConstants.MTOM_TYPE.equals(attachments.getAttachmentSpecType())) { + builder = new MTOMStAXSOAPModelBuilder(reader, attachments, envelopeNamespace); + } + else { + throw new AxiomSoapMessageCreationException( + "Unknown attachment type: [" + attachments.getAttachmentSpecType() + "]"); + } + return new AxiomSoapMessage(builder.getSOAPMessage(), attachments, soapAction, this.payloadCaching, + this.langAttributeOnSoap11FaultString); + } + + private String getSoapEnvelopeNamespace(String contentType) { + if (contentType.contains(SOAP11Constants.SOAP_11_CONTENT_TYPE)) { + return SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI; + } + else if (contentType.contains(SOAP12Constants.SOAP_12_CONTENT_TYPE)) { + return SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI; + } + else { + throw new AxiomSoapMessageCreationException("Unknown content type '" + contentType + "'"); + } + + } + + /** + * Returns the character set from the given content type. Mostly copied + * @return the character set encoding + */ + protected String getCharSetEncoding(String contentType) { + int charSetIdx = contentType.indexOf(CHARSET_PARAMETER); + if (charSetIdx == -1) { + return DEFAULT_CHARSET_ENCODING; + } + int eqIdx = contentType.indexOf("=", charSetIdx); + + int indexOfSemiColon = contentType.indexOf(";", eqIdx); + String value; + + if (indexOfSemiColon > 0) { + value = contentType.substring(eqIdx + 1, indexOfSemiColon); + } + else { + value = contentType.substring(eqIdx + 1, contentType.length()).trim(); + } + if (value.startsWith("\"")) { + value = value.substring(1); + } + if (value.endsWith("\"")) { + return value.substring(0, value.length() - 1); + } + if ("null".equalsIgnoreCase(value)) { + return DEFAULT_CHARSET_ENCODING; + } + else { + return value.trim(); + } + } + + /** + * Create a {@code XMLInputFactory} that this resolver will use to create + * {@link XMLStreamReader} objects. + *

+ * Can be overridden in subclasses, adding further initialization of the factory. The + * resulting factory is cached, so this method will only be called once. + *

+ * By default this method creates a standard {@link XMLInputFactory} and configures it + * based on the {@link #setReplacingEntityReferences(boolean) + * replacingEntityReferences} and {@link #setSupportingExternalEntities(boolean) + * supportingExternalEntities} properties. + * @return the created factory + */ + protected XMLInputFactory createXmlInputFactory() { + XMLInputFactory inputFactory = XMLInputFactoryUtils.newInstance(); + inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, this.replacingEntityReferences); + inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, this.supportingExternalEntities); + return inputFactory; + } + + public String toString() { + StringBuilder builder = new StringBuilder("AxiomSoapMessageFactory["); + if (this.soapFactory.getSOAPVersion() == SOAP11Version.getSingleton()) { + builder.append("SOAP 1.1"); + } + else if (this.soapFactory.getSOAPVersion() == SOAP12Version.getSingleton()) { + builder.append("SOAP 1.2"); + } + builder.append(','); + if (this.payloadCaching) { + builder.append("PayloadCaching enabled"); + } + else { + builder.append("PayloadCaching disabled"); + } + builder.append(']'); + return builder.toString(); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/CachingPayload.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/CachingPayload.java new file mode 100644 index 00000000..b451dcfd --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/CachingPayload.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Result; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMXMLStreamReaderConfiguration; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPFactory; + +/** + * Caching payload in Axiom. + * + * @author Arjen Poutsma + * @since 1.5.2 + */ +@SuppressWarnings("Since15") +class CachingPayload extends AbstractPayload { + + CachingPayload(SOAPBody axiomBody, SOAPFactory axiomFactory) { + super(axiomBody, axiomFactory); + } + + @Override + protected XMLStreamReader getStreamReader(OMElement payloadElement) { + OMXMLStreamReaderConfiguration config = new OMXMLStreamReaderConfiguration(); + config.setPreserveNamespaceContext(true); + return payloadElement.getXMLStreamReader(true, config); + } + + @Override + public Result getResultInternal() { + return getAxiomBody().getSAXResult(); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/NonCachingPayload.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/NonCachingPayload.java new file mode 100644 index 00000000..b72cf431 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/NonCachingPayload.java @@ -0,0 +1,297 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.io.ByteArrayOutputStream; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Result; + +import org.apache.axiom.blob.Blobs; +import org.apache.axiom.om.OMDataSource; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.om.ds.BlobOMDataSource; +import org.apache.axiom.om.util.StAXUtils; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPFactory; + +import org.springframework.util.xml.StaxUtils; + +/** + * Non-caching payload in Axiom. + * + * @author Jim Cummings + * @author Arjen Poutsma + * @since 1.5.2 + */ +class NonCachingPayload extends AbstractPayload { + + private static final int BUF_SIZE = 1024; + + NonCachingPayload(SOAPBody axiomBody, SOAPFactory axiomFactory) { + super(axiomBody, axiomFactory); + } + + @Override + public Result getResultInternal() { + return StaxUtils.createCustomStaxResult(new DelegatingStreamWriter()); + } + + @Override + protected XMLStreamReader getStreamReader(OMElement payloadElement) { + return payloadElement.getXMLStreamReaderWithoutCaching(); + } + + private final class DelegatingStreamWriter implements XMLStreamWriter { + + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(BUF_SIZE); + + private final XMLStreamWriter delegate; + + private QName name; + + private String encoding = "UTF-8"; + + private int elementDepth = 0; + + private boolean payloadAdded = false; + + private DelegatingStreamWriter() { + try { + this.delegate = StAXUtils.createXMLStreamWriter(this.baos); + } + catch (XMLStreamException ex) { + throw new AxiomSoapBodyException("Could not determine payload root element", ex); + } + } + + @Override + public void writeStartDocument() throws XMLStreamException { + // ignored + } + + @Override + public void writeStartDocument(String version) throws XMLStreamException { + // ignored + } + + @Override + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + this.encoding = encoding; + } + + @Override + public void writeStartElement(String localName) throws XMLStreamException { + if (this.name == null) { + this.name = new QName(localName); + } + this.elementDepth++; + this.delegate.writeStartElement(localName); + } + + @Override + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + if (this.name == null) { + this.name = new QName(namespaceURI, localName); + } + this.elementDepth++; + this.delegate.writeStartElement(namespaceURI, localName); + } + + @Override + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + if (this.name == null) { + this.name = new QName(namespaceURI, localName, prefix); + } + this.elementDepth++; + this.delegate.writeStartElement(prefix, localName, namespaceURI); + } + + @Override + public void writeEndElement() throws XMLStreamException { + this.elementDepth--; + this.delegate.writeEndElement(); + addPayload(); + } + + private void addPayload() throws XMLStreamException { + if (this.elementDepth <= 0 && !this.payloadAdded) { + this.delegate.flush(); + if (this.baos.size() > 0) { + byte[] buf = this.baos.toByteArray(); + OMDataSource dataSource = new BlobOMDataSource(Blobs.createBlob(buf), this.encoding); + OMNamespace namespace = getAxiomFactory().createOMNamespace(this.name.getNamespaceURI(), + this.name.getPrefix()); + OMElement payloadElement = getAxiomFactory().createOMElement(dataSource, this.name.getLocalPart(), + namespace); + getAxiomBody().addChild(payloadElement); + this.payloadAdded = true; + } + } + } + + @Override + public void writeEmptyElement(String localName) throws XMLStreamException { + if (this.name == null) { + this.name = new QName(localName); + } + this.delegate.writeEmptyElement(localName); + addPayload(); + } + + @Override + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + if (this.name == null) { + this.name = new QName(namespaceURI, localName); + } + this.delegate.writeEmptyElement(namespaceURI, localName); + addPayload(); + } + + @Override + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + if (this.name == null) { + this.name = new QName(namespaceURI, localName, prefix); + } + this.delegate.writeEmptyElement(prefix, localName, namespaceURI); + addPayload(); + } + + @Override + public void writeEndDocument() throws XMLStreamException { + this.elementDepth = 0; + this.delegate.writeEndDocument(); + addPayload(); + } + + // Delegation + + @Override + public void close() throws XMLStreamException { + addPayload(); + this.delegate.close(); + } + + @Override + public void flush() throws XMLStreamException { + this.delegate.flush(); + } + + @Override + public NamespaceContext getNamespaceContext() { + return this.delegate.getNamespaceContext(); + } + + @Override + public String getPrefix(String uri) throws XMLStreamException { + return this.delegate.getPrefix(uri); + } + + @Override + public Object getProperty(String name) throws IllegalArgumentException { + return this.delegate.getProperty(name); + } + + @Override + public void setDefaultNamespace(String uri) throws XMLStreamException { + this.delegate.setDefaultNamespace(uri); + } + + @Override + public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { + this.delegate.setNamespaceContext(context); + } + + @Override + public void setPrefix(String prefix, String uri) throws XMLStreamException { + this.delegate.setPrefix(prefix, uri); + } + + @Override + public void writeAttribute(String localName, String value) throws XMLStreamException { + this.delegate.writeAttribute(localName, value); + } + + @Override + public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { + this.delegate.writeAttribute(namespaceURI, localName, value); + } + + @Override + public void writeAttribute(String prefix, String namespaceURI, String localName, String value) + throws XMLStreamException { + this.delegate.writeAttribute(prefix, namespaceURI, localName, value); + } + + @Override + public void writeCData(String data) throws XMLStreamException { + this.delegate.writeCData(data); + } + + @Override + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + this.delegate.writeCharacters(text, start, len); + } + + @Override + public void writeCharacters(String text) throws XMLStreamException { + this.delegate.writeCharacters(text); + } + + @Override + public void writeComment(String data) throws XMLStreamException { + this.delegate.writeComment(data); + } + + @Override + public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { + this.delegate.writeDefaultNamespace(namespaceURI); + } + + @Override + public void writeDTD(String dtd) throws XMLStreamException { + this.delegate.writeDTD(dtd); + } + + @Override + public void writeEntityRef(String name) throws XMLStreamException { + this.delegate.writeEntityRef(name); + } + + @Override + public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { + this.delegate.writeNamespace(prefix, namespaceURI); + } + + @Override + public void writeProcessingInstruction(String target) throws XMLStreamException { + this.delegate.writeProcessingInstruction(target); + } + + @Override + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + this.delegate.writeProcessingInstruction(target, data); + } + + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/Payload.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/Payload.java new file mode 100644 index 00000000..677b1f08 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/Payload.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +/** + * Defines the contract for payloads in Axiom. + * + * @author Arjen Poutsma + * @since 1.5.2 + */ +abstract class Payload { + + /** + * Returns the source of the payload. + * @return the source of the payload + */ + public abstract Source getSource(); + + /** + * Returns the result of the payload. + * @return the result of the payload + */ + public abstract Result getResult(); + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/StreamingOMDataSource.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/StreamingOMDataSource.java new file mode 100644 index 00000000..737d445c --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/StreamingOMDataSource.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.axiom.om.OMDataSource; +import org.apache.axiom.om.ds.AbstractPushOMDataSource; + +import org.springframework.util.Assert; +import org.springframework.ws.stream.StreamingPayload; + +/** + * Implementation of {@link OMDataSource} that wraps a {@link StreamingPayload}. + * + * @author Arjen Poutsma + * @since 2.0 + */ +class StreamingOMDataSource extends AbstractPushOMDataSource { + + private final StreamingPayload payload; + + StreamingOMDataSource(StreamingPayload payload) { + Assert.notNull(payload, "'payload' must not be null"); + this.payload = payload; + } + + @Override + public boolean isDestructiveWrite() { + return false; + } + + @Override + public void serialize(XMLStreamWriter xmlWriter) throws XMLStreamException { + this.payload.writeTo(xmlWriter); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/package-info.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/package-info.java new file mode 100644 index 00000000..a156b9cc --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * AXis Object Model (AXIOM) support for Spring-WS soap message infrastructure. + */ +package org.springframework.ws.soap.axiom; diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/support/AxiomUtils.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/support/AxiomUtils.java new file mode 100644 index 00000000..c8df77a2 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/support/AxiomUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom.support; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Iterator; +import java.util.Locale; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.dom.DOMSource; + +import org.apache.axiom.om.OMContainer; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.om.OMXMLBuilderFactory; +import org.apache.axiom.soap.SOAPEnvelope; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.springframework.util.StringUtils; +import org.springframework.xml.DocumentBuilderFactoryUtils; + +/** + * Collection of generic utility methods to work with Axiom. Includes conversion from + * {@code OMNamespace}s to {@code QName}s. + * + * @author Arjen Poutsma + * @author Tareq Abed Rabbo + * @since 1.0.0 + * @see org.apache.axiom.om.OMNamespace + * @see javax.xml.namespace.QName + */ +@SuppressWarnings("Since15") +public abstract class AxiomUtils { + + /** + * Converts a {@code javax.xml.namespace.QName} to a + * {@code org.apache.axiom.om.OMNamespace}. A {@code OMElement} is used to resolve the + * namespace, or to declare a new one. + * @param qName the {@code QName} to convert + * @param resolveElement the element used to resolve the Q + * @return the converted SAAJ Name + * @throws OMException if conversion is unsuccessful + * @throws IllegalArgumentException if {@code qName} is not fully qualified + */ + public static OMNamespace toNamespace(QName qName, OMElement resolveElement) throws OMException { + String prefix = qName.getPrefix(); + if (StringUtils.hasLength(qName.getNamespaceURI()) && StringUtils.hasLength(prefix)) { + return resolveElement.declareNamespace(qName.getNamespaceURI(), prefix); + } + else if (StringUtils.hasLength(qName.getNamespaceURI())) { + // check for existing namespace, and declare if necessary + return resolveElement.declareNamespace(qName.getNamespaceURI(), ""); + } + else { + throw new IllegalArgumentException("qName [" + qName + "] does not contain a namespace"); + } + } + + /** + * Converts the given locale to a {@code xml:lang} string, as used in Axiom Faults. + * @param locale the locale + * @return the language string + */ + public static String toLanguage(Locale locale) { + return locale.toString().replace('_', '-'); + } + + /** + * Converts the given locale to a {@code xml:lang} string, as used in Axiom Faults. + * @param language the language string + * @return the locale + */ + public static Locale toLocale(String language) { + language = language.replace('-', '_'); + return StringUtils.parseLocaleString(language); + } + + /** Removes the contents (i.e. children) of the container. */ + public static void removeContents(OMContainer container) { + for (Iterator iterator = container.getChildren(); iterator.hasNext();) { + iterator.next(); + iterator.remove(); + } + } + + /** + * Converts a given AXIOM {@link org.apache.axiom.soap.SOAPEnvelope} to a + * {@link Document}. + * @param envelope the SOAP envelope to be converted + * @return the converted document + * @throws IllegalArgumentException in case of errors + */ + public static Document toDocument(SOAPEnvelope envelope) { + try { + if (envelope instanceof Element) { + return ((Element) envelope).getOwnerDocument(); + } + else { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + envelope.build(); + envelope.serialize(bos); + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactoryUtils.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + return documentBuilderFactory.newDocumentBuilder().parse(bis); + } + } + catch (Exception ex) { + throw new IllegalArgumentException("Error in converting SOAP Envelope to Document", ex); + } + } + + /** + * Converts a given {@link Document} to an AXIOM + * {@link org.apache.axiom.soap.SOAPEnvelope}. + * @param document the document to be converted + * @return the converted envelope + * @throws IllegalArgumentException in case of errors + */ + public static SOAPEnvelope toEnvelope(Document document) { + return OMXMLBuilderFactory.createSOAPModelBuilder(new DOMSource(document)).getSOAPEnvelope(); + } + +} diff --git a/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/support/package-info.java b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/support/package-info.java new file mode 100644 index 00000000..e491c047 --- /dev/null +++ b/spring-ws-core/src/main/java/org/springframework/ws/soap/axiom/support/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support classes for working with the AXis Object Model (AXIOM). + */ +package org.springframework.ws.soap.axiom.support; diff --git a/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomNonStreamingSoap11WebServiceTemplateIntegrationTest.java b/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomNonStreamingSoap11WebServiceTemplateIntegrationTest.java new file mode 100644 index 00000000..3bf326b8 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomNonStreamingSoap11WebServiceTemplateIntegrationTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.client.core; + +import org.springframework.ws.soap.SoapMessageFactory; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; + +/** + * @author Arjen Poutsma + */ +public class AxiomNonStreamingSoap11WebServiceTemplateIntegrationTest + extends AbstractSoap11WebServiceTemplateIntegrationTest { + + @Override + public SoapMessageFactory createMessageFactory() throws Exception { + return new AxiomSoapMessageFactory(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomNonStreamingSoap12WebServiceTemplateIntegrationTest.java b/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomNonStreamingSoap12WebServiceTemplateIntegrationTest.java new file mode 100644 index 00000000..ae3af6d4 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomNonStreamingSoap12WebServiceTemplateIntegrationTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.client.core; + +import org.springframework.ws.soap.SoapMessageFactory; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; + +/** + * @author Arjen Poutsma + */ +public class AxiomNonStreamingSoap12WebServiceTemplateIntegrationTest + extends AbstractSoap12WebServiceTemplateIntegrationTest { + + @Override + public SoapMessageFactory createMessageFactory() { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setSoapVersion(SoapVersion.SOAP_12); + return messageFactory; + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomStreamingSoap11WebServiceTemplateIntegrationTest.java b/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomStreamingSoap11WebServiceTemplateIntegrationTest.java new file mode 100644 index 00000000..e83a55d8 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomStreamingSoap11WebServiceTemplateIntegrationTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.client.core; + +import org.springframework.ws.soap.SoapMessageFactory; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; + +/** + * @author Arjen Poutsma + */ +public class AxiomStreamingSoap11WebServiceTemplateIntegrationTest + extends AbstractSoap11WebServiceTemplateIntegrationTest { + + @Override + public SoapMessageFactory createMessageFactory() throws Exception { + AxiomSoapMessageFactory axiomFactory = new AxiomSoapMessageFactory(); + axiomFactory.setPayloadCaching(false); + return axiomFactory; + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomStreamingSoap12WebServiceTemplateIntegrationTest.java b/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomStreamingSoap12WebServiceTemplateIntegrationTest.java new file mode 100644 index 00000000..70959a72 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/client/core/AxiomStreamingSoap12WebServiceTemplateIntegrationTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.client.core; + +import org.springframework.ws.soap.SoapMessageFactory; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; + +/** + * @author Arjen Poutsma + */ +public class AxiomStreamingSoap12WebServiceTemplateIntegrationTest + extends AbstractSoap12WebServiceTemplateIntegrationTest { + + @Override + public SoapMessageFactory createMessageFactory() throws Exception { + AxiomSoapMessageFactory axiomFactory = new AxiomSoapMessageFactory(); + axiomFactory.setSoapVersion(SoapVersion.SOAP_12); + axiomFactory.setPayloadCaching(false); + return axiomFactory; + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractMethodArgumentResolverTest.java b/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractMethodArgumentResolverTest.java index b9772f68..33913ae5 100644 --- a/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractMethodArgumentResolverTest.java +++ b/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/AbstractMethodArgumentResolverTest.java @@ -21,11 +21,15 @@ import javax.xml.transform.TransformerException; import jakarta.xml.soap.MessageFactory; import jakarta.xml.soap.SOAPException; import jakarta.xml.soap.SOAPMessage; +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; import org.springframework.ws.MockWebServiceMessage; import org.springframework.ws.MockWebServiceMessageFactory; import org.springframework.ws.context.DefaultMessageContext; import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; import org.springframework.ws.soap.saaj.SaajSoapMessage; import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; import org.springframework.xml.transform.StringSource; @@ -55,22 +59,21 @@ public class AbstractMethodArgumentResolverTest extends TransformerObjectSupport } protected MessageContext createCachingAxiomMessageContext() throws Exception { - - MessageFactory messageFactory = MessageFactory.newInstance(); - SaajSoapMessage request = new SaajSoapMessage(messageFactory.createMessage(), true, messageFactory); + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory, true, false); transform(new StringSource(XML), request.getPayloadResult()); - SaajSoapMessageFactory soapMessageFactory = new SaajSoapMessageFactory(); + AxiomSoapMessageFactory soapMessageFactory = new AxiomSoapMessageFactory(); soapMessageFactory.afterPropertiesSet(); return new DefaultMessageContext(request, soapMessageFactory); } protected MessageContext createNonCachingAxiomMessageContext() throws Exception { - - MessageFactory messageFactory = MessageFactory.newInstance(); - SaajSoapMessage request = new SaajSoapMessage(messageFactory.createMessage(), true, messageFactory); + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory, false, false); transform(new StringSource(XML), request.getPayloadResult()); - SaajSoapMessageFactory soapMessageFactory = new SaajSoapMessageFactory(); + AxiomSoapMessageFactory soapMessageFactory = new AxiomSoapMessageFactory(); + soapMessageFactory.setPayloadCaching(false); soapMessageFactory.afterPropertiesSet(); return new DefaultMessageContext(request, soapMessageFactory); diff --git a/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/jaxb/JaxbElementPayloadMethodProcessorTest.java b/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/jaxb/JaxbElementPayloadMethodProcessorTest.java index a8952b9e..2dbeb082 100644 --- a/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/jaxb/JaxbElementPayloadMethodProcessorTest.java +++ b/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/jaxb/JaxbElementPayloadMethodProcessorTest.java @@ -16,7 +16,10 @@ package org.springframework.ws.server.endpoint.adapter.method.jaxb; +import java.io.ByteArrayOutputStream; + import javax.xml.namespace.QName; +import javax.xml.transform.Transformer; import jakarta.xml.bind.JAXBElement; import jakarta.xml.bind.JAXBException; @@ -34,6 +37,10 @@ import org.springframework.ws.context.DefaultMessageContext; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.endpoint.annotation.RequestPayload; import org.springframework.ws.server.endpoint.annotation.ResponsePayload; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.TransformerFactoryUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -133,6 +140,45 @@ public class JaxbElementPayloadMethodProcessorTest { assertThat(messageContext.hasResponse()).isFalse(); } + @Test + public void handleReturnValueAxiom() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + MessageContext messageContext = new DefaultMessageContext(messageFactory); + + MyType type = new MyType(); + type.setString("Foo"); + JAXBElement element = new JAXBElement<>(new QName("http://springframework.org", "type"), MyType.class, + type); + + this.processor.handleReturnValue(messageContext, this.supportedReturnType, element); + + assertThat(messageContext.hasResponse()).isTrue(); + + AxiomSoapMessage response = (AxiomSoapMessage) messageContext.getResponse(); + + Transformer transformer = TransformerFactoryUtils.newInstance().newTransformer(); + StringResult payloadResult = new StringResult(); + transformer.transform(response.getPayloadSource(), payloadResult); + + XmlAssert.assertThat(payloadResult.toString()) + .and("Foo") + .ignoreWhitespace() + .areIdentical(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + response.writeTo(bos); + String messageResult = bos.toString("UTF-8"); + + XmlAssert.assertThat(messageResult) + .and("" + + "Foo" + + "") + .ignoreWhitespace() + .areIdentical(); + + } + @ResponsePayload public JAXBElement supported(@RequestPayload JAXBElement element) { return element; diff --git a/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/jaxb/XmlRootElementPayloadMethodProcessorTest.java b/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/jaxb/XmlRootElementPayloadMethodProcessorTest.java index 4f38d577..2c88fca3 100644 --- a/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/jaxb/XmlRootElementPayloadMethodProcessorTest.java +++ b/spring-ws-core/src/test/java/org/springframework/ws/server/endpoint/adapter/method/jaxb/XmlRootElementPayloadMethodProcessorTest.java @@ -16,10 +16,12 @@ package org.springframework.ws.server.endpoint.adapter.method.jaxb; +import java.io.ByteArrayOutputStream; import java.io.OutputStream; import javax.xml.transform.Result; import javax.xml.transform.Source; +import javax.xml.transform.Transformer; import javax.xml.transform.sax.SAXSource; import jakarta.xml.bind.JAXBException; @@ -43,7 +45,11 @@ import org.springframework.ws.context.DefaultMessageContext; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.endpoint.annotation.RequestPayload; import org.springframework.ws.server.endpoint.annotation.ResponsePayload; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; import org.springframework.xml.sax.AbstractXmlReader; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.TransformerFactoryUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -208,6 +214,71 @@ public class XmlRootElementPayloadMethodProcessorTest { assertThat(messageContext.hasResponse()).isFalse(); } + @Test + public void handleReturnValueAxiom() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + MessageContext messageContext = new DefaultMessageContext(messageFactory); + + MyRootElement rootElement = new MyRootElement(); + rootElement.setString("Foo"); + + this.processor.handleReturnValue(messageContext, this.rootElementReturnType, rootElement); + + assertThat(messageContext.hasResponse()).isTrue(); + + AxiomSoapMessage response = (AxiomSoapMessage) messageContext.getResponse(); + + Transformer transformer = TransformerFactoryUtils.newInstance().newTransformer(); + StringResult payloadResult = new StringResult(); + transformer.transform(response.getPayloadSource(), payloadResult); + + XmlAssert.assertThat(payloadResult.toString()) + .and("Foo") + .ignoreWhitespace() + .areIdentical(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + response.writeTo(bos); + String messageResult = bos.toString("UTF-8"); + + XmlAssert.assertThat(messageResult) + .and("" + + "Foo" + + "") + .ignoreWhitespace() + .areIdentical(); + + } + + @Test + public void handleReturnValueAxiomNoPayloadCaching() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + MessageContext messageContext = new DefaultMessageContext(messageFactory); + + MyRootElement rootElement = new MyRootElement(); + rootElement.setString("Foo"); + + this.processor.handleReturnValue(messageContext, this.rootElementReturnType, rootElement); + + assertThat(messageContext.hasResponse()).isTrue(); + + AxiomSoapMessage response = (AxiomSoapMessage) messageContext.getResponse(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + response.writeTo(bos); + String messageResult = bos.toString("UTF-8"); + + XmlAssert.assertThat(messageResult) + .and("" + + "Foo" + + "") + .ignoreWhitespace() + .areIdentical(); + } + @ResponsePayload public MyRootElement rootElement(@RequestPayload MyRootElement rootElement) { return rootElement; diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11BodyTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11BodyTest.java new file mode 100644 index 00000000..645208aa --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11BodyTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.junit.jupiter.api.Test; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.soap11.AbstractSoap11BodyTest; +import org.springframework.xml.transform.StringSource; + +public class AxiomSoap11BodyTest extends AbstractSoap11BodyTest { + + @Override + protected SoapBody createSoapBody() { + + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + + return axiomSoapMessage.getSoapBody(); + } + + @Test + public void testPayloadNoCaching() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + messageFactory.setSoapVersion(SoapVersion.SOAP_11); + + AxiomSoapMessage axiomSoapMessage = messageFactory.createWebServiceMessage(); + this.soapBody = axiomSoapMessage.getSoapBody(); + + String payload = ""; + this.transformer.transform(new StringSource(payload), this.soapBody.getPayloadResult()); + assertPayloadEqual(payload); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11EnvelopeTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11EnvelopeTest.java new file mode 100644 index 00000000..705da4b9 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11EnvelopeTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.soap11.AbstractSoap11EnvelopeTest; + +public class AxiomSoap11EnvelopeTest extends AbstractSoap11EnvelopeTest { + + @Override + protected SoapEnvelope createSoapEnvelope() { + + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + + return axiomSoapMessage.getEnvelope(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11HeaderTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11HeaderTest.java new file mode 100644 index 00000000..a2bc8940 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11HeaderTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.soap11.AbstractSoap11HeaderTest; + +public class AxiomSoap11HeaderTest extends AbstractSoap11HeaderTest { + + @Override + protected SoapHeader createSoapHeader() { + + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + + return axiomSoapMessage.getSoapHeader(); + } + + @Override + public void testGetResult() { + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageFactoryTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageFactoryTest.java new file mode 100644 index 00000000..d22de033 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageFactoryTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; + +import org.junit.jupiter.api.Test; +import org.xmlunit.assertj.XmlAssert; + +import org.springframework.ws.InvalidXmlException; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.soap.soap11.AbstractSoap11MessageFactoryTest; +import org.springframework.ws.transport.MockTransportInputStream; +import org.springframework.ws.transport.TransportInputStream; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.TransformerFactoryUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +public class AxiomSoap11MessageFactoryTest extends AbstractSoap11MessageFactoryTest { + + private Transformer transformer; + + @Override + protected WebServiceMessageFactory createMessageFactory() throws Exception { + + this.transformer = TransformerFactoryUtils.newInstance().newTransformer(); + + AxiomSoapMessageFactory factory = new AxiomSoapMessageFactory(); + factory.afterPropertiesSet(); + return factory; + } + + @Override + public void doTestCreateSoapMessageIllFormedXml() { + + // Axiom parses the contents of XML lazily, so it will not throw an + // InvalidXmlException when a message is parsed + throw new InvalidXmlException(null, null); + } + + @Test + public void testGetCharsetEncoding() { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + + assertThat(messageFactory.getCharSetEncoding("text/html; charset=utf-8")).isEqualTo("utf-8"); + assertThat(messageFactory.getCharSetEncoding("application/xop+xml;type=text/xml; charset=utf-8")) + .isEqualTo("utf-8"); + assertThat(messageFactory.getCharSetEncoding("application/xop+xml;type=\"text/xml; charset=utf-8\"")) + .isEqualTo("utf-8"); + } + + @Test + public void testRepetitiveReadCaching() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(true); + messageFactory.afterPropertiesSet(); + + String xml = "" + + "" + + ""; + TransportInputStream tis = new MockTransportInputStream(new ByteArrayInputStream(xml.getBytes())); + WebServiceMessage message = messageFactory.createWebServiceMessage(tis); + + StringResult result = new StringResult(); + this.transformer.transform(message.getPayloadSource(), result); + this.transformer.transform(message.getPayloadSource(), result); + } + + @Test + public void testRepetitiveReadNoCaching() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + messageFactory.afterPropertiesSet(); + + String xml = "" + + "" + + ""; + TransportInputStream tis = new MockTransportInputStream(new ByteArrayInputStream(xml.getBytes())); + WebServiceMessage message = messageFactory.createWebServiceMessage(tis); + + StringResult result = new StringResult(); + this.transformer.transform(message.getPayloadSource(), result); + + try { + this.transformer.transform(message.getPayloadSource(), result); + fail("TransformerException expected"); + } + catch (TransformerException expected) { + // ignore + } + } + + /** + * See http://jira.springframework.org/browse/SWS-502 + */ + @Test + public void testSWS502() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + messageFactory.afterPropertiesSet(); + + String envelope = "" + + "" + + "" + + "" + "" + "true" + + "0" + + "ok]]]]>>" + + "]]>" + "" + + ""; + + InputStream inputStream = new ByteArrayInputStream(envelope.getBytes("UTF-8")); + AxiomSoapMessage message = messageFactory.createWebServiceMessage(new MockTransportInputStream(inputStream)); + + StringResult result = new StringResult(); + this.transformer.transform(message.getPayloadSource(), result); + + String expectedPayload = "" + + "" + + "" + "" + "true" + + "0" + + "ok]]]]>>" + + "]]>" + ""; + + XmlAssert.assertThat(result.toString()).and(expectedPayload).ignoreWhitespace().areIdentical(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageTest.java new file mode 100644 index 00000000..90a4ebbd --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.soap11.AbstractSoap11MessageTest; + +public class AxiomSoap11MessageTest extends AbstractSoap11MessageTest { + + @Override + protected String getNS() { + return "soapenv"; + } + + @Override + protected SoapMessage createSoapMessage() { + + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + return new AxiomSoapMessage(axiomFactory); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11NonCachingBodyTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11NonCachingBodyTest.java new file mode 100644 index 00000000..0f58114e --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11NonCachingBodyTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.soap11.AbstractSoap11BodyTest; + +public class AxiomSoap11NonCachingBodyTest extends AbstractSoap11BodyTest { + + @Override + protected SoapBody createSoapBody() { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + messageFactory.setSoapVersion(SoapVersion.SOAP_11); + + AxiomSoapMessage axiomSoapMessage = messageFactory.createWebServiceMessage(); + return axiomSoapMessage.getSoapBody(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11NonCachingMessageTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11NonCachingMessageTest.java new file mode 100644 index 00000000..90380408 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11NonCachingMessageTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMSourcedElement; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.soap11.AbstractSoap11MessageTest; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AxiomSoap11NonCachingMessageTest extends AbstractSoap11MessageTest { + + @Override + protected String getNS() { + return "soapenv"; + } + + @Override + protected SoapMessage createSoapMessage() { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + messageFactory.setSoapVersion(SoapVersion.SOAP_11); + + return messageFactory.createWebServiceMessage(); + } + + @Override + public void testWriteToTransportOutputStream() throws Exception { + + super.testWriteToTransportOutputStream(); + + SoapBody body = this.soapMessage.getSoapBody(); + OMSourcedElement axiomPayloadEle = (OMSourcedElement) ((AxiomSoapBody) body).getAxiomElement() + .getFirstElement(); + + assertThat(axiomPayloadEle.isExpanded()).isFalse(); + + axiomPayloadEle.getFirstElement(); + + assertThat(axiomPayloadEle.isExpanded()).isTrue(); + assertThat(axiomPayloadEle.getLocalName()).isEqualTo("payload"); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12BodyTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12BodyTest.java new file mode 100644 index 00000000..c46f5e73 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12BodyTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.junit.jupiter.api.Test; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.soap12.AbstractSoap12BodyTest; +import org.springframework.xml.transform.StringSource; + +public class AxiomSoap12BodyTest extends AbstractSoap12BodyTest { + + @Override + protected SoapBody createSoapBody() { + + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP12Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + return axiomSoapMessage.getSoapBody(); + } + + @Test + public void testPayloadNoCaching() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + messageFactory.setSoapVersion(SoapVersion.SOAP_12); + + AxiomSoapMessage axiomSoapMessage = messageFactory.createWebServiceMessage(); + this.soapBody = axiomSoapMessage.getSoapBody(); + + String payload = ""; + this.transformer.transform(new StringSource(payload), this.soapBody.getPayloadResult()); + + assertPayloadEqual(payload); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12EnvelopeTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12EnvelopeTest.java new file mode 100644 index 00000000..309c3d78 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12EnvelopeTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.soap12.AbstractSoap12EnvelopeTest; + +public class AxiomSoap12EnvelopeTest extends AbstractSoap12EnvelopeTest { + + @Override + protected SoapEnvelope createSoapEnvelope() { + + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP12Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + + return axiomSoapMessage.getEnvelope(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12HeaderTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12HeaderTest.java new file mode 100644 index 00000000..9ab4c35e --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12HeaderTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.soap12.AbstractSoap12HeaderTest; + +public class AxiomSoap12HeaderTest extends AbstractSoap12HeaderTest { + + @Override + protected SoapHeader createSoapHeader() { + + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP12Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + + return axiomSoapMessage.getSoapHeader(); + } + + @Override + public void testGetResult() { + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageFactoryTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageFactoryTest.java new file mode 100644 index 00000000..d520a8d8 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageFactoryTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.InvalidXmlException; +import org.springframework.ws.WebServiceMessageFactory; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.soap12.AbstractSoap12MessageFactoryTest; + +public class AxiomSoap12MessageFactoryTest extends AbstractSoap12MessageFactoryTest { + + @Override + protected WebServiceMessageFactory createMessageFactory() throws Exception { + + AxiomSoapMessageFactory factory = new AxiomSoapMessageFactory(); + factory.setSoapVersion(SoapVersion.SOAP_12); + factory.afterPropertiesSet(); + + return factory; + } + + @Override + public void doTestCreateSoapMessageIllFormedXml() { + + // Axiom parses the contents of XML lazily, so it will not throw an + // InvalidXmlException when a message is parsed + throw new InvalidXmlException(null, null); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageTest.java new file mode 100644 index 00000000..3edf43ef --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.soap12.AbstractSoap12MessageTest; + +public class AxiomSoap12MessageTest extends AbstractSoap12MessageTest { + + @Override + protected String getNS() { + return "soapenv"; + } + + @Override + protected SoapMessage createSoapMessage() { + + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP12Factory(); + return new AxiomSoapMessage(axiomFactory); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12NonCachingBodyTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12NonCachingBodyTest.java new file mode 100644 index 00000000..9e914330 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12NonCachingBodyTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.soap12.AbstractSoap12BodyTest; + +public class AxiomSoap12NonCachingBodyTest extends AbstractSoap12BodyTest { + + @Override + protected SoapBody createSoapBody() { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + messageFactory.setSoapVersion(SoapVersion.SOAP_12); + + AxiomSoapMessage axiomSoapMessage = messageFactory.createWebServiceMessage(); + + return axiomSoapMessage.getSoapBody(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12NonCachingMessageTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12NonCachingMessageTest.java new file mode 100644 index 00000000..74a459e2 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12NonCachingMessageTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMSourcedElement; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.soap12.AbstractSoap12MessageTest; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AxiomSoap12NonCachingMessageTest extends AbstractSoap12MessageTest { + + @Override + protected String getNS() { + return "soapenv"; + } + + @Override + protected SoapMessage createSoapMessage() { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(false); + messageFactory.setSoapVersion(SoapVersion.SOAP_12); + + return messageFactory.createWebServiceMessage(); + } + + @Override + public void testWriteToTransportOutputStream() throws Exception { + + super.testWriteToTransportOutputStream(); + + SoapBody body = this.soapMessage.getSoapBody(); + OMSourcedElement axiomPayloadEle = (OMSourcedElement) ((AxiomSoapBody) body).getAxiomElement() + .getFirstElement(); + + assertThat(axiomPayloadEle.isExpanded()).isFalse(); + + axiomPayloadEle.getFirstElement(); + + assertThat(axiomPayloadEle.isExpanded()).isTrue(); + assertThat(axiomPayloadEle.getLocalName()).isEqualTo("payload"); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailTest.java new file mode 100644 index 00000000..824b417d --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.io.StringReader; + +import org.apache.axiom.om.OMXMLBuilderFactory; +import org.apache.axiom.soap.SOAPMessage; +import org.apache.axiom.soap.SOAPModelBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.SoapFaultDetail; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("Since15") +public class AxiomSoapFaultDetailTest { + + private static final String FAILING_FAULT = "\n " + "\n " + + "\n " + "Client\n " + + "Client Error\n " + "\n " + + "\n " + "\n " + + "" + "" + "" + "" + ""; + + private static final String SUCCEEDING_FAULT = "\n " + "\n " + + "\n " + "Client\n " + + "Client Error\n " + "" + + "\n " + "\n " + + "" + "" + "" + "" + ""; + + private AxiomSoapMessage failingMessage; + + private AxiomSoapMessage succeedingMessage; + + @BeforeEach + public void setUp() throws Exception { + + SOAPModelBuilder builder = OMXMLBuilderFactory.createSOAPModelBuilder(new StringReader(FAILING_FAULT)); + SOAPMessage soapMessage = builder.getSOAPMessage(); + + this.failingMessage = new AxiomSoapMessage(soapMessage, null, false, true); + + builder = OMXMLBuilderFactory.createSOAPModelBuilder(new StringReader(SUCCEEDING_FAULT)); + soapMessage = builder.getSOAPMessage(); + + this.succeedingMessage = new AxiomSoapMessage(soapMessage, null, false, true); + + } + + @Test + public void testGetDetailEntriesWorksWithWhitespaceNodes() { + + SoapFault fault = this.failingMessage.getSoapBody().getFault(); + + assertThat(fault).isNotNull(); + assertThat(fault.getFaultDetail()).isNotNull(); + + SoapFaultDetail detail = fault.getFaultDetail(); + + assertThat(detail.getDetailEntries().hasNext()).isTrue(); + + detail.getDetailEntries().next(); + } + + @Test + public void testGetDetailEntriesWorksWithoutWhitespaceNodes() { + + SoapFault fault = this.succeedingMessage.getSoapBody().getFault(); + + assertThat(fault).isNotNull(); + assertThat(fault.getFaultDetail()).isNotNull(); + + SoapFaultDetail detail = fault.getFaultDetail(); + + assertThat(detail.getDetailEntries().hasNext()).isTrue(); + + detail.getDetailEntries().next(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/NonCachingPayloadTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/NonCachingPayloadTest.java new file mode 100644 index 00000000..433b84f8 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/NonCachingPayloadTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom; + +import java.io.StringWriter; + +import javax.xml.stream.XMLStreamWriter; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPBody; +import org.apache.axiom.soap.SOAPFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.util.xml.StaxUtils; + +import static org.xmlunit.assertj.XmlAssert.assertThat; + +@SuppressWarnings("Since15") +public class NonCachingPayloadTest { + + private Payload payload; + + private SOAPBody body; + + @BeforeEach + public final void setUp() { + + SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory(); + this.body = soapFactory.createSOAPBody(); + this.payload = new NonCachingPayload(this.body, soapFactory); + } + + @Test + public void testDelegatingStreamWriter() throws Exception { + + XMLStreamWriter streamWriter = StaxUtils.getXMLStreamWriter(this.payload.getResult()); + + String namespace = "http://springframework.org/spring-ws"; + streamWriter.setDefaultNamespace(namespace); + streamWriter.writeStartElement(namespace, "root"); + streamWriter.writeDefaultNamespace(namespace); + streamWriter.writeStartElement(namespace, "child"); + streamWriter.writeCharacters("text"); + streamWriter.writeEndElement(); + streamWriter.writeEndElement(); + streamWriter.flush(); + + StringWriter writer = new StringWriter(); + this.body.serialize(writer); + + String expected = "" + + "" + "text" + + ""; + + assertThat(writer.toString()).and(expected).ignoreWhitespace().areIdentical(); + } + + @Test + public void testDelegatingStreamWriterWriteEndDocument() throws Exception { + + XMLStreamWriter streamWriter = StaxUtils.getXMLStreamWriter(this.payload.getResult()); + + String namespace = "http://springframework.org/spring-ws"; + streamWriter.setDefaultNamespace(namespace); + streamWriter.writeStartElement(namespace, "root"); + streamWriter.writeDefaultNamespace(namespace); + streamWriter.writeStartElement(namespace, "child"); + streamWriter.writeCharacters("text"); + streamWriter.writeEndDocument(); + streamWriter.flush(); + + StringWriter writer = new StringWriter(); + this.body.serialize(writer); + + String expected = "" + + "" + "text" + + ""; + + assertThat(writer.toString()).and(expected).ignoreWhitespace().areIdentical(); + } + + @Test + public void testDelegatingStreamWriterWriteEmptyElement() throws Exception { + XMLStreamWriter streamWriter = StaxUtils.getXMLStreamWriter(this.payload.getResult()); + + String namespace = "http://springframework.org/spring-ws"; + streamWriter.setDefaultNamespace(namespace); + streamWriter.writeStartElement(namespace, "root"); + streamWriter.writeDefaultNamespace(namespace); + streamWriter.writeEmptyElement(namespace, "child"); + streamWriter.writeEndElement(); + streamWriter.flush(); + + StringWriter writer = new StringWriter(); + this.body.serialize(writer); + + String expected = "" + + "" + "" + ""; + + assertThat(writer.toString()).and(expected).ignoreWhitespace().areIdentical(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/support/AxiomUtilsTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/support/AxiomUtilsTest.java new file mode 100644 index 00000000..f73cb422 --- /dev/null +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/axiom/support/AxiomUtilsTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.axiom.support; + +import java.io.StringWriter; +import java.util.Locale; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMFactory; +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.om.OMXMLBuilderFactory; +import org.apache.axiom.soap.SOAPEnvelope; +import org.apache.axiom.soap.SOAPMessage; +import org.apache.axiom.soap.SOAPModelBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.xmlunit.assertj.XmlAssert; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.util.FileCopyUtils; +import org.springframework.xml.DocumentBuilderFactoryUtils; +import org.springframework.xml.sax.SaxUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AxiomUtilsTest { + + private OMElement element; + + @BeforeEach + public void setUp() throws Exception { + + OMFactory factory = OMAbstractFactory.getOMFactory(); + OMNamespace namespace = factory.createOMNamespace("http://www.springframework.org", "prefix"); + this.element = factory.createOMElement("element", namespace); + } + + @Test + public void testToNamespaceDeclared() { + + QName qName = new QName(this.element.getNamespace().getNamespaceURI(), "localPart"); + OMNamespace namespace = AxiomUtils.toNamespace(qName, this.element); + + assertThat(namespace).isNotNull(); + assertThat(namespace.getNamespaceURI()).isEqualTo(qName.getNamespaceURI()); + } + + @Test + public void testToNamespaceUndeclared() { + + QName qName = new QName("http://www.example.com", "localPart"); + OMNamespace namespace = AxiomUtils.toNamespace(qName, this.element); + + assertThat(namespace).isNotNull(); + assertThat(namespace.getNamespaceURI()).isEqualTo(qName.getNamespaceURI()); + assertThat(namespace.getPrefix()).isNotEqualTo("prefix"); + } + + @Test + public void testToNamespacePrefixDeclared() { + + QName qName = new QName(this.element.getNamespace().getNamespaceURI(), "localPart", "prefix"); + OMNamespace namespace = AxiomUtils.toNamespace(qName, this.element); + + assertThat(namespace).isNotNull(); + assertThat(namespace.getNamespaceURI()).isEqualTo(qName.getNamespaceURI()); + assertThat(namespace.getPrefix()).isEqualTo("prefix"); + } + + @Test + public void testToNamespacePrefixUndeclared() { + + QName qName = new QName("http://www.example.com", "localPart", "otherPrefix"); + OMNamespace namespace = AxiomUtils.toNamespace(qName, this.element); + + assertThat(namespace).isNotNull(); + assertThat(namespace.getNamespaceURI()).isEqualTo(qName.getNamespaceURI()); + assertThat(namespace.getPrefix()).isEqualTo(qName.getPrefix()); + } + + @Test + public void testToLanguage() { + + assertThat(AxiomUtils.toLanguage(Locale.CANADA_FRENCH)).isEqualTo("fr-CA"); + assertThat(AxiomUtils.toLanguage(Locale.ENGLISH)).isEqualTo("en"); + } + + @Test + public void testToLocale() { + + assertThat(AxiomUtils.toLocale("fr-CA")).isEqualTo(Locale.CANADA_FRENCH); + assertThat(AxiomUtils.toLocale("en")).isEqualTo(Locale.ENGLISH); + } + + @Test + @SuppressWarnings("Since15") + public void testToDocument() throws Exception { + + Resource resource = new ClassPathResource("org/springframework/ws/soap/soap11/soap11.xml"); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactoryUtils.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document expected = documentBuilder.parse(SaxUtils.createInputSource(resource)); + + SOAPModelBuilder builder = OMXMLBuilderFactory.createSOAPModelBuilder(resource.getInputStream(), null); + SOAPMessage soapMessage = builder.getSOAPMessage(); + + Document result = AxiomUtils.toDocument(soapMessage.getSOAPEnvelope()); + + XmlAssert.assertThat(result).and(expected).ignoreWhitespace().areIdentical(); + } + + @Test + public void testToEnvelope() throws Exception { + + Resource resource = new ClassPathResource("org/springframework/ws/soap/soap11/soap11.xml"); + + byte[] buf = FileCopyUtils.copyToByteArray(resource.getFile()); + String expected = new String(buf, "UTF-8"); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactoryUtils.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.parse(SaxUtils.createInputSource(resource)); + + SOAPEnvelope envelope = AxiomUtils.toEnvelope(document); + StringWriter writer = new StringWriter(); + envelope.serialize(writer); + String result = writer.toString(); + + XmlAssert.assertThat(result).and(expected).ignoreWhitespace().areIdentical(); + } + +} diff --git a/spring-ws-core/src/test/java/org/springframework/ws/soap/server/endpoint/interceptor/PayloadValidatingInterceptorTest.java b/spring-ws-core/src/test/java/org/springframework/ws/soap/server/endpoint/interceptor/PayloadValidatingInterceptorTest.java index 4a52343a..2b3f4678 100644 --- a/spring-ws-core/src/test/java/org/springframework/ws/soap/server/endpoint/interceptor/PayloadValidatingInterceptorTest.java +++ b/spring-ws-core/src/test/java/org/springframework/ws/soap/server/endpoint/interceptor/PayloadValidatingInterceptorTest.java @@ -16,6 +16,7 @@ package org.springframework.ws.soap.server.endpoint.interceptor; +import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.Locale; @@ -29,19 +30,26 @@ import jakarta.xml.soap.SOAPMessage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.LocatorImpl; +import org.xmlunit.assertj.XmlAssert; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.ws.MockWebServiceMessage; import org.springframework.ws.MockWebServiceMessageFactory; +import org.springframework.ws.WebServiceMessage; import org.springframework.ws.context.DefaultMessageContext; import org.springframework.ws.context.MessageContext; import org.springframework.ws.soap.SoapMessage; import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; import org.springframework.ws.soap.saaj.SaajSoapMessage; import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; import org.springframework.ws.soap.saaj.support.SaajUtils; import org.springframework.ws.soap.soap11.Soap11Fault; import org.springframework.ws.soap.soap12.Soap12Fault; +import org.springframework.ws.transport.MockTransportInputStream; +import org.springframework.ws.transport.TransportInputStream; import org.springframework.xml.transform.TransformerFactoryUtils; import org.springframework.xml.validation.ValidationErrorHandler; import org.springframework.xml.xsd.SimpleXsdSchema; @@ -316,6 +324,30 @@ public class PayloadValidatingInterceptorTest { assertThat(result).isTrue(); } + @Test + public void testCreateRequestValidationFaultAxiom() throws Exception { + LocatorImpl locator = new LocatorImpl(); + locator.setLineNumber(0); + locator.setColumnNumber(0); + SAXParseException[] exceptions = new SAXParseException[] { new SAXParseException("Message 1", locator), + new SAXParseException("Message 2", locator), }; + MessageContext messageContext = new DefaultMessageContext(new AxiomSoapMessageFactory()); + this.interceptor.handleRequestValidationErrors(messageContext, exceptions); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + messageContext.getResponse().writeTo(os); + + XmlAssert.assertThat(os.toString()) + .and("" + "" + + "" + "" + "soapenv:Client" + + "Validation error" + "" + + "Message 1" + + "Message 2" + + "" + "" + "" + "") + .ignoreWhitespace() + .areIdentical(); + + } + @Test public void testXsdSchema() throws Exception { @@ -335,6 +367,46 @@ public class PayloadValidatingInterceptorTest { assertThat(this.context.hasResponse()).isFalse(); } + @Test + public void testAxiom() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(true); + messageFactory.afterPropertiesSet(); + + PayloadValidatingInterceptor interceptor = new PayloadValidatingInterceptor(); + interceptor.setSchema(new ClassPathResource("codexws.xsd", getClass())); + interceptor.afterPropertiesSet(); + + Resource resource = new ClassPathResource("axiom.xml", getClass()); + TransportInputStream tis = new MockTransportInputStream(resource.getInputStream()); + WebServiceMessage message = messageFactory.createWebServiceMessage(tis); + MessageContext context = new DefaultMessageContext(message, messageFactory); + boolean result = interceptor.handleRequest(context, null); + + assertThat(result).isTrue(); + } + + @Test + public void testMultipleNamespacesAxiom() throws Exception { + + AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); + messageFactory.setPayloadCaching(true); + messageFactory.afterPropertiesSet(); + + PayloadValidatingInterceptor interceptor = new PayloadValidatingInterceptor(); + interceptor.setSchema(new ClassPathResource("multipleNamespaces.xsd", getClass())); + interceptor.afterPropertiesSet(); + + Resource resource = new ClassPathResource("multipleNamespaces.xml", getClass()); + TransportInputStream tis = new MockTransportInputStream(resource.getInputStream()); + WebServiceMessage message = messageFactory.createWebServiceMessage(tis); + MessageContext context = new DefaultMessageContext(message, messageFactory); + boolean result = interceptor.handleRequest(context, null); + + assertThat(result).isTrue(); + } + @Test public void customErrorHandler() throws Exception { diff --git a/spring-ws-docs/src/docs/asciidoc/client.adoc b/spring-ws-docs/src/docs/asciidoc/client.adoc index f69cd943..24f981a6 100644 --- a/spring-ws-docs/src/docs/asciidoc/client.adoc +++ b/spring-ws-docs/src/docs/asciidoc/client.adoc @@ -199,7 +199,8 @@ The following example shows how to use the XMPP transport: ==== Message factories In addition to a message sender, the `WebServiceTemplate` requires a web service message factory. -By default, `SaajSoapMessageFactory` is used. +There are two message factories for SOAP: `SaajSoapMessageFactory` and `AxiomSoapMessageFactory`. +If no message factory is specified (by setting the `messageFactory` property), Spring-WS uses the `SaajSoapMessageFactory` by default. === Sending and Receiving a `WebServiceMessage` diff --git a/spring-ws-docs/src/docs/asciidoc/common.adoc b/spring-ws-docs/src/docs/asciidoc/common.adoc index df6b211f..719fce54 100644 --- a/spring-ws-docs/src/docs/asciidoc/common.adoc +++ b/spring-ws-docs/src/docs/asciidoc/common.adoc @@ -59,7 +59,9 @@ Only when it is necessary to perform SOAP-specific actions (such as adding a hea Concrete message implementations are created by a `WebServiceMessageFactory`. This factory can create an empty message or read a message from an input stream. -One concrete implementations of `WebServiceMessageFactory` is provided based on SAAJ, the SOAP with Attachments API for Java. +There are two concrete implementations of `WebServiceMessageFactory`. +One is based on SAAJ, the SOAP with Attachments API for Java. +The other is based on Axis 2’s AXIOM (AXis Object Model). ==== `SaajSoapMessageFactory` @@ -98,14 +100,48 @@ You can wire up a `SaajSoapMessageFactory` as follows: ---- ==== -NOTE: SAAJ is based on DOM, the Document Object Model. +[NOTE] +==== +SAAJ is based on DOM, the Document Object Model. This means that all SOAP messages are stored in memory. For larger SOAP messages, this may not be performant. +In that case, the `AxiomSoapMessageFactory` might be more applicable. +==== + +==== `AxiomSoapMessageFactory` + +The `AxiomSoapMessageFactory` uses the AXis 2 Object Model (AXIOM) to create `SoapMessage` implementations. +AXIOM is based on StAX, the Streaming API for XML. +StAX provides a pull-based mechanism for reading XML messages, which can be more efficient for larger messages. + +To increase reading performance on the `AxiomSoapMessageFactory`, you can set the `payloadCaching` property to false (default is true). +Doing so causes the contents of the SOAP body to be read directly from the socket stream. +When this setting is enabled, the payload can be read only once. +This means that you have to make sure that any pre-processing (logging or other work) of the message does not consume it. + +You can use the `AxiomSoapMessageFactory` as follows: + +==== +[source,xml] +---- + + + +---- +==== + +In addition to payload caching, AXIOM supports full streaming messages, as defined in the `StreamingWebServiceMessage`. +This means that you can directly set the payload on the response message, rather than writing it to a DOM tree or buffer. + +Full streaming for AXIOM is used when a handler method returns a JAXB2-supported object. +It automatically sets this marshalled object into the response message and writes it out to the outgoing socket stream when the response is going out. + +For more information about full streaming, see {spring-ws-api}/stream/StreamingWebServiceMessage.html[`StreamingWebServiceMessage`] and {spring-ws-api}/stream/StreamingPayload.html[`StreamingPayload`]. [[soap_11_or_12]] ==== SOAP 1.1 or 1.2 -`SaajSoapMessageFactory` has a `soapVersion` property, where you can inject a `SoapVersion` constant. +Both the `SaajSoapMessageFactory` and the `AxiomSoapMessageFactory` have a `soapVersion` property, where you can inject a `SoapVersion` constant. By default, the version is 1.1, but you can set it to 1.2: ==== diff --git a/spring-ws-docs/src/docs/asciidoc/security.adoc b/spring-ws-docs/src/docs/asciidoc/security.adoc index d26cabc5..b9d68495 100644 --- a/spring-ws-docs/src/docs/asciidoc/security.adoc +++ b/spring-ws-docs/src/docs/asciidoc/security.adoc @@ -729,7 +729,7 @@ WSS4J implements the following standards: * Username Token profile V1.0. * X.509 Token Profile V1.0. -This interceptor supports messages created by the `SaajSoapMessageFactory`. +This interceptor supports messages created by the `AxiomSoapMessageFactory` and the `SaajSoapMessageFactory`. === Configuring `Wss4jSecurityInterceptor` diff --git a/spring-ws-platform/build.gradle b/spring-ws-platform/build.gradle index ec927473..624feba3 100644 --- a/spring-ws-platform/build.gradle +++ b/spring-ws-platform/build.gradle @@ -36,6 +36,11 @@ dependencies { api("org.apache.httpcomponents:httpclient:4.5.14") api("org.apache.santuario:xmlsec:4.0.3") api("org.apache.wss4j:wss4j-ws-security-dom:4.0.0") + api("org.apache.ws.commons.axiom:axiom-api:2.0.0") + api("org.apache.ws.commons.axiom:axiom-compat:2.0.0") + api("org.apache.ws.commons.axiom:axiom-dom:2.0.0") + api("org.apache.ws.commons.axiom:axiom-impl:2.0.0") + api("org.apache.ws.commons.axiom:axiom-legacy-attachments:2.0.0") api("org.apache.ws.xmlschema:xmlschema-core:2.3.1") api("org.aspectj:aspectjrt:1.9.22") api("org.aspectj:aspectjweaver:1.9.22") diff --git a/spring-ws-security/build.gradle b/spring-ws-security/build.gradle index e15ea89b..803621e5 100644 --- a/spring-ws-security/build.gradle +++ b/spring-ws-security/build.gradle @@ -22,6 +22,12 @@ dependencies { api("org.springframework.security:spring-security-core") optional("com.sun.xml.messaging.saaj:saaj-impl") + optional("org.apache.ws.commons.axiom:axiom-impl") { + exclude(group: "commons-logging", module: "commons-logging") + } + optional("org.apache.ws.commons.axiom:axiom-legacy-attachments") { + exclude(group: "commons-logging", module: "commons-logging") + } testImplementation("org.apache.logging.log4j:log4j-core") testImplementation("org.apache.logging.log4j:log4j-slf4j2-impl") diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jInterceptorTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jInterceptorTest.java new file mode 100644 index 00000000..fdf2bef9 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jInterceptorTest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jInterceptorTest extends Wss4jInterceptorTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorEncryptionTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorEncryptionTest.java new file mode 100644 index 00000000..88e71aa9 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorEncryptionTest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorEncryptionTest extends Wss4jMessageInterceptorEncryptionTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorHeaderTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorHeaderTest.java new file mode 100644 index 00000000..daeee204 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorHeaderTest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorHeaderTest extends Wss4jMessageInterceptorHeaderTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSamlTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSamlTest.java new file mode 100644 index 00000000..c6052c96 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSamlTest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorSamlTest extends Wss4jMessageInterceptorSamlTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSignTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSignTest.java new file mode 100644 index 00000000..f9e4de85 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSignTest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorSignTest extends Wss4jMessageInterceptorSignTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSoapActionTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSoapActionTest.java new file mode 100644 index 00000000..0a6a3be9 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSoapActionTest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorSoapActionTest extends Wss4jMessageInterceptorSoapActionTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java new file mode 100644 index 00000000..4375979d --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest.java @@ -0,0 +1,22 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorSpringSecurityCallbackHandlerTest + extends Wss4jMessageInterceptorSpringSecurityCallbackHandlerTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorTimestampTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorTimestampTest.java new file mode 100644 index 00000000..3045c3a8 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorTimestampTest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorTimestampTest extends Wss4jMessageInterceptorTimestampTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java new file mode 100644 index 00000000..310dc7ad --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenSignatureTest.java @@ -0,0 +1,22 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorUsernameTokenSignatureTest + extends Wss4jMessageInterceptorUsernameTokenSignatureTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenTest.java new file mode 100644 index 00000000..9636b2a9 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorUsernameTokenTest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +public class AxiomWss4jMessageInterceptorUsernameTokenTest extends Wss4jMessageInterceptorUsernameTokenTest { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorX509Test.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorX509Test.java new file mode 100644 index 00000000..ded1a1c3 --- /dev/null +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/AxiomWss4jMessageInterceptorX509Test.java @@ -0,0 +1,24 @@ +/* + * Copyright 2005-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.security.wss4j2; + +/** + * @author tareq + */ +public class AxiomWss4jMessageInterceptorX509Test extends Wss4jMessageInterceptorX509Test { + +} diff --git a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jTest.java b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jTest.java index 52a41acf..b2499c62 100644 --- a/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jTest.java +++ b/spring-ws-security/src/test/java/org/springframework/ws/soap/security/wss4j2/Wss4jTest.java @@ -26,6 +26,8 @@ import jakarta.xml.soap.MessageFactory; import jakarta.xml.soap.MimeHeaders; import jakarta.xml.soap.SOAPConstants; import jakarta.xml.soap.SOAPMessage; +import org.apache.axiom.om.OMXMLBuilderFactory; +import org.apache.axiom.soap.SOAPModelBuilder; import org.junit.jupiter.api.BeforeEach; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -38,6 +40,9 @@ import org.springframework.ws.context.MessageContext; import org.springframework.ws.soap.SoapMessage; import org.springframework.ws.soap.SoapMessageFactory; import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; +import org.springframework.ws.soap.axiom.support.AxiomUtils; import org.springframework.ws.soap.saaj.SaajSoapMessage; import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; import org.springframework.xml.transform.StringSource; @@ -51,6 +56,8 @@ public abstract class Wss4jTest { protected MessageFactory saajSoap12MessageFactory; + protected final boolean axiomTest = this.getClass().getSimpleName().startsWith("Axiom"); + protected final boolean saajTest = this.getClass().getSimpleName().startsWith("Saaj"); protected Jaxp13XPathTemplate xpathTemplate = new Jaxp13XPathTemplate(); @@ -58,7 +65,7 @@ public abstract class Wss4jTest { @BeforeEach public final void setUp() throws Exception { - if (!this.saajTest) { + if (!this.axiomTest && !this.saajTest) { throw new IllegalArgumentException("test class name must start with Saaj"); } @@ -145,22 +152,57 @@ public abstract class Wss4jTest { } } - protected Object getMessage(SoapMessage soapMessage) { + protected AxiomSoapMessage loadAxiom11Message(String fileName) throws Exception { + Resource resource = new ClassPathResource(fileName, getClass()); + + assertThat(resource.exists()).isTrue(); + + try (InputStream is = resource.getInputStream()) { + + SOAPModelBuilder builder = OMXMLBuilderFactory.createSOAPModelBuilder(is, null); + org.apache.axiom.soap.SOAPMessage soapMessage = builder.getSOAPMessage(); + builder.detach(); + return new AxiomSoapMessage(soapMessage, "", true, true); + } + } + + @SuppressWarnings("Since15") + protected AxiomSoapMessage loadAxiom12Message(String fileName) throws Exception { + + Resource resource = new ClassPathResource(fileName, getClass()); + + assertThat(resource.exists()).isTrue(); + + try (InputStream is = resource.getInputStream()) { + + SOAPModelBuilder builder = OMXMLBuilderFactory.createSOAPModelBuilder(is, null); + org.apache.axiom.soap.SOAPMessage soapMessage = builder.getSOAPMessage(); + builder.detach(); + return new AxiomSoapMessage(soapMessage, "", true, true); + } + } + + protected Object getMessage(SoapMessage soapMessage) { + if (soapMessage instanceof AxiomSoapMessage) { + return ((AxiomSoapMessage) soapMessage).getAxiomMessage(); + + } if (soapMessage instanceof SaajSoapMessage) { return ((SaajSoapMessage) soapMessage).getSaajMessage(); } - throw new IllegalArgumentException("Illegal message: " + soapMessage); } protected void setMessage(SoapMessage soapMessage, Object message) { - + if (soapMessage instanceof AxiomSoapMessage) { + ((AxiomSoapMessage) soapMessage).setAxiomMessage((org.apache.axiom.soap.SOAPMessage) message); + return; + } if (soapMessage instanceof SaajSoapMessage) { ((SaajSoapMessage) soapMessage).setSaajMessage((SOAPMessage) message); return; } - throw new IllegalArgumentException("Illegal message: " + message); } @@ -168,46 +210,54 @@ public abstract class Wss4jTest { } protected SoapMessage loadSoap11Message(String fileName) throws Exception { - + if (this.axiomTest) { + return loadAxiom11Message(fileName); + } if (this.saajTest) { return loadSaaj11Message(fileName); } - throw new IllegalArgumentException(); } protected SoapMessage loadSoap12Message(String fileName) throws Exception { - + if (this.axiomTest) { + return loadAxiom12Message(fileName); + } if (this.saajTest) { return loadSaaj12Message(fileName); } - throw new IllegalArgumentException(); } protected SoapMessageFactory getSoap11MessageFactory() { - + if (this.axiomTest) { + return new AxiomSoapMessageFactory(); + } if (this.saajTest) { return new SaajSoapMessageFactory(this.saajSoap11MessageFactory); } - throw new IllegalArgumentException(); } protected SoapMessageFactory getSoap12MessageFactory() { - SoapMessageFactory messageFactory; - if (this.saajTest) { + if (this.axiomTest) { + messageFactory = new AxiomSoapMessageFactory(); + } + else if (this.saajTest) { messageFactory = new SaajSoapMessageFactory(this.saajSoap12MessageFactory); } - else + else { throw new IllegalArgumentException(); + } messageFactory.setSoapVersion(SoapVersion.SOAP_12); return messageFactory; } protected Document getDocument(SoapMessage message) { - + if (this.axiomTest) { + return AxiomUtils.toDocument(((AxiomSoapMessage) message).getAxiomMessage().getSOAPEnvelope()); + } if (this.saajTest) { return ((SaajSoapMessage) message).getSaajMessage().getSOAPPart(); }