Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix #36 #40

Merged
merged 1 commit into from
Apr 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,6 @@
<artifactId>kotlin-test-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ru.sokomishalov.commons</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-random-core</artifactId>
Expand Down Expand Up @@ -119,7 +113,7 @@
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
Expand Down Expand Up @@ -148,6 +142,9 @@
<wsdlOption>
<wsdl>${project.basedir}/src/test/resources/wsdl/PersonService.wsdl</wsdl>
</wsdlOption>
<wsdlOption>
<wsdl>${project.basedir}/src/test/resources/wsdl/issue-36.wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")

package ru.sokomishalov.jackson.dataformat.soap

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package ru.sokomishalov.jackson.dataformat.soap
/**
* @author sokomishalov
*/
data class SoapFault @JvmOverloads constructor(
override val message: String? = null,
val code: String? = null
data class SoapFault internal constructor(
override val message: String?,
val code: String?
) : RuntimeException(message ?: "Unknown SOAP error occurred")
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector
import ru.sokomishalov.jackson.dataformat.soap.ser.SoapEnvelopeSerializers
import ru.sokomishalov.jackson.dataformat.soap.util.NamespaceCache
import java.lang.reflect.Field
import java.lang.reflect.Method
import javax.xml.bind.annotation.XmlAttribute
import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlElementWrapper
Expand All @@ -44,7 +45,7 @@ open class SoapJaxbAnnotationIntrospector : JaxbAnnotationIntrospector(TypeFacto
ann.annotated.isAnnotationPresent(XmlAttribute::class.java) -> ""

// fields with primitives or collections have parent namespaces
ann.annotated is Field || ClassUtil.isJDKClass(t) -> (ann.annotated as Field).declaringClass.namespace
ann.annotated is Field || ann.annotated is Method || ClassUtil.isJDKClass(t) -> ann.parentNamespace

// other classes have their own namespaces
else -> t.namespace
Expand All @@ -61,5 +62,10 @@ open class SoapJaxbAnnotationIntrospector : JaxbAnnotationIntrospector(TypeFacto
else -> super.findWrapperName(ann)
}

protected val Annotated.parentNamespace: String get() = when (val annotatedElement = annotated) {
is Field -> annotatedElement.declaringClass.namespace
is Method -> annotatedElement.declaringClass.namespace
else -> ""
}
protected val Class<*>.namespace: String get() = NamespaceCache.getNamespace(this)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2021-present Mikhael Sokolov
*
* 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
*
* http://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 ru.sokomishalov.jackson.dataformat.soap

/**
* @author sokomishalov
*/
data class SoapMultipleHeaders<H : Any>(val headers: Iterable<H>) : Iterable<H> by headers {
constructor(vararg headers: H) : this(headers.toList())
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.ser.Serializers
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator
import ru.sokomishalov.jackson.dataformat.soap.util.NamespaceCache
import ru.sokomishalov.jackson.dataformat.soap.SoapAddressingHeaders
import ru.sokomishalov.jackson.dataformat.soap.SoapConstants.SOAP_ADDRESSING_NAMESPACE
import ru.sokomishalov.jackson.dataformat.soap.SoapConstants.XML_SCHEMA_NAMESPACE
import ru.sokomishalov.jackson.dataformat.soap.SoapEnvelope
import ru.sokomishalov.jackson.dataformat.soap.util.NamespaceCache
import javax.xml.bind.JAXBElement
import javax.xml.bind.annotation.XmlRootElement
import javax.xml.namespace.QName
Expand All @@ -38,7 +38,7 @@ internal class SoapEnvelopeSerializers(private val ns: String) : Serializers.Bas
config: SerializationConfig,
type: JavaType,
beanDesc: BeanDescription
): JsonSerializer<*>? = when {
): JsonSerializer<*>? = when {
SoapEnvelope::class.java.isAssignableFrom(type.rawClass) -> SoapEnvelopeSerializer(ns)
JAXBElement::class.java.isAssignableFrom(type.rawClass) -> JaxbElementSerializer
else -> super.findSerializer(config, type, beanDesc)
Expand All @@ -52,21 +52,21 @@ internal class SoapEnvelopeSerializers(private val ns: String) : Serializers.Bas
setNextName(QName(ns, "Envelope"))
writeStartObject()
staxWriter.writeNamespace("xsi", XML_SCHEMA_NAMESPACE)
serializeElement("Header", envelope.header)
serializeElement("Body", envelope.body)
serializeElement(localPart = "Header", element = envelope.header, isHeader = true)
serializeElement(localPart = "Body", element = envelope.body, isHeader = false)
writeEndObject()
}
}
}

private fun ToXmlGenerator.serializeElement(localPart: String, element: Any?) {
private fun ToXmlGenerator.serializeElement(localPart: String, element: Any?, isHeader: Boolean) {
with(this) {
setNextName(QName(ns, localPart))
writeFieldName(localPart)

val annotation = element?.javaClass?.getAnnotation(XmlRootElement::class.java)
when {
element is SoapAddressingHeaders -> {
isHeader && element is SoapAddressingHeaders -> {
writeStartObject()
staxWriter.writeNamespace("wsa", SOAP_ADDRESSING_NAMESPACE)

Expand All @@ -80,6 +80,17 @@ internal class SoapEnvelopeSerializers(private val ns: String) : Serializers.Bas

writeEndObject()
}
isHeader && element is Iterable<*> -> {
writeStartObject()
element.forEach {
if (it != null) {
setNextName(QName(NamespaceCache.getNamespace(it.javaClass), annotation?.name ?: it.javaClass.simpleName))
writeFieldName(annotation?.name ?: it.javaClass.simpleName)
writeObject(it)
}
}
writeEndObject()
}
annotation != null -> {
writeStartObject()
setNextName(QName(NamespaceCache.getNamespace(element.javaClass), annotation.name))
Expand Down Expand Up @@ -116,7 +127,6 @@ internal class SoapEnvelopeSerializers(private val ns: String) : Serializers.Bas
}

internal object JaxbElementSerializer : StdSerializer<JAXBElement<*>>(JAXBElement::class.java) {

override fun serialize(value: JAXBElement<*>?, gen: JsonGenerator, provider: SerializerProvider) {
when {
value != null && !value.isNil -> gen.writeObject(value.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
package ru.sokomishalov.jackson.dataformat.soap

import com.oracle.xmlns.apps.mdm.customer.GetPersonOutput
import de.addmore.grouplist.grouplist_001.AppMonDetailsStrict
import de.addmore.grouplist.grouplist_001.ControllObject
import de.addmore.grouplist.grouplist_001.GetGroupListRequest
import org.jeasy.random.EasyRandom
import org.jeasy.random.EasyRandomParameters
import org.junit.jupiter.api.Test
import ru.sokomishalov.commons.core.log.Loggable
import ru.sokomishalov.commons.core.string.isNotNullOrBlank
import javax.xml.bind.JAXBContext
import javax.xml.bind.JAXBElement
import javax.xml.namespace.QName
Expand All @@ -41,11 +42,9 @@ class SoapMapperTest {

val envelope = SoapEnvelope(body = body, header = header)

val result = mapper.writeValueAsString(envelope)
val result = mapper.writeValueAsString(envelope).also { println(it) }

logInfo { result }

assertTrue { result.isNotNullOrBlank() }
assertFalse { result.isNullOrBlank() }

listOf(
":Action>",
Expand Down Expand Up @@ -87,7 +86,7 @@ class SoapMapperTest {
val content = readResource("/example/get_person_output_ws_addr.xml")
val soapEnvelope = mapper.readValue<SoapAddressingHeaders?, GetPersonOutput?>(content)

logInfo { mapper.writeValueAsString(soapEnvelope) }
mapper.writeValueAsString(soapEnvelope).also { println(it) }
assertNotNull(soapEnvelope)

assertNotNull(soapEnvelope.header)
Expand All @@ -104,9 +103,9 @@ class SoapMapperTest {
assertNotNull(pojo.listOfSwiPersonIO)
assertEquals(2, pojo.listOfSwiPersonIO?.contact?.size)
pojo.listOfSwiPersonIO.contact.forEach {
assertTrue { it.id.value.isNotNullOrBlank() }
assertTrue { it.firstName.isNotNullOrBlank() }
assertTrue { it.lastName.isNotNullOrBlank() }
assertFalse { it.id.value.isNullOrBlank() }
assertFalse { it.firstName.isNullOrBlank() }
assertFalse { it.lastName.isNullOrBlank() }
}
}

Expand All @@ -115,7 +114,7 @@ class SoapMapperTest {
val content = readResource("/example/soap_fault_1_1.xml")
val soapFault = assertFailsWith<SoapFault> { mapper.readValueBody<GetPersonOutput>(content) }

logInfo { mapper.writeValueAsString(soapFault) }
mapper.writeValueAsString(soapFault).also { println(it) }
assertNotNull(soapFault)
assertNotNull(soapFault)
assertFalse { soapFault.message.isNullOrBlank() }
Expand All @@ -127,17 +126,45 @@ class SoapMapperTest {
val content = readResource("/example/soap_fault_1_2.xml")
val soapFault = assertFailsWith<SoapFault> { mapper.readValueBody<GetPersonOutput>(content) }

logInfo { mapper.writeValueAsString(soapFault) }
mapper.writeValueAsString(soapFault).also { println(it) }
assertNotNull(soapFault)
assertNotNull(soapFault)
assertFalse { soapFault.message.isNullOrBlank() }
assertFalse { soapFault.code.isNullOrBlank() }
}

@Test
fun `GH-36`() {
val first = AppMonDetailsStrict().apply {
bpId = random()
bpName = random()
}
val second = ControllObject().apply {
timeout = random()
}

val header = SoapMultipleHeaders(first, second)
val body = random<GetGroupListRequest>()

val envelope = SoapEnvelope(body = body, header = header)

val result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(envelope).also { println(it) }

listOf(
":AppMonDetailsStrict",
":ControllObject",
":bpId",
":bpName",
":timeout",
).forEach {
assertTrue("$it is not present") { it in result }
}
}

private fun readResource(path: String) = javaClass.getResource(path)?.readText().orEmpty()
private inline fun <reified T> random(): T = RANDOM.nextObject(T::class.java)

companion object : Loggable {
companion object {
private val RANDOM: EasyRandom = EasyRandom(
EasyRandomParameters()
.charset(Charsets.UTF_8)
Expand Down
104 changes: 104 additions & 0 deletions src/test/resources/wsdl/issue-36.wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.addmore.de/GroupList/GroupList-001.wsdl"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
targetNamespace="http://www.addmore.de/GroupList/GroupList-001.wsdl">
<wsdl:types>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.addmore.de/GroupList/GroupList-001.wsdl">
<xs:element name="GetGroupListRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="rowCount" type="integer"/>
<xs:element name="banList">
<xs:complexType>
<xs:sequence>
<xs:element name="ban" type="integer" maxOccurs="10"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="GetGroupListResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="rowCount" type="integer"/>
<xs:element name="vpnGroup" type="string" minOccurs="0" maxOccurs="600"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="appMonDetailsStrict">
<xs:complexType>
<xs:sequence>
<element name="bpId" type="string"/>
<element name="bpName" type="string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="controllObject">
<xs:complexType>
<xs:sequence>
<element name="timeout" type="unsignedInt"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="functionalException"></xs:element>
<xs:element name="technicalException"></xs:element>
</schema>
</wsdl:types>
<wsdl:message name="appMonDetailsStrictHeader">
<wsdl:part name="AppMonDetailsStrict" element="tns:appMonDetailsStrict"/>
</wsdl:message>
<wsdl:message name="controllObject">
<wsdl:part name="ControllObject" element="tns:controllObject"/>
</wsdl:message>
<wsdl:message name="functionalException">
<wsdl:part name="FunctionalException" element="tns:functionalException"/>
</wsdl:message>
<wsdl:message name="technicalException">
<wsdl:part name="TechnicalException" element="tns:technicalException"/>
</wsdl:message>
<wsdl:message name="GetGroupListRequest">
<wsdl:part element="tns:GetGroupListRequest" name="GetGroupListRequest"/>
</wsdl:message>
<wsdl:message name="GetGroupListResponse">
<wsdl:part element="tns:GetGroupListResponse" name="GetGroupListResponse"/>
</wsdl:message>
<wsdl:portType name="GroupListPortType">
<wsdl:operation name="GetGroupList">
<wsdl:input message="tns:GetGroupListRequest" name="GetGroupListRequest"/>
<wsdl:output message="tns:GetGroupListResponse" name="GetGroupListResponse"/>
<wsdl:fault message="tns:functionalException" name="functionalException"/>
<wsdl:fault message="tns:technicalException" name="technicalException"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="GroupListEndpointBinding" type="tns:GroupListPortType">
<soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="GetGroupList">
<soap12:operation soapAction="/GroupList-001/GetGroupList" soapActionRequired="true"/>
<wsdl:input>
<soap12:body parts="GetGroupListRequest" use="literal"/>
<soap12:header message="tns:appMonDetailsStrictHeader" part="AppMonDetailsStrict" use="literal"
wsdl:required="true"/>
<soap12:header message="tns:controllObject" part="ControllObject" use="literal"/>
</wsdl:input>
<wsdl:output>
<soap12:body parts="GetGroupListResponse" use="literal"/>
</wsdl:output>
<wsdl:fault name="functionalException">
<soap12:fault name="functionalException" use="literal"/>
</wsdl:fault>
<wsdl:fault name="technicalException">
<soap12:fault name="technicalException" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="GroupList">
<wsdl:port binding="tns:GroupListEndpointBinding" name="GroupListEndpoint">
<soap12:address location="http://www.addmore.de/"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>