commit 66d3aef26cd9b519337d95c4b0503091c30e44dc Author: Arjen Poutsma Date: Sun Sep 24 19:22:56 2006 +0000 Moved Spring-WS to separate dir. diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 00000000..c67dff2d --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,104 @@ + + + + spring-ws + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + spring-ws-core + jar + Spring WS Core + Spring Web Services Core package. + + + + org.springframework.ws + spring-xml + + + org.springframework.ws + spring-oxm + true + + + + org.springframework + spring-context + + + org.springframework + spring-web + + + org.springframework + spring-webmvc + + + org.springframework + spring-mock + + + + jdom + jdom + true + + + dom4j + dom4j + true + + + xom + xom + true + + + stax + stax-api + + + xerces + xercesImpl + test + + + woodstox + wstx-asl + test + + + + ws-commons + axiom-api + true + + + ws-commons + axiom-impl + true + + + com.sun.xml.messaging.saaj + saaj-impl + + + + javax.xml.soap + saaj-api + + + javax.servlet + servlet-api + + + javax.mail + mail + + + javax.activation + activation + + + diff --git a/core/src/etc/test-resources/log4j.properties b/core/src/etc/test-resources/log4j.properties new file mode 100644 index 00000000..92e896c8 --- /dev/null +++ b/core/src/etc/test-resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootCategory=INFO, stdout +log4j.logger.org.springframework.ws=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/invalidMessage.xml b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/invalidMessage.xml new file mode 100644 index 00000000..a7551338 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/invalidMessage.xml @@ -0,0 +1,4 @@ + + + 20 + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/productSchema.xsd b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/productSchema.xsd new file mode 100644 index 00000000..83146da1 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/productSchema.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/schema.xsd b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/schema.xsd new file mode 100644 index 00000000..60d338c5 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/schema.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/schema2.xsd b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/schema2.xsd new file mode 100644 index 00000000..8f41ba4a --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/schema2.xsd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/sizeSchema.xsd b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/sizeSchema.xsd new file mode 100644 index 00000000..60613d2b --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/sizeSchema.xsd @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformInput.xml b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformInput.xml new file mode 100644 index 00000000..e60dcdad --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformInput.xml @@ -0,0 +1,5 @@ + + + 42 + 10 + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformOutput.xml b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformOutput.xml new file mode 100644 index 00000000..0174ca66 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformOutput.xml @@ -0,0 +1,6 @@ + + + 2006-01-01 + 42 + 10 + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformation.xslt b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformation.xslt new file mode 100644 index 00000000..79100bb6 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/transformation.xslt @@ -0,0 +1,18 @@ + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/validMessage.xml b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/validMessage.xml new file mode 100644 index 00000000..ccd9449b --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/validMessage.xml @@ -0,0 +1,5 @@ + + + 42 + 10 + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/validSoapMessage.xml b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/validSoapMessage.xml new file mode 100644 index 00000000..0b968837 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/endpoint/interceptor/validSoapMessage.xml @@ -0,0 +1,12 @@ + + + + + + 123 + Fritz + Meier + + + + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/payload.xml b/core/src/etc/test-resources/org/springframework/ws/payload.xml new file mode 100644 index 00000000..5f2f3067 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/payload.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/soap/context/soap11-attachment.bin b/core/src/etc/test-resources/org/springframework/ws/soap/context/soap11-attachment.bin new file mode 100644 index 00000000..bff842e8 Binary files /dev/null and b/core/src/etc/test-resources/org/springframework/ws/soap/context/soap11-attachment.bin differ diff --git a/core/src/etc/test-resources/org/springframework/ws/soap/context/soap11.xml b/core/src/etc/test-resources/org/springframework/ws/soap/context/soap11.xml new file mode 100644 index 00000000..09c6e70b --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/soap/context/soap11.xml @@ -0,0 +1,8 @@ + + + + DIS + + + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/soap/context/soap12-attachment.bin b/core/src/etc/test-resources/org/springframework/ws/soap/context/soap12-attachment.bin new file mode 100644 index 00000000..1836a6b6 Binary files /dev/null and b/core/src/etc/test-resources/org/springframework/ws/soap/context/soap12-attachment.bin differ diff --git a/core/src/etc/test-resources/org/springframework/ws/soap/context/soap12.xml b/core/src/etc/test-resources/org/springframework/ws/soap/context/soap12.xml new file mode 100644 index 00000000..f299ed50 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/soap/context/soap12.xml @@ -0,0 +1,7 @@ + + + + Pick up Mary at school at 2pm + + + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/soap/saaj/support/soapMessage.xml b/core/src/etc/test-resources/org/springframework/ws/soap/saaj/support/soapMessage.xml new file mode 100644 index 00000000..bdf261ea --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/soap/saaj/support/soapMessage.xml @@ -0,0 +1,5 @@ + + + QQQ + + \ No newline at end of file diff --git a/core/src/etc/test-resources/org/springframework/ws/soap/soap11/soap11.xsd b/core/src/etc/test-resources/org/springframework/ws/soap/soap11/soap11.xsd new file mode 100644 index 00000000..24eb4c67 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/soap/soap11/soap11.xsd @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Prose in the spec does not specify that attributes are allowed on the Body + element + + + + + + + + + + + + + + + + + + + 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing + element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern + described in SOAP specification + + + + + + + + + + + + + + Fault reporting structure + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/soap/soap12/soap12.xsd b/core/src/etc/test-resources/org/springframework/ws/soap/soap12/soap12.xsd new file mode 100644 index 00000000..7b720804 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/soap/soap12/soap12.xsd @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + Elements replacing the wildcard MUST be namespace qualified, but can be in the + targetNamespace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fault reporting structure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/soap/soap12/xml.xsd b/core/src/etc/test-resources/org/springframework/ws/soap/soap12/xml.xsd new file mode 100644 index 00000000..0d4c3845 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/soap/soap12/xml.xsd @@ -0,0 +1,114 @@ + + + + + + See http://www.w3.org/XML/1998/namespace.html and http://www.w3.org/TR/REC-xml for information about this + namespace. + + This schema document describes the XML namespace, in a form suitable for import by other schema documents. + + Note that local names in this namespace are intended to be defined only by the World Wide Web Consortium or + its subgroups. The following names are currently defined in this namespace and should not be used with + conflicting semantics by any Working Group, specification, or document instance: + + base (as an attribute name): denotes an attribute whose value provides a URI to be used as the base for + interpreting any relative URIs in the scope of the element on which it appears; its value is inherited. This + name is reserved by virtue of its definition in the XML Base specification. + + id (as an attribute name): denotes an attribute whose value should be interpreted as if declared to be of + type ID. The xml:id specification is not yet a W3C Recommendation, but this attribute is included here to + facilitate experimentation with the mechanisms it proposes. Note that it is _not_ included in the + specialAttrs attribute group. + + lang (as an attribute name): denotes an attribute whose value is a language code for the natural language of + the content of any element; its value is inherited. This name is reserved by virtue of its definition in the + XML specification. + + space (as an attribute name): denotes an attribute whose value is a keyword indicating what whitespace + processing discipline is intended for the content of the element; its value is inherited. This name is + reserved by virtue of its definition in the XML specification. + + Father (in any context at all): denotes Jon Bosak, the chair of the original XML Working Group. This name is + reserved by the following decision of the W3C XML Plenary and XML Coordination groups: + + In appreciation for his vision, leadership and dedication the W3C XML Plenary on this 10th day of February, + 2000 reserves for Jon Bosak in perpetuity the XML name xml:Father + + + + This schema defines attributes and an attribute group suitable for use by schemas wishing to + allow xml:base, xml:lang, xml:space or xml:id attributes on elements they define. + + To enable this, such a schema must import this schema for the XML namespace, e.g. as follows: <schema . . + .> . . . <import namespace="http://www.w3.org/XML/1998/namespace" + schemaLocation="http://www.w3.org/2001/xml.xsd"/> + + Subsequently, qualified reference to any of the attributes or the group defined below will have the desired + effect, e.g. + + <type . . .> . . . <attributeGroup ref="xml:specialAttrs"/> + + will define a type which will schema-validate an instance element with any of those + attributes + + + + In keeping with the XML Schema WG's standard versioning policy, this schema document will + persist at http://www.w3.org/2005/08/xml.xsd. At the date of issue it can also be found at + http://www.w3.org/2001/xml.xsd. The schema document at that URI may however change in the future, in order + to remain compatible with the latest version of XML Schema itself, or with the XML namespace itself. In + other words, if the XML Schema or XML namespaces change, the version of this document at + http://www.w3.org/2001/xml.xsd will change accordingly; the version at http://www.w3.org/2005/08/xml.xsd + will not change. + + + + + Attempting to install the relevant ISO 2- and 3-letter codes as the enumerated possible + values is probably never going to be a realistic possibility. See RFC 3066 at + http://www.ietf.org/rfc/rfc3066.txt and the IANA registry at + http://www.iana.org/assignments/lang-tag-apps.htm for further information. + + The union allows for the 'un-declaration' of xml:lang with the empty string. + + + + + + + + + + + + + + + + + + + + + + + + See http://www.w3.org/TR/xmlbase/ for information about this attribute. + + + + + + See http://www.w3.org/TR/xml-id/ for information about this attribute. + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/transport/http/wsdl11-expected.wsdl b/core/src/etc/test-resources/org/springframework/ws/transport/http/wsdl11-expected.wsdl new file mode 100644 index 00000000..38a86f7f --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/transport/http/wsdl11-expected.wsdl @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/transport/http/wsdl11-input.wsdl b/core/src/etc/test-resources/org/springframework/ws/transport/http/wsdl11-input.wsdl new file mode 100644 index 00000000..ae208091 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/transport/http/wsdl11-input.wsdl @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/abstract.wsdl b/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/abstract.wsdl new file mode 100644 index 00000000..97d9e6e4 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/abstract.wsdl @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/complete.wsdl b/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/complete.wsdl new file mode 100644 index 00000000..19c36925 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/complete.wsdl @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/concrete.wsdl b/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/concrete.wsdl new file mode 100644 index 00000000..e75e2b5d --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/concrete.wsdl @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/types.xsd b/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/types.xsd new file mode 100644 index 00000000..88e0c486 --- /dev/null +++ b/core/src/etc/test-resources/org/springframework/ws/wsdl/wsdl11/types.xsd @@ -0,0 +1,6 @@ + + + + + diff --git a/core/src/main/java/org/springframework/ws/EndpointAdapter.java b/core/src/main/java/org/springframework/ws/EndpointAdapter.java new file mode 100644 index 00000000..d6663d32 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/EndpointAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +import org.springframework.ws.context.MessageContext; + +/** + * Interface that must be implemented for each endpoint type to handle a message request. This interface is used to + * allow the MessageDispatcher to be indefintely extensible. It accesses all installed endpoints through + * this interface, meaning that is does not contain code specific to any endpoint type. + *

+ * This interface is not intended for application developers. It is available for those who want to develop their own + * message flow. + * + * @author Arjen Poutsma + * @see MessageDispatcher + */ +public interface EndpointAdapter { + + /** + * Given an endpoint instance, return whether or not this EndpointAdapter can support it. Typical + * EndpointAdapters will base the decision on the endpoint type. + * + * @param endpoint endpoint object to check + * @return whether or not this adapter can adapt the given endpoint + */ + boolean supports(Object endpoint); + + /** + * Use the given endpoint to handle the request. + * + * @param messageContext the current message context + * @param endpoint the endpoint to use. This object must have previously been passed to the + * supports method of this interface, which must have returned true + * @throws Exception in case of errors + */ + void invoke(MessageContext messageContext, Object endpoint) throws Exception; +} diff --git a/core/src/main/java/org/springframework/ws/EndpointExceptionResolver.java b/core/src/main/java/org/springframework/ws/EndpointExceptionResolver.java new file mode 100644 index 00000000..69c3a080 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/EndpointExceptionResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +import org.springframework.ws.context.MessageContext; + +/** + * Defines the interface for objects than can resolve exceptions thrown during endpoint execution, resulting in SOAP + * messages. + * + * @author Arjen Poutsma + */ +public interface EndpointExceptionResolver { + + /** + * Try to resolve the given exception that got thrown during on endpoint execution. + * + * @param messageContext current message context + * @param endpoint the executed endpoint, or null if none chosen at the time of the exception + * @param ex the exception that got thrown during endpoint execution + * @return true if resolved; false otherwise + */ + boolean resolveException(MessageContext messageContext, Object endpoint, Exception ex); +} diff --git a/core/src/main/java/org/springframework/ws/EndpointInterceptor.java b/core/src/main/java/org/springframework/ws/EndpointInterceptor.java new file mode 100644 index 00000000..c02aeb8f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/EndpointInterceptor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +import org.springframework.ws.context.MessageContext; + +/** + * Workflow interface that allows for customized endpoint invocation chains. Applications can register any number of + * existing or custom interceptors for certain groups of endpoints, to add common preprocessing behavior without needing + * to modify each endpoint implementation. + *

+ * A EndpointInterceptor gets called before the appropriate EndpointAdapter triggers the + * invocation of the endpoint itself. This mechanism can be used for a large field of preprocessing aspects, e.g. for + * authorization checks, or message header checks. Its main purpose is to allow for factoring out repetitive endpoint + * code. + *

+ * Typically an interceptor chain is defined per EndpointMapping bean, sharing its granularity. To be able + * to apply a certain interceptor chain to a group of handlers, one needs to map the desired handlers via one + * EndpointMapping bean. + * + * @author Arjen Poutsma + * @see EndpointInvocationChain#getInterceptors() + * @see org.springframework.ws.endpoint.interceptor.EndpointInterceptorAdapter + * @see org.springframework.ws.endpoint.mapping.AbstractEndpointMapping#setInterceptors(EndpointInterceptor[]) + */ +public interface EndpointInterceptor { + + /** + * Processes the incoming request message. + * + * @param messageContext contains the incoming request message + * @param endpoint chosen endpoint to invoke + * @return true to continue processing of the request interceptor chain; false to indicate + * blocking of the request handler chain, without invoking the endpoint + */ + boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception; + + /** + * Processes the outgoing response message. + * + * @param messageContext contains both request and response messages + * @param endpoint chosen endpoint to invoke + * @return true to continue processing of the reponse interceptor chain; false to indicate + * blocking of the response handler chain. + */ + boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception; + +} diff --git a/core/src/main/java/org/springframework/ws/EndpointInvocationChain.java b/core/src/main/java/org/springframework/ws/EndpointInvocationChain.java new file mode 100644 index 00000000..8e09d3e7 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/EndpointInvocationChain.java @@ -0,0 +1,69 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +/** + * Endpoint invocation chain, consisting of an endpoint object and any preprocessing interceptors. + * + * @author Arjen Poutsma + * @see EndpointInterceptor + */ +public class EndpointInvocationChain { + + private Object endpoint; + + private EndpointInterceptor[] interceptors; + + /** + * Create new EndpointInvocationChain. + * + * @param endpoint the endpoint object to invoke + */ + public EndpointInvocationChain(Object endpoint) { + this.endpoint = endpoint; + } + + /** + * Create new EndpointInvocationChain. + * + * @param endpoint the endpoint object to invoke + * @param interceptors the array of interceptors to apply + */ + public EndpointInvocationChain(Object endpoint, EndpointInterceptor[] interceptors) { + this.endpoint = endpoint; + this.interceptors = interceptors; + } + + /** + * Returns the endpoint object to invoke. + * + * @return the endpoint object + */ + public Object getEndpoint() { + return endpoint; + } + + /** + * Returns the array of interceptors to apply before the handler executes. + * + * @return the array of interceptors + */ + public EndpointInterceptor[] getInterceptors() { + return interceptors; + } + +} diff --git a/core/src/main/java/org/springframework/ws/EndpointMapping.java b/core/src/main/java/org/springframework/ws/EndpointMapping.java new file mode 100644 index 00000000..c582fb17 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/EndpointMapping.java @@ -0,0 +1,58 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +import org.springframework.ws.context.MessageContext; + +/** + * Interface to be implemented by objects that define a mapping between message requests and endpoint objects. + *

+ * This class can be implemented by application developers, although this is not necessary, as + * PayloadRootQNameEndpointMapping and SoapActionEndpointMapping are included. + *

+ * HandlerMapping implementations can support mapped interceptors but do not have to. An endpoint will always be wrapped + * in a EndpointExecutionChain instance, optionally accompanied by some EndpointInterceptor + * instances. The MessageDispacher will first call each EndpointInterceptor's + * handlerRequest method in the given order, finally invoking the endpoint itself if all + * handlerRequest methods have returned true. + * + * @author Arjen Poutsma + * @see org.springframework.ws.endpoint.mapping.AbstractEndpointMapping + * @see org.springframework.ws.endpoint.mapping.PayloadRootQNameEndpointMapping + * @see org.springframework.ws.soap.endpoint.mapping.SoapActionEndpointMapping + */ +public interface EndpointMapping { + + /** + * Returns an endpoint and any interceptors for this message context. The choice may be made on message contents, + * transport request url, a routing table, or any factor the implementing class chooses. + *

+ * The returned EndpointExecutionChain contains an endpoint Object, rather than even a tag interface, + * so that endpoints are not constrained in any way. For example, a EndpointAdapter could be written to + * allow another framework's endpoint objects to be used. + *

+ * Returns null if no match was found. This is not an error. The MessageDispatcher will + * query all registered EndpointMapping beans to find a match, and only decide there is an error if + * none can find an endpoint. + * + * @return a HandlerExecutionChain instance containing endpoint object and any interceptors, or null if + * no mapping found + * @throws Exception if there is an internal error + */ + EndpointInvocationChain getEndpoint(MessageContext messageContext) throws Exception; + +} diff --git a/core/src/main/java/org/springframework/ws/MessageDispatcher.java b/core/src/main/java/org/springframework/ws/MessageDispatcher.java new file mode 100644 index 00000000..de63fa63 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/MessageDispatcher.java @@ -0,0 +1,398 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.support.ApplicationObjectSupport; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.endpoint.MessageEndpoint; + +/** + * Central dispatcher for use withing Spring-WS. Dispatches SOAP messages to registered endoints. + *

+ * This dispatcher is quite similar to Spring MVCs DispatcherServlet. Just like it's counterpart, this + * dispatcher is very flexible.

+ * A web application can use any number of MessageDispatchers. Since a MessageDispatcher + * also implements MessageEndpoint, it is also possible to chain them: one dispatcher can be registered as + * the endpoint of another. + * + * @author Arjen Poutsma + * @see EndpointMapping + * @see EndpointAdapter + * @see EndpointExceptionResolver + * @see org.springframework.web.servlet.DispatcherServlet + */ +public class MessageDispatcher extends ApplicationObjectSupport implements MessageEndpoint, BeanNameAware { + + /** + * Name of the class path resource (relative to the MessageDispatcher class) that defines MessageDispatcher's + * default strategy names. + */ + private static final String DEFAULT_STRATEGIES_PATH = "MessageDispatcher.properties"; + + /** + * Log category to use when no mapped endpoint is found for a request. + */ + public static final String ENDPOINT_NOT_FOUND_LOG_CATEGORY = "org.springframework.ws.EndpointNotFound"; + + /** + * Additional logger to use when no mapped endpoint is found for a request. + */ + protected static final Log endpointNotFoundLogger = LogFactory.getLog(ENDPOINT_NOT_FOUND_LOG_CATEGORY); + + private static final Properties defaultStrategies = new Properties(); + + static { + // Load default strategy implementations from properties file. + // This is currently strictly internal and not meant to be customized + // by application developers. + try { + ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, MessageDispatcher.class); + InputStream is = resource.getInputStream(); + try { + defaultStrategies.load(is); + } + finally { + is.close(); + } + } + catch (IOException ex) { + throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); + } + } + + /** + * List of EndpointMappings used in this dispatcher + */ + private List endpointMappings; + + /** + * List of EndpointAdapters used in this dispatcher + */ + private List endpointAdapters; + + /** + * List of EndpointExceptionResolvers used in this dispatcher + */ + private List endpointExceptionResolvers; + + /** + * The registered bean name for this dispatcher. + */ + private String beanName; + + /** + * Initializes the dispatcher. + */ + public void initApplicationContext() throws BeansException { + initEndpointMappings(); + initEndpointAdapters(); + initEndpointExceptionResolvers(); + } + + /** + * Initialize the EndpointMappings used in this class. + */ + private void initEndpointMappings() { + // Ensure we have at least one EndpointMapping, by registering a default if not others are found + if (endpointMappings == null) { + endpointMappings = getDefaultStrategies(EndpointMapping.class); + logger.info("No EndpointMapping found: using default"); + } + } + + /** + * Initialize the EndpointAdapters used by this class. + */ + private void initEndpointAdapters() { + if (endpointAdapters == null) { + // Ensure we have at least some EndpointAdapters, by registereing a default if no others are found + endpointAdapters = getDefaultStrategies(EndpointAdapter.class); + logger.info("No EndpointAdapters found: using default"); + } + } + + /** + * Initialize the EndpointExceptionResolvers used in this class. + */ + private void initEndpointExceptionResolvers() { + if (endpointExceptionResolvers == null) { + // Ensure we have at least some EndpointExceptionResolvers, by registereing a default if no others are found + endpointAdapters = getDefaultStrategies(EndpointExceptionResolver.class); + logger.info("No EndpointExceptionResolver found: using default"); + } + } + + /** + * Create a List of default strategy objects for the given strategy interface.

The default implementation uses + * the "MessageDispatcher.properties" file (in the same package as the MessageDispatcher class) to determine the + * class names. It instantiates the strategy objects and satisifies ApplicationContextAware and InitializingBean if + * necessary. + * + * @param strategyInterface the strategy interface + * @return the List of corresponding strategy objects + * @throws BeansException if initialization failed + */ + protected List getDefaultStrategies(Class strategyInterface) throws BeansException { + String key = strategyInterface.getName(); + try { + List strategies = null; + String value = defaultStrategies.getProperty(key); + if (value != null) { + String[] classNames = StringUtils.commaDelimitedListToStringArray(value); + strategies = new ArrayList(classNames.length); + for (int i = 0; i < classNames.length; i++) { + Class clazz = Class.forName(classNames[i], true, getClass().getClassLoader()); + Object strategy = BeanUtils.instantiateClass(clazz); + if (strategy instanceof ApplicationContextAware) { + ((ApplicationContextAware) strategy).setApplicationContext(getApplicationContext()); + } + strategies.add(strategy); + } + } + else { + strategies = Collections.EMPTY_LIST; + } + return strategies; + } + catch (ClassNotFoundException ex) { + throw new BeanInitializationException( + "Could not find MessageDispatcher's default strategy class for interface [" + key + "]", + ex); + } + } + + public void invoke(MessageContext messageContext) throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("MessageDispatcher with name '" + beanName + "' received request [" + + messageContext.getRequest() + "]"); + } + dispatch(messageContext); + if (logger.isDebugEnabled() && messageContext.hasResponse()) { + logger.debug("MessageDispatcher with name '" + beanName + "' sends response [" + + messageContext.getResponse() + "]"); + } + } + + /** + * Dispatches the request in the given MessageContext according to the configuration. + * + * @param messageContext the message context + * @throws NoEndpointFoundException thrown when an endpoint cannot be resolved for the incoming message + */ + protected final void dispatch(MessageContext messageContext) throws Exception { + EndpointInvocationChain mappedEndpoint = null; + int interceptorIndex = -1; + try { + // Determine endpoint for the current context + mappedEndpoint = getEndpoint(messageContext); + if (mappedEndpoint == null || mappedEndpoint.getEndpoint() == null) { + throw new NoEndpointFoundException(messageContext.getRequest()); + } + if (!handleRequest(mappedEndpoint, messageContext)) { + return; + } + // Apply handleRequest of registered interceptors + if (!ObjectUtils.isEmpty(mappedEndpoint.getInterceptors())) { + for (int i = 0; i < mappedEndpoint.getInterceptors().length; i++) { + EndpointInterceptor interceptor = mappedEndpoint.getInterceptors()[i]; + interceptorIndex = i; + if (!interceptor.handleRequest(messageContext, mappedEndpoint.getEndpoint())) { + triggerHandleResponse(mappedEndpoint, interceptorIndex, messageContext); + return; + } + } + } + // Acutally invoke the endpoint + EndpointAdapter endpointAdapter = getEndpointAdapter(mappedEndpoint.getEndpoint()); + endpointAdapter.invoke(messageContext, mappedEndpoint.getEndpoint()); + + // Apply handleResponse methods of registered interceptors + triggerHandleResponse(mappedEndpoint, interceptorIndex, messageContext); + } + catch (NoEndpointFoundException ex) { + // No triggering of interceptors if no endpoint is found + if (endpointNotFoundLogger.isWarnEnabled()) { + endpointNotFoundLogger.warn("No endpoint mapping found for [" + messageContext.getRequest() + "]"); + } + throw ex; + } + catch (Exception ex) { + Object endpoint = mappedEndpoint != null ? mappedEndpoint.getEndpoint() : null; + processEndpointException(messageContext, endpoint, ex); + triggerHandleResponse(mappedEndpoint, interceptorIndex, messageContext); + } + } + + /** + * Callback for pre-processing of given invocation chain and message context. Gets called before invocation of + * handleRequest on the interceptors. + *

+ * Default implementation does nothing, and returns true. + * + * @param mappedEndpoint the mapped EndpointInvocationChain + * @param messageContext the message context + * @return true if processing should continue; false otherwise + */ + protected boolean handleRequest(EndpointInvocationChain mappedEndpoint, MessageContext messageContext) { + return true; + } + + /** + * Trigger handleResponse or handleFault on the mapped EndpointInterceptors. Will just invoke said method on all + * interceptors whose handleRequest invocation returned true, in addition to the last interceptor who + * returned false. + * + * @param mappedEndpoint the mapped EndpointInvocationChain + * @param interceptorIndex index of last interceptor that was called + * @param messageContext the message context, whose request and response are filled + * @see EndpointInterceptor#handleResponse(MessageContext, Object) + */ + protected void triggerHandleResponse(EndpointInvocationChain mappedEndpoint, + int interceptorIndex, + MessageContext messageContext) throws Exception { + if (mappedEndpoint != null && messageContext.hasResponse() && + !ObjectUtils.isEmpty(mappedEndpoint.getInterceptors())) { + boolean resume = true; + for (int i = interceptorIndex; resume && i >= 0; i--) { + EndpointInterceptor interceptor = mappedEndpoint.getInterceptors()[i]; + resume = interceptor.handleResponse(messageContext, mappedEndpoint.getEndpoint()); + } + } + } + + /** + * Returns the endpoint for this request. All endpoint mappings are tried, in order. + * + * @return the EndpointInvocationChain, or null if no endpoint could be found. + */ + protected EndpointInvocationChain getEndpoint(MessageContext messageContext) throws Exception { + for (Iterator iterator = endpointMappings.iterator(); iterator.hasNext();) { + EndpointMapping endpointMapping = (EndpointMapping) iterator.next(); + EndpointInvocationChain endpoint = endpointMapping.getEndpoint(messageContext); + if (endpoint != null) { + if (logger.isDebugEnabled()) { + logger.debug("Endpoint mapping [" + endpointMapping + "] maps request to endpoint [" + + endpoint.getEndpoint() + "]"); + } + return endpoint; + } + else if (logger.isDebugEnabled()) { + logger.debug("Endpoint mapping [" + endpointMapping + "] has no mapping for request"); + } + + } + return null; + } + + /** + * Returns the EndpointAdapter for the given endpoint. + * + * @param endpoint the endpoint to find an adapter for + * @return the adapter + */ + protected EndpointAdapter getEndpointAdapter(Object endpoint) { + for (Iterator iterator = endpointAdapters.iterator(); iterator.hasNext();) { + EndpointAdapter endpointAdapter = (EndpointAdapter) iterator.next(); + if (logger.isDebugEnabled()) { + logger.debug("Testing endpoint adapter [" + endpointAdapter + "]"); + } + if (endpointAdapter.supports(endpoint)) { + return endpointAdapter; + } + } + throw new IllegalStateException("No adapter for endpoint [" + endpoint + "]: Does your endpoint implement a " + + "supported interface like MessageHandler or PayloadEndpoint?"); + } + + /** + * Determine an error SOAPMessage respone via the registered EndpointExceptionResolvers. + * Most likely, the response contains a SOAPFault. If no suitable resolver was found, the exception is + * rethrown. + * + * @param messageContext current SOAPMessage request + * @param endpoint the executed endpoint, or null if none chosen at the time of the exception + * @param ex the exception that got thrown during handler execution + * @throws Exception if no suitable resolver is found + */ + protected void processEndpointException(MessageContext messageContext, Object endpoint, Exception ex) + throws Exception { + for (Iterator iterator = endpointExceptionResolvers.iterator(); iterator.hasNext();) { + EndpointExceptionResolver resolver = (EndpointExceptionResolver) iterator.next(); + if (logger.isDebugEnabled()) { + logger.debug("Testing endpoint exception resolver [" + resolver + "]"); + } + if (resolver.resolveException(messageContext, endpoint, ex)) { + logger.warn("Endpoint invocation resulted in exception - responding with SOAP Fault", ex); + return; + } + } + // exception not resolved + throw ex; + } + + /** + * Sets the EndpointMappings to use by this MessageDispatcher. + */ + public void setEndpointMappings(List endpointMappings) { + this.endpointMappings = endpointMappings; + } + + /** + * Sets the EndpointAdapters to use by this MessageDispatcher. + */ + public void setEndpointAdapters(List endpointAdapters) { + this.endpointAdapters = endpointAdapters; + } + + /** + * Sets the EndpointExceptionResolvers to use by this MessageDispatcher. + */ + public void setEndpointExceptionResolvers(List endpointExceptionResolvers) { + this.endpointExceptionResolvers = endpointExceptionResolvers; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + +} diff --git a/core/src/main/java/org/springframework/ws/NoEndpointFoundException.java b/core/src/main/java/org/springframework/ws/NoEndpointFoundException.java new file mode 100644 index 00000000..113644a0 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/NoEndpointFoundException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +/** + * Exception thrown when an endpoint cannot be resolved for an incoming message request. + * + * @author Arjen Poutsma + */ +public final class NoEndpointFoundException extends WebServiceException { + + public NoEndpointFoundException(WebServiceMessage request) { + super("No endpoint can be found for request [" + request + "]"); + } +} diff --git a/core/src/main/java/org/springframework/ws/WebServiceException.java b/core/src/main/java/org/springframework/ws/WebServiceException.java new file mode 100644 index 00000000..e030ef7b --- /dev/null +++ b/core/src/main/java/org/springframework/ws/WebServiceException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +import java.io.Serializable; + +import org.springframework.core.NestedRuntimeException; + +/** + * Root of the hierarchy of Web Service exceptions. + * + * @author Arjen Poutsma + */ +public abstract class WebServiceException extends NestedRuntimeException implements Serializable { + + /** + * Constructor for WebServiceException. + */ + public WebServiceException(String msg) { + super(msg); + } + + /** + * Constructor for WebServiceException. + */ + public WebServiceException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/WebServiceMessage.java b/core/src/main/java/org/springframework/ws/WebServiceMessage.java new file mode 100644 index 00000000..ddde8ffe --- /dev/null +++ b/core/src/main/java/org/springframework/ws/WebServiceMessage.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +import java.io.IOException; +import java.io.OutputStream; +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +/** + * Represents a protocol agnostic XML message. Contains methods that provide access to the payload of the message. + * + * @author Arjen Poutsma + * @see org.springframework.ws.soap.SoapMessage + */ +public interface WebServiceMessage { + + /** + * Returns the contents of the message as a java.xml.transform.Source. Depending on the implementation, + * this can be retrieved multiple times, or just a single time. + * + * @return the message contents + */ + Source getPayloadSource(); + + /** + * Returns the contents of the message as a java.xml.transform.Result. Some implementations are + * read-only, and may throws an UnsupportedOperationException. + * + * @return the messaage contents + * @throws UnsupportedOperationException if the message is read-only + */ + Result getPayloadResult(); + + /** + * Writes the entire message to the given output stream. + * + * @param outputStream the stream to write to + * @throws IOException if an I/O exception occurs + */ + void writeTo(OutputStream outputStream) throws IOException; + +} diff --git a/core/src/main/java/org/springframework/ws/WebServiceMessageException.java b/core/src/main/java/org/springframework/ws/WebServiceMessageException.java new file mode 100644 index 00000000..991cff93 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/WebServiceMessageException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +/** + * Base class for all web service message exceptions. + * + * @author Arjen Poutsma + */ +public abstract class WebServiceMessageException extends WebServiceException { + + /** + * Constructor for WebServiceMessageException. + */ + public WebServiceMessageException(String msg) { + super(msg); + } + + /** + * Constructor for WebServiceMessageException. + */ + public WebServiceMessageException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/context/AbstractMessageContext.java b/core/src/main/java/org/springframework/ws/context/AbstractMessageContext.java new file mode 100644 index 00000000..22be2c99 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/context/AbstractMessageContext.java @@ -0,0 +1,123 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.context; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.util.Assert; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.transport.TransportRequest; + +/** + * Abstract implementation of the MessageContext interface. Contains functionality to set and remove + * properties. + * + * @author Arjen Poutsma + */ +public abstract class AbstractMessageContext implements MessageContext { + + private WebServiceMessage request; + + private WebServiceMessage response; + + private final TransportRequest transportRequest; + + /** + * Keys are Strings, values are Objects. Lazily initalized by + * getProperties(). + */ + private Map properties; + + /** + * Construct a new instance of the AbstractMessageContext with the given request message and + * transportRequest. + */ + protected AbstractMessageContext(WebServiceMessage request, TransportRequest transportRequest) { + Assert.notNull(request, "No request given"); + Assert.notNull(transportRequest, "No transport request given"); + this.request = request; + this.transportRequest = transportRequest; + } + + public final WebServiceMessage getRequest() { + return request; + } + + /** + * Protected method that sets the request message directly. + */ + protected final void setRequest(WebServiceMessage request) { + Assert.notNull(request); + this.request = request; + } + + public final WebServiceMessage getResponse() { + if (response == null) { + response = createResponseMessage(); + } + return response; + } + + public final boolean hasResponse() { + return response != null; + } + + public final TransportRequest getTransportRequest() { + return transportRequest; + } + + /** + * Protected method that sets the response message directly. + */ + protected final void setResponse(WebServiceMessage response) { + Assert.notNull(response); + this.response = response; + } + + private Map getProperties() { + if (properties == null) { + properties = new HashMap(); + } + return properties; + } + + public boolean containsProperty(String name) { + return getProperties().containsKey(name); + } + + public Object getProperty(String name) { + return getProperties().get(name); + } + + public String[] getPropertyNames() { + return (String[]) getProperties().keySet().toArray(new String[getProperties().size()]); + } + + public void removeProperty(String name) { + getProperties().remove(name); + } + + public void setProperty(String name, Object value) { + getProperties().put(name, value); + } + + /** + * Abstract template method that creates a new WebServiceMessage. + */ + protected abstract WebServiceMessage createResponseMessage(); +} diff --git a/core/src/main/java/org/springframework/ws/context/MessageContext.java b/core/src/main/java/org/springframework/ws/context/MessageContext.java new file mode 100644 index 00000000..b5071f8b --- /dev/null +++ b/core/src/main/java/org/springframework/ws/context/MessageContext.java @@ -0,0 +1,113 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.context; + +import java.io.IOException; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.transport.TransportRequest; +import org.springframework.ws.transport.TransportResponse; + +/** + * Context holder for message requests. Contains both the message request as well as the response. Response message are + * usually lazily created. + *

+ * MessageContext implementations are constructed using a MessageContextFactory, taking a + * TransportContext as a parameter. + * + * @author Arjen Poutsma + * @see MessageContextFactory#createContext(org.springframework.ws.transport.TransportContext) + */ +public interface MessageContext { + + /** + * Returns the request message. + * + * @return the request message + */ + WebServiceMessage getRequest(); + + /** + * Returns the response message. Creates a new response if no response is present. + * + * @return the response message + * @see #hasResponse() + */ + WebServiceMessage getResponse(); + + /** + * Indicates whether this context has a resonse. + * + * @return true if this context has a response; false otherwise + */ + boolean hasResponse(); + + /** + * Returns the transport request used to create this context. Call be used for URL-based message routing. + * + * @return the transport request + */ + TransportRequest getTransportRequest(); + + /** + * Sends the response to the given transport response. + * + * @param transportResponse the transport used for sending + * @throws IOException if an I/O exception occurs + */ + void sendResponse(TransportResponse transportResponse) throws IOException; + + /** + * Sets the name and value of a property associated with the MessageContext. If the + * MessageContext contains a value of the same property, the old value is replaced. + * + * @param name name of the property associated with the value + * @param value value of the property + */ + void setProperty(String name, Object value); + + /** + * Gets the value of a specific property from the MessageContext. + * + * @param name name of the property whose value is to be retrieved + * @return value of the property + */ + Object getProperty(String name); + + /** + * Removes a property from the MessageContext. + * + * @param name name of the property to be removed + */ + void removeProperty(String name); + + /** + * Check if this message context contains a property with the given name. + * + * @param name the name of the property to look fo + * @return true if the MessageContext contains the property; false otherwise + */ + boolean containsProperty(String name); + + /** + * Return the names of all properties in this MessageContext. + * + * @return the names of all properties in this context, or an empty array if none defined + */ + String[] getPropertyNames(); + +} \ No newline at end of file diff --git a/core/src/main/java/org/springframework/ws/context/MessageContextFactory.java b/core/src/main/java/org/springframework/ws/context/MessageContextFactory.java new file mode 100644 index 00000000..2b17f279 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/context/MessageContextFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.context; + +import java.io.IOException; + +import org.springframework.ws.transport.TransportContext; + +/** + * The MessageContextFactory serves as factory for MessageContexts. Allows creation of + * contexts based on TransportRequest. + * + * @author Arjen Poutsma + */ +public interface MessageContextFactory { + + /** + * Creates a MessageContext based on the given transport context. Implementations use the context + * request's input stream to create a request message, and possibly copy the request headers to the message. + *

+ * Implementations are free to store the transport context for later reference. For instance, streaming + * implementations of MessageContextFactory might use the transport response to directly write a + * response message. + * + * @param transportContext the transport context which contains the request + * @return the created message context + * @throws IOException if an I/O exception occurs + */ + MessageContext createContext(TransportContext transportContext) throws IOException; + +} diff --git a/core/src/main/java/org/springframework/ws/context/package.html b/core/src/main/java/org/springframework/ws/context/package.html new file mode 100644 index 00000000..3ac40184 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/context/package.html @@ -0,0 +1,5 @@ + + +Contains the MessageContext, and MessageContextFactory interfaces. + + diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractDom4jPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractDom4jPayloadEndpoint.java new file mode 100644 index 00000000..a3acd9eb --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractDom4jPayloadEndpoint.java @@ -0,0 +1,62 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; + +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.io.DocumentResult; +import org.dom4j.io.DocumentSource; + +/** + * Abstract base class for endpoints that handle the message payload as dom4j elements. Offers the message payload as a + * dom4j Element, and allows subclasses to create a response by returning an Element. + *

+ * An AbstractDom4JPayloadEndpoint only accept one payload element. Multiple payload elements are not in + * accordance with WS-I. + * + * @author Arjen Poutsma + * @see org.dom4j.Element + */ +public abstract class AbstractDom4jPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint { + + public final Source invoke(Source request) throws Exception { + Transformer transformer = createTransformer(); + DocumentResult dom4jResult = new DocumentResult(); + transformer.transform(request, dom4jResult); + Element requestElement = dom4jResult.getDocument().getRootElement(); + Document responseDocument = DocumentHelper.createDocument(); + Element responseElement = invokeInternal(requestElement, responseDocument); + return responseElement != null ? new DocumentSource(responseElement) : null; + } + + /** + * Template method. Subclasses must implement this. Offers the request payload as a dom4j Element, and + * allows subclasses to return a response Element. + *

+ * The given dom4j Document is to be used for constructing a response element, by using + * addElement. + * + * @param requestElement the contents of the SOAP message as dom4j elements + * @param responseDocument a dom4j document to be used for constructing a response + * @return the response element. Can be null to specify no response. + */ + protected abstract Element invokeInternal(Element requestElement, Document responseDocument) throws Exception; +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractDomPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractDomPayloadEndpoint.java new file mode 100644 index 00000000..5c6d4a81 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractDomPayloadEndpoint.java @@ -0,0 +1,123 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Abstract base class for endpoints that handle the message payload as DOM elements. Offers the message payload as a + * DOM Element, and allows subclasses to create a response by returning an Element. + *

+ * An AbstractDomPayloadEndpoint only accept one payload element. Multiple payload elements are not in + * accordance with WS-I. + * + * @author Arjen Poutsma + * @author Alef Arendsen + * @see #invokeInternal(org.w3c.dom.Element, org.w3c.dom.Document) + */ +public abstract class AbstractDomPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint { + + private DocumentBuilderFactory documentBuilderFactory; + + private boolean validating = false; + + private boolean namespaceAware = true; + + /** + * Set whether or not the XML parser should be XML namespace aware. Default is true. + */ + public void setNamespaceAware(boolean namespaceAware) { + this.namespaceAware = namespaceAware; + } + + /** + * Set if the XML parser should validate the document. Default is false. + */ + public void setValidating(boolean validating) { + this.validating = validating; + } + + public final Source invoke(Source request) throws Exception { + Transformer transformer = createTransformer(); + if (documentBuilderFactory == null) { + documentBuilderFactory = createDocumentBuilderFactory(); + } + DocumentBuilder documentBuilder = createDocumentBuilder(documentBuilderFactory); + Document requestDocument = documentBuilder.newDocument(); + DOMResult domResult = new DOMResult(requestDocument); + transformer.transform(request, domResult); + Element requestElement = (Element) requestDocument.getFirstChild(); + Document resonseDocument = documentBuilder.newDocument(); + Element responseElement = invokeInternal(requestElement, resonseDocument); + if (responseElement != null) { + return new DOMSource(responseElement); + } + else { + return null; + } + } + + /** + * Create a DocumentBuilder that this endpoint will use for parsing XML documents. Can be overridden in + * subclasses, adding further initialization of the builder. + * + * @param factory the DocumentBuilderFactory that the DocumentBuilder should be created with + * @return the DocumentBuilder + * @throws ParserConfigurationException if thrown by JAXP methods + */ + protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory) + throws ParserConfigurationException { + return factory.newDocumentBuilder(); + } + + /** + * Create a DocumentBuilderFactory that this endpoint will use for constructing XML documents. Can be + * overridden in subclasses, adding further initialization of the factory. The resulting + * DocumentBuilderFactory is cached, so this method will only be called once. + * + * @return the DocumentBuilderFactory + * @throws ParserConfigurationException if thrown by JAXP methods + */ + protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserConfigurationException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(this.validating); + factory.setNamespaceAware(this.namespaceAware); + return factory; + } + + /** + * Template method. Subclasses must implement this. Offers the request payload as a DOM Element, and + * allows subclasses to return a response Element. + *

+ * The given DOM Document is to be used for constructing Nodes, by using the various + * create methods. + * + * @param requestElement the contents of the SOAP message as DOM elements + * @param responseDocument a DOM document to be used for constructing Nodes + * @return the response element. Can be null to specify no response. + */ + protected abstract Element invokeInternal(Element requestElement, Document responseDocument) throws Exception; +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractEndpointExceptionResolver.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractEndpointExceptionResolver.java new file mode 100644 index 00000000..a171bf7f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractEndpointExceptionResolver.java @@ -0,0 +1,76 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.ws.EndpointExceptionResolver; +import org.springframework.ws.context.MessageContext; + +/** + * Abstract base class for ExceptionResolvers. Provides a set of mapped endpoints that the resolver should + * map. + * + * @author Arjen Poutsma + */ +public abstract class AbstractEndpointExceptionResolver implements EndpointExceptionResolver { + + protected final Log logger = LogFactory.getLog(getClass()); + + private Set mappedEndpoints; + + /** + * Specify the set of endpoints that this exception resolver should map. The exception mappings and the default + * fault will only apply to the specified endpoints. + *

+ * If no endpoints set, both the exception mappings and the default fault will apply to all handlers. This means + * that a specified default fault will be used as fallback for all exceptions; any further + * EndpointExceptionResolvers in the chain will be ignored in this case. + */ + public void setMappedEndpoints(Set mappedEndpoints) { + this.mappedEndpoints = mappedEndpoints; + } + + /** + * Default implementation. Checks whether the given endpoint is in the set of mapped endpoints. Calls + * resolveExceptionInternal. + * + * @see #resolveExceptionInternal(org.springframework.ws.context.MessageContext, Object, Exception) + */ + public final boolean resolveException(MessageContext messageContext, Object endpoint, Exception ex) { + if (this.mappedEndpoints != null && !this.mappedEndpoints.contains(endpoint)) { + return false; + } + return resolveExceptionInternal(messageContext, endpoint, ex); + } + + /** + * Template method for resolving exceptions. Gets called after resolveException. + * + * @param messageContext current message context + * @param endpoint the executed endpoint, or null if none chosen at the time of the exception + * @param ex the exception that got thrown during endpoint execution + * @return true if resolved; false otherwise + * @see #resolveException(org.springframework.ws.context.MessageContext, Object, Exception) + */ + protected abstract boolean resolveExceptionInternal(MessageContext messageContext, Object endpoint, Exception ex); + + +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractJDomPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractJDomPayloadEndpoint.java new file mode 100644 index 00000000..5ee7b7a7 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractJDomPayloadEndpoint.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; + +import org.jdom.Element; +import org.jdom.transform.JDOMResult; +import org.jdom.transform.JDOMSource; + +/** + * Abstract base class for endpoints that handle the message payload as JDOM elements. Offers the message payload as a + * JDOM Element, and allows subclasses to create a response by returning an Element. + *

+ * An AbstractJDomPayloadEndpoint only accept one payload element. Multiple payload elements are not in + * accordance with WS-I. + * + * @author Arjen Poutsma + * @see Element + */ +public abstract class AbstractJDomPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint { + + public final Source invoke(Source request) throws Exception { + Transformer transformer = createTransformer(); + JDOMResult jdomResult = new JDOMResult(); + transformer.transform(request, jdomResult); + Element requestElement = jdomResult.getDocument().getRootElement(); + Element responseElement = invokeInternal(requestElement); + return responseElement != null ? new JDOMSource(responseElement) : null; + } + + /** + * Template method. Subclasses must implement this. Offers the request payload as a JDOM Element, and + * allows subclasses to return a response Element. + * + * @param requestElement the contents of the SOAP message as JDOM element + * @return the response element. Can be null to specify no response. + */ + protected abstract Element invokeInternal(Element requestElement) throws Exception; + + +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractLoggingInterceptor.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractLoggingInterceptor.java new file mode 100644 index 00000000..b48ec0d4 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractLoggingInterceptor.java @@ -0,0 +1,112 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import java.io.StringWriter; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamResult; + +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; + +/** + * Abstract base class for EndpointInterceptor instances that log a part of a + * WebServiceMessage. By default, both request and response messages are logged, but this behaviour can be + * changed using the logRequest and logResponse properties. + * + * @author Arjen Poutsma + */ +public abstract class AbstractLoggingInterceptor extends TransformerObjectSupport implements EndpointInterceptor { + + private boolean logRequest = true; + + private boolean logResponse = true; + + /** + * Indicates whether the request should be logged. Default is true. + */ + public final void setLogRequest(boolean logRequest) { + this.logRequest = logRequest; + } + + /** + * Indicates whether the response should be logged. Default is true. + */ + public final void setLogResponse(boolean logResponse) { + this.logResponse = logResponse; + } + + /** + * Logs the request message payload. Logging only ocurs if logRequest is set to true, + * which is the default. + * + * @param messageContext the message context + * @return true + * @throws TransformerException when the payload cannot be transformed to a string + */ + public final boolean handleRequest(MessageContext messageContext, Object endpoint) throws TransformerException { + if (logRequest && logger.isDebugEnabled()) { + logMessageSource("Request: ", getSource(messageContext.getRequest())); + } + return true; + } + + /** + * Logs the response message payload. Logging only ocurs if logResponse is set to true, + * which is the default. + * + * @param messageContext the message context + * @return true + * @throws TransformerException when the payload cannot be transformed to a string + */ + public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { + if (logResponse && logger.isDebugEnabled()) { + logMessageSource("Response: ", getSource(messageContext.getResponse())); + } + return true; + } + + private Transformer createNonIndentingTransformer() throws TransformerConfigurationException { + Transformer transformer = createTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "no"); + return transformer; + } + + protected void logMessageSource(String logMessage, Source source) throws TransformerException { + if (source != null) { + Transformer transformer = createNonIndentingTransformer(); + StringWriter writer = new StringWriter(); + transformer.transform(source, new StreamResult(writer)); + logger.debug(logMessage + writer.toString()); + } + } + + /** + * Abstract template method that returns the Source for the given WebServiceMessage. + * + * @param message the message + * @return the source of the message + */ + protected abstract Source getSource(WebServiceMessage message); +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractMarshallingPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractMarshallingPayloadEndpoint.java new file mode 100644 index 00000000..8e6a2962 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractMarshallingPayloadEndpoint.java @@ -0,0 +1,115 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.Unmarshaller; +import org.springframework.util.Assert; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; + +/** + * Endpoint that unmarshals the request payload, and marshals the response object. This endpoint needs a + * Marshaller and Unmarshaller, both of which can be set using properties. An abstract + * template method is invoked using the request object as a parameter, and allows for a response object to be returned. + * + * @author Arjen Poutsma + * @see #setMarshaller(org.springframework.oxm.Marshaller) + * @see Marshaller + * @see #setUnmarshaller(org.springframework.oxm.Unmarshaller) + * @see Unmarshaller + * @see #invokeInternal(Object) + */ +public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpoint, InitializingBean { + + /** + * Logger available to subclasses. + */ + protected final Log logger = LogFactory.getLog(getClass()); + + private Marshaller marshaller; + + private Unmarshaller unmarshaller; + + /** + * Returns the marshalling used for transforming objects into XML. + */ + public final Marshaller getMarshaller() { + return marshaller; + } + + /** + * Sets the marshalling used for transforming objects into XML. + */ + public final void setMarshaller(Marshaller marshaller) { + this.marshaller = marshaller; + } + + /** + * Returns the unmarshaller used for transforming XML into objects. + */ + public final Unmarshaller getUnmarshaller() { + return unmarshaller; + } + + /** + * Sets the unmarshaller used for transforming XML into objects. + */ + public final void setUnmarshaller(Unmarshaller unmarshaller) { + this.unmarshaller = unmarshaller; + } + + public final void afterPropertiesSet() throws Exception { + Assert.notNull(marshaller, "marshaller is required"); + Assert.notNull(unmarshaller, "unmarshaller is required"); + afterMarshallerSet(); + } + + public final void invoke(MessageContext messageContext) throws Exception { + WebServiceMessage request = messageContext.getRequest(); + Object requestObject = unmarshaller.unmarshal(request.getPayloadSource()); + if (logger.isDebugEnabled()) { + logger.debug("Unmarshalled payload request to [" + requestObject + "]"); + } + Object responseObject = invokeInternal(requestObject); + if (responseObject != null) { + if (logger.isDebugEnabled()) { + logger.debug("Marshalling [" + responseObject + "] to response payload"); + } + WebServiceMessage response = messageContext.getResponse(); + marshaller.marshal(responseObject, response.getPayloadResult()); + } + } + + /** + * Template method that gets called after the marshaller and unmarshaller have been set. + */ + public void afterMarshallerSet() throws Exception { + } + + /** + * Template method. Subclasses must implement this. The unmarshaled request object is passed as a parameter, and an + * the returned object is marshalled to a response. If no response is required, return null. + * + * @param requestObject the unnmarshalled message payload as object + * @return the object to be marshalled as response, or null if a response is not required + */ + protected abstract Object invokeInternal(Object requestObject) throws Exception; +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractSaxPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractSaxPayloadEndpoint.java new file mode 100644 index 00000000..e8431413 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractSaxPayloadEndpoint.java @@ -0,0 +1,70 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.sax.SAXResult; + +import org.xml.sax.ContentHandler; + +/** + * Abstract base class for endpoints that handle the message payload with a SAX ContentHandler. Allows + * subclasses to create a response by returning a Source. + *

+ * Implementations of this class should create a new handler for each call of createContentHandler, because + * of thread safety. The handlers is later passed on to createResponse, so it can be used for holding + * request-specific state. + * + * @author Arjen Poutsma + * @see #createContentHandler() + * @see #getResponse(org.xml.sax.ContentHandler) + */ +public abstract class AbstractSaxPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint { + + /** + * Invokes the provided ContentHandler and LexicalHandler on the given request. After + * parsing has been done, the provided response is returned. + * + * @see #createContentHandler() + * @see #getResponse(org.xml.sax.ContentHandler) + */ + public final Source invoke(Source request) throws Exception { + Transformer transformer = createTransformer(); + ContentHandler contentHandler = createContentHandler(); + SAXResult result = new SAXResult(contentHandler); + transformer.transform(request, result); + return getResponse(contentHandler); + } + + /** + * Returns the SAX ContentHandler used to parse the incoming request payload. A new instance should be + * created for each call, because of thread-safety. The content handler can be used to hold request-specific state. + * + * @return a SAX content handler to be used for parsing + */ + protected abstract ContentHandler createContentHandler(); + + /** + * Returns the response to be given, if any. This method is called after the request payload has been parse using + * the SAX ContentHandler. The passed ContentHandler is created by + * createContentHandler: it can be used to hold request-specific state. + * + * @param contentHandler the content handler used to parse the request + */ + protected abstract Source getResponse(ContentHandler contentHandler); +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxEventPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxEventPayloadEndpoint.java new file mode 100644 index 00000000..58a0f4d1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxEventPayloadEndpoint.java @@ -0,0 +1,235 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.xml.transform.StaxResult; +import org.springframework.xml.transform.StaxSource; + +/** + * Abstract base class for endpoints that handle the message payload with event-based StAX. Allows subclasses to read + * the request with a XMLEventReader, and to create a response using a XMLEventWriter. + * + * @author Arjen Poutsma + * @see #invokeInternal(javax.xml.stream.XMLEventReader, javax.xml.stream.util.XMLEventConsumer, + * javax.xml.stream.XMLEventFactory) + * @see XMLEventReader + * @see XMLEventWriter + */ +public abstract class AbstractStaxEventPayloadEndpoint extends AbstractStaxPayloadEndpoint implements MessageEndpoint { + + private XMLEventFactory eventFactory; + + public final void invoke(MessageContext messageContext) throws Exception { + XMLEventReader eventReader = getEventReader(messageContext.getRequest().getPayloadSource()); + XMLEventWriter streamWriter = new ResponseCreatingEventWriter(messageContext); + invokeInternal(eventReader, streamWriter, getEventFactory()); + streamWriter.flush(); + } + + /** + * Create a XMLEventFactory that this endpoint will use to create XMLEvents. Can be + * overridden in subclasses, adding further initialization of the factory. The resulting + * XMLEventFactory is cached, so this method will only be called once. + * + * @return the created XMLEventFactory + */ + protected XMLEventFactory createXmlEventFactory() { + return XMLEventFactory.newInstance(); + } + + /** + * Returns an XMLEventFactory to read XML from. + */ + private XMLEventFactory getEventFactory() { + if (eventFactory == null) { + eventFactory = createXmlEventFactory(); + } + return eventFactory; + } + + private XMLEventReader getEventReader(Source source) throws XMLStreamException, TransformerException { + XMLEventReader eventReader = null; + if (source instanceof StaxSource) { + StaxSource staxSource = (StaxSource) source; + eventReader = staxSource.getXMLEventReader(); + if (eventReader == null && staxSource.getXMLStreamReader() != null) { + try { + eventReader = getInputFactory().createXMLEventReader(staxSource.getXMLStreamReader()); + } + catch (XMLStreamException ex) { + // ignore + } + } + } + if (eventReader == null) { + try { + eventReader = getInputFactory().createXMLEventReader(source); + } + catch (XMLStreamException ex) { + // ignore + } + } + if (eventReader == null) { + // as a final resort, transform the source to a stream, and read from that + Transformer transformer = createTransformer(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + transformer.transform(source, new StreamResult(os)); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + eventReader = getInputFactory().createXMLEventReader(is); + } + return eventReader; + } + + private XMLEventWriter getEventWriter(Result result) { + XMLEventWriter eventWriter = null; + if (result instanceof StaxResult) { + StaxResult staxResult = (StaxResult) result; + eventWriter = staxResult.getXMLEventWriter(); + } + if (eventWriter == null) { + try { + eventWriter = getOutputFactory().createXMLEventWriter(result); + } + catch (XMLStreamException ex) { + // ignore + } + } + return eventWriter; + } + + /** + * Template method. Subclasses must implement this. Offers the request payload as a XMLEventReader, and + * a XMLEventWriter to write the response payload to. + * + * @param eventReader the reader to read the payload events from + * @param eventWriter the writer to write payload events to + * @param eventFactory an XMLEventFactory that can be used to create events + */ + protected abstract void invokeInternal(XMLEventReader eventReader, + XMLEventConsumer eventWriter, + XMLEventFactory eventFactory) throws Exception; + + /** + * Implementation of the XMLEventWriter interface that creates a response + * WebServiceMessage as soon as any method is called, thus lazily creating the response. + */ + private class ResponseCreatingEventWriter implements XMLEventWriter { + + private XMLEventWriter eventWriter; + + private MessageContext messageContext; + + private ByteArrayOutputStream os; + + public ResponseCreatingEventWriter(MessageContext messageContext) { + this.messageContext = messageContext; + } + + public NamespaceContext getNamespaceContext() { + return eventWriter.getNamespaceContext(); + } + + public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { + createEventWriter(); + eventWriter.setNamespaceContext(context); + } + + public void add(XMLEventReader reader) throws XMLStreamException { + createEventWriter(); + while (reader.hasNext()) { + add(reader.nextEvent()); + } + } + + public void add(XMLEvent event) throws XMLStreamException { + createEventWriter(); + eventWriter.add(event); + if (event.isEndDocument()) { + if (os != null) { + eventWriter.flush(); + // if we used an output stream cache, we have to transform it to the response again + try { + Transformer transformer = createTransformer(); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + transformer.transform(new StreamSource(is), messageContext.getResponse().getPayloadResult()); + } + catch (TransformerException ex) { + throw new XMLStreamException(ex); + } + } + } + } + + public void close() throws XMLStreamException { + if (eventWriter != null) { + eventWriter.close(); + } + } + + public void flush() throws XMLStreamException { + if (eventWriter != null) { + eventWriter.flush(); + } + } + + public String getPrefix(String uri) throws XMLStreamException { + createEventWriter(); + return eventWriter.getPrefix(uri); + } + + public void setDefaultNamespace(String uri) throws XMLStreamException { + createEventWriter(); + eventWriter.setDefaultNamespace(uri); + } + + public void setPrefix(String prefix, String uri) throws XMLStreamException { + createEventWriter(); + eventWriter.setPrefix(prefix, uri); + } + + private void createEventWriter() throws XMLStreamException { + if (eventWriter == null) { + WebServiceMessage response = messageContext.getResponse(); + eventWriter = getEventWriter(response.getPayloadResult()); + if (eventWriter == null) { + // as a final resort, use a stream, and transform that at endDocument() + os = new ByteArrayOutputStream(); + eventWriter = getOutputFactory().createXMLEventWriter(os); + } + } + } + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxPayloadEndpoint.java new file mode 100644 index 00000000..3d57418c --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxPayloadEndpoint.java @@ -0,0 +1,77 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; + +/** + * Abstract base class for endpoints use StAX. Provides an XMLInputFactory and an + * XMLOutputFactory. + * + * @author Arjen Poutsma + * @see XMLInputFactory + * @see XMLOutputFactory + */ +public abstract class AbstractStaxPayloadEndpoint extends TransformerObjectSupport { + + private XMLInputFactory inputFactory; + + private XMLOutputFactory outputFactory; + + /** + * Returns an XMLInputFactory to read XML from. + */ + protected final XMLInputFactory getInputFactory() { + if (inputFactory == null) { + inputFactory = createXmlInputFactory(); + } + return inputFactory; + } + + /** + * Returns an XMLOutputFactory to write XML to. + */ + protected final XMLOutputFactory getOutputFactory() { + if (outputFactory == null) { + outputFactory = createXmlOutputFactory(); + } + return outputFactory; + } + + /** + * Create a XMLInputFactory that this endpoint will use to create XMLStreamReaders or + * XMLEventReader. Can be overridden in subclasses, adding further initialization of the factory. The + * resulting XMLInputFactory is cached, so this method will only be called once. + * + * @return the created XMLInputFactory + */ + protected XMLInputFactory createXmlInputFactory() { + return XMLInputFactory.newInstance(); + } + + /** + * Create a XMLOutputFactory that this endpoint will use to create XMLStreamWriterss or + * XMLEventWriters. Can be overridden in subclasses, adding further initialization of the factory. The + * resulting XMLOutputFactory is cached, so this method will only be called once. + * + * @return the created XMLOutputFactory + */ + protected XMLOutputFactory createXmlOutputFactory() { + return XMLOutputFactory.newInstance(); + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxStreamPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxStreamPayloadEndpoint.java new file mode 100644 index 00000000..2448f217 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractStaxStreamPayloadEndpoint.java @@ -0,0 +1,321 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import javax.xml.namespace.NamespaceContext; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.xml.stream.XmlEventStreamReader; +import org.springframework.xml.transform.StaxResult; +import org.springframework.xml.transform.StaxSource; + +/** + * Abstract base class for endpoints that handle the message payload with streaming StAX. Allows subclasses to read the + * request with a XMLStreamReader, and to create a response using a XMLStreamWriter. + * + * @author Arjen Poutsma + * @see #invokeInternal(javax.xml.stream.XMLStreamReader, javax.xml.stream.XMLStreamWriter) + * @see XMLStreamReader + * @see XMLStreamWriter + */ +public abstract class AbstractStaxStreamPayloadEndpoint extends AbstractStaxPayloadEndpoint implements MessageEndpoint { + + public final void invoke(MessageContext messageContext) throws Exception { + XMLStreamReader streamReader = getStreamReader(messageContext.getRequest().getPayloadSource()); + XMLStreamWriter streamWriter = new ResponseCreatingStreamWriter(messageContext); + invokeInternal(streamReader, streamWriter); + streamWriter.close(); + } + + private XMLStreamReader getStreamReader(Source source) throws XMLStreamException, TransformerException { + XMLStreamReader streamReader = null; + if (source instanceof StaxSource) { + streamReader = ((StaxSource) source).getXMLStreamReader(); + StaxSource staxSource = (StaxSource) source; + streamReader = staxSource.getXMLStreamReader(); + if (streamReader == null && staxSource.getXMLEventReader() != null) { + try { + streamReader = new XmlEventStreamReader(staxSource.getXMLEventReader()); + } + catch (XMLStreamException ex) { + // ignore + } + } + + } + if (streamReader == null) { + try { + streamReader = getInputFactory().createXMLStreamReader(source); + } + catch (XMLStreamException ex) { + // ignore + } + } + if (streamReader == null) { + // as a final resort, transform the source to a stream, and read from that + Transformer transformer = createTransformer(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + transformer.transform(source, new StreamResult(os)); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + streamReader = getInputFactory().createXMLStreamReader(is); + } + return streamReader; + } + + private XMLStreamWriter getStreamWriter(Result result) { + XMLStreamWriter streamWriter = null; + if (result instanceof StaxResult) { + StaxResult staxResult = (StaxResult) result; + streamWriter = staxResult.getXMLStreamWriter(); + } + if (streamWriter == null) { + try { + streamWriter = getOutputFactory().createXMLStreamWriter(result); + } + catch (XMLStreamException ex) { + // ignore + } + } + return streamWriter; + } + + /** + * Template method. Subclasses must implement this. Offers the request payload as a XMLStreamReader, + * and a XMLStreamWriter to write the response payload to. + * + * @param streamReader the reader to read the payload from + * @param streamWriter the writer to write the payload to + */ + protected abstract void invokeInternal(XMLStreamReader streamReader, XMLStreamWriter streamWriter) throws Exception; + + /** + * Implementation of the XMLStreamWriter interface that creates a response + * WebServiceMessage as soon as any method is called, thus lazily creating the response. + */ + private class ResponseCreatingStreamWriter implements XMLStreamWriter { + + private MessageContext messageContext; + + private XMLStreamWriter streamWriter; + + private ByteArrayOutputStream os; + + private ResponseCreatingStreamWriter(MessageContext messageContext) { + this.messageContext = messageContext; + } + + public NamespaceContext getNamespaceContext() { + return streamWriter.getNamespaceContext(); + } + + public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { + createStreamWriter(); + streamWriter.setNamespaceContext(context); + } + + public void close() throws XMLStreamException { + if (streamWriter != null) { + streamWriter.close(); + if (os != null) { + streamWriter.flush(); + // if we used an output stream cache, we have to transform it to the response again + try { + Transformer transformer = createTransformer(); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + transformer.transform(new StreamSource(is), messageContext.getResponse().getPayloadResult()); + os = null; + } + catch (TransformerException ex) { + throw new XMLStreamException(ex); + } + } + streamWriter = null; + } + + } + + public void flush() throws XMLStreamException { + if (streamWriter != null) { + streamWriter.flush(); + } + } + + public String getPrefix(String uri) throws XMLStreamException { + createStreamWriter(); + return streamWriter.getPrefix(uri); + } + + public Object getProperty(String name) throws IllegalArgumentException { + return streamWriter.getProperty(name); + } + + public void setDefaultNamespace(String uri) throws XMLStreamException { + createStreamWriter(); + streamWriter.setDefaultNamespace(uri); + } + + public void setPrefix(String prefix, String uri) throws XMLStreamException { + createStreamWriter(); + streamWriter.setPrefix(prefix, uri); + } + + public void writeAttribute(String localName, String value) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeAttribute(localName, value); + } + + public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeAttribute(namespaceURI, localName, value); + } + + public void writeAttribute(String prefix, String namespaceURI, String localName, String value) + throws XMLStreamException { + createStreamWriter(); + streamWriter.writeAttribute(prefix, namespaceURI, localName, value); + } + + public void writeCData(String data) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeCData(data); + } + + public void writeCharacters(String text) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeCharacters(text); + } + + public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeCharacters(text, start, len); + } + + public void writeComment(String data) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeComment(data); + } + + public void writeDTD(String dtd) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeDTD(dtd); + } + + public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeDefaultNamespace(namespaceURI); + } + + public void writeEmptyElement(String localName) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeEmptyElement(localName); + } + + public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeEmptyElement(namespaceURI, localName); + } + + public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeEmptyElement(prefix, localName, namespaceURI); + } + + public void writeEndDocument() throws XMLStreamException { + createStreamWriter(); + streamWriter.writeEndDocument(); + } + + public void writeEndElement() throws XMLStreamException { + createStreamWriter(); + streamWriter.writeEndElement(); + } + + public void writeEntityRef(String name) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeEntityRef(name); + } + + public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeNamespace(prefix, namespaceURI); + } + + public void writeProcessingInstruction(String target) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeProcessingInstruction(target); + } + + public void writeProcessingInstruction(String target, String data) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeProcessingInstruction(target, data); + } + + public void writeStartDocument() throws XMLStreamException { + createStreamWriter(); + streamWriter.writeStartDocument(); + } + + public void writeStartDocument(String version) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeStartDocument(version); + } + + public void writeStartDocument(String encoding, String version) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeStartDocument(encoding, version); + } + + public void writeStartElement(String localName) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeStartElement(localName); + } + + public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeStartElement(namespaceURI, localName); + } + + public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { + createStreamWriter(); + streamWriter.writeStartElement(prefix, localName, namespaceURI); + } + + private void createStreamWriter() throws XMLStreamException { + if (streamWriter == null) { + WebServiceMessage response = messageContext.getResponse(); + streamWriter = getStreamWriter(response.getPayloadResult()); + if (streamWriter == null) { + // as a final resort, use a stream, and transform that at endDocument() + os = new ByteArrayOutputStream(); + streamWriter = getOutputFactory().createXMLStreamWriter(os); + } + } + } + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/AbstractXomPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/AbstractXomPayloadEndpoint.java new file mode 100644 index 00000000..6366fb4d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/AbstractXomPayloadEndpoint.java @@ -0,0 +1,123 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import java.io.IOException; + +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; + +import nu.xom.Builder; +import nu.xom.Document; +import nu.xom.Element; +import nu.xom.ParsingException; +import nu.xom.converters.DOMConverter; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +import org.springframework.xml.transform.StringSource; + +/** + * Abstract base class for endpoints that handle the message payload as XOM elements. Offers the message payload as a + * XOM Element, and allows subclasses to create a response by returning an Element. + *

+ * An AbstractXomPayloadEndpoint only accept one payload element. Multiple payload elements are not in + * accordance with WS-I. + * + * @author Arjen Poutsma + * @see Element + */ +public abstract class AbstractXomPayloadEndpoint implements PayloadEndpoint { + + public final Source invoke(Source request) throws Exception { + Element requestElement = null; + if (request instanceof DOMSource) { + requestElement = handleDomSource(request); + } + else if (request instanceof SAXSource) { + requestElement = handleSaxSource(request); + } + else if (request instanceof StreamSource) { + requestElement = handleStreamSource(request); + } + else { + throw new IllegalArgumentException( + "Source [" + request.getClass().getName() + "] is neither SAXSource, DOMSource, nor StreamSource"); + } + Element responseElement = invokeInternal(requestElement); + return responseElement != null ? new StringSource(responseElement.toXML()) : null; + } + + private Element handleStreamSource(Source request) throws ParsingException, IOException { + StreamSource streamSource = (StreamSource) request; + Builder builder = new Builder(); + Document document = null; + if (streamSource.getInputStream() != null) { + document = builder.build(streamSource.getInputStream()); + } + else if (streamSource.getReader() != null) { + document = builder.build(streamSource.getReader()); + } + else { + throw new IllegalArgumentException("StreamSource contains neither byte stream nor character stream"); + } + return document.getRootElement(); + } + + private Element handleSaxSource(Source request) throws ParsingException, IOException { + SAXSource saxSource = (SAXSource) request; + Builder builder = new Builder(saxSource.getXMLReader()); + InputSource inputSource = saxSource.getInputSource(); + Document document = null; + if (inputSource.getByteStream() != null) { + document = builder.build(inputSource.getByteStream()); + } + else if (inputSource.getCharacterStream() != null) { + document = builder.build(inputSource.getCharacterStream()); + } + else { + throw new IllegalArgumentException( + "InputSource in SAXSource contains neither byte stream nor " + "character stream"); + } + return document.getRootElement(); + } + + private Element handleDomSource(Source request) { + Node w3cNode = ((DOMSource) request).getNode(); + org.w3c.dom.Element w3cElement = null; + if (w3cNode.getNodeType() == Node.ELEMENT_NODE) { + w3cElement = (org.w3c.dom.Element) w3cNode; + } + else if (w3cNode.getNodeType() == Node.DOCUMENT_NODE) { + org.w3c.dom.Document w3cDocument = (org.w3c.dom.Document) w3cNode; + w3cElement = w3cDocument.getDocumentElement(); + } + return DOMConverter.convert(w3cElement); + } + + /** + * Template method. Subclasses must implement this. Offers the request payload as a XOM Element, and + * allows subclasses to return a response Element. + * + * @param requestElement the contents of the SOAP message as XOM element + * @return the response element. Can be null to specify no response. + */ + protected abstract Element invokeInternal(Element requestElement) throws Exception; + +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/MessageEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/MessageEndpoint.java new file mode 100644 index 00000000..d606d5f9 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/MessageEndpoint.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import org.springframework.ws.context.MessageContext; + +/** + * Defines the basic contract for Web Services interested in the entire mesage payload. + *

+ * The main entrypoint is invoke, which gets invoked with the a message context. This context contains the + * request, and can be used to create a response. + * + * @author Arjen Poutsma + * @see #invoke(org.springframework.ws.context.MessageContext) + */ +public interface MessageEndpoint { + + /** + * Invokes an operation. The given message context can be used to create a response. + * + * @param messageContext the message context + * @throws Exception if an exception occurs + */ + void invoke(MessageContext messageContext) throws Exception; + +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/MessageEndpointAdapter.java b/core/src/main/java/org/springframework/ws/endpoint/MessageEndpointAdapter.java new file mode 100644 index 00000000..22cd94ca --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/MessageEndpointAdapter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import org.springframework.ws.EndpointAdapter; +import org.springframework.ws.context.MessageContext; + +/** + * Adapter to use a MessageEndpoint as the endpoint for a EndpointInvocationChain. + * + * @author Arjen Poutsma + * @see org.springframework.ws.EndpointInvocationChain + */ +public class MessageEndpointAdapter implements EndpointAdapter { + + public boolean supports(Object endpoint) { + return endpoint instanceof MessageEndpoint; + } + + public void invoke(MessageContext messageContext, Object endpoint) throws Exception { + ((MessageEndpoint) endpoint).invoke(messageContext); + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/PayloadEndpoint.java b/core/src/main/java/org/springframework/ws/endpoint/PayloadEndpoint.java new file mode 100644 index 00000000..5d7a8916 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/PayloadEndpoint.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Source; + +/** + * Defines the basic contract for Web Services interested in the mesage payload. + *

+ * The main entrypoint is invoke, which gets invoked with the contents of the requesting message. + * + * @author Arjen Poutsma + * @see #invoke(javax.xml.transform.Source) + */ +public interface PayloadEndpoint { + + /** + * Invokes an operation. + * + * @param request the request message + * @return the response message, may be null + * @throws Exception if an exception occurs + */ + Source invoke(Source request) throws Exception; +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/PayloadEndpointAdapter.java b/core/src/main/java/org/springframework/ws/endpoint/PayloadEndpointAdapter.java new file mode 100644 index 00000000..44ef5a55 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/PayloadEndpointAdapter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; + +import org.springframework.ws.EndpointAdapter; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; + +/** + * Adapter to use a PayloadEndpoint as the endpoint for a EndpointInvocationChain. + * + * @author Arjen Poutsma + * @see PayloadEndpoint + * @see org.springframework.ws.EndpointInvocationChain + */ +public class PayloadEndpointAdapter extends TransformerObjectSupport implements EndpointAdapter { + + public boolean supports(Object endpoint) { + return endpoint instanceof PayloadEndpoint; + } + + public void invoke(MessageContext messageContext, Object endpoint) throws Exception { + PayloadEndpoint payloadEndpoint = (PayloadEndpoint) endpoint; + Source requestSource = messageContext.getRequest().getPayloadSource(); + Source responseSource = payloadEndpoint.invoke(requestSource); + if (responseSource != null) { + WebServiceMessage response = messageContext.getResponse(); + Transformer transformer = createTransformer(); + transformer.transform(responseSource, response.getPayloadResult()); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/TransformerObjectSupport.java b/core/src/main/java/org/springframework/ws/endpoint/TransformerObjectSupport.java new file mode 100644 index 00000000..1b12f9c0 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/TransformerObjectSupport.java @@ -0,0 +1,72 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Convenient base class for objects that use a Transformer. Subclasses can call + * createTransformer to obtain a transformer. This should be done per incoming request, because + * Transformer instances are not thread-safe. + * + * @author Arjen Poutsma + * @see Transformer + * @see #createTransformer() + */ +public abstract class TransformerObjectSupport { + + /** + * Logger available to subclasses. + */ + protected final Log logger = LogFactory.getLog(getClass()); + + private static TransformerFactory transformerFactory; + + /** + * Creates a new Transformer. Must be called per request, as transformer is not thread-safe. + * + * @return the created transformer + * @throws TransformerConfigurationException + * if thrown by JAXP methods + */ + protected final Transformer createTransformer() throws TransformerConfigurationException { + if (transformerFactory == null) { + transformerFactory = createTransformerFactory(); + } + return transformerFactory.newTransformer(); + } + + /** + * Create a TransformerFactory that this endpoint will use to create Transformers. Can be + * overridden in subclasses, adding further initialization of the factory. The resulting + * TransformerFactory is cached, so this method will only be called once. + * + * @return the created TransformerFactory + * @throws TransformerFactoryConfigurationError + * if thrown by JAXP methods + */ + protected TransformerFactory createTransformerFactory() throws TransformerFactoryConfigurationError { + return TransformerFactory.newInstance(); + } + +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/interceptor/AbstractValidatingInterceptor.java b/core/src/main/java/org/springframework/ws/endpoint/interceptor/AbstractValidatingInterceptor.java new file mode 100644 index 00000000..78c7a59b --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/interceptor/AbstractValidatingInterceptor.java @@ -0,0 +1,327 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint.interceptor; + +import java.io.IOException; +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.endpoint.TransformerObjectSupport; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.SoapFaultDetailElement; +import org.springframework.ws.soap.context.SoapMessageContext; +import org.springframework.xml.namespace.QNameUtils; +import org.springframework.xml.validation.XmlValidator; +import org.springframework.xml.validation.XmlValidatorFactory; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Abstract base class for EndpointInterceptor implementations that validate part of the message using a + * schema. The exact message part is determined by the getValidationRequestSource and + * getValidationResponseSource template methods. + *

+ * By default, only the request message is validated, but this behaviour can be changed using the + * validateRequest and validateResponse properties. Responses that contains SOAP faults are + * not validated. + * + * @author Arjen Poutsma + * @see #getValidationRequestSource(org.springframework.ws.WebServiceMessage) + * @see #getValidationResponseSource(org.springframework.ws.WebServiceMessage) + */ +public abstract class AbstractValidatingInterceptor extends TransformerObjectSupport + implements EndpointInterceptor, InitializingBean { + + /** + * Default SOAP Fault Detail name used when a validation errors occur on the request. + * + * @see #setDetailElementName(javax.xml.namespace.QName) + */ + public static final QName DEFAULT_DETAIL_ELEMENT_NAME = + QNameUtils.createQName("http://springframework.org/spring-ws", "ValidationError", "spring-ws"); + + /** + * Default SOAP Fault string used when a validation errors occur on the request. + * + * @see #setFaultStringOrReason(String) + */ + public static final String DEFAULT_FAULTSTRING_OR_REASON = "Validation error"; + + protected final Log logger = LogFactory.getLog(getClass()); + + private boolean addValidationErrorDetail = true; + + private QName detailElementName = DEFAULT_DETAIL_ELEMENT_NAME; + + private String faultStringOrReason = DEFAULT_FAULTSTRING_OR_REASON; + + private Locale faultStringOrReasonLocale = Locale.ENGLISH; + + private String schemaLanguage = XmlValidatorFactory.SCHEMA_W3C_XML; + + private Resource[] schemas; + + private boolean validateRequest = true; + + private boolean validateResponse = false; + + private XmlValidator validator; + + public boolean getAddValidationErrorDetail() { + return addValidationErrorDetail; + } + + /** + * Indicates whether a SOAP Fault detail element should be created when a validation error occurs. This detail + * element will contain the exact validation errors. It is only added when the underlying message is a + * SoapMessage. Defaults to true. + * + * @see org.springframework.ws.soap.SoapFault#addFaultDetail() + */ + public void setAddValidationErrorDetail(boolean addValidationErrorDetail) { + this.addValidationErrorDetail = addValidationErrorDetail; + } + + /** + * Returns the fault detail element name when validation errors occur on the request. + */ + public QName getDetailElementName() { + return detailElementName; + } + + /** + * Sets the fault detail element name when validation errors occur on the request. Defaults to + * DEFAULT_DETAIL_ELEMENT_NAME. + * + * @see #DEFAULT_DETAIL_ELEMENT_NAME + */ + public void setDetailElementName(QName detailElementName) { + this.detailElementName = detailElementName; + } + + /** + * Sets the SOAP faultstring or Reason used when validation errors occur on the request. + */ + public String getFaultStringOrReason() { + return faultStringOrReason; + } + + /** + * Sets the SOAP faultstring or Reason used when validation errors occur on the request. + * It is only added when the underlying message is a SoapMessage. Defaults to + * DEFAULT_FAULTSTRING_OR_REASON. + * + * @see #DEFAULT_FAULTSTRING_OR_REASON + */ + public void setFaultStringOrReason(String faultStringOrReason) { + this.faultStringOrReason = faultStringOrReason; + } + + /** + * Returns the SOAP fault reason locale used when validation errors occur on the request. + */ + public Locale getFaultStringOrReasonLocale() { + return faultStringOrReasonLocale; + } + + /** + * Sets the SOAP fault reason locale used when validation errors occur on the request. It is only added when the + * underlying message is a SoapMessage. Defaults to English. + * + * @see java.util.Locale#ENGLISH + */ + public void setFaultStringOrReasonLocale(Locale faultStringOrReasonLocale) { + this.faultStringOrReasonLocale = faultStringOrReasonLocale; + } + + public String getSchemaLanguage() { + return schemaLanguage; + } + + /** + * Sets the schema language. Default is the W3C XML Schema: http://www.w3.org/2001/XMLSchema". + * + * @see org.springframework.xml.validation.XmlValidatorFactory#SCHEMA_W3C_XML + * @see org.springframework.xml.validation.XmlValidatorFactory#SCHEMA_RELAX_NG + */ + public void setSchemaLanguage(String schemaLanguage) { + this.schemaLanguage = schemaLanguage; + } + + /** + * Returns the schema resources to use for validation. + */ + public Resource[] getSchemas() { + return schemas; + } + + /** + * Sets the schema resources to use for validation. Setting either this property or schema is + * required. + */ + public void setSchemas(Resource[] schemas) { + Assert.notEmpty(schemas, "schemas must not be empty or null"); + for (int i = 0; i < schemas.length; i++) { + Assert.notNull(schemas[i], "schema must not be null"); + Assert.isTrue(schemas[i].exists(), "schema \"" + schemas[i] + "\" does not exit"); + } + this.schemas = schemas; + } + + /** + * Sets the schema resource to use for validation. Setting either this property or schemas is + * required. + */ + public void setSchema(Resource schema) { + setSchemas(new Resource[]{schema}); + } + + /** + * Indicates whether the request should be validated against the schema. Default is true. + */ + public void setValidateRequest(boolean validateRequest) { + this.validateRequest = validateRequest; + } + + /** + * Indicates whether the response should be validated against the schema. Default is false. + */ + public void setValidateResponse(boolean validateResponse) { + this.validateResponse = validateResponse; + } + + /** + * Validates the request message in the given message context. Validation only occurs if + * validateRequest is set to true, which is the default. + *

+ * Returns true if the request is valid, or false if it isn't. Additionally, when the + * messageContext is a SoapMessageContext, a SOAP Fault is added as response. + * + * @param messageContext the message context + * @return true if the message is valid; false otherwise + * @see #setValidateRequest(boolean) + */ + public boolean handleRequest(MessageContext messageContext, Object endpoint) + throws IOException, SAXException, TransformerException { + if (validateRequest) { + Source requestSource = getValidationRequestSource(messageContext.getRequest()); + if (requestSource != null) { + SAXParseException[] errors = validator.validate(requestSource); + if (!ObjectUtils.isEmpty(errors)) { + for (int i = 0; i < errors.length; i++) { + logger.warn("XML validation error on request: " + errors[i].getMessage()); + } + if (messageContext instanceof SoapMessageContext) { + createRequestValidationFault((SoapMessageContext) messageContext, errors); + } + return false; + } + else if (logger.isDebugEnabled()) { + logger.debug("Request message validated"); + } + } + } + return true; + } + + /** + * Validates the response message in the given message context. Validation only occurs if + * validateResponse is set to true, which is not the default. + *

+ * Returns true if the request is valid, or false if it isn't. + * + * @param messageContext the message context. + * @return true if the response is valid; false otherwise + * @see #setValidateResponse(boolean) + */ + public boolean handleResponse(MessageContext messageContext, Object endpoint) throws IOException, SAXException { + if (validateResponse) { + Source responseSource = getValidationResponseSource(messageContext.getResponse()); + if (responseSource != null) { + SAXParseException[] errors = validator.validate(responseSource); + if (!ObjectUtils.isEmpty(errors)) { + for (int i = 0; i < errors.length; i++) { + logger.error("XML validation error on response: " + errors[i].getMessage()); + } + return false; + } + else if (logger.isDebugEnabled()) { + logger.debug("Response message validated"); + } + } + } + return true; + } + + public void afterPropertiesSet() throws Exception { + Assert.notEmpty(schemas, "setting either the schema or schemas property is required"); + Assert.hasLength(schemaLanguage, "schemaLanguage is required"); + for (int i = 0; i < schemas.length; i++) { + Assert.isTrue(schemas[i].exists(), "schema [" + schemas + "] does not exist"); + } + if (logger.isInfoEnabled()) { + logger.info("Validating using " + StringUtils.arrayToCommaDelimitedString(schemas)); + } + validator = XmlValidatorFactory.createValidator(schemas, schemaLanguage); + } + + /** + * Creates a response soap message containing a SoapFault that descibes the validation errors. + */ + protected void createRequestValidationFault(SoapMessageContext context, SAXParseException[] errors) + throws TransformerException { + SoapBody body = context.getSoapResponse().getSoapBody(); + SoapFault fault = body.addClientOrSenderFault(getFaultStringOrReason(), getFaultStringOrReasonLocale()); + if (getAddValidationErrorDetail()) { + SoapFaultDetail detail = fault.addFaultDetail(); + for (int i = 0; i < errors.length; i++) { + SoapFaultDetailElement detailElement = detail.addFaultDetailElement(getDetailElementName()); + detailElement.addText(errors[i].getMessage()); + } + } + } + + /** + * Abstract template method that returns the part of the request message that is to be validated. + * + * @param request the request message + * @return the part of the message that is to validated, or null not to validate anything + */ + protected abstract Source getValidationRequestSource(WebServiceMessage request); + + /** + * Abstract template method that returns the part of the response message that is to be validated. + * + * @param response the response message + * @return the part of the message that is to validated, or null not to validate anything + */ + protected abstract Source getValidationResponseSource(WebServiceMessage response); +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/interceptor/EndpointInterceptorAdapter.java b/core/src/main/java/org/springframework/ws/endpoint/interceptor/EndpointInterceptorAdapter.java new file mode 100644 index 00000000..c9e14a23 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/interceptor/EndpointInterceptorAdapter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint.interceptor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.context.MessageContext; +import org.w3c.dom.Element; + +/** + * Default implementation of the EndpointInterceptor interface, for simplified implementation of + * pre-only/post-only interceptors. + * + * @author Arjen Poutsma + */ +public class EndpointInterceptorAdapter implements EndpointInterceptor { + + /** + * Logger available to subclasses + */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** + * Returns false. + */ + public boolean understands(Element header) { + return false; + } + + /** + * Returns true. + * + * @return true + */ + public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { + return true; + } + + /** + * Returns true. + * + * @return true + */ + public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { + return true; + } + + /** + * Returns true. + * + * @return true + */ + public boolean handleFault(MessageContext messageContext, Object endpoint) { + return true; + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadLoggingInterceptor.java b/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadLoggingInterceptor.java new file mode 100644 index 00000000..9167112d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadLoggingInterceptor.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint.interceptor; + +import javax.xml.transform.Source; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.endpoint.AbstractLoggingInterceptor; + +/** + * Simple EndpointInterceptor that logs the payload of request and response messages. By default, both + * request and response messages are logged, but this behaviour can be changed using the logRequest and + * logResponse properties. + * + * @author Arjen Poutsma + * @see #setLogRequest(boolean) + * @see #setLogResponse(boolean) + */ +public class PayloadLoggingInterceptor extends AbstractLoggingInterceptor { + + protected Source getSource(WebServiceMessage message) { + return message.getPayloadSource(); + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadTransformingInterceptor.java b/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadTransformingInterceptor.java new file mode 100644 index 00000000..64450831 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadTransformingInterceptor.java @@ -0,0 +1,131 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint.interceptor; + +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.xml.sax.SaxUtils; + +/** + * Interceptor that transforms the payload of WebServiceMessages using XSLT stylesheet. Allows for seperate + * stylesheets for request and response. This interceptor is especially useful when supporting with multiple version of + * a Web service: you can transform the older message format to the new format. + *

+ * The stylesheets to use can be set using the requestXslt and responseXslt properties. Both + * of these are optional: if not set, the message is simply not transformed. Setting one of the two is required, + * though. + * + * @author Arjen Poutsma + * @see #setRequestXslt(org.springframework.core.io.Resource) + * @see #setResponseXslt(org.springframework.core.io.Resource) + */ +public class PayloadTransformingInterceptor implements EndpointInterceptor, InitializingBean { + + private static final Log logger = LogFactory.getLog(PayloadTransformingInterceptor.class); + + private Resource requestXslt; + + private Resource responseXslt; + + private Templates requestTemplates; + + private Templates responseTemplates; + + /** + * Sets the XSLT stylesheet to use for transforming incoming request. + */ + public void setRequestXslt(Resource requestXslt) { + this.requestXslt = requestXslt; + } + + /** + * Sets the XSLT stylesheet to use for transforming outgoing responses. + */ + public void setResponseXslt(Resource responseXslt) { + this.responseXslt = responseXslt; + } + + /** + * Transforms the request message in the given message context using a provided stylesheet. Transformation only + * occurs if the requestXslt has been set. + * + * @param messageContext the message context + * @return always returns true + * @see #setRequestXslt(org.springframework.core.io.Resource) + */ + public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { + if (requestTemplates != null) { + WebServiceMessage request = messageContext.getRequest(); + Transformer transformer = requestTemplates.newTransformer(); + transformer.transform(request.getPayloadSource(), request.getPayloadResult()); + logger.debug("Request message transformed"); + } + return true; + } + + /** + * Transforms the response message in the given message context using a stylesheet. Transformation only occurs if + * the responseXslt has been set. + * + * @param messageContext the message context + * @return always returns true + * @see #setResponseXslt(org.springframework.core.io.Resource) + */ + public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { + if (responseTemplates != null) { + WebServiceMessage response = messageContext.getResponse(); + Transformer transformer = responseTemplates.newTransformer(); + transformer.transform(response.getPayloadSource(), response.getPayloadResult()); + logger.debug("Response message transformed"); + } + return true; + } + + public void afterPropertiesSet() throws Exception { + if (requestXslt == null && responseXslt == null) { + throw new IllegalArgumentException("Setting either 'requestXslt' or 'responseXslt' is required"); + } + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + if (requestXslt != null) { + Assert.isTrue(requestXslt.exists(), "requestXslt \"" + requestXslt + "\" does not exit"); + if (logger.isInfoEnabled()) { + logger.info("Transforming request using " + requestXslt); + } + SAXSource requestSource = new SAXSource(SaxUtils.createInputSource(requestXslt)); + requestTemplates = transformerFactory.newTemplates(requestSource); + } + if (responseXslt != null) { + Assert.isTrue(responseXslt.exists(), "responseXslt \"" + responseXslt + "\" does not exit"); + if (logger.isInfoEnabled()) { + logger.info("Transforming response using " + responseXslt); + } + SAXSource responseSource = new SAXSource(SaxUtils.createInputSource(responseXslt)); + responseTemplates = transformerFactory.newTemplates(responseSource); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadValidatingInterceptor.java b/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadValidatingInterceptor.java new file mode 100644 index 00000000..e33819a9 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/interceptor/PayloadValidatingInterceptor.java @@ -0,0 +1,54 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint.interceptor; + +import javax.xml.transform.Source; + +import org.springframework.ws.WebServiceMessage; + +/** + * Interceptor that validates the contents of WebServiceMessages using a schema. Allows for both W3C XML + * and RELAX NG schemas. + *

+ * When the payload is invalid, this interceptor stops processing of the interceptor chain. Additionally, if the message + * is a SOAP request message, a SOAP Fault is created as reply. Invalid SOAP responses do not result in a fault. + *

+ * The schema to validate against is set with the schema property. By default, only the request message is + * validated, but this behaviour can be changed using the validateRequest and validateResponse + * properties. Responses that contains faults are not validated. + * + * @author Arjen Poutsma + * @see #setSchema + * @see #setValidateRequest(boolean) + * @see #setValidateResponse(boolean) + */ +public class PayloadValidatingInterceptor extends AbstractValidatingInterceptor { + + /** + * Returns the payload source of the given message. + */ + protected Source getValidationRequestSource(WebServiceMessage request) { + return request.getPayloadSource(); + } + + /** + * Returns the payload source of the given message. + */ + protected Source getValidationResponseSource(WebServiceMessage response) { + return response.getPayloadSource(); + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/interceptor/package.html b/core/src/main/java/org/springframework/ws/endpoint/interceptor/package.html new file mode 100644 index 00000000..639946a6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/interceptor/package.html @@ -0,0 +1,5 @@ + + +Provides miscellaneous endpoints EndpointInterceptor implementations. + + diff --git a/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractEndpointMapping.java b/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractEndpointMapping.java new file mode 100644 index 00000000..9097414f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractEndpointMapping.java @@ -0,0 +1,147 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint.mapping; + +import org.springframework.context.support.ApplicationObjectSupport; +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.EndpointInvocationChain; +import org.springframework.ws.EndpointMapping; +import org.springframework.ws.context.MessageContext; + +/** + * Abstract base class for EndpointMapping implementations. Supports a default endpoint, and endpoint interceptors. + * + * @author Arjen Poutsma + * @see #getEndpointInternal(org.springframework.ws.context.MessageContext) + * @see org.springframework.ws.EndpointInterceptor + */ +public abstract class AbstractEndpointMapping extends ApplicationObjectSupport implements EndpointMapping { + + private Object defaultEndpoint; + + private EndpointInterceptor[] interceptors; + + /** + * Returns the default endpoint for this endpoint mapping. + * + * @return the default endpoint mapping, or null if none + */ + protected final Object getDefaultEndpoint() { + return defaultEndpoint; + } + + /** + * Sets the default endpoint for this endpoint mapping. This endpoint will be returned if no specific mapping was + * found. + *

+ * Default is null, indicating no default endpoint. + * + * @param defaultEndpoint the default endpoint, or null if none + */ + public final void setDefaultEndpoint(Object defaultEndpoint) { + this.defaultEndpoint = defaultEndpoint; + logger.info("Default mapping to endpoint [" + this.defaultEndpoint + "]"); + } + + /** + * Returns the the endpoint interceptors to apply to all endpoints mapped by this endpoint mapping. + * + * @return array of endpoint interceptors, or null if none + */ + public EndpointInterceptor[] getInterceptors() { + return interceptors; + } + + /** + * Sets the endpoint interceptors to apply to all endpoints mapped by this endpoint mapping. + * + * @param interceptors array of endpoint interceptors, or null if none + */ + public final void setInterceptors(EndpointInterceptor[] interceptors) { + this.interceptors = interceptors; + } + + /** + * Look up an endpoint for the given message context, falling back to the default endpoint if no specific one is + * found. + * + * @return the looked up endpoint instance, or the default endpoint + * @see #getEndpointInternal(org.springframework.ws.context.MessageContext) + */ + public final EndpointInvocationChain getEndpoint(MessageContext messageContext) throws Exception { + Object endpoint = getEndpointInternal(messageContext); + if (endpoint == null) { + endpoint = defaultEndpoint; + } + if (endpoint == null) { + return null; + } + if (endpoint instanceof String) { + String endpointName = (String) endpoint; + endpoint = resolveStringEndpoint(endpointName); + if (endpoint == null) { + return null; + } + } + return createEndpointInvocationChain(messageContext, endpoint, interceptors); + } + + /** + * Creates a new EndpointInvocationChain based on the given message context, endpoint, and + * interceptors. Default implementation creates a simple EndpointInvocationChain based on the set + * interceptors. + * + * @param endpoint the endpoint + * @param interceptors the endpoint interceptors + * @return the created invocation chain + * @see #setInterceptors(org.springframework.ws.EndpointInterceptor[]) + */ + protected EndpointInvocationChain createEndpointInvocationChain(MessageContext messageContext, + Object endpoint, + EndpointInterceptor[] interceptors) { + return new EndpointInvocationChain(endpoint, interceptors); + } + + /** + * Resolves an endpoint string. If the given string can is a bean name, it is resolved using the application + * context. + * + * @param endpointName the endpoint name + * @return the resolved enpoint, or null if the name could not be resolved + */ + protected Object resolveStringEndpoint(String endpointName) { + if (getApplicationContext().containsBean(endpointName)) { + return getApplicationContext().getBean(endpointName); + } + else { + return null; + } + } + + /** + * Lookup an endpoint for the given request, returning null if no specific one is found. This template + * method is called by getEndpoint, a null return value will lead to the default handler, if one is + * set. + *

+ * The returned endpoint can be a string, in which case it is resolved as a bean name. Also, it can take the form + * beanName#method, in which case the method is resolved. + * + * @return the looked up endpoint instance, or null + * @throws Exception if there is an error + */ + protected abstract Object getEndpointInternal(MessageContext messageContext) throws Exception; +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractMapBasedEndpointMapping.java b/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractMapBasedEndpointMapping.java new file mode 100644 index 00000000..f4db00b9 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractMapBasedEndpointMapping.java @@ -0,0 +1,194 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint.mapping; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContextException; +import org.springframework.util.StringUtils; +import org.springframework.ws.context.MessageContext; + +/** + * Abstract base class for endpoint mapping that are based on a Map. Provides mappings of application + * context beans as well as a settable map. + *

+ * Subclasses determine the exact nature of the key in the enpoint map; this can be a qualified name, a SOAP Header, the + * result of a XPath validation. The values are always endpoint objects, or bean names of endpoint objects. + * + * @author Arjen Poutsma + */ +public abstract class AbstractMapBasedEndpointMapping extends AbstractEndpointMapping { + + private boolean lazyInitEndpoints = false; + + private boolean registerBeanNames = false; + + private final Map endpointMap = new HashMap(); + + // holds mappings set via setEndpointMap and setMappings + private Map temporaryEndpointMap = new HashMap(); + + /** + * Set whether to lazily initialize endpoints. Only applicable to singleton endpoints, as prototypes are always + * lazily initialized. Default is false, as eager initialization allows for more efficiency through + * referencing the controller objects directly. + *

+ * If you want to allow your endpoints to be lazily initialized, make them "lazy-init" and set this flag to + * true. Just making them "lazy-init" will not work, as they are initialized through the references + * from the endpoint mapping in this case. + */ + public void setLazyInitEndpoints(boolean lazyInitEndpoints) { + this.lazyInitEndpoints = lazyInitEndpoints; + } + + /** + * Set whether to register bean names found in the application context. Setting this to true will + * register all beans found in the application context under their name. Default is false. + */ + public final void setRegisterBeanNames(boolean registerBeanNames) { + this.registerBeanNames = registerBeanNames; + } + + /** + * Sets a Map with keys and endpoint beans as values. The nature of the keys in the given map depends on the exact + * subclass used. They can be qualified names, for instance, or mime headers. + * + * @throws IllegalArgumentException if the endpoint is invalid + */ + public final void setEndpointMap(Map endpointMap) { + temporaryEndpointMap.putAll(endpointMap); + } + + /** + * Maps keys to endpoint bean names. The nature of the property names depends on the exact subclass used. They can + * be qualified names, for instance, or mime headers. + */ + public void setMappings(Properties mappings) { + temporaryEndpointMap.putAll(mappings); + } + + /** + * Validates the given endpoint key. Should return true is the given string is valid. + */ + protected abstract boolean validateLookupKey(String key); + + /** + * Returns the the endpoint keys for the given message context. + * + * @return the registration keys + */ + protected abstract String getLookupKeyForMessage(MessageContext messageContext) throws Exception; + + /** + * Lookup an endpoint for the given message. The extraction of the endpoint key is delegated to the concrete + * subclass. + * + * @return the looked up endpoint, or null + */ + protected Object getEndpointInternal(MessageContext messageContext) throws Exception { + String key = getLookupKeyForMessage(messageContext); + if (!StringUtils.hasLength(key)) { + return null; + } + if (logger.isDebugEnabled()) { + logger.debug("Looking up endpoint for [" + key + "]"); + } + return lookupEndpoint(key); + } + + /** + * Looks up an endpoint instance for the given keys. All keys are tried in order. + * + * @param key key the beans are mapped to + * @return the associated endpoint instance, or null if not found + */ + protected Object lookupEndpoint(String key) { + return endpointMap.get(key); + } + + /** + * Register the given endpoint instance under the registration key. + * + * @param key the string representation of the registration key + * @param endpoint the endpoint instance + * @throws org.springframework.beans.BeansException + * if the endpoint could not be registered + */ + protected void registerEndpoint(String key, Object endpoint) throws BeansException { + Object mappedEndpoint = endpointMap.get(key); + if (mappedEndpoint != null) { + throw new ApplicationContextException("Cannot map endpoint [" + endpoint + "] on registration key [" + key + + "]: there's already endpoint [" + mappedEndpoint + "] mapped"); + } + if (!lazyInitEndpoints && endpoint instanceof String) { + String endpointName = (String) endpoint; + endpoint = resolveStringEndpoint(endpointName); + } + if (endpoint == null) { + throw new ApplicationContextException("Could not find endpoint for key [" + key + "]"); + } + endpointMap.put(key, endpoint); + if (logger.isDebugEnabled()) { + logger.debug("Mapped key [" + key + "] onto endpoint [" + endpoint + "]"); + } + } + + /** + * Registers annd checks the set endpoints. Checks the beans set through setEndpointMap and + * setMappings, and registers the bean names found in the application context, if + * registerBeanNames is set to true. + * + * @throws ApplicationContextException if either of the endpoints defined via setEndpointMap or + * setMappings is invalid + * @see #setEndpointMap(java.util.Map) + * @see #setMappings(java.util.Properties) + * @see #setRegisterBeanNames(boolean) + */ + protected final void initApplicationContext() throws BeansException { + for (Iterator iter = temporaryEndpointMap.keySet().iterator(); iter.hasNext();) { + String key = (String) iter.next(); + Object endpoint = temporaryEndpointMap.get(key); + if (!validateLookupKey(key)) { + throw new ApplicationContextException("Invalid key [" + key + "] for endpoint [" + endpoint + "]"); + } + registerEndpoint(key, endpoint); + } + temporaryEndpointMap = null; + if (registerBeanNames) { + if (logger.isDebugEnabled()) { + logger.debug("Looking for endpoint mappings in application context: [" + getApplicationContext() + "]"); + } + String[] beanNames = getApplicationContext().getBeanDefinitionNames(); + for (int i = 0; i < beanNames.length; i++) { + if (validateLookupKey(beanNames[i])) { + registerEndpoint(beanNames[i], beanNames[i]); + } + String[] aliases = getApplicationContext().getAliases(beanNames[i]); + for (int j = 0; j < aliases.length; j++) { + if (validateLookupKey(aliases[j])) { + registerEndpoint(aliases[j], beanNames[i]); + } + } + } + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractQNameEndpointMapping.java b/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractQNameEndpointMapping.java new file mode 100644 index 00000000..91c0f6a6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/mapping/AbstractQNameEndpointMapping.java @@ -0,0 +1,47 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint.mapping; + +import javax.xml.namespace.QName; + +import org.springframework.ws.context.MessageContext; +import org.springframework.xml.namespace.QNameUtils; + +/** + * Abstract base class for EndpointMappings that resolve qualified names as registration keys. + * + * @author Arjen Poutsma + */ +public abstract class AbstractQNameEndpointMapping extends AbstractMapBasedEndpointMapping { + + protected final String getLookupKeyForMessage(MessageContext messageContext) throws Exception { + QName qName = resolveQName(messageContext); + return qName != null ? qName.toString() : null; + } + + /** + * Template method that resolves the qualified names from the given SOAP message. + * + * @return an array of qualified names that serve as registration keys + */ + protected abstract QName resolveQName(MessageContext messageContext) throws Exception; + + protected boolean validateLookupKey(String key) { + return QNameUtils.validateQName(key); + } + +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/mapping/PayloadRootQNameEndpointMapping.java b/core/src/main/java/org/springframework/ws/endpoint/mapping/PayloadRootQNameEndpointMapping.java new file mode 100644 index 00000000..16b1b0c3 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/mapping/PayloadRootQNameEndpointMapping.java @@ -0,0 +1,70 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint.mapping; + +import javax.xml.namespace.QName; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.xml.namespace.QNameUtils; +import org.w3c.dom.Element; + +/** + * Implementation of the EndpointMapping interface to map from the qualified name of the request payload + * root element. Supports both mapping to bean instances and mapping to bean names: the latter is required for prototype + * handlers. + *

+ * The endpointMap property is suitable for populating the endpoint map with bean references, e.g. via the + * map element in XML bean definitions. + *

+ * Mappings to bean names can be set via the mappings property, in a form accepted by the + * java.util.Properties class, like as follows: + *

+ * {http://www.springframework.org/spring-ws/samples/airline/schemas}BookFlight=bookFlightEndpoint
+ * {http://www.springframework.org/spring-ws/samples/airline/schemas}GetFlights=getFlightsEndpoint
+ * 
+ * The syntax is QNAME=ENDPOINT_BEAN_NAME. Qualified names are parsed using the syntax described in + * QNameEditor. + * + * @author Arjen Poutsma + * @see org.springframework.xml.namespace.QNameEditor + */ +public class PayloadRootQNameEndpointMapping extends AbstractQNameEndpointMapping implements InitializingBean { + + private static TransformerFactory transformerFactory; + + protected QName resolveQName(MessageContext messageContext) throws TransformerException { + Element payloadElement = getMessagePayloadElement(messageContext.getRequest()); + return QNameUtils.getQNameForNode(payloadElement); + } + + private Element getMessagePayloadElement(WebServiceMessage message) throws TransformerException { + Transformer transformer = transformerFactory.newTransformer(); + DOMResult domResult = new DOMResult(); + transformer.transform(message.getPayloadSource(), domResult); + return (Element) domResult.getNode().getFirstChild(); + } + + public final void afterPropertiesSet() throws Exception { + transformerFactory = TransformerFactory.newInstance(); + } +} diff --git a/core/src/main/java/org/springframework/ws/endpoint/mapping/package.html b/core/src/main/java/org/springframework/ws/endpoint/mapping/package.html new file mode 100644 index 00000000..97023738 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/mapping/package.html @@ -0,0 +1,5 @@ + + +Provides miscellaneous endpoints EndpointMapping implementations. + + diff --git a/core/src/main/java/org/springframework/ws/endpoint/package.html b/core/src/main/java/org/springframework/ws/endpoint/package.html new file mode 100644 index 00000000..66aa032a --- /dev/null +++ b/core/src/main/java/org/springframework/ws/endpoint/package.html @@ -0,0 +1,5 @@ + + +Provides standard endpoint, and EndpointAdapter implementations. + + diff --git a/core/src/main/java/org/springframework/ws/package.html b/core/src/main/java/org/springframework/ws/package.html new file mode 100644 index 00000000..1eaa583d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/package.html @@ -0,0 +1,5 @@ + + +Provides the core functionality of the Spring Web Services framework. + + diff --git a/core/src/main/java/org/springframework/ws/pox/PoxMessage.java b/core/src/main/java/org/springframework/ws/pox/PoxMessage.java new file mode 100644 index 00000000..122c6c97 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/PoxMessage.java @@ -0,0 +1,28 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox; + +import org.springframework.ws.WebServiceMessage; + +/** + * Defines the contract for Plain Old XML messages. Currently only a tagging interface. + * + * @author Arjen Poutsma + */ +public interface PoxMessage extends WebServiceMessage { + +} diff --git a/core/src/main/java/org/springframework/ws/pox/PoxMessageException.java b/core/src/main/java/org/springframework/ws/pox/PoxMessageException.java new file mode 100644 index 00000000..0875499c --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/PoxMessageException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox; + +import org.springframework.ws.WebServiceMessageException; + +/** + * Specific subclass of WebServiceMessageException for Plain Old XML messages. + * + * @author Arjen Poutsma + */ +public abstract class PoxMessageException extends WebServiceMessageException { + + public PoxMessageException(String msg) { + super(msg); + } + + public PoxMessageException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/pox/context/AbstractPoxMessageContext.java b/core/src/main/java/org/springframework/ws/pox/context/AbstractPoxMessageContext.java new file mode 100644 index 00000000..e75910fc --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/context/AbstractPoxMessageContext.java @@ -0,0 +1,49 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.context; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.AbstractMessageContext; +import org.springframework.ws.pox.PoxMessage; +import org.springframework.ws.transport.TransportRequest; + +/** + * Abstract implementation of the PoxMessageContext interface. Implements base MessageContext + * methods by delegating to PoxMessageContext functionality. + * + * @author Arjen Poutsma + */ +public abstract class AbstractPoxMessageContext extends AbstractMessageContext implements PoxMessageContext { + + protected AbstractPoxMessageContext(PoxMessage request, TransportRequest transportRequest) { + super(request, transportRequest); + } + + public final PoxMessage getPoxResponse() { + return (PoxMessage) getResponse(); + } + + public final PoxMessage getPoxRequest() { + return (PoxMessage) getRequest(); + } + + protected final WebServiceMessage createResponseMessage() { + return createResponsePoxMessage(); + } + + protected abstract PoxMessage createResponsePoxMessage(); +} diff --git a/core/src/main/java/org/springframework/ws/pox/context/PoxMessageContext.java b/core/src/main/java/org/springframework/ws/pox/context/PoxMessageContext.java new file mode 100644 index 00000000..658965dd --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/context/PoxMessageContext.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.context; + +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.pox.PoxMessage; + +/** + * Plain Old Xml-specific extension of the MessageContext interface. Contains methods to obtain + * PoxMessages instead of WebServiceMessages. + * + * @author Arjen Poutsma + */ +public interface PoxMessageContext extends MessageContext { + + /** + * Returns the request POX message. + * + * @return the request message + */ + PoxMessage getPoxRequest(); + + /** + * Returns the response message, if created. Returns null if no response message was created so far. + * + * @return the response message, or null if none was created + * @see #hasResponse() + */ + PoxMessage getPoxResponse(); + +} diff --git a/core/src/main/java/org/springframework/ws/pox/context/package.html b/core/src/main/java/org/springframework/ws/pox/context/package.html new file mode 100644 index 00000000..3b4c1eaf --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/context/package.html @@ -0,0 +1,5 @@ + + +Contains the PoxMessageContext interface. + + \ No newline at end of file diff --git a/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessage.java b/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessage.java new file mode 100644 index 00000000..c40f273b --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessage.java @@ -0,0 +1,77 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.dom; + +import java.io.IOException; +import java.io.OutputStream; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.springframework.ws.pox.PoxMessage; +import org.w3c.dom.Document; + +/** + * Implementation of the PoxMessage interface that is based on a DOM Document. + * + * @author Arjen Poutsma + * @see Document + */ +public class DomPoxMessage implements PoxMessage { + + private final Document document; + + private Transformer transformer; + + /** + * Constructs a new instance of the DomPoxMessage with the given document. + * + * @param document the document to base the message on + */ + public DomPoxMessage(Document document, Transformer transformer) { + this.document = document; + this.transformer = transformer; + } + + /** + * Returns the document underlying this message. + */ + public Document getDocument() { + return document; + } + + public Result getPayloadResult() { + return new DOMResult(document); + } + + public Source getPayloadSource() { + return new DOMSource(document); + } + + public void writeTo(OutputStream outputStream) throws IOException { + try { + transformer.transform(getPayloadSource(), new StreamResult(outputStream)); + } + catch (TransformerException ex) { + throw new DomPoxMessageException("Could not create transformer", ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageContext.java b/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageContext.java new file mode 100644 index 00000000..5a5bbd23 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageContext.java @@ -0,0 +1,67 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.dom; + +import java.io.IOException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.transform.Transformer; + +import org.springframework.util.Assert; +import org.springframework.ws.pox.PoxMessage; +import org.springframework.ws.pox.context.AbstractPoxMessageContext; +import org.springframework.ws.transport.TransportRequest; +import org.springframework.ws.transport.TransportResponse; +import org.w3c.dom.Document; + +/** + * Implementation of the MessageContext that contains a DomPoxMessage. + * + * @author Arjen Poutsma + * @see DomPoxMessage + */ +public class DomPoxMessageContext extends AbstractPoxMessageContext { + + private DocumentBuilder documentBuilder; + + private Transformer transformer; + + /** + * Creates a new DomPoxMessageContext with the given parameters. + */ + public DomPoxMessageContext(Document request, + TransportRequest transportRequest, + DocumentBuilder documentBuilder, + Transformer transformer) { + super(new DomPoxMessage(request, transformer), transportRequest); + Assert.notNull(documentBuilder, "documentBuilder must not be null"); + Assert.notNull(transformer, "transformer must not be null"); + this.documentBuilder = documentBuilder; + this.transformer = transformer; + } + + protected PoxMessage createResponsePoxMessage() { + Document document = documentBuilder.newDocument(); + return new DomPoxMessage(document, transformer); + } + + public void sendResponse(TransportResponse transportResponse) throws IOException { + if (hasResponse()) { + transportResponse.addHeader("Content-Type", "text/xml"); + getResponse().writeTo(transportResponse.getOutputStream()); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageContextFactory.java b/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageContextFactory.java new file mode 100644 index 00000000..0a2cf0a0 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageContextFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.dom; + +import java.io.IOException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.transport.TransportContext; +import org.springframework.ws.transport.TransportRequest; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Implementation of the MessageContextFactory interface that creates a DOM + * + * @author Arjen Poutsma + * @see DomPoxMessageContext + */ +public class DomPoxMessageContextFactory implements MessageContextFactory, InitializingBean { + + private DocumentBuilderFactory documentBuilderFactory; + + private boolean namespaceAware = true; + + private TransformerFactory transformerFactory; + + private boolean validating = false; + + /** + * Set whether or not the XML parser should be XML namespace aware. Default is true. + */ + public void setNamespaceAware(boolean namespaceAware) { + this.namespaceAware = namespaceAware; + } + + /** + * Set if the XML parser should validate the document. Default is false. + */ + public void setValidating(boolean validating) { + this.validating = validating; + } + + public void afterPropertiesSet() throws Exception { + documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setValidating(validating); + documentBuilderFactory.setNamespaceAware(namespaceAware); + transformerFactory = TransformerFactory.newInstance(); + } + + public MessageContext createContext(TransportContext transportContext) throws IOException { + TransportRequest transportRequest = transportContext.getTransportRequest(); + try { + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document request = documentBuilder.parse(transportRequest.getInputStream()); + return new DomPoxMessageContext(request, + transportRequest, + documentBuilder, + transformerFactory.newTransformer()); + } + catch (ParserConfigurationException ex) { + throw new DomPoxMessageException("Could not create message context", ex); + } + catch (SAXException ex) { + throw new DomPoxMessageException("Could not parse request message", ex); + } + catch (TransformerConfigurationException ex) { + throw new DomPoxMessageException("Could not create transormer", ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageException.java b/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageException.java new file mode 100644 index 00000000..7d1075df --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/dom/DomPoxMessageException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.dom; + +import org.springframework.ws.pox.PoxMessageException; + +/** + * Specific subclass of PoxMessageException for DOM Plain Old XML messages. + * + * @author Arjen Poutsma + */ +public class DomPoxMessageException extends PoxMessageException { + + public DomPoxMessageException(String msg) { + super(msg); + } + + public DomPoxMessageException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/pox/dom/package.html b/core/src/main/java/org/springframework/ws/pox/dom/package.html new file mode 100644 index 00000000..808e0a65 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/dom/package.html @@ -0,0 +1,5 @@ + + +Contains an implementation of the POX interfaces that is based on DOM. + + diff --git a/core/src/main/java/org/springframework/ws/pox/package.html b/core/src/main/java/org/springframework/ws/pox/package.html new file mode 100644 index 00000000..83ff844d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/pox/package.html @@ -0,0 +1,6 @@ + + +Provides the Plain Old XML (POX) functionality of the Spring Web Services framework. Contains the PoxMessage and related +interfaces. + + diff --git a/core/src/main/java/org/springframework/ws/soap/AbstractSoapMessage.java b/core/src/main/java/org/springframework/ws/soap/AbstractSoapMessage.java new file mode 100644 index 00000000..894e2d72 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/AbstractSoapMessage.java @@ -0,0 +1,76 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +/** + * Abstract implementation of the SoapMessage interface. + * + * @author Arjen Poutsma + */ +public abstract class AbstractSoapMessage implements SoapMessage { + + private SoapVersion version; + + /** + * Returns getEnvelope().getBody(). + */ + public SoapBody getSoapBody() { + return getEnvelope().getBody(); + } + + /** + * Returns getEnvelope().getHeader(). + */ + public SoapHeader getSoapHeader() { + return getEnvelope().getHeader(); + } + + /** + * Returns getSoapBody().getPayloadSource(). + */ + public Source getPayloadSource() { + return getSoapBody().getPayloadSource(); + } + + /** + * Returns getSoapBody().getPayloadResult(). + */ + public Result getPayloadResult() { + return getSoapBody().getPayloadResult(); + } + + public SoapVersion getVersion() { + if (version == null) { + String envelopeNamespace = getEnvelope().getName().getNamespaceURI(); + if (SoapVersion.SOAP_11.getEnvelopeNamespaceUri().equals(envelopeNamespace)) { + version = SoapVersion.SOAP_11; + } + else if (SoapVersion.SOAP_12.getEnvelopeNamespaceUri().equals(envelopeNamespace)) { + version = SoapVersion.SOAP_12; + } + else { + throw new IllegalStateException( + "Unknown Envelope namespace uri '" + envelopeNamespace + "'. " + "Cannot deduce SoapVersion."); + } + } + return version; + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/Attachment.java b/core/src/main/java/org/springframework/ws/soap/Attachment.java new file mode 100644 index 00000000..42bc89fa --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/Attachment.java @@ -0,0 +1,70 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Represents an attachment to a SoapMessage. + * + * @author Arjen Poutsma + * @see SoapMessage#getAttachments() + * @see SoapMessage#addAttachment(java.io.File) + * @see SoapMessage#addAttachment(org.springframework.core.io.InputStreamSource, String) + */ +public interface Attachment { + + /** + * Returns the identifier of the attachment. Depending on the implementation used, this may be a MIME + * Content-Id header, a DIME ID, etc. + * + * @return the attachment identifier, or null if empty or not defined + */ + String getId(); + + /** + * Sets the identifier of the attachment. Depending on the implementation used, this may be a MIME + * Content-Id header, a DIME ID, etc. + * + * @param id the new attachment identifier, or null if empty or not defined + */ + void setId(String id); + + /** + * Returns the content type of the attachment. + * + * @return the content type, or null if empty or not defined + */ + String getContentType(); + + /** + * Return an InputStream to read the contents of the attachment from. The user is responsible for + * closing the stream. + * + * @return the contents of the file as stream, or an empty stream if empty + * @throws IOException in case of access I/O errors + */ + InputStream getInputStream() throws IOException; + + /** + * Returns the size of the attachment in bytes. + * + * @return the size of the attachment, or 0 if empty + */ + long getSize(); +} diff --git a/core/src/main/java/org/springframework/ws/soap/AttachmentException.java b/core/src/main/java/org/springframework/ws/soap/AttachmentException.java new file mode 100644 index 00000000..ff8d9e3d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/AttachmentException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +/** + * Exception thrown when a SOAP attachment could not be accessed. + * + * @author Arjen Poutsma + * @see Attachment + */ +public class AttachmentException extends SoapMessageException { + + public AttachmentException(String msg) { + super(msg); + } + + public AttachmentException(String msg, Throwable ex) { + super(msg, ex); + } + + public AttachmentException(Throwable ex) { + super("Could not access body: " + ex.getMessage(), ex); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapBody.java b/core/src/main/java/org/springframework/ws/soap/SoapBody.java new file mode 100644 index 00000000..279c70fa --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapBody.java @@ -0,0 +1,112 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import java.util.Locale; +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +/** + * Represents the Body element in a SOAP message. A SOAP body contains the payload of the + * message. This payload can be custom XML, or a SoapFault (but not both). + *

+ * Note that the source returned by getSource() includes the SOAP Body element itself. For the contents of + * the body, use getPayloadSource(). + * + * @author Arjen Poutsma + * @see SoapEnvelope#getBody() + * @see #getPayloadSource() + * @see #getPayloadResult() + * @see SoapFault + */ +public interface SoapBody extends SoapElement { + + /** + * Returns a Source that represents the contents of the body. + * + * @return the message contents + */ + Source getPayloadSource(); + + /** + * Returns a Result that represents the contents of the body. + * + * @return the messaage contents + */ + Result getPayloadResult(); + + /** + * Adds a MustUnderstand fault to the body. A MustUnderstand is returned when a SOAP + * header with a MustUnderstand attribute is not understood. + *

+ * Adding a fault removes the current content of the body. + * + * @param faultStringOrReason the SOAP 1.1 fault string or SOAP 1.2 reason text + * @param locale the language of faultStringOrReason. Optional for SOAP 1.1 + * @return the created SoapFault + */ + SoapFault addMustUnderstandFault(String faultStringOrReason, Locale locale) throws SoapFaultException; + + /** + * Adds a Client/Sender fault to the body. For SOAP 1.1, this adds a fault with a + * Client fault code. For SOAP 1.2, this adds a fault with a Sender code. + *

+ * Adding a fault removes the current content of the body. + * + * @param faultStringOrReason the SOAP 1.1 fault string or SOAP 1.2 reason text + * @param locale the language of faultStringOrReason. Optional for SOAP 1.1 + * @return the created SoapFault + */ + SoapFault addClientOrSenderFault(String faultStringOrReason, Locale locale) throws SoapFaultException; + + /** + * Adds a Server/Receiver fault to the body.For SOAP 1.1, this adds a fault with a + * Server fault code. For SOAP 1.2, this adds a fault with a Receiver code. + *

+ * Adding a fault removes the current content of the body. + * + * @param faultStringOrReason the SOAP 1.1 fault string or SOAP 1.2 reason text + * @param locale the language of faultStringOrReason. Optional for SOAP 1.1 + * @return the created SoapFault + */ + SoapFault addServerOrReceiverFault(String faultStringOrReason, Locale locale) throws SoapFaultException; + + /** + * Adds a VersionMismatch fault to the body. + *

+ * Adding a fault removes the current content of the body. + * + * @param faultStringOrReason the SOAP 1.1 fault string or SOAP 1.2 reason text + * @param locale the language of faultStringOrReason. Optional for SOAP 1.1 + * @return the created SoapFault + */ + SoapFault addVersionMismatchFault(String faultStringOrReason, Locale locale) throws SoapFaultException; + + /** + * Indicates whether this body has a SoapFault. + * + * @return true if the body has a fault; false otherwise + */ + boolean hasFault(); + + /** + * Returns the SoapFault of this body. + * + * @return the SoapFault, or null if none is present + */ + SoapFault getFault(); +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapBodyException.java b/core/src/main/java/org/springframework/ws/soap/SoapBodyException.java new file mode 100644 index 00000000..21406797 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapBodyException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +/** + * Exception thrown when a SOAP body could not be accessed. + * + * @author Arjen Poutsma + */ +public class SoapBodyException extends SoapMessageException { + + public SoapBodyException(String msg) { + super(msg); + } + + public SoapBodyException(String msg, Throwable ex) { + super(msg, ex); + } + + public SoapBodyException(Throwable ex) { + super("Could not access body: " + ex.getMessage(), ex); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapElement.java b/core/src/main/java/org/springframework/ws/soap/SoapElement.java new file mode 100644 index 00000000..02f9746f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapElement.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import javax.xml.namespace.QName; +import javax.xml.transform.Source; + +/** + * The base interface for all elements that are contained in a SOAP message. + * + * @author Arjen Poutsma + * @see SoapMessage + */ +public interface SoapElement { + + /** + * Returns the qualified name of this element. + * + * @return the qualified name of this element + */ + QName getName(); + + /** + * Returns the Source of this element. This includes the element itself, i.e. + * SoapEnvelope.getSource() will include the Envelope tag. + * + * @return the Source of this element + */ + Source getSource(); + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapEndpointInterceptor.java b/core/src/main/java/org/springframework/ws/soap/SoapEndpointInterceptor.java new file mode 100644 index 00000000..4f758697 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapEndpointInterceptor.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.context.MessageContext; + +/** + * SOAP-specific extension of the EndpointInterceptor interface. Allows for handling of SOAP faults, which + * are considered different from regular responses. + * + * @author Arjen Poutsma + */ +public interface SoapEndpointInterceptor extends EndpointInterceptor { + + /** + * Processes the fault. Both request and response of the message context should be filled; the body of the response + * message contains the fault. + * + * @param messageContext contains both request and response messages, the response should contains a SOAP Fault + * @param endpoint chosen endpoint to invoke + * @return true to continue processing of the reponse interceptor chain; false to indicate + * blocking of the response handler chain. + */ + boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception; + + /** + * Given a SoapHeaderElement, return whether or not this SoapEndpointInterceptor + * understands it. + * + * @param header the header + * @return true if understood, false otherwise + */ + boolean understands(SoapHeaderElement header); + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapEndpointInvocationChain.java b/core/src/main/java/org/springframework/ws/soap/SoapEndpointInvocationChain.java new file mode 100644 index 00000000..0acee05a --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapEndpointInvocationChain.java @@ -0,0 +1,75 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.EndpointInvocationChain; + +/** + * SOAP-specific subclass of the EndpointInvocationChain. Adds associated actors (SOAP 1.1) or roles (SOAP + * 1.2). Used by the SoapMessageDispatcher to determine the MustUnderstand headers for particular + * endpoint. + * + * @author Arjen Poutsma + * @see #getActorsOrRoles() + * @see SoapMessageDispatcher + */ +public class SoapEndpointInvocationChain extends EndpointInvocationChain { + + private String[] actorsOrRoles; + + /** + * Create new SoapEndpointInvocationChain. + * + * @param endpoint the endpoint object to invoke + */ + public SoapEndpointInvocationChain(Object endpoint) { + super(endpoint); + } + + /** + * Create new SoapEndpointInvocationChain. + * + * @param endpoint the endpoint object to invoke + * @param interceptors the array of interceptors to apply + */ + public SoapEndpointInvocationChain(Object endpoint, EndpointInterceptor[] interceptors) { + super(endpoint, interceptors); + } + + /** + * Create new EndpointInvocationChain. + * + * @param endpoint the endpoint object to invoke + * @param interceptors the array of interceptors to apply + * @param actorsOrRoles the array of actorsOrRoles to set + */ + public SoapEndpointInvocationChain(Object endpoint, EndpointInterceptor[] interceptors, String[] actorsOrRoles) { + super(endpoint, interceptors); + this.actorsOrRoles = actorsOrRoles; + } + + /** + * Gets the actors (SOAP 1.1) or roles (SOAP 1.2) associated with an invocation of this chain and its contained + * interceptors and endpoint. + * + * @return a string array of URIs for SOAP actors/roles + */ + public String[] getActorsOrRoles() { + return actorsOrRoles; + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapEndpointMapping.java b/core/src/main/java/org/springframework/ws/soap/SoapEndpointMapping.java new file mode 100644 index 00000000..16cf0725 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapEndpointMapping.java @@ -0,0 +1,43 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import org.springframework.ws.EndpointMapping; + +/** + * SOAP-specific sub-interface of the EndpointMapping. Adds associated actors (SOAP 1.1) or roles (SOAP + * 1.2). Used by the SoapMessageDispatcher to determine the MustUnderstand headers for particular + * endpoint. + *

+ * The main purpose for this interface is to add consitency between all SOAP-specific EndpointMappings. The + * SoapMessageDispatcher does not require all endpoint mappings to implement this interface. + * + * @author Arjen Poutsma + */ +public interface SoapEndpointMapping extends EndpointMapping { + + /** + * Sets a single SOAP actor/actorOrRole to apply to all endpoints mapped by the delegate endpoint mapping. + */ + void setActorOrRole(String actorOrRole); + + /** + * Sets the array of SOAP actors/actorsOrRoles to apply to all endpoints mapped by the delegate endpoint mapping. + */ + void setActorsOrRoles(String[] actorsOrRoles); + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapEnvelope.java b/core/src/main/java/org/springframework/ws/soap/SoapEnvelope.java new file mode 100644 index 00000000..48bacbd3 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapEnvelope.java @@ -0,0 +1,43 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +/** + * Represents the Envelope element in a SOAP message. The header contains the optional + * SoapHeader and SoapBody. + * + * @author Arjen Poutsma + */ +public interface SoapEnvelope extends SoapElement { + + /** + * Returns the SoapHeader. Returns null if no header is present. + * + * @return the SoapHeader, or null + * @throws SoapHeaderException if the header cannot be returned + */ + SoapHeader getHeader() throws SoapHeaderException; + + /** + * Returns the SoapBody. + * + * @return the SoapBody + * @throws SoapBodyException if the header cannot be returned + */ + SoapBody getBody() throws SoapBodyException; + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapEnvelopeException.java b/core/src/main/java/org/springframework/ws/soap/SoapEnvelopeException.java new file mode 100644 index 00000000..718d1614 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapEnvelopeException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +/** + * Exception thrown when a SOAP body could not be accessed. + * + * @author Arjen Poutsma + */ +public class SoapEnvelopeException extends SoapMessageException { + + public SoapEnvelopeException(String msg) { + super(msg); + } + + public SoapEnvelopeException(String msg, Throwable ex) { + super(msg, ex); + } + + public SoapEnvelopeException(Throwable ex) { + super("Could not access envelope: " + ex.getMessage(), ex); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapFault.java b/core/src/main/java/org/springframework/ws/soap/SoapFault.java new file mode 100644 index 00000000..9b12ef14 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapFault.java @@ -0,0 +1,59 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import javax.xml.namespace.QName; + +/** + * Represents the Fault element in the body of a SOAP message. A faul consists of a fault code, string, and + * role. + * + * @author Arjen Poutsma + */ +public interface SoapFault extends SoapElement { + + /** + * Returns the fault code. + * + * @return a QName representing the fault code + */ + QName getFaultCode(); + + /** + * Returns the fault actor or role. For SOAP 1.1, this returns the actor. For SOAP 1.2, this returns the role. + */ + String getFaultActorOrRole(); + + /** + * Sets the fault actor. For SOAP 1.1, this sets the actor. For SOAP 1.2, this sets the role. + */ + void setFaultActorOrRole(String faultActor); + + /** + * Creates an optional SoapFaultDetail object and assigns it to this fault. + * + * @return the created detail + */ + SoapFaultDetail getFaultDetail(); + + /** + * Returns the optional detail element for this SoapFault. + * + * @return a fault detail + */ + SoapFaultDetail addFaultDetail(); +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapFaultDetail.java b/core/src/main/java/org/springframework/ws/soap/SoapFaultDetail.java new file mode 100644 index 00000000..526c6f86 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapFaultDetail.java @@ -0,0 +1,48 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import java.util.Iterator; + +import javax.xml.namespace.QName; + +/** + * Represents the detail element in a SOAP fault. A detail contains SoapFaultDetailElements, + * which represent the individual details. + * + * @author Arjen Poutsma + * @see SoapFaultDetailElement + */ +public interface SoapFaultDetail extends SoapElement { + + /** + * Adds a new SoapFaultDetailElement with the specified qualified name to this detail. + * + * @param name the qualified name of the new detail element + * @return the created SoapFaultDetailElement + */ + SoapFaultDetailElement addFaultDetailElement(QName name); + + /** + * Gets an iterator over all of the SoapFaultDetailElements in this detail. + * + * @return an iterator over all the SoapFaultDetailElements + * @see SoapFaultDetailElement + */ + Iterator getDetailEntries(); + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapFaultDetailElement.java b/core/src/main/java/org/springframework/ws/soap/SoapFaultDetailElement.java new file mode 100644 index 00000000..e1367c00 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapFaultDetailElement.java @@ -0,0 +1,39 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import javax.xml.transform.Result; + +/** + * Represents the content for an individual SOAP detail entry in a SOAP Message. All + * SoapFaultDetailElements are contained in a SoapDetail. + * + * @author Arjen Poutsma + * @see SoapFaultDetail + */ +public interface SoapFaultDetailElement extends SoapElement { + + /** + * Returns a Result that allows for writing to the contents of the detail element. + */ + Result getResult(); + + /** + * Adds a new text node to this element. + */ + void addText(String text); +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapFaultException.java b/core/src/main/java/org/springframework/ws/soap/SoapFaultException.java new file mode 100644 index 00000000..f9cccb89 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapFaultException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +/** + * Exception thrown when a SOAP fault could not be accessed. + * + * @author Arjen Poutsma + */ +public class SoapFaultException extends SoapEnvelopeException { + + public SoapFaultException(String msg) { + super(msg); + } + + public SoapFaultException(String msg, Throwable ex) { + super(msg, ex); + } + + public SoapFaultException(Throwable ex) { + super("Could not access fault: " + ex.getMessage(), ex); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapHeader.java b/core/src/main/java/org/springframework/ws/soap/SoapHeader.java new file mode 100644 index 00000000..6a9e3a63 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapHeader.java @@ -0,0 +1,61 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import java.util.Iterator; +import javax.xml.namespace.QName; + +/** + * Represents the Header element in a SOAP message. A SOAP header contains SoapHeaderElements, + * which represent the individual headers. + * + * @author Arjen Poutsma + * @see SoapHeaderElement + * @see SoapEnvelope#getHeader() + */ +public interface SoapHeader extends SoapElement { + + /** + * Adds a new SoapHeaderElement with the specified qualified name to this header. + * + * @param name the qualified name of the new header element + * @return the created SoapHeaderElement + * @throws SoapHeaderException if the header cannot be created + */ + SoapHeaderElement addHeaderElement(QName name) throws SoapHeaderException; + + /** + * Returns an Iterator over all the SoapHeaderElements that have the specified actor or + * role and that have a MustUnderstand attribute whose value is equivalent to true. + * + * @param actorOrRole the actor (SOAP 1.1) or role (SOAP 1.2) for which to search + * @return an iterator over all the header elements that contain the specified actor/role and are marked as + * MustUnderstand + * @throws SoapHeaderException if the headers cannot be returned + * @see SoapHeaderElement + */ + Iterator examineMustUnderstandHeaderElements(String actorOrRole) throws SoapHeaderException; + + /** + * Returns an Iterator over all the SoapHeaderElements in this header. + * + * @return an iterator over all the header elements + * @throws SoapHeaderException if the header cannot be returned + * @see SoapHeaderElement + */ + Iterator examineAllHeaderElements() throws SoapHeaderException; +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapHeaderElement.java b/core/src/main/java/org/springframework/ws/soap/SoapHeaderElement.java new file mode 100644 index 00000000..0cc5fbf5 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapHeaderElement.java @@ -0,0 +1,67 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import javax.xml.transform.Result; + +/** + * Represents the contents of an individual SOAP header in the a SOAP message. All SoapHeaderElements are + * contained in a SoapHeader. + * + * @author Arjen Poutsma + * @see SoapHeader + */ +public interface SoapHeaderElement extends SoapElement { + + /** + * Returns the actor or role for this header element. In a SOAP 1.1 compliant message, this will read the + * actor attribute; in SOAP 1.2, the role attribute. + * + * @return the role of the header + */ + String getActorOrRole() throws SoapHeaderException; + + /** + * Sets the actor or role for this header element. In a SOAP 1.1 compliant message, this will result in an + * actor attribute being set; in SOAP 1.2, a actorOrRole attribute. + * + * @param actorOrRole the actorOrRole value + */ + void setActorOrRole(String actorOrRole) throws SoapHeaderException; + + /** + * Indicates whether the mustUnderstand attribute for this header element is set. + * + * @return true if the mustUnderstand attribute is set; false otherwise + */ + boolean getMustUnderstand() throws SoapHeaderException; + + /** + * Sets the mustUnderstand attribute for this header element. If the attribute is on, the role who + * receives the header must process it. + * + * @param mustUnderstand true to set the mustUnderstand attribute on; false + * to turn it off + */ + void setMustUnderstand(boolean mustUnderstand) throws SoapHeaderException; + + /** + * Returns a Result that allows for writing to the contents of the header element. + */ + Result getResult() throws SoapHeaderException; + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapHeaderException.java b/core/src/main/java/org/springframework/ws/soap/SoapHeaderException.java new file mode 100644 index 00000000..e68b142f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapHeaderException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +/** + * Exception thrown when a SOAP header could not be accessed. + * + * @author Arjen Poutsma + */ +public class SoapHeaderException extends SoapMessageException { + + public SoapHeaderException(String msg) { + super(msg); + } + + public SoapHeaderException(String msg, Throwable ex) { + super(msg, ex); + } + + public SoapHeaderException(Throwable ex) { + super("Could not access header: " + ex.getMessage(), ex); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapMessage.java b/core/src/main/java/org/springframework/ws/soap/SoapMessage.java new file mode 100644 index 00000000..46dd8743 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapMessage.java @@ -0,0 +1,115 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +import java.io.File; +import java.util.Iterator; + +import org.springframework.core.io.InputStreamSource; +import org.springframework.ws.WebServiceMessage; + +/** + * Represents an abstraction for SOAP messages, providing access to a SOAP Envelope. The contents of the SOAP body can + * be retrieved by getPayloadSource() and getPayloadResult() on + * WebServiceMessage, the super-interface of this interface. + * + * @author Arjen Poutsma + * @see #getPayloadSource() + * @see #getPayloadResult() + * @see #getEnvelope() + */ +public interface SoapMessage extends WebServiceMessage { + + /** + * Returns the SoapEnvelope associated with this SoapMessage. + */ + SoapEnvelope getEnvelope() throws SoapEnvelopeException; + + /** + * Returns the SoapBody associated with this SoapMessage. This is a convenience method for + * getEnvelope().getBody(). + * + * @see SoapEnvelope#getBody() + */ + SoapBody getSoapBody() throws SoapBodyException; + + /** + * Returns the SoapHeader associated with this SoapMessage. This is a convenience method + * for getEnvelope().getHeader(). + * + * @see SoapEnvelope#getHeader() + */ + SoapHeader getSoapHeader() throws SoapHeaderException; + + /** + * Returns the SOAP version of this message. This can be either SOAP 1.1 or SOAP 1.2. + * + * @return the SOAP version + * @see SoapVersion#SOAP_11 + * @see SoapVersion#SOAP_12 + */ + SoapVersion getVersion(); + + /** + * Returns the Attachment with the specified content Id. + * + * @return the attachment with the specified content id; or null if it cannot be found + * @throws AttachmentException in case of errors + */ + Attachment getAttachment(String contentId) throws AttachmentException; + + /** + * Returns an Iterator over all Attachments that are part of this + * SoapMessage. + * + * @return an iterator over all attachments + * @throws AttachmentException in case of errors + * @see Attachment + */ + Iterator getAttachments() throws AttachmentException; + + /** + * Add an attachment to the SoapMessage, taking the content from a java.io.File. + *

+ * The content type will be determined by the name of the given content file. Do not use this for temporary files + * with arbitrary filenames (possibly ending in ".tmp" or the like)! + * + * @param file the File resource to take the content from + * @return the added attachment + * @throws AttachmentException in case of errors + * @see #addAttachment(InputStreamSource, String) + */ + Attachment addAttachment(File file) throws AttachmentException; + + /** + * Add an attachment to the SoapMessage, taking the content from an + * org.springframework.core.io.InputStreamResource. + *

+ * Note that the InputStream returned by the source needs to be a fresh one on each call, as + * underlying implementations can invoke getInputStream() multiple times. + * + * @param inputStreamSource the resource to take the content from (all of Spring's Resource implementations can be + * passed in here) + * @param contentType the content type to use for the element + * @return the added attachment + * @throws AttachmentException in case of errors + * @see #addAttachment(java.io.File) + * @see org.springframework.core.io.Resource + */ + Attachment addAttachment(InputStreamSource inputStreamSource, String contentType); + +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapMessageCreationException.java b/core/src/main/java/org/springframework/ws/soap/SoapMessageCreationException.java new file mode 100644 index 00000000..750e9572 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapMessageCreationException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +/** + * Exception thrown when a web service message cannot be created. + * + * @author Arjen Poutsma + */ +public class SoapMessageCreationException extends SoapMessageException { + + /** + * Constructor for SoapMessageCreationException. + */ + public SoapMessageCreationException(String msg) { + super(msg); + } + + /** + * Constructor for SoapMessageCreationException. + */ + public SoapMessageCreationException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapMessageDispatcher.java b/core/src/main/java/org/springframework/ws/soap/SoapMessageDispatcher.java new file mode 100644 index 00000000..451b2bf6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapMessageDispatcher.java @@ -0,0 +1,213 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.EndpointInvocationChain; +import org.springframework.ws.MessageDispatcher; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.context.SoapMessageContext; +import org.springframework.ws.soap.soap12.Soap12Header; + +/** + * SOAP-specific subclass of the MessageDispatcher. Adds functionality for adding actor roles to a endpoint + * invocation chain, and endpoint interception using SoapEndpointInterceptors. + * + * @author Arjen Poutsma + * @see SoapMessage + * @see SoapEndpointInterceptor + */ +public class SoapMessageDispatcher extends MessageDispatcher { + + /** + * Default message used when creating a SOAP MustUnderstand fault. + */ + public static final String DEFAULT_MUST_UNDERSTAND_FAULT = + "One or more mandatory SOAP header blocks not understood"; + + private String mustUnderstandFault = DEFAULT_MUST_UNDERSTAND_FAULT; + + private Locale mustUnderstandFaultLocale = Locale.ENGLISH; + + /** + * Sets the message used for MustUnderstand fault. Default to DEFAULT_MUST_UNDERSTAND_FAULT. + * + * @see #DEFAULT_MUST_UNDERSTAND_FAULT + */ + public void setMustUnderstandFault(String mustUnderstandFault) { + this.mustUnderstandFault = mustUnderstandFault; + } + + /** + * Sets the locale of the message used for MustUnderstand fault. Default to + * Locale.ENGLISH. + */ + public void setMustUnderstandFaultLocale(Locale mustUnderstandFaultLocale) { + this.mustUnderstandFaultLocale = mustUnderstandFaultLocale; + } + + /** + * Process the MustUnderstand headers in the incoming SOAP request message. Iterates over all SOAP + * headers which should be understood, and determines whether these are supported. Generates a SOAP MustUnderstand + * fault if a header is not understood. + * + * @param mappedEndpoint the mapped EndpointInvocationChain + * @param messageContext the message context + * @return true if all necessary headers are understood; false otherwise + * @see SoapEndpointInvocationChain#getActorsOrRoles() + * @see SoapHeader#examineMustUnderstandHeaderElements(String) + */ + protected boolean handleRequest(EndpointInvocationChain mappedEndpoint, MessageContext messageContext) { + if (messageContext instanceof SoapMessageContext) { + SoapMessageContext soapContext = (SoapMessageContext) messageContext; + if (soapContext.getSoapRequest().getSoapHeader() == null) { + // no headers to process + return true; + } + String[] roles = getRoles(mappedEndpoint, soapContext.getSoapRequest().getVersion()); + if (logger.isDebugEnabled()) { + logger.debug("Handling MustUnderstand headers for actors/roles [" + + StringUtils.arrayToCommaDelimitedString(roles)); + } + for (int i = 0; i < roles.length; i++) { + if (!handleRequestForRole(mappedEndpoint, soapContext, roles[i])) { + return false; + } + } + } + return true; + } + + /** + * Determines the roles for a specific SOAP invocation chain. Gets the roles specified on the chain, and adds the + * SOAP-version specific 'next' role to it. + * + * @see SoapVersion#getNextActorOrRoleUri() + */ + protected String[] getRoles(EndpointInvocationChain mappedEndpoint, SoapVersion version) { + String[] mappedRoles = null; + if (mappedEndpoint instanceof SoapEndpointInvocationChain) { + SoapEndpointInvocationChain soapEndpoint = (SoapEndpointInvocationChain) mappedEndpoint; + mappedRoles = soapEndpoint.getActorsOrRoles(); + } + if (mappedRoles == null) { + mappedRoles = new String[0]; + } + return StringUtils.addStringToArray(mappedRoles, version.getNextActorOrRoleUri()); + } + + /** + * Handles the request for a single SOAP actor/role. Iterates over all MustUnderstand headers for a + * specific SOAP 1.1 actor or SOAP 1.2 role, and determines whether these are understood by any of the registered + * SoapEndpointInterceptor. If they are, returns true. If they are not, a SOAP fault is + * created, and false is returned. + * + * @see SoapEndpointInterceptor#understands(SoapHeaderElement) + */ + private boolean handleRequestForRole(EndpointInvocationChain mappedEndpoint, + SoapMessageContext messageContext, + String actorOrRole) { + SoapHeader requestHeader = messageContext.getSoapRequest().getSoapHeader(); + List notUnderstoodHeaderNames = new ArrayList(); + for (Iterator iterator = requestHeader.examineMustUnderstandHeaderElements(actorOrRole); iterator.hasNext();) { + SoapHeaderElement headerElement = (SoapHeaderElement) iterator.next(); + QName headerName = headerElement.getName(); + if (logger.isDebugEnabled()) { + logger.debug( + "Received mustUnderstand header [" + headerName + "] for actor/role [" + actorOrRole + "]"); + } + boolean understood = false; + for (int i = 0; i < mappedEndpoint.getInterceptors().length; i++) { + EndpointInterceptor interceptor = mappedEndpoint.getInterceptors()[i]; + if (interceptor instanceof SoapEndpointInterceptor && + ((SoapEndpointInterceptor) interceptor).understands(headerElement)) { + understood = true; + break; + } + } + if (!understood) { + notUnderstoodHeaderNames.add(headerName); + } + } + if (notUnderstoodHeaderNames.isEmpty()) { + return true; + } + else { + if (logger.isWarnEnabled()) { + logger.warn("Could not handle mustUnderstand headers: " + + StringUtils.collectionToCommaDelimitedString(notUnderstoodHeaderNames) + ". Returning fault"); + } + SoapBody responseBody = messageContext.getSoapResponse().getSoapBody(); + SoapFault fault = responseBody.addMustUnderstandFault(mustUnderstandFault, mustUnderstandFaultLocale); + fault.setFaultActorOrRole(actorOrRole); + SoapHeader header = messageContext.getSoapResponse().getSoapHeader(); + if (header instanceof Soap12Header) { + Soap12Header soap12Header = (Soap12Header) header; + for (Iterator iterator = notUnderstoodHeaderNames.iterator(); iterator.hasNext();) { + QName headerName = (QName) iterator.next(); + soap12Header.addNotUnderstoodHeaderElement(headerName); + } + } + return false; + } + } + + /** + * Trigger handleResponse or handleFault on the mapped EndpointInterceptors. Will just invoke said method on all + * interceptors whose handleRequest invocation returned true, in addition to the last interceptor who + * returned false. + * + * @param mappedEndpoint the mapped EndpointInvocationChain + * @param interceptorIndex index of last interceptor that was called + * @param messageContext the message context, whose request and response are filled + * @see org.springframework.ws.EndpointInterceptor#handleResponse(org.springframework.ws.context.MessageContext, + * Object) + */ + protected void triggerHandleResponse(EndpointInvocationChain mappedEndpoint, + int interceptorIndex, + MessageContext messageContext) throws Exception { + if (mappedEndpoint != null && messageContext.hasResponse() && + !ObjectUtils.isEmpty(mappedEndpoint.getInterceptors())) { + boolean hasFault = false; + if (messageContext instanceof SoapMessageContext) { + SoapMessageContext soapMessageContext = (SoapMessageContext) messageContext; + hasFault = soapMessageContext.getSoapResponse().getSoapBody().hasFault(); + } + boolean resume = true; + for (int i = interceptorIndex; resume && i >= 0; i--) { + EndpointInterceptor interceptor = mappedEndpoint.getInterceptors()[i]; + if (hasFault) { + if (interceptor instanceof SoapEndpointInterceptor) { + SoapEndpointInterceptor soapEndpointInterceptor = (SoapEndpointInterceptor) interceptor; + resume = soapEndpointInterceptor.handleFault(messageContext, mappedEndpoint.getEndpoint()); + } + } + else { + resume = interceptor.handleResponse(messageContext, mappedEndpoint.getEndpoint()); + } + } + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapMessageException.java b/core/src/main/java/org/springframework/ws/soap/SoapMessageException.java new file mode 100644 index 00000000..6196dea3 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapMessageException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap; + +import org.springframework.ws.WebServiceMessageException; + +/** + * Base class for all SOAP message exceptions. + * + * @author Arjen Poutsma + */ +public abstract class SoapMessageException extends WebServiceMessageException { + + /** + * Constructor for SoapMessageException. + */ + public SoapMessageException(String msg) { + super(msg); + } + + /** + * Constructor for SoapMessageException. + */ + public SoapMessageException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/SoapVersion.java b/core/src/main/java/org/springframework/ws/soap/SoapVersion.java new file mode 100644 index 00000000..66312994 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/SoapVersion.java @@ -0,0 +1,308 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import javax.xml.namespace.QName; + +/** + * Interface that defines a specific version of the SOAP specification. Contains properties for elements that make up a + * soap envelope. + * + * @author Arjen Poutsma + * @see #SOAP_11 + * @see #SOAP_12 + */ +public interface SoapVersion { + + /** + * Represents version 1.1 of the SOAP specification. + * + * @see SOAP 1.1 specification + */ + SoapVersion SOAP_11 = new SoapVersion() { + + private static final String ENVELOPE_NAMESPACE_URI = "http://schemas.xmlsoap.org/soap/envelope/"; + + private static final String NEXT_ROLE_URI = "http://schemas.xmlsoap.org/soap/actor/next"; + + private static final String CONTENT_TYPE = "text/xml"; + + private final QName ENVELOPE_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Envelope"); + + private final QName HEADER_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Header"); + + private final QName BODY_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Body"); + + private final QName FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Fault"); + + private final QName MUST_UNDERSTAND_ATTRIBUTE_NAME = new QName(ENVELOPE_NAMESPACE_URI, "mustUnderstand"); + + private final QName ACTOR_NAME = new QName(ENVELOPE_NAMESPACE_URI, "actor"); + + private final QName CLIENT_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Client"); + + private final QName SERVER_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Server"); + + private final QName MUST_UNDERSTAND_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "MustUnderstand"); + + private final QName VERSION_MISMATCH_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "VersionMismatch"); + + public QName getBodyName() { + return BODY_NAME; + } + + public QName getEnvelopeName() { + return ENVELOPE_NAME; + } + + public String getEnvelopeNamespaceUri() { + return ENVELOPE_NAMESPACE_URI; + } + + public QName getFaultName() { + return FAULT_NAME; + } + + public QName getHeaderName() { + return HEADER_NAME; + } + + public String getNextActorOrRoleUri() { + return NEXT_ROLE_URI; + } + + public String getNoneActorOrRoleUri() { + return ""; + } + + public QName getServerOrReceiverFaultName() { + return SERVER_FAULT_NAME; + } + + public String getUltimateReceiverRoleUri() { + return ""; + } + + public QName getActorOrRoleName() { + return ACTOR_NAME; + } + + public QName getClientOrSenderFaultName() { + return CLIENT_FAULT_NAME; + } + + public String getContentType() { + return CONTENT_TYPE; + } + + public QName getMustUnderstandAttributeName() { + return MUST_UNDERSTAND_ATTRIBUTE_NAME; + } + + public QName getMustUnderstandFaultName() { + return MUST_UNDERSTAND_FAULT_NAME; + } + + public QName getVersionMismatchFaultName() { + return VERSION_MISMATCH_FAULT_NAME; + } + + public String toString() { + return "SOAP 1.1"; + } + }; + + /** + * Represents version 1.2 of the SOAP specification. + * + * @see SOAP 1.2 specification + */ + SoapVersion SOAP_12 = new SoapVersion() { + + private static final String ENVELOPE_NAMESPACE_URI = "http://www.w3.org/2003/05/soap-envelope"; + + private static final String NEXT_ROLE_URI = ENVELOPE_NAMESPACE_URI + "/role/next"; + + private static final String NONE_ROLE_URI = ENVELOPE_NAMESPACE_URI + "/role/none"; + + private static final String ULTIMATE_RECEIVER_ROLE_URI = ENVELOPE_NAMESPACE_URI + "/role/ultimateReceiver"; + + private static final String CONTENT_TYPE = "application/soap+xml"; + + private final QName ENVELOPE_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Envelope"); + + private final QName HEADER_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Header"); + + private final QName BODY_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Body"); + + private final QName FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Fault"); + + private final QName MUST_UNDERSTAND_ATTRIBUTE_NAME = new QName(ENVELOPE_NAMESPACE_URI, "mustUnderstand"); + + private final QName ROLE_NAME = new QName(ENVELOPE_NAMESPACE_URI, "role"); + + private final QName SENDER_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Sender"); + + private final QName RECEIVER_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "Receiver"); + + private final QName MUST_UNDERSTAND_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "MustUnderstand"); + + private final QName VERSION_MISMATCH_FAULT_NAME = new QName(ENVELOPE_NAMESPACE_URI, "VersionMismatch"); + + public QName getBodyName() { + return BODY_NAME; + } + + public QName getEnvelopeName() { + return ENVELOPE_NAME; + } + + public String getEnvelopeNamespaceUri() { + return ENVELOPE_NAMESPACE_URI; + } + + public QName getFaultName() { + return FAULT_NAME; + } + + public QName getHeaderName() { + return HEADER_NAME; + } + + public String getNextActorOrRoleUri() { + return NEXT_ROLE_URI; + } + + public String getNoneActorOrRoleUri() { + return NONE_ROLE_URI; + } + + public QName getServerOrReceiverFaultName() { + return RECEIVER_FAULT_NAME; + } + + public String getUltimateReceiverRoleUri() { + return ULTIMATE_RECEIVER_ROLE_URI; + } + + public QName getActorOrRoleName() { + return ROLE_NAME; + } + + public QName getClientOrSenderFaultName() { + return SENDER_FAULT_NAME; + } + + public String getContentType() { + return CONTENT_TYPE; + } + + public QName getMustUnderstandAttributeName() { + return MUST_UNDERSTAND_ATTRIBUTE_NAME; + } + + public QName getMustUnderstandFaultName() { + return MUST_UNDERSTAND_FAULT_NAME; + } + + public QName getVersionMismatchFaultName() { + return VERSION_MISMATCH_FAULT_NAME; + } + + public String toString() { + return "SOAP 1.2"; + } + + }; + + /** + * Returns the qualified name for a SOAP body. + */ + QName getBodyName(); + + /** + * Returns the Content-Type MIME header for a SOAP message. + */ + String getContentType(); + + /** + * Returns the qualified name for a SOAP envelope. + */ + QName getEnvelopeName(); + + /** + * Returns the namespace URI for the SOAP envelope namespace. + */ + String getEnvelopeNamespaceUri(); + + /** + * Returns the qualified name for a SOAP fault. + */ + QName getFaultName(); + + /** + * Returns the qualified name for a SOAP header. + */ + QName getHeaderName(); + + /** + * Returns the qualified name of the SOAP MustUnderstand attribute. + */ + QName getMustUnderstandAttributeName(); + + /** + * Returns the URI indicating that a header element is intended for the next SOAP application that processes the + * message. + */ + String getNextActorOrRoleUri(); + + /** + * Returns the URI indicating that a header element should never be directly processed. + */ + String getNoneActorOrRoleUri(); + + /** + * Returns the qualified name of the MustUnderstand SOAP Fault value. + */ + QName getMustUnderstandFaultName(); + + /** + * Returns the qualified name of the Receiver/Server SOAP Fault value. + */ + QName getServerOrReceiverFaultName(); + + /** + * Returns the qualified name of the VersionMismatch SOAP Fault value. + */ + QName getVersionMismatchFaultName(); + + /** + * Returns the qualified name of the SOAP actor/role attribute. + */ + QName getActorOrRoleName(); + + /** + * Returns the qualified name of the Sender/Client SOAP Fault value. + */ + QName getClientOrSenderFaultName(); + + /** + * Returns the URI indicating that a header element should only be processed by nodes acting as the ultimate + * receiver of a message. + */ + String getUltimateReceiverRoleUri(); +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachmentException.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachmentException.java new file mode 100644 index 00000000..a8852c96 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomAttachmentException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.AttachmentException; + +/** + * @author Arjen Poutsma + */ +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/core/src/main/java/org/springframework/ws/soap/axiom/AxiomContentHandler.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomContentHandler.java new file mode 100644 index 00000000..8190dcf9 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomContentHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.impl.builder.SAXOMBuilder; +import org.springframework.util.Assert; +import org.xml.sax.SAXException; + +/** + * Specific SAX ContentHandler that adds the resulting AXIOM OMElement to a specified parent element when + * endDocument is called. Used for returing SAXResults from Axiom elements. + * + * @author Arjen Poutsma + */ +class AxiomContentHandler extends SAXOMBuilder { + + private OMElement parentElement = null; + + public AxiomContentHandler(OMElement parentElement) { + Assert.notNull(parentElement, "No parentElement given"); + this.parentElement = parentElement; + } + + public void endDocument() throws SAXException { + super.endDocument(); + parentElement.addChild(super.getRootElement()); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Body.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Body.java new file mode 100644 index 00000000..6249d7a8 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Body.java @@ -0,0 +1,117 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import java.util.Locale; +import javax.xml.namespace.QName; + +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.SOAPFaultText; +import org.apache.axiom.soap.SOAPFaultValue; +import org.apache.axiom.soap.SOAPProcessingException; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.soap11.Soap11Body; +import org.springframework.ws.soap.soap11.Soap11Fault; +import org.springframework.xml.namespace.QNameUtils; + +/** + * Axiom-specific version of org.springframework.ws.soap.Soap11Body. + * + * @author Arjen Poutsma + */ +class AxiomSoap11Body extends AxiomSoapBody implements Soap11Body { + + AxiomSoap11Body(SOAPBody axiomBody, SOAPFactory axiomFactory, boolean payloadCaching) { + super(axiomBody, axiomFactory, payloadCaching); + } + + public SoapFault addMustUnderstandFault(String faultString, Locale locale) { + SOAPFault fault = addStandardFault(SOAP11Constants.FAULT_CODE_MUST_UNDERSTAND, faultString, locale); + return new AxiomSoap11Fault(fault, axiomFactory); + } + + public SoapFault addClientOrSenderFault(String faultString, Locale locale) { + SOAPFault fault = addStandardFault(SOAP11Constants.FAULT_CODE_SENDER, faultString, locale); + return new AxiomSoap11Fault(fault, axiomFactory); + } + + public SoapFault addServerOrReceiverFault(String faultString, Locale locale) { + SOAPFault fault = addStandardFault(SOAP11Constants.FAULT_CODE_RECEIVER, faultString, locale); + return new AxiomSoap11Fault(fault, axiomFactory); + } + + public SoapFault addVersionMismatchFault(String faultString, Locale locale) { + SOAPFault fault = addStandardFault(SOAP11Constants.FAULT_CODE_VERSION_MISMATCH, faultString, locale); + return new AxiomSoap11Fault(fault, axiomFactory); + } + + public Soap11Fault addFault(QName code, String faultString, Locale locale) { + 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"); + } + try { + detachAllBodyChildren(); + SOAPFault fault = axiomFactory.createSOAPFault(axiomBody); + SOAPFaultCode faultCode = axiomFactory.createSOAPFaultCode(fault); + SOAPFaultValue faultCodeValue = axiomFactory.createSOAPFaultValue(faultCode); + setValueText(code, fault, faultCodeValue); + SOAPFaultReason faultReason = axiomFactory.createSOAPFaultReason(fault); + SOAPFaultText faultText = axiomFactory.createSOAPFaultText(faultReason); + if (locale != null) { + faultText.setLang(AxiomUtils.toLanguage(locale)); + } + faultText.setText(faultString); + return new AxiomSoap11Fault(fault, axiomFactory); + + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + private void setValueText(QName code, SOAPFault fault, SOAPFaultValue faultValue) { + String prefix = QNameUtils.getPrefix(code); + 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) { + throw new IllegalArgumentException("Could not resolve namespace of code [" + code + "]"); + } + code = QNameUtils.createQName(code.getNamespaceURI(), code.getLocalPart(), namespace.getPrefix()); + } + faultValue.setText(prefix + ":" + code.getLocalPart()); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Fault.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Fault.java new file mode 100644 index 00000000..893456ea --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap11Fault.java @@ -0,0 +1,62 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import java.util.Locale; + +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPFault; +import org.apache.axiom.soap.SOAPFaultText; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.ws.soap.soap11.Soap11Fault; + +/** + * Axiom-specific version of org.springframework.ws.soap.Soap11Fault. + * + * @author Arjen Poutsma + */ +class AxiomSoap11Fault extends AxiomSoapFault implements Soap11Fault { + + AxiomSoap11Fault(SOAPFault axiomFault, SOAPFactory axiomFactory) { + super(axiomFault, axiomFactory); + } + + public String getFaultString() { + if (axiomFault.getReason() != null) { + SOAPFaultText soapText = axiomFault.getReason().getFirstSOAPText(); + if (soapText != null) { + return soapText.getText(); + } + } + return null; + } + + public Locale getFaultStringLocale() { + if (axiomFault.getReason() != null) { + SOAPFaultText soapText = axiomFault.getReason().getFirstSOAPText(); + if (soapText != null) { + String xmlLangString = soapText.getLang(); + if (xmlLangString != null) { + return AxiomUtils.toLocale(xmlLangString); + } + + } + } + return null; + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Body.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Body.java new file mode 100644 index 00000000..ee65c130 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Body.java @@ -0,0 +1,72 @@ +/* + * Copyright 2006 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 + * + * 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 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.springframework.util.Assert; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.soap12.Soap12Body; +import org.springframework.ws.soap.soap12.Soap12Fault; + +/** + * Axiom-specific version of org.springframework.ws.soap.Soap12Body. + * + * @author Arjen Poutsma + */ +class AxiomSoap12Body extends AxiomSoapBody implements Soap12Body { + + AxiomSoap12Body(SOAPBody axiomBody, SOAPFactory axiomFactory, boolean payloadCaching) { + super(axiomBody, axiomFactory, payloadCaching); + } + + public SoapFault 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, axiomFactory); + } + + public SoapFault addClientOrSenderFault(String reason, Locale locale) { + Assert.notNull(locale, "No locale given"); + SOAPFault fault = addStandardFault(SOAP12Constants.FAULT_CODE_SENDER, reason, locale); + return new AxiomSoap12Fault(fault, axiomFactory); + } + + public SoapFault addServerOrReceiverFault(String reason, Locale locale) { + Assert.notNull(locale, "No locale given"); + SOAPFault fault = addStandardFault(SOAP12Constants.FAULT_CODE_RECEIVER, reason, locale); + return new AxiomSoap12Fault(fault, axiomFactory); + } + + public SoapFault 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, axiomFactory); + } + + 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, axiomFactory); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Fault.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Fault.java new file mode 100644 index 00000000..3f9166c1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Fault.java @@ -0,0 +1,140 @@ +/* + * Copyright 2006 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 + * + * 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 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; +import org.springframework.xml.namespace.QNameUtils; + +/** + * Axiom-specific version of org.springframework.ws.soap.Soap12Fault. + */ +class AxiomSoap12Fault extends AxiomSoapFault implements Soap12Fault { + + AxiomSoap12Fault(SOAPFault axiomFault, SOAPFactory axiomFactory) { + super(axiomFault, axiomFactory); + } + + public Iterator getFaultSubcodes() { + List subcodes = new ArrayList(); + SOAPFaultSubCode subcode = axiomFault.getCode().getSubCode(); + while (subcode != null) { + subcodes.add(getFaultCode(subcode.getValue())); + subcode = subcode.getSubCode(); + } + return subcodes.iterator(); + } + + public void addFaultSubcode(QName subcode) { + SOAPFaultCode faultCode = axiomFault.getCode(); + SOAPFaultSubCode faultSubCode = null; + if (faultCode.getSubCode() == null) { + faultSubCode = axiomFactory.createSOAPFaultSubCode(faultCode); + } + else { + faultSubCode = faultCode.getSubCode(); + while (true) { + if (faultSubCode.getSubCode() != null) { + faultSubCode = faultSubCode.getSubCode(); + } + else { + faultSubCode = axiomFactory.createSOAPFaultSubCode(faultSubCode); + break; + } + } + } + SOAPFaultValue faultValue = axiomFactory.createSOAPFaultValue(faultSubCode); + setValueText(subcode, faultValue); + } + + private void setValueText(QName code, SOAPFaultValue faultValue) { + String prefix = QNameUtils.getPrefix(code); + if (StringUtils.hasLength(code.getNamespaceURI()) && StringUtils.hasLength(prefix)) { + OMNamespace namespace = axiomFault.findNamespaceURI(prefix); + if (namespace == null) { + axiomFault.declareNamespace(code.getNamespaceURI(), prefix); + } + } + else if (StringUtils.hasLength(code.getNamespaceURI())) { + OMNamespace namespace = axiomFault.findNamespace(code.getNamespaceURI(), null); + if (namespace == null) { + throw new IllegalArgumentException("Could not resolve namespace of code [" + code + "]"); + } + code = QNameUtils.createQName(code.getNamespaceURI(), code.getLocalPart(), namespace.getPrefix()); + } + faultValue.setText(prefix + ":" + code.getLocalPart()); + } + + public String getFaultNode() { + SOAPFaultNode faultNode = axiomFault.getNode(); + if (faultNode == null) { + return null; + } + else { + return faultNode.getNodeValue(); + } + } + + public void setFaultNode(String uri) { + try { + SOAPFaultNode faultNode = axiomFactory.createSOAPFaultNode(axiomFault); + faultNode.setNodeValue(uri); + axiomFault.setNode(faultNode); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + } + + public String getFaultReasonText(Locale locale) { + SOAPFaultReason faultReason = axiomFault.getReason(); + String language = AxiomUtils.toLanguage(locale); + SOAPFaultText faultText = faultReason.getSOAPFaultText(language); + return faultText != null ? faultText.getText() : null; + } + + public void setFaultReasonText(Locale locale, String text) { + SOAPFaultReason faultReason = axiomFault.getReason(); + String language = AxiomUtils.toLanguage(locale); + try { + SOAPFaultText faultText = axiomFactory.createSOAPFaultText(faultReason); + faultText.setLang(language); + faultText.setText(text); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Header.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Header.java new file mode 100644 index 00000000..11fcc0f2 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoap12Header.java @@ -0,0 +1,71 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +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.SOAPFactory; +import org.apache.axiom.soap.SOAPHeader; +import org.apache.axiom.soap.SOAPHeaderBlock; +import org.apache.axiom.soap.SOAPProcessingException; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.soap12.Soap12Header; +import org.springframework.xml.namespace.QNameUtils; + +/** + * Axiom-specific version of org.springframework.ws.soap.Soap12Header. + * + * @author Arjen Poutsma + */ +class AxiomSoap12Header extends AxiomSoapHeader implements Soap12Header { + + AxiomSoap12Header(SOAPHeader axiomHeader, SOAPFactory axiomFactory) { + super(axiomHeader, axiomFactory); + } + + public SoapHeaderElement addNotUnderstoodHeaderElement(QName headerName) { + try { + SOAPHeaderBlock notUnderstood = axiomHeader.addHeaderBlock("NotUnderstood", axiomHeader.getNamespace()); + OMNamespace headerNamespace = + notUnderstood.declareNamespace(headerName.getNamespaceURI(), QNameUtils.getPrefix(headerName)); + notUnderstood.addAttribute("qname", headerNamespace.getPrefix() + ":" + headerName.getLocalPart(), null); + return new AxiomSoapHeaderElement(notUnderstood, axiomFactory); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + public SoapHeaderElement addUpgradeHeaderElement(String[] supportedSoapUris) { + try { + SOAPHeaderBlock upgrade = axiomHeader.addHeaderBlock("Upgrade", axiomHeader.getNamespace()); + for (int i = 0; i < supportedSoapUris.length; i++) { + OMElement supportedEnvelope = + axiomFactory.createOMElement("SupportedEnvelope", axiomHeader.getNamespace(), upgrade); + OMNamespace namespace = supportedEnvelope.declareNamespace(supportedSoapUris[i], ""); + supportedEnvelope.addAttribute("qname", namespace.getPrefix() + ":Envelope", null); + } + return new AxiomSoapHeaderElement(upgrade, axiomFactory); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBody.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBody.java new file mode 100644 index 00000000..7930c8e6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBody.java @@ -0,0 +1,139 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import java.util.Iterator; +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.sax.SAXResult; + +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.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.SoapBody; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.axiom.support.AxiomUtils; +import org.springframework.xml.transform.StaxSource; + +/** + * Axiom-specific version of org.springframework.ws.soap.Soap11Body. + * + * @author Arjen Poutsma + */ +abstract class AxiomSoapBody implements SoapBody { + + protected final SOAPBody axiomBody; + + protected final SOAPFactory axiomFactory; + + private boolean payloadCaching; + + protected AxiomSoapBody(SOAPBody axiomBody, SOAPFactory axiomFactory, boolean payloadCaching) { + Assert.notNull(axiomBody, "No axiomBody given"); + Assert.notNull(axiomFactory, "No axiomFactory given"); + this.axiomBody = axiomBody; + this.axiomFactory = axiomFactory; + this.payloadCaching = payloadCaching; + } + + public Source getPayloadSource() { + try { + OMElement payloadElement = getPayloadElement(); + if (payloadElement == null) { + return null; + } + else if (payloadCaching) { + return new StaxSource(payloadElement.getXMLStreamReader()); + } + else { + return new StaxSource(payloadElement.getXMLStreamReaderWithoutCaching()); + } + } + catch (OMException ex) { + throw new AxiomSoapBodyException(ex); + } + } + + public Result getPayloadResult() { + return new SAXResult(new AxiomContentHandler(axiomBody)); + } + + public boolean hasFault() { + return axiomBody.hasFault(); + } + + public SoapFault getFault() { + SOAPFault axiomFault = axiomBody.getFault(); + return axiomFault != null ? new AxiomSoap11Fault(axiomFault, axiomFactory) : null; + } + + public QName getName() { + return axiomBody.getQName(); + } + + public Source getSource() { + return new StaxSource(axiomBody.getXMLStreamReader()); + } + + private OMElement getPayloadElement() throws OMException { + return axiomBody.getFirstElement(); + } + + protected void detachAllBodyChildren() { + try { + for (Iterator iterator = axiomBody.getChildElements(); iterator.hasNext();) { + OMElement child = (OMElement) iterator.next(); + child.detach(); + } + } + catch (OMException ex) { + throw new AxiomSoapBodyException(ex); + } + + } + + protected SOAPFault addStandardFault(String localName, String faultStringOrReason, Locale locale) { + Assert.notNull(faultStringOrReason, "No faultStringOrReason given"); + try { + detachAllBodyChildren(); + SOAPFault fault = axiomFactory.createSOAPFault(axiomBody); + SOAPFaultCode code = axiomFactory.createSOAPFaultCode(fault); + SOAPFaultValue value = axiomFactory.createSOAPFaultValue(code); + value.setText(fault.getNamespace().getPrefix() + ":" + localName); + SOAPFaultReason reason = axiomFactory.createSOAPFaultReason(fault); + SOAPFaultText text = axiomFactory.createSOAPFaultText(reason); + if (locale != null) { + text.setLang(AxiomUtils.toLanguage(locale)); + } + text.setText(faultStringOrReason); + return fault; + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBodyException.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBodyException.java new file mode 100644 index 00000000..1e3fdfc3 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapBodyException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapEnvelopeException; + +/** + * @author Arjen Poutsma + */ +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/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelope.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelope.java new file mode 100644 index 00000000..287b541e --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelope.java @@ -0,0 +1,100 @@ +package org.springframework.ws.soap.axiom; + +import javax.xml.namespace.QName; +import javax.xml.transform.Source; + +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.util.Assert; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.SoapHeader; +import org.springframework.xml.transform.StaxSource; + +/** + * Axiom-Specific version of org.springframework.ws.soap.SoapEnvelope. + * + * @author Arjen Poutsma + */ +class AxiomSoapEnvelope implements SoapEnvelope { + + private final SOAPEnvelope axiomEnvelope; + + private final SOAPFactory axiomFactory; + + boolean payloadCaching; + + private AxiomSoapBody body; + + public AxiomSoapEnvelope(SOAPEnvelope axiomEnvelope, SOAPFactory axiomFactory, boolean payloadCaching) { + Assert.notNull(axiomEnvelope, "No axiomEnvelope given"); + Assert.notNull(axiomFactory, "No axiomFactory given"); + this.axiomEnvelope = axiomEnvelope; + this.axiomFactory = axiomFactory; + this.payloadCaching = payloadCaching; + } + + public QName getName() { + return axiomEnvelope.getQName(); + } + + public Source getSource() { + try { + return new StaxSource(axiomEnvelope.getXMLStreamReader()); + } + catch (OMException ex) { + throw new AxiomSoapEnvelopeException(ex); + } + } + + public SoapHeader getHeader() { + try { + if (axiomEnvelope.getHeader() == null) { + return null; + } + else { + SOAPHeader axiomHeader = axiomEnvelope.getHeader(); + String namespaceURI = axiomEnvelope.getNamespace().getName(); + if (SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(namespaceURI)) { + return new AxiomSoapHeader(axiomHeader, axiomFactory); + } + else if (SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(namespaceURI)) { + return new AxiomSoap12Header(axiomHeader, axiomFactory); + } + else { + throw new AxiomSoapEnvelopeException("Unknown SOAP namespace \"" + namespaceURI + "\""); + } + } + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + public SoapBody getBody() { + if (body == null) { + try { + SOAPBody axiomBody = axiomEnvelope.getBody(); + String namespaceURI = axiomEnvelope.getNamespace().getName(); + if (SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(namespaceURI)) { + body = new AxiomSoap11Body(axiomBody, axiomFactory, payloadCaching); + } + else if (SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI.equals(namespaceURI)) { + body = new AxiomSoap12Body(axiomBody, axiomFactory, payloadCaching); + } + else { + throw new AxiomSoapEnvelopeException("Unknown SOAP namespace \"" + namespaceURI + "\""); + } + } + catch (OMException ex) { + throw new AxiomSoapBodyException(ex); + } + } + return body; + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelopeException.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelopeException.java new file mode 100644 index 00000000..189fdef6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapEnvelopeException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapEnvelopeException; + +/** + * @author Arjen Poutsma + */ +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/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFault.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFault.java new file mode 100644 index 00000000..8de42347 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFault.java @@ -0,0 +1,112 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import javax.xml.namespace.QName; +import javax.xml.transform.Source; + +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.SOAPFaultValue; +import org.apache.axiom.soap.SOAPProcessingException; +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.xml.namespace.QNameUtils; +import org.springframework.xml.transform.StaxSource; + +/** + * @author Arjen Poutsma + */ +abstract class AxiomSoapFault implements SoapFault { + + protected final SOAPFault axiomFault; + + protected final SOAPFactory axiomFactory; + + protected AxiomSoapFault(SOAPFault axiomFault, SOAPFactory axiomFactory) { + Assert.notNull(axiomFault, "No axiomFault given"); + Assert.notNull(axiomFactory, "No axiomFactory given"); + this.axiomFault = axiomFault; + this.axiomFactory = axiomFactory; + } + + public QName getName() { + return axiomFault.getQName(); + } + + public Source getSource() { + return new StaxSource(axiomFault.getXMLStreamReader()); + } + + public QName getFaultCode() { + return getFaultCode(axiomFault.getCode().getValue()); + } + + /** + * Axiom 1.0's getTextAsQName is broken for SOAPFaultValues, hence this. + */ + protected QName getFaultCode(SOAPFaultValue value) { + String text = value.getText(); + int idx = text.indexOf(':'); + String prefix = text.substring(0, idx); + String localPart = text.substring(idx + 1); + String namespaceUri = axiomFault.getCode().getValue().findNamespaceURI(prefix).getName(); + return QNameUtils.createQName(namespaceUri, localPart, prefix); + } + + public String getFaultActorOrRole() { + SOAPFaultRole faultRole = axiomFault.getRole(); + return faultRole != null ? faultRole.getRoleValue() : null; + } + + public void setFaultActorOrRole(String actor) { + try { + SOAPFaultRole axiomFaultRole = axiomFactory.createSOAPFaultRole(axiomFault); + axiomFaultRole.setRoleValue(actor); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + public SoapFaultDetail getFaultDetail() { + try { + SOAPFaultDetail axiomFaultDetail = axiomFault.getDetail(); + return axiomFaultDetail != null ? new AxiomSoapFaultDetail(axiomFaultDetail, axiomFactory) : null; + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + public SoapFaultDetail addFaultDetail() { + try { + SOAPFaultDetail axiomFaultDetail = axiomFactory.createSOAPFaultDetail(axiomFault); + return new AxiomSoapFaultDetail(axiomFaultDetail, axiomFactory); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetail.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetail.java new file mode 100644 index 00000000..47ab1f16 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetail.java @@ -0,0 +1,107 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; + +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.util.Assert; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.SoapFaultDetailElement; +import org.springframework.xml.transform.StaxSource; + +/** + * Axiom-specific version of org.springframework.ws.soap.SoapFaultDetail. + * + * @author Arjen Poutsma + */ +class AxiomSoapFaultDetail implements SoapFaultDetail { + + private final SOAPFaultDetail axiomFaultDetail; + + private final SOAPFactory axiomFactory; + + public AxiomSoapFaultDetail(SOAPFaultDetail axiomFaultDetail, SOAPFactory axiomFactory) { + Assert.notNull(axiomFaultDetail, "No axiomFaultDetail given"); + Assert.notNull(axiomFactory, "No axiomFactory given"); + this.axiomFaultDetail = axiomFaultDetail; + this.axiomFactory = axiomFactory; + } + + public SoapFaultDetailElement addFaultDetailElement(QName name) { + try { + OMElement element = axiomFactory.createOMElement(name, axiomFaultDetail); + return new AxiomSoapFaultDetailElement(element); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + public Iterator getDetailEntries() { + return new AxiomSoapFaultDetailElementIterator(axiomFaultDetail.getAllDetailEntries()); + } + + public QName getName() { + return axiomFaultDetail.getQName(); + } + + public Source getSource() { + try { + return new StaxSource(axiomFaultDetail.getXMLStreamReader()); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + private class AxiomSoapFaultDetailElementIterator implements Iterator { + + private final Iterator axiomIterator; + + private AxiomSoapFaultDetailElementIterator(Iterator axiomIterator) { + this.axiomIterator = axiomIterator; + } + + public boolean hasNext() { + return axiomIterator.hasNext(); + } + + public Object next() { + try { + OMElement axiomElement = (OMElement) axiomIterator.next(); + return new AxiomSoapFaultDetailElement(axiomElement); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + public void remove() { + axiomIterator.remove(); + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailElement.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailElement.java new file mode 100644 index 00000000..86f4af4c --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultDetailElement.java @@ -0,0 +1,74 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import javax.xml.namespace.QName; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.sax.SAXResult; + +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMException; +import org.springframework.ws.soap.SoapFaultDetailElement; +import org.springframework.xml.transform.StaxSource; + +/** + * Axiom-specific version of org.springframework.ws.soap.SoapFaultDetailElement. + * + * @author Arjen Poutsma + */ +class AxiomSoapFaultDetailElement implements SoapFaultDetailElement { + + private final OMElement axiomElement; + + public AxiomSoapFaultDetailElement(OMElement axiomElement) { + this.axiomElement = axiomElement; + } + + public Result getResult() { + try { + return new SAXResult(new AxiomContentHandler(axiomElement)); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } + + public void addText(String text) { + try { + axiomElement.setText(text); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + } + + public QName getName() { + return axiomElement.getQName(); + } + + public Source getSource() { + try { + return new StaxSource(axiomElement.getXMLStreamReader()); + } + catch (OMException ex) { + throw new AxiomSoapFaultException(ex); + } + + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultException.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultException.java new file mode 100644 index 00000000..6c4f5c42 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapFaultException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapFaultException; + +/** + * @author Arjen Poutsma + */ +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/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeader.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeader.java new file mode 100644 index 00000000..b7d2f09a --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeader.java @@ -0,0 +1,116 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; + +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.util.Assert; +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.xml.namespace.QNameUtils; +import org.springframework.xml.transform.StaxSource; + +/** + * Axiom-specific version of org.springframework.ws.soap.SoapHeader. + * + * @author Arjen Poutsma + */ +class AxiomSoapHeader implements SoapHeader { + + protected final SOAPHeader axiomHeader; + + protected final SOAPFactory axiomFactory; + + AxiomSoapHeader(SOAPHeader axiomHeader, SOAPFactory axiomFactory) { + Assert.notNull(axiomHeader, "No axiomHeader given"); + Assert.notNull(axiomFactory, "No axiomFactory given"); + this.axiomHeader = axiomHeader; + this.axiomFactory = axiomFactory; + } + + public QName getName() { + return axiomHeader.getQName(); + } + + public Source getSource() { + return new StaxSource(axiomHeader.getXMLStreamReader()); + } + + public SoapHeaderElement addHeaderElement(QName name) { + try { + OMNamespace namespace = axiomFactory.createOMNamespace(name.getNamespaceURI(), QNameUtils.getPrefix(name)); + SOAPHeaderBlock axiomHeaderBlock = axiomHeader.addHeaderBlock(name.getLocalPart(), namespace); + return new AxiomSoapHeaderElement(axiomHeaderBlock, axiomFactory); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + public Iterator examineMustUnderstandHeaderElements(String role) { + try { + return new AxiomSoapHeaderElementIterator(axiomHeader.examineMustUnderstandHeaderBlocks(role)); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + public Iterator examineAllHeaderElements() { + try { + return new AxiomSoapHeaderElementIterator(axiomHeader.examineAllHeaderBlocks()); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + + } + + private class AxiomSoapHeaderElementIterator implements Iterator { + + private final Iterator axiomIterator; + + private AxiomSoapHeaderElementIterator(Iterator axiomIterator) { + this.axiomIterator = axiomIterator; + } + + public boolean hasNext() { + return axiomIterator.hasNext(); + } + + public Object next() { + try { + SOAPHeaderBlock axiomHeaderBlock = (SOAPHeaderBlock) axiomIterator.next(); + return new AxiomSoapHeaderElement(axiomHeaderBlock, axiomFactory); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } + + public void remove() { + axiomIterator.remove(); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderElement.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderElement.java new file mode 100644 index 00000000..95cf2b43 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderElement.java @@ -0,0 +1,101 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import javax.xml.namespace.QName; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.sax.SAXResult; + +import org.apache.axiom.om.OMAttribute; +import org.apache.axiom.om.OMException; +import org.apache.axiom.om.OMNamespace; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPHeaderBlock; +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapHeaderException; +import org.springframework.xml.namespace.QNameUtils; +import org.springframework.xml.transform.StaxSource; + +/** + * Axiom-specific version of org.springframework.ws.soap.SoapHeaderHeaderElement. + */ +class AxiomSoapHeaderElement implements SoapHeaderElement { + + private final SOAPHeaderBlock axiomHeaderBlock; + + private final SOAPFactory axiomFactory; + + public AxiomSoapHeaderElement(SOAPHeaderBlock axiomHeaderBlock, SOAPFactory axiomFactory) { + Assert.notNull(axiomHeaderBlock, "No axiomHeaderBlock given"); + Assert.notNull(axiomFactory, "No axiomFactory given"); + this.axiomHeaderBlock = axiomHeaderBlock; + this.axiomFactory = axiomFactory; + } + + public QName getName() { + return axiomHeaderBlock.getQName(); + } + + public Source getSource() { + try { + return new StaxSource(axiomHeaderBlock.getXMLStreamReader()); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + + } + + public String getActorOrRole() { + return axiomHeaderBlock.getRole(); + } + + public void setActorOrRole(String role) { + axiomHeaderBlock.setRole(role); + } + + public boolean getMustUnderstand() { + return axiomHeaderBlock.getMustUnderstand(); + } + + public void setMustUnderstand(boolean mustUnderstand) { + axiomHeaderBlock.setMustUnderstand(mustUnderstand); + } + + public Result getResult() { + try { + return new SAXResult(new AxiomContentHandler(axiomHeaderBlock)); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + + } + + public void addAttribute(QName name, String value) throws SoapHeaderException { + try { + OMNamespace namespace = axiomFactory.createOMNamespace(name.getNamespaceURI(), QNameUtils.getPrefix(name)); + OMAttribute attribute = axiomFactory.createOMAttribute(name.getLocalPart(), namespace, value); + axiomHeaderBlock.addAttribute(attribute); + } + catch (OMException ex) { + throw new AxiomSoapHeaderException(ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderException.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderException.java new file mode 100644 index 00000000..11eea38b --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapHeaderException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapHeaderException; + +/** + * @author Arjen Poutsma + */ +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/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java new file mode 100644 index 00000000..489a6059 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java @@ -0,0 +1,224 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Iterator; +import javax.mail.MessagingException; +import javax.xml.stream.XMLStreamException; + +import org.apache.axiom.attachments.Attachments; +import org.apache.axiom.attachments.Part; +import org.apache.axiom.om.OMException; +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.springframework.core.io.InputStreamSource; +import org.springframework.ws.soap.AbstractSoapMessage; +import org.springframework.ws.soap.Attachment; +import org.springframework.ws.soap.SoapEnvelope; + +/** + * AXIOM-specific implementation of the SoapMessage interface. Accessed via the + * AxiomSoapMessageContext. + *

+ * Note that Axiom does support reading SOAP with Attachments (SwA) messages, but does not support creating them + * manually. Hence, the addAttachment methods throw an UnsupportedOperationException. + * + * @author Arjen Poutsma + * @see SOAPMessage + * @see AxiomSoapMessageContext + */ +public class AxiomSoapMessage extends AbstractSoapMessage { + + private final SOAPMessage axiomMessage; + + private final SOAPFactory axiomFactory; + + private final Attachments attachments; + + private boolean payloadCaching; + + private AxiomSoapEnvelope envelope; + + /** + * Create a new, empty AxiomSoapMessage. + * + * @param soapFactory the AXIOM SOAPFactory + */ + public AxiomSoapMessage(SOAPFactory soapFactory) { + SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope(); + axiomFactory = soapFactory; + axiomMessage = axiomFactory.createSOAPMessage(soapEnvelope, soapEnvelope.getBuilder()); + attachments = null; + payloadCaching = true; + } + + /** + * Create a new AxiomSoapMessage based on the given AXIOM SOAPMessage. + * + * @param soapMessage the AXIOM SOAPMessage + * @param attachments the attachments + * @param payloadCaching whether the contents of the SOAP body should be cached or not + */ + public AxiomSoapMessage(SOAPMessage soapMessage, Attachments attachments, boolean payloadCaching) { + axiomMessage = soapMessage; + axiomFactory = (SOAPFactory) soapMessage.getSOAPEnvelope().getOMFactory(); + this.attachments = attachments; + this.payloadCaching = payloadCaching; + } + + /** + * Return the AXIOM SOAPMessage that this AxiomSoapMessage is based on. + */ + public final SOAPMessage getAxiomMessage() { + return axiomMessage; + } + + public SoapEnvelope getEnvelope() { + if (envelope == null) { + try { + envelope = new AxiomSoapEnvelope(axiomMessage.getSOAPEnvelope(), axiomFactory, payloadCaching); + } + catch (SOAPProcessingException ex) { + throw new AxiomSoapEnvelopeException(ex); + } + } + return envelope; + } + + public Attachment getAttachment(String contentId) { + Part part = attachments.getPart(contentId); + return part != null ? new AxiomAttachment(part) : null; + } + + public Iterator getAttachments() { + return new AxiomAttachmentIterator(); + } + + /** + * Axiom does not support adding attachments manually. + * + * @throws UnsupportedOperationException always + */ + public Attachment addAttachment(File file) throws UnsupportedOperationException { + throw new UnsupportedOperationException("Axiom does not support adding SwA attachments."); + } + + /** + * Axiom does not support adding attachments manually. + * + * @throws UnsupportedOperationException always + */ + public Attachment addAttachment(InputStreamSource inputStreamSource, String contentType) + throws UnsupportedOperationException { + throw new UnsupportedOperationException("Axiom does not support adding SwA attachments."); + } + + public void writeTo(OutputStream outputStream) throws IOException { + try { + axiomMessage.serialize(outputStream); + } + 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); + } + } + + /** + * Axiom-specific implementation of org.springframework.ws.soap.Attachment + */ + private static class AxiomAttachment implements Attachment { + + private final Part part; + + private AxiomAttachment(Part part) { + this.part = part; + } + + public String getId() { + try { + return part.getContentID(); + } + catch (MessagingException ex) { + throw new AxiomAttachmentException(ex); + } + } + + public void setId(String id) { + throw new UnsupportedOperationException("Axiom does not support setting the Content-ID of attachments."); + } + + public String getContentType() { + try { + return part.getContentType(); + } + catch (MessagingException ex) { + throw new AxiomAttachmentException(ex); + } + } + + public InputStream getInputStream() throws IOException { + try { + return part.getInputStream(); + } + catch (MessagingException ex) { + throw new AxiomAttachmentException(ex); + } + } + + public long getSize() { + try { + return part.getSize(); + } + catch (MessagingException ex) { + throw new AxiomAttachmentException(ex); + } + } + } + + private class AxiomAttachmentIterator implements Iterator { + + private final Iterator iterator; + + private AxiomAttachmentIterator() { + iterator = Arrays.asList(attachments.getAllContentIDs()).iterator(); + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public Object next() { + String contentId = (String) iterator.next(); + Part part = attachments.getPart(contentId); + return part != null ? new AxiomAttachment(part) : null; + } + + public void remove() { + iterator.remove(); + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageContext.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageContext.java new file mode 100644 index 00000000..7e43aace --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageContext.java @@ -0,0 +1,90 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import java.io.IOException; +import javax.xml.stream.XMLStreamException; + +import org.apache.axiom.om.OMOutputFormat; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPMessage; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.context.AbstractSoapMessageContext; +import org.springframework.ws.transport.TransportRequest; +import org.springframework.ws.transport.TransportResponse; + +/** + * AXIOM-specific implementation of the SoapMessageContext interface. Created by the + * AxiomSoapMessageContextFactory. + * + * @author Arjen Poutsma + * @see AxiomSoapMessageContextFactory + */ +public class AxiomSoapMessageContext extends AbstractSoapMessageContext { + + /** + * Creates a new instance based on the given Axiom request message, and a SOAP factory. + * + * @param messageRequest the request message + */ + public AxiomSoapMessageContext(AxiomSoapMessage messageRequest, TransportRequest transportRequest) { + super(messageRequest, transportRequest); + } + + protected SoapMessage createResponseSoapMessage() { + SOAPFactory soapFactory = (SOAPFactory) getAxiomRequest().getSOAPEnvelope().getOMFactory(); + return new AxiomSoapMessage(soapFactory); + } + + /** + * Returns the request as an Axiom SOAP message. + */ + public SOAPMessage getAxiomRequest() { + return ((AxiomSoapMessage) getSoapRequest()).getAxiomMessage(); + } + + /** + * Returns the response as an Axiom SOAP message. + */ + public SOAPMessage getAxiomResponse() { + return ((AxiomSoapMessage) getSoapResponse()).getAxiomMessage(); + } + + public void sendResponse(TransportResponse transportResponse) throws IOException { + try { + if (hasResponse()) { + AxiomSoapMessage response = (AxiomSoapMessage) getSoapResponse(); + SOAPMessage axiomResponse = response.getAxiomMessage(); + String charsetEncoding = axiomResponse.getCharsetEncoding(); + + OMOutputFormat format = new OMOutputFormat(); + format.setCharSetEncoding(charsetEncoding); + format.setSOAP11(response.getVersion() == SoapVersion.SOAP_11); + String contentType = format.getContentType(); + contentType += "; charset=\"" + charsetEncoding + "\""; + + transportResponse.addHeader("Content-Type", contentType); + axiomResponse.serializeAndConsume(transportResponse.getOutputStream(), format); + } + } + catch (XMLStreamException ex) { + throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex); + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageContextFactory.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageContextFactory.java new file mode 100644 index 00000000..f4944c8a --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageContextFactory.java @@ -0,0 +1,212 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +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.OMException; +import org.apache.axiom.om.impl.MTOMConstants; +import org.apache.axiom.om.impl.mtom.MTOMStAXSOAPModelBuilder; +import org.apache.axiom.soap.SOAP11Constants; +import org.apache.axiom.soap.SOAP12Constants; +import org.apache.axiom.soap.SOAPFactory; +import org.apache.axiom.soap.SOAPMessage; +import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder; +import org.apache.axiom.soap.impl.llom.soap11.SOAP11Factory; +import org.apache.axiom.soap.impl.llom.soap12.SOAP12Factory; +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.ws.context.MessageContext; +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.soap.SoapMessageCreationException; +import org.springframework.ws.transport.TransportContext; +import org.springframework.ws.transport.TransportRequest; + +/** + * Axiom-specific implementation of the MessageContextFactory interface. Creates a + * AxiomSoapMessageContext. + *

+ * To increase reading performance on the the SOAP request created by this message context factory, you can set the + * payloadCaching property to false (default is true). This this will read the + * contents of the body directly from the TransportRequest. 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 PayloadRootQNameEndpointMapping, the + * PayloadValidatingInterceptor, or the PayloadLoggingInterceptor) cannot be used. Instead, + * use an endpoint mapping that does not consume the payload (i.e. the SoapActionEndpointMapping). + *

+ * Mostly derived from org.apache.axis2.transport.http.HTTPTransportUtils and + * org.apache.axis2.transport.TransportUtils, which we cannot use since they are not part of the Axiom + * distribution. + * + * @author Arjen Poutsma + * @see AxiomSoapMessageContext + * @see #setPayloadCaching(boolean) + */ +public class AxiomSoapMessageContextFactory implements MessageContextFactory, InitializingBean { + + private static final Log logger = LogFactory.getLog(AxiomSoapMessageContextFactory.class); + + private static final String CHAR_SET_ENCODING = "charset"; + + private static final String DEFAULT_CHAR_SET_ENCODING = "UTF-8"; + + private static final String CONTENT_TYPE_HEADER = "Content-Type"; + + private static final String MULTI_PART_RELATED_CONTENT_TYPE = "multipart/related"; + + private boolean payloadCaching = true; + + private SOAP11Factory soap11Factory; + + private SOAP12Factory soap12Factory; + + /** + * Indicates whether the SOAP Body payload should be cached or not. Default is true. Setting this to + * 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; + } + + private XMLInputFactory inputFactory; + + public void afterPropertiesSet() throws Exception { + inputFactory = XMLInputFactory.newInstance(); + soap11Factory = new SOAP11Factory(); + soap12Factory = new SOAP12Factory(); + if (logger.isInfoEnabled()) { + logger.info(payloadCaching ? "Enabled payload caching" : "Disabled payload caching"); + } + } + + public MessageContext createContext(TransportContext transportContext) throws IOException { + TransportRequest transportRequest = transportContext.getTransportRequest(); + Iterator iterator = transportRequest.getHeaders(CONTENT_TYPE_HEADER); + Assert.isTrue(iterator.hasNext(), "No " + CONTENT_TYPE_HEADER + " header present of TransportRequest"); + String contentType = (String) iterator.next(); + Assert.hasLength(contentType, "No " + CONTENT_TYPE_HEADER + " header present of TransportRequest"); + InputStream inputStream = transportRequest.getInputStream(); + try { + AxiomSoapMessage requestMessage; + if (contentType.indexOf(MULTI_PART_RELATED_CONTENT_TYPE) == -1) { + XMLStreamReader reader = + inputFactory.createXMLStreamReader(inputStream, getCharSetEncoding(contentType)); + SOAPFactory soapFactory = getSoapFactory(contentType); + StAXSOAPModelBuilder builder = + new StAXSOAPModelBuilder(reader, soapFactory, soapFactory.getSoapVersionURI()); + requestMessage = createAxiomSoapMessage(builder, null); + } + else { + requestMessage = createMultiPartAxiomSoapMessage(inputStream, contentType); + } + return new AxiomSoapMessageContext(requestMessage, transportRequest); + + } + catch (XMLStreamException ex) { + throw new SoapMessageCreationException("Could not create message: " + ex.getMessage(), ex); + } + catch (OMException ex) { + throw new SoapMessageCreationException("Could not create message: " + ex.getMessage(), ex); + } + } + + private AxiomSoapMessage createMultiPartAxiomSoapMessage(InputStream inputStream, String contentType) + throws XMLStreamException { + Attachments attachments = new Attachments(inputStream, contentType); + if (!(attachments.getAttachmentSpecType().equals(MTOMConstants.SWA_TYPE) || + attachments.getAttachmentSpecType().equals(MTOMConstants.MTOM_TYPE))) { + throw new SoapMessageCreationException( + "Unknown attachment type: [" + attachments.getAttachmentSpecType() + "]"); + } + XMLStreamReader reader = inputFactory.createXMLStreamReader(attachments.getSOAPPartInputStream(), + getCharSetEncoding(attachments.getSOAPPartContentType())); + SOAPFactory soapFactory = getSoapFactory(attachments.getSOAPPartContentType()); + StAXSOAPModelBuilder builder = null; + if (attachments.getAttachmentSpecType().equals(MTOMConstants.SWA_TYPE)) { + builder = new StAXSOAPModelBuilder(reader, soapFactory, soapFactory.getSoapVersionURI()); + } + else if (attachments.getAttachmentSpecType().equals(MTOMConstants.MTOM_TYPE)) { + builder = new MTOMStAXSOAPModelBuilder(reader, attachments, soapFactory.getSoapVersionURI()); + } + return new AxiomSoapMessage(builder.getSoapMessage(), attachments, payloadCaching); + } + + /** + * Creates a new AxiomSoapMessage based on the given parameters. + * + * @param modelBuilder the builder used to optain the Axiom SOAPMessage + * @param attachments the attachments, can be null + * @return the created message + */ + private AxiomSoapMessage createAxiomSoapMessage(StAXSOAPModelBuilder modelBuilder, Attachments attachments) { + SOAPMessage soapMessage = modelBuilder.getSoapMessage(); + return new AxiomSoapMessage(soapMessage, attachments, payloadCaching); + } + + private SOAPFactory getSoapFactory(String contentType) { + if (contentType.indexOf(SOAP11Constants.SOAP_11_CONTENT_TYPE) != -1) { + return soap11Factory; + } + else if (contentType.indexOf(SOAP12Constants.SOAP_12_CONTENT_TYPE) != -1) { + return soap12Factory; + } + else { + throw new SoapMessageCreationException("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 index = contentType.indexOf(CHAR_SET_ENCODING); + if (index == -1) { + return DEFAULT_CHAR_SET_ENCODING; + } + int idx = contentType.indexOf("=", index); + + int indexOfSemiColon = contentType.indexOf(";", idx); + String value; + + if (indexOfSemiColon > 0) { + value = contentType.substring(idx + 1, indexOfSemiColon); + } + else { + value = contentType.substring(idx + 1, contentType.length()).trim(); + } + if (value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') { + return value.substring(1, value.length() - 1); + } + if ("null".equalsIgnoreCase(value)) { + return DEFAULT_CHAR_SET_ENCODING; + } + else { + return value.trim(); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageCreationException.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageCreationException.java new file mode 100644 index 00000000..e42253ec --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageCreationException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapMessageCreationException; + +/** + * @author Arjen Poutsma + */ +public class AxiomSoapMessageCreationException extends SoapMessageCreationException { + + public AxiomSoapMessageCreationException(String msg) { + super(msg); + } + + public AxiomSoapMessageCreationException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageException.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageException.java new file mode 100644 index 00000000..91aba4a6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessageException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.springframework.ws.soap.SoapMessageException; + +/** + * @author Arjen Poutsma + */ +public class AxiomSoapMessageException extends SoapMessageException { + + public AxiomSoapMessageException(String msg) { + super(msg); + } + + public AxiomSoapMessageException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/package.html b/core/src/main/java/org/springframework/ws/soap/axiom/package.html new file mode 100644 index 00000000..d3660a66 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/package.html @@ -0,0 +1,5 @@ + + +AXis Object Model (AXIOM) support for Spring-WS' soap message infrastructure. + + diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/support/AxiomUtils.java b/core/src/main/java/org/springframework/ws/soap/axiom/support/AxiomUtils.java new file mode 100644 index 00000000..03cb498f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/support/AxiomUtils.java @@ -0,0 +1,84 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom.support; + +import java.util.Locale; +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.springframework.util.StringUtils; +import org.springframework.xml.namespace.QNameUtils; + +/** + * Collection of generic utility methods to work with Axiom. Includes conversion from OMNamespaces to + * QNames. + * + * @author Arjen Poutsma + * @see org.apache.axiom.om.OMNamespace + * @see javax.xml.namespace.QName + */ +public abstract class AxiomUtils { + + /** + * Converts a javax.xml.namespace.QName to a org.apache.axiom.om.OMNamespace. A + * OMElement is used to resolve the namespace, or to declare a new one. + * + * @param qName the 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 qName is not fully qualified + */ + public static OMNamespace toNamespace(QName qName, OMElement resolveElement) throws OMException { + String prefix = QNameUtils.getPrefix(qName); + 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 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 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); + } + + +} diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/support/package.html b/core/src/main/java/org/springframework/ws/soap/axiom/support/package.html new file mode 100644 index 00000000..cca976b3 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/axiom/support/package.html @@ -0,0 +1,5 @@ + + +Support classes for working with the AXis Object Model (AXIOM). + + diff --git a/core/src/main/java/org/springframework/ws/soap/context/AbstractSoapMessageContext.java b/core/src/main/java/org/springframework/ws/soap/context/AbstractSoapMessageContext.java new file mode 100644 index 00000000..7a32656d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/context/AbstractSoapMessageContext.java @@ -0,0 +1,49 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.context; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.AbstractMessageContext; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.transport.TransportRequest; + +/** + * Abstract implementation of the SoapMessageContext interface. Implements base MessageContext + * methods by delegating to SoapMessageContext functionality. + * + * @author Arjen Poutsma + */ +public abstract class AbstractSoapMessageContext extends AbstractMessageContext implements SoapMessageContext { + + protected AbstractSoapMessageContext(SoapMessage request, TransportRequest transportRequest) { + super(request, transportRequest); + } + + public final SoapMessage getSoapResponse() { + return (SoapMessage) getResponse(); + } + + public final SoapMessage getSoapRequest() { + return (SoapMessage) getRequest(); + } + + protected final WebServiceMessage createResponseMessage() { + return createResponseSoapMessage(); + } + + protected abstract SoapMessage createResponseSoapMessage(); +} diff --git a/core/src/main/java/org/springframework/ws/soap/context/SoapMessageContext.java b/core/src/main/java/org/springframework/ws/soap/context/SoapMessageContext.java new file mode 100644 index 00000000..a5161271 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/context/SoapMessageContext.java @@ -0,0 +1,46 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.context; + +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapMessage; + +/** + * SOAP-specific extension of the MessageContext interface. Contains methods to obtain + * SoapMessages instead of WebServiceMessages. + * + * @author Arjen Poutsma + */ +public interface SoapMessageContext extends MessageContext { + + /** + * Returns the request SOAP message. + * + * @return the request message + */ + SoapMessage getSoapRequest(); + + /** + * Returns the response message, if created. Returns null if no response message was created so far. + * + * @return the response message, or null if none was created + * @see #hasResponse() + */ + SoapMessage getSoapResponse(); + + +} diff --git a/core/src/main/java/org/springframework/ws/soap/context/package.html b/core/src/main/java/org/springframework/ws/soap/context/package.html new file mode 100644 index 00000000..7f5e8b44 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/context/package.html @@ -0,0 +1,5 @@ + + +Contains the SoapMessageContext interface. + + diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/SimpleSoapExceptionResolver.java b/core/src/main/java/org/springframework/ws/soap/endpoint/SimpleSoapExceptionResolver.java new file mode 100644 index 00000000..bfa2ea64 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/SimpleSoapExceptionResolver.java @@ -0,0 +1,38 @@ +package org.springframework.ws.soap.endpoint; + +import java.util.Locale; + +import org.springframework.util.StringUtils; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.endpoint.AbstractEndpointExceptionResolver; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.context.SoapMessageContext; + +/** + * Simple, SOAP-specific implementation of the EndpointExceptionResolver that stores the exception's + * message as the fault string. The fault code is always set to a Sender (in SOAP 1.1) or Receiver (SOAP 1.2). + * + * @author Arjen Poutsma + */ +public class SimpleSoapExceptionResolver extends AbstractEndpointExceptionResolver { + + private Locale locale = Locale.ENGLISH; + + /** + * Sets the locale for the faultstring or reason of the SOAP Fault. Defaults to english. + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + protected boolean resolveExceptionInternal(MessageContext messageContext, Object endpoint, Exception ex) { + if (!(messageContext instanceof SoapMessageContext)) { + throw new IllegalArgumentException("SimpleSoapExceptionResolver requires a SoapMessageContext"); + } + String faultString = StringUtils.hasLength(ex.getMessage()) ? ex.getMessage() : ex.toString(); + SoapMessageContext soapContext = (SoapMessageContext) messageContext; + SoapBody body = soapContext.getSoapResponse().getSoapBody(); + body.addServerOrReceiverFault(faultString, locale); + return true; + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultDefinition.java b/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultDefinition.java new file mode 100644 index 00000000..970ce666 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultDefinition.java @@ -0,0 +1,111 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.endpoint; + +import java.util.Locale; +import javax.xml.namespace.QName; + +/** + * Defines properties for a SOAP Fault. Used by the SoapFaultDefinitionEditor and the + * SoapFaultMappingExceptionResolver. + * + * @author Arjen Poutsma + * @see SoapFaultDefinitionEditor + * @see SoapFaultMappingExceptionResolver + */ +public class SoapFaultDefinition { + + /** + * Constant QName used to indicate that a Client fault must be created. + * + * @see org.springframework.ws.soap.SoapBody#addClientOrSenderFault(String, java.util.Locale) + */ + public static final QName CLIENT = new QName("CLIENT"); + + /** + * Constant QName used to indicate that a Receiver fault must be created. + * + * @see org.springframework.ws.soap.SoapBody#addServerOrReceiverFault(String, java.util.Locale) + */ + public static final QName RECEIVER = new QName("RECEIVER"); + + /** + * Constant QName used to indicate that a Sender fault must be created. + * + * @see org.springframework.ws.soap.SoapBody#addServerOrReceiverFault(String, java.util.Locale) + */ + public static final QName SENDER = new QName("SENDER"); + + /** + * Constant QName used to indicate that a Server fault must be created. + * + * @see org.springframework.ws.soap.SoapBody#addClientOrSenderFault(String, java.util.Locale) + */ + public static final QName SERVER = new QName("SERVER"); + + private QName faultCode; + + private String faultStringOrReason; + + private Locale locale = Locale.ENGLISH; + + /** + * Returns the fault code. + */ + public QName getFaultCode() { + return faultCode; + } + + /** + * Sets the fault code. + */ + public void setFaultCode(QName faultCode) { + this.faultCode = faultCode; + } + + /** + * Returns the fault string or reason text. + */ + public String getFaultStringOrReason() { + return faultStringOrReason; + } + + /** + * Sets the fault string or reason text. + */ + public void setFaultStringOrReason(String faultStringOrReason) { + this.faultStringOrReason = faultStringOrReason; + } + + /** + * Gets the fault string locale. By default, it is English. + * + * @see Locale#ENGLISH + */ + public Locale getLocale() { + return locale; + } + + /** + * Sets the fault string locale. By default, it is English. + * + * @see Locale#ENGLISH + */ + public void setLocale(Locale locale) { + this.locale = locale; + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultDefinitionEditor.java b/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultDefinitionEditor.java new file mode 100644 index 00000000..34251ac1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultDefinitionEditor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.endpoint; + +import java.beans.PropertyEditorSupport; +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.springframework.beans.propertyeditors.LocaleEditor; +import org.springframework.util.StringUtils; +import org.springframework.xml.namespace.QNameEditor; + +/** + * PropertyEditor for SoapFaultDefinition objects. Takes strings of form + *

+ * faultCode,faultString,locale
+ * 
+ * where faultCode is the string representation of a QName, faultStringOrReason + * is the fault string, and locale is the optional string representations for the + * faultStringOrReasonlanguage. By default, the language is set to English. + *

+ * Instead of supplying a custom fault code, you can use the constants SERVER or RECEIVER + * indicate a Server/Receiver fault, or or CLIENT or SENDER + * toClient/Sender fault respectivaly. + *

+ * For example: + *

+ * RECEIVER,Server error
+ * 
+ * or + *
+ * CLIENT,Client error
+ * 
+ * + * @author Arjen Poutsma + * @see javax.xml.namespace.QName#toString() + * @see org.springframework.xml.namespace.QNameEditor + * @see SoapFaultDefinition#RECEIVER + * @see SoapFaultDefinition#SENDER + * @see org.springframework.ws.soap.SoapFault#getFaultCode() + */ +public class SoapFaultDefinitionEditor extends PropertyEditorSupport { + + private static final int FAULT_CODE_INDEX = 0; + + private static final int FAULT_STRING_INDEX = 1; + + private static final int FAULT_STRING_LOCALE_INDEX = 2; + + public void setAsText(String text) throws IllegalArgumentException { + if (!StringUtils.hasLength(text)) { + setValue(null); + } + else { + String[] tokens = StringUtils.commaDelimitedListToStringArray(text); + if (tokens.length < 2) { + throw new IllegalArgumentException("Invalid amount of comma delimited values in [" + text + + "]: SoapFaultDefinitionEditor requires at least 2"); + } + SoapFaultDefinition definition = new SoapFaultDefinition(); + QNameEditor qNameEditor = new QNameEditor(); + qNameEditor.setAsText(tokens[FAULT_CODE_INDEX].trim()); + definition.setFaultCode((QName) qNameEditor.getValue()); + definition.setFaultStringOrReason(tokens[FAULT_STRING_INDEX].trim()); + if (tokens.length > 2) { + LocaleEditor localeEditor = new LocaleEditor(); + localeEditor.setAsText(tokens[FAULT_STRING_LOCALE_INDEX].trim()); + definition.setLocale((Locale) localeEditor.getValue()); + } + setValue(definition); + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultMappingExceptionResolver.java b/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultMappingExceptionResolver.java new file mode 100644 index 00000000..8219215d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/SoapFaultMappingExceptionResolver.java @@ -0,0 +1,144 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.endpoint; + +import java.util.Enumeration; +import java.util.Properties; + +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.endpoint.AbstractEndpointExceptionResolver; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.context.SoapMessageContext; +import org.springframework.ws.soap.soap11.Soap11Body; + +/** + * Exception resolver that generates a <soap:fault> based on the given exception. + * + * @author Arjen Poutsma + */ +public class SoapFaultMappingExceptionResolver extends AbstractEndpointExceptionResolver { + + private Properties exceptionMappings; + + private SoapFaultDefinition defaultFault; + + /** + * Set the mappings between exception class names and SOAP Faults. The exception class name can be a substring, with + * no wildcard support at present. + *

+ * The values of the given properties object should use the format described in + * SoapFaultDefinitionEditor. + *

+ * Follows the same matching algorithm as RuleBasedTransactionAttribute and RollbackRuleAttribute. + * + * @param mappings exception patterns (can also be fully qualified class names) as keys, fault definition texts as + * values + * @see SoapFaultDefinitionEditor + * @see org.springframework.web.servlet.handler.SimpleMappingExceptionResolver + */ + public void setExceptionMappings(Properties mappings) { + exceptionMappings = mappings; + } + + /** + * Set the default fault. This fault will be returned if no specific mapping was found. + */ + public void setDefaultFault(SoapFaultDefinition defaultFault) { + this.defaultFault = defaultFault; + } + + protected boolean resolveExceptionInternal(MessageContext messageContext, Object endpoint, Exception ex) { + if (!(messageContext instanceof SoapMessageContext)) { + throw new IllegalArgumentException("SoapFaultMappingExceptionResolver requires a SoapMessageContext"); + } + SoapFaultDefinition definition = getFaultDefinition(ex); + if (definition == null) { + return false; + } + SoapMessageContext soapContext = (SoapMessageContext) messageContext; + SoapMessage response = soapContext.getSoapResponse(); + SoapBody soapBody = response.getSoapBody(); + + if (SoapFaultDefinition.SERVER.equals(definition.getFaultCode()) || + SoapFaultDefinition.RECEIVER.equals(definition.getFaultCode())) { + soapBody.addServerOrReceiverFault(definition.getFaultStringOrReason(), definition.getLocale()); + } + else if (SoapFaultDefinition.CLIENT.equals(definition.getFaultCode()) || + SoapFaultDefinition.SENDER.equals(definition.getFaultCode())) { + soapBody.addClientOrSenderFault(definition.getFaultStringOrReason(), definition.getLocale()); + } + else { + // custom code, only supported for SOAP 1.1 + if (soapBody instanceof Soap11Body) { + Soap11Body soap11Body = (Soap11Body) soapBody; + soap11Body.addFault(definition.getFaultCode(), definition.getFaultStringOrReason(), + definition.getLocale()); + } + } + return true; + } + + private SoapFaultDefinition getFaultDefinition(Exception ex) { + SoapFaultDefinition definition = null; + if (exceptionMappings != null) { + String definitionText = null; + int deepest = Integer.MAX_VALUE; + for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();) { + String exceptionMapping = (String) names.nextElement(); + int depth = getDepth(exceptionMapping, ex); + if (depth >= 0 && depth < deepest) { + deepest = depth; + definitionText = exceptionMappings.getProperty(exceptionMapping); + } + } + if (definitionText != null) { + SoapFaultDefinitionEditor editor = new SoapFaultDefinitionEditor(); + editor.setAsText(definitionText); + definition = (SoapFaultDefinition) editor.getValue(); + } + } + if (definition != null || defaultFault == null) { + return definition; + } + definition = defaultFault; + return definition; + } + + /** + * Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if + * there's no match. Otherwise, returns depth. Lowest depth wins. + *

+ * Follows the same algorithm as RollbackRuleAttribute, and SimpleMappingExceptionResolver + * + * @see org.springframework.web.servlet.handler.SimpleMappingExceptionResolver + */ + public int getDepth(String exceptionMapping, Exception ex) { + return getDepth(exceptionMapping, ex.getClass(), 0); + } + + private int getDepth(String exceptionMapping, Class exceptionClass, int depth) { + if (exceptionClass.getName().indexOf(exceptionMapping) != -1) { + return depth; + } + if (exceptionClass.equals(Throwable.class)) { + return -1; + } + return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/interceptor/SoapEnvelopeLoggingInterceptor.java b/core/src/main/java/org/springframework/ws/soap/endpoint/interceptor/SoapEnvelopeLoggingInterceptor.java new file mode 100644 index 00000000..0e909887 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/interceptor/SoapEnvelopeLoggingInterceptor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.endpoint.interceptor; + +import javax.xml.transform.Source; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.endpoint.AbstractLoggingInterceptor; +import org.springframework.ws.soap.SoapEndpointInterceptor; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapMessage; + +/** + * SOAP-specific EndpointInterceptor that logs the complete request and response envelope of + * SoapMessage messages. By default, request, response and fault messages are logged, but this behaviour + * can be changed using the logRequest, logResponse, logFault properties. + * + * @author Arjen Poutsma + * @see #setLogRequest(boolean) + * @see #setLogResponse(boolean) + * @see #setLogFault(boolean) + */ +public class SoapEnvelopeLoggingInterceptor extends AbstractLoggingInterceptor implements SoapEndpointInterceptor { + + private boolean logFault = true; + + /** + * Indicates whether a SOAP Fault should be logged. Default is true. + */ + public void setLogFault(boolean logFault) { + this.logFault = logFault; + } + + public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception { + if (logFault && logger.isDebugEnabled()) { + logMessageSource("Fault: ", getSource(messageContext.getResponse())); + } + return true; + } + + public boolean understands(SoapHeaderElement header) { + return false; + } + + protected Source getSource(WebServiceMessage message) { + if (message instanceof SoapMessage) { + SoapMessage soapMessage = (SoapMessage) message; + return soapMessage.getEnvelope().getSource(); + } + else { + return null; + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/interceptor/package.html b/core/src/main/java/org/springframework/ws/soap/endpoint/interceptor/package.html new file mode 100644 index 00000000..f0928c3b --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/interceptor/package.html @@ -0,0 +1,5 @@ + + +Provides miscellaneous endpoints EndpointInterceptor implementations for SOAP purposes. + + diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/mapping/DelegatingSoapEndpointMapping.java b/core/src/main/java/org/springframework/ws/soap/endpoint/mapping/DelegatingSoapEndpointMapping.java new file mode 100644 index 00000000..7975e11f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/mapping/DelegatingSoapEndpointMapping.java @@ -0,0 +1,82 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.endpoint.mapping; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.ws.EndpointInvocationChain; +import org.springframework.ws.EndpointMapping; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapEndpointInvocationChain; +import org.springframework.ws.soap.SoapEndpointMapping; + +/** + * EndpointMapping implement that adds SOAP actors or roles to a delegate endpoint. Delegates to another + * EndpointMapping, set by delegate, and adds the actors or roles specified by + * actorsOrRoles. + *

+ * This endpoint mapping makes it possible to set actors/roles on a specific endpoint, without making the all endpoint + * mappings depend on SOAP-specific functionality. For normal use, setting an actor or role on an endpoint is not + * required, the default 'next' role is sufficient. + *

+ * It is only in a scenario when a certain endpoint act as a SOAP intermediary for another endpoint, as described in the + * SOAP specificication, this mapping is useful. + * + * @author Arjen Poutsma + * @see org.springframework.ws.soap.SoapHeader#examineMustUnderstandHeaderElements(String) + * @see org.springframework.ws.soap.SoapVersion#getNextActorOrRoleUri() + */ +public class DelegatingSoapEndpointMapping implements InitializingBean, SoapEndpointMapping { + + private EndpointMapping delegate; + + private String[] actorsOrRoles; + + /** + * Sets the delegate EndpointMapping to resolve the endpoint with. + */ + public void setDelegate(EndpointMapping delegate) { + this.delegate = delegate; + } + + public final void setActorOrRole(String actorOrRole) { + Assert.notNull(actorOrRole, "actorOrRole must not be null"); + actorsOrRoles = new String[]{actorOrRole}; + } + + public final void setActorsOrRoles(String[] actorsOrRoles) { + Assert.notEmpty(actorsOrRoles, "actorsOrRoles must not be empty"); + this.actorsOrRoles = actorsOrRoles; + } + + /** + * Creates a new SoapEndpointInvocationChain based on the delegate endpoint, the delegate interceptors, + * and set actors/roles. + * + * @see #setActorsOrRoles(String[]) + */ + public EndpointInvocationChain getEndpoint(MessageContext messageContext) throws Exception { + EndpointInvocationChain delegateChain = delegate.getEndpoint(messageContext); + return new SoapEndpointInvocationChain(delegateChain.getEndpoint(), + delegateChain.getInterceptors(), + actorsOrRoles); + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(delegate, "delegate is required"); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/mapping/SoapActionEndpointMapping.java b/core/src/main/java/org/springframework/ws/soap/endpoint/mapping/SoapActionEndpointMapping.java new file mode 100644 index 00000000..cd354fa7 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/mapping/SoapActionEndpointMapping.java @@ -0,0 +1,105 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.endpoint.mapping; + +import java.util.Iterator; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.EndpointInvocationChain; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.endpoint.mapping.AbstractMapBasedEndpointMapping; +import org.springframework.ws.soap.SoapEndpointInvocationChain; +import org.springframework.ws.soap.SoapEndpointMapping; + +/** + * Implementation of the EndpointMapping interface to map from SOAPAction headers to endpoint + * beans. Supports both mapping to bean instances and mapping to bean names: the latter is required for prototype + * handlers. + *

+ * The endpointMap property is suitable for populating the endpoint map with bean references, e.g. via the + * map element in XML bean definitions. + *

+ * Mappings to bean names can be set via the mappings property, in a form accepted by the + * java.util.Properties class, like as follows: + *

+ * http://www.springframework.org/spring-ws/samples/airline/BookFlight=bookFlightEndpoint
+ * http://www.springframework.org/spring-ws/samples/airline/GetFlights=getFlightsEndpoint
+ * 
+ * The syntax is SOAP_ACTION=ENDPOINT_BEAN_NAME. + *

+ * This endpoint mapping does not read from the request message, and therefore is more suitable for message contexts + * which directly read from the transport request (such as the AxiomSoapMessageContextFactory with the + * payloadCaching disabled). + * + * @author Arjen Poutsma + */ +public class SoapActionEndpointMapping extends AbstractMapBasedEndpointMapping implements SoapEndpointMapping { + + /** + * The name of the SOAPAction TransportRequest header. + */ + public static final String SOAP_ACTION_HEADER = "SOAPAction"; + + private String[] actorsOrRoles; + + public final void setActorOrRole(String actorOrRole) { + Assert.notNull(actorOrRole, "actorOrRole must not be null"); + actorsOrRoles = new String[]{actorOrRole}; + } + + public final void setActorsOrRoles(String[] actorsOrRoles) { + Assert.notEmpty(actorsOrRoles, "actorsOrRoles must not be empty"); + this.actorsOrRoles = actorsOrRoles; + } + + /** + * Creates a new SoapEndpointInvocationChain based on the given endpoint, and the set interceptors, and + * actors/roles. + * + * @param endpoint the endpoint + * @param interceptors the endpoint interceptors + * @return the created invocation chain + * @see #setInterceptors(org.springframework.ws.EndpointInterceptor[]) + * @see #setActorsOrRoles(String[]) + */ + protected final EndpointInvocationChain createEndpointInvocationChain(MessageContext messageContext, + Object endpoint, + EndpointInterceptor[] interceptors) { + return new SoapEndpointInvocationChain(endpoint, interceptors, actorsOrRoles); + } + + protected String getLookupKeyForMessage(MessageContext messageContext) throws Exception { + Iterator iterator = messageContext.getTransportRequest().getHeaders(SOAP_ACTION_HEADER); + String soapAction = ""; + if (iterator.hasNext()) { + soapAction = (String) iterator.next(); + } + if (StringUtils.hasLength(soapAction) && soapAction.charAt(0) == '"' && + soapAction.charAt(soapAction.length() - 1) == '"') { + return soapAction.substring(1, soapAction.length() - 1); + } + else { + return soapAction; + } + } + + protected boolean validateLookupKey(String key) { + return StringUtils.hasLength(key); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/endpoint/package.html b/core/src/main/java/org/springframework/ws/soap/endpoint/package.html new file mode 100644 index 00000000..cd2074f2 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/endpoint/package.html @@ -0,0 +1,6 @@ + + +Provides EndpointAdapter, EndpointMapping, and EndpointExceptionResolver +implementations for SOAP. + + \ No newline at end of file diff --git a/core/src/main/java/org/springframework/ws/soap/package.html b/core/src/main/java/org/springframework/ws/soap/package.html new file mode 100644 index 00000000..b8e0e018 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/package.html @@ -0,0 +1,5 @@ + + +Provides the SOAP functionality of the Spring Web Services framework. Contains the SoapMessage and related interfaces. + + diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12Soap11Body.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12Soap11Body.java new file mode 100644 index 00000000..192a0740 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12Soap11Body.java @@ -0,0 +1,158 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Iterator; +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPBody; +import javax.xml.soap.SOAPBodyElement; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.saaj.support.SaajUtils; +import org.springframework.ws.soap.soap11.Soap11Body; +import org.springframework.ws.soap.soap11.Soap11Fault; + +/** + * Internal class that uses SAAJ 1.2 to implement the Soap11Body interface. + * + * @author Arjen Poutsma + */ +class Saaj12Soap11Body implements Soap11Body { + + private final SOAPBody saajBody; + + Saaj12Soap11Body(SOAPBody saajBody) { + Assert.notNull(saajBody, "No saajBody given"); + this.saajBody = saajBody; + } + + public Source getPayloadSource() { + SOAPBodyElement payloadElement = getPayloadElement(); + return payloadElement != null ? new DOMSource(payloadElement) : null; + } + + public Result getPayloadResult() { + saajBody.removeContents(); + return new DOMResult(saajBody); + } + + public Soap11Fault addFault(QName faultCode, String faultString, Locale faultStringLocale) { + Assert.notNull(faultCode, "No faultCode given"); + Assert.hasLength(faultString, "faultString cannot be empty"); + if (!StringUtils.hasLength(faultCode.getNamespaceURI())) { + throw new IllegalArgumentException( + "A fault code with namespace and local part must be specific for a custom fault code"); + } + try { + Name name = SaajUtils.toName(faultCode, saajBody, getEnvelope()); + saajBody.removeContents(); + SOAPFault saajFault; + if (faultStringLocale == null) { + saajFault = saajBody.addFault(name, faultString); + } + else { + saajFault = saajBody.addFault(name, faultString, faultStringLocale); + } + return new Saaj12Soap11Fault(saajFault); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public SoapFault addMustUnderstandFault(String faultString, Locale locale) { + return addStandardFault("MustUnderstand", faultString, locale); + } + + public SoapFault addClientOrSenderFault(String faultString, Locale locale) { + return addStandardFault("Client", faultString, locale); + } + + public SoapFault addServerOrReceiverFault(String faultString, Locale locale) { + return addStandardFault("Server", faultString, locale); + } + + public SoapFault addVersionMismatchFault(String faultString, Locale locale) { + return addStandardFault("VersionMismatch", faultString, locale); + } + + private Soap11Fault addStandardFault(String localName, String faultString, Locale locale) { + try { + Name faultCode = getEnvelope() + .createName(localName, saajBody.getElementName().getPrefix(), saajBody.getElementName().getURI()); + saajBody.removeContents(); + SOAPFault saajFault; + if (locale == null) { + saajFault = saajBody.addFault(faultCode, faultString); + } + else { + saajFault = saajBody.addFault(faultCode, faultString, locale); + } + return new Saaj12Soap11Fault(saajFault); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public boolean hasFault() { + return saajBody.hasFault(); + } + + public SoapFault getFault() { + return new Saaj12Soap11Fault(saajBody.getFault()); + } + + public QName getName() { + return SaajUtils.toQName(saajBody.getElementName()); + } + + public Source getSource() { + return new DOMSource(saajBody); + } + + private SOAPEnvelope getEnvelope() { + return (SOAPEnvelope) saajBody.getParentElement(); + } + + /** + * Retrieves the payload of the wrapped SAAJ message as a single DOM element. The payload of a message is the + * contents of the SOAP body. + * + * @return the message payload, or null if none is set. + */ + private SOAPBodyElement getPayloadElement() { + for (Iterator iterator = saajBody.getChildElements(); iterator.hasNext();) { + Object child = iterator.next(); + if (child instanceof SOAPBodyElement) { + return (SOAPBodyElement) child; + } + } + return null; + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12Soap11Fault.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12Soap11Fault.java new file mode 100644 index 00000000..2681a60a --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12Soap11Fault.java @@ -0,0 +1,93 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.soap.Detail; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.saaj.support.SaajUtils; +import org.springframework.ws.soap.soap11.Soap11Fault; + +/** + * Internal class that uses SAAJ 1.2 to implement the Soap11Fault interface. + * + * @author Arjen Poutsma + */ +class Saaj12Soap11Fault implements Soap11Fault { + + private final SOAPFault saajFault; + + Saaj12Soap11Fault(SOAPFault saajFault) { + Assert.notNull(saajFault, "No saajFault given"); + this.saajFault = saajFault; + } + + public QName getName() { + return SaajUtils.toQName(saajFault.getElementName()); + } + + public Source getSource() { + return new DOMSource(saajFault); + } + + public QName getFaultCode() { + return SaajUtils.toQName(saajFault.getFaultCodeAsName()); + } + + public String getFaultString() { + return saajFault.getFaultString(); + } + + public String getFaultActorOrRole() { + return saajFault.getFaultActor(); + } + + public void setFaultActorOrRole(String faultActor) { + try { + saajFault.setFaultActor(faultActor); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public Locale getFaultStringLocale() { + return saajFault.getFaultStringLocale(); + } + + public SoapFaultDetail getFaultDetail() { + Detail saajDetail = saajFault.getDetail(); + return saajDetail != null ? new Saaj12SoapFaultDetail(saajDetail) : null; + } + + public SoapFaultDetail addFaultDetail() { + try { + Detail saajDetail = saajFault.addDetail(); + return saajDetail != null ? new Saaj12SoapFaultDetail(saajDetail) : null; + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapEnvelope.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapEnvelope.java new file mode 100644 index 00000000..c810369b --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapEnvelope.java @@ -0,0 +1,83 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.saaj.support.SaajUtils; + +/** + * Internal class that uses SAAJ 1.2 to implement the SoapEnvelope interface. Used by + * SaajSoapMessage. + * + * @author Arjen Poutsma + */ +class Saaj12SoapEnvelope implements SoapEnvelope { + + private final SOAPEnvelope saajEnvelope; + + private Saaj12SoapHeader header; + + private Saaj12Soap11Body body; + + Saaj12SoapEnvelope(SOAPEnvelope saajEnvelope) { + Assert.notNull(saajEnvelope, "No saajEnvelope given"); + this.saajEnvelope = saajEnvelope; + } + + public QName getName() { + return SaajUtils.toQName(saajEnvelope.getElementName()); + } + + public Source getSource() { + return new DOMSource(saajEnvelope); + } + + public SoapHeader getHeader() { + if (header != null) { + try { + header = saajEnvelope.getHeader() != null ? new Saaj12SoapHeader(saajEnvelope.getHeader()) : null; + } + catch (SOAPException ex) { + throw new SaajSoapHeaderException(ex); + } + } + return header; + } + + public SoapBody getBody() { + if (body == null) { + try { + body = new Saaj12Soap11Body(saajEnvelope.getBody()); + } + catch (SOAPException ex) { + throw new SaajSoapBodyException(ex); + } + } + return body; + } + + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapFaultDetail.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapFaultDetail.java new file mode 100644 index 00000000..30523418 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapFaultDetail.java @@ -0,0 +1,100 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.soap.Detail; +import javax.xml.soap.DetailEntry; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.SoapFaultDetailElement; +import org.springframework.ws.soap.saaj.support.SaajUtils; + +/** + * Internal class that uses SAAJ 1.2 to implement the SoapFaultDetail interface. + * + * @author Arjen Poutsma + */ +class Saaj12SoapFaultDetail implements SoapFaultDetail { + + private Detail saajDetail; + + Saaj12SoapFaultDetail(Detail saajDetail) { + Assert.notNull(saajDetail, "No saajDetail given"); + this.saajDetail = saajDetail; + } + + public QName getName() { + return SaajUtils.toQName(saajDetail.getElementName()); + } + + public Source getSource() { + return new DOMSource(saajDetail); + } + + public SoapFaultDetailElement addFaultDetailElement(QName name) { + try { + Name detailEntryName = SaajUtils.toName(name, saajDetail, getEnvelope()); + DetailEntry saajDetailEntry = saajDetail.addDetailEntry(detailEntryName); + return new Saaj12SoapFaultDetailElement(saajDetailEntry); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public Iterator getDetailEntries() { + return new Saaj12SoapFaultDetailIterator(saajDetail.getDetailEntries()); + } + + private SOAPEnvelope getEnvelope() { + return (SOAPEnvelope) saajDetail.getParentElement().getParentElement().getParentElement(); + } + + private static class Saaj12SoapFaultDetailIterator implements Iterator { + + private final Iterator saajIterator; + + public Saaj12SoapFaultDetailIterator(Iterator saajIterator) { + Assert.notNull(saajIterator, "No saajIterator given"); + this.saajIterator = saajIterator; + } + + public boolean hasNext() { + return saajIterator.hasNext(); + } + + public Object next() { + DetailEntry saajDetailEntry = (DetailEntry) saajIterator.next(); + return new Saaj12SoapFaultDetailElement(saajDetailEntry); + } + + public void remove() { + saajIterator.remove(); + } + + } + + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapFaultDetailElement.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapFaultDetailElement.java new file mode 100644 index 00000000..6676f645 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapFaultDetailElement.java @@ -0,0 +1,65 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.namespace.QName; +import javax.xml.soap.DetailEntry; +import javax.xml.soap.SOAPException; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapFaultDetailElement; +import org.springframework.ws.soap.saaj.support.SaajUtils; + +/** + * Internal class that uses SAAJ 1.2 to implement the SoapFaultDetailElement interface. + * + * @author Arjen Poutsma + */ +class Saaj12SoapFaultDetailElement implements SoapFaultDetailElement { + + private DetailEntry saajDetailEntry; + + Saaj12SoapFaultDetailElement(DetailEntry saajDetailEntry) { + Assert.notNull(saajDetailEntry, "No saajDetailEntry given"); + this.saajDetailEntry = saajDetailEntry; + } + + public Result getResult() { + return new DOMResult(saajDetailEntry); + } + + public void addText(String text) { + try { + saajDetailEntry.addTextNode(text); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public QName getName() { + return SaajUtils.toQName(saajDetailEntry.getElementName()); + } + + public Source getSource() { + return new DOMSource(saajDetailEntry); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapHeader.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapHeader.java new file mode 100644 index 00000000..1a12b237 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapHeader.java @@ -0,0 +1,100 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPHeader; +import javax.xml.soap.SOAPHeaderElement; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.saaj.support.SaajUtils; + +/** + * Internal class that uses SAAJ 1.2 to implement the SoapHeader interface. + * + * @author Arjen Poutsma + */ +class Saaj12SoapHeader implements SoapHeader { + + private final SOAPHeader saajHeader; + + Saaj12SoapHeader(SOAPHeader saajHeader) { + Assert.notNull(saajHeader, "No saajHeader given"); + this.saajHeader = saajHeader; + } + + public QName getName() { + return SaajUtils.toQName(saajHeader.getElementName()); + } + + public Source getSource() { + return new DOMSource(saajHeader); + } + + public SoapHeaderElement addHeaderElement(QName name) { + try { + Name saajName = SaajUtils.toName(name, saajHeader, getEnvelope()); + SOAPHeaderElement saajHeaderElement = saajHeader.addHeaderElement(saajName); + return new Saaj12SoapHeaderElement(saajHeaderElement); + } + catch (SOAPException ex) { + throw new SaajSoapHeaderException(ex); + } + } + + public Iterator examineMustUnderstandHeaderElements(String role) { + return new SaajSoapHeaderElementIterator(saajHeader.examineMustUnderstandHeaderElements(role)); + } + + public Iterator examineAllHeaderElements() { + return new SaajSoapHeaderElementIterator(saajHeader.examineAllHeaderElements()); + } + + private SOAPEnvelope getEnvelope() { + return (SOAPEnvelope) saajHeader.getParentElement(); + } + + private static class SaajSoapHeaderElementIterator implements Iterator { + + private final Iterator saajIterator; + + private SaajSoapHeaderElementIterator(Iterator saajIterator) { + this.saajIterator = saajIterator; + } + + public boolean hasNext() { + return saajIterator.hasNext(); + } + + public Object next() { + SOAPHeaderElement saajHeaderElement = (SOAPHeaderElement) saajIterator.next(); + return new Saaj12SoapHeaderElement(saajHeaderElement); + } + + public void remove() { + saajIterator.remove(); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapHeaderElement.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapHeaderElement.java new file mode 100644 index 00000000..9d112de1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj12SoapHeaderElement.java @@ -0,0 +1,90 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.namespace.QName; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPHeaderElement; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapHeaderException; +import org.springframework.ws.soap.saaj.support.SaajUtils; + +/** + * Internal class that uses SAAJ 1.2 to implement the SoapHeaderElement interface. + * + * @author Arjen Poutsma + */ +class Saaj12SoapHeaderElement implements SoapHeaderElement { + + private final SOAPHeaderElement saajHeaderElement; + + Saaj12SoapHeaderElement(SOAPHeaderElement saajHeaderElement) { + Assert.notNull(saajHeaderElement, "No saajHeaderElement given"); + this.saajHeaderElement = saajHeaderElement; + } + + public QName getName() { + return SaajUtils.toQName(saajHeaderElement.getElementName()); + } + + public Source getSource() { + return new DOMSource(saajHeaderElement); + } + + public String getActorOrRole() { + return saajHeaderElement.getActor(); + } + + public void setActorOrRole(String role) { + saajHeaderElement.setActor(role); + } + + public boolean getMustUnderstand() { + return saajHeaderElement.getMustUnderstand(); + } + + public void setMustUnderstand(boolean mustUnderstand) { + saajHeaderElement.setMustUnderstand(mustUnderstand); + } + + public Result getResult() { + return new DOMResult(saajHeaderElement); + } + + public void addAttribute(QName name, String value) throws SoapHeaderException { + try { + Name saajName = SaajUtils.toName(name, saajHeaderElement, getEnvelope()); + saajHeaderElement.addAttribute(saajName, value); + } + catch (SOAPException ex) { + throw new SaajSoapHeaderException(ex); + } + } + + private SOAPEnvelope getEnvelope() { + return (SOAPEnvelope) saajHeaderElement.getParentElement().getParentElement(); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap11Body.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap11Body.java new file mode 100644 index 00000000..6c400ee1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap11Body.java @@ -0,0 +1,102 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPBody; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.soap11.Soap11Body; +import org.springframework.ws.soap.soap11.Soap11Fault; +import org.springframework.xml.namespace.QNameUtils; + +/** + * Internal class that uses SAAJ 1.3 to implement the Soap11Body interface. Used by + * Saaj13SoapEnvelope. + * + * @author Arjen Poutsma + */ +class Saaj13Soap11Body extends Saaj13SoapBody implements Soap11Body { + + Saaj13Soap11Body(SOAPBody saajBody) { + super(saajBody); + } + + public Soap11Fault addFault(QName faultCode, String faultString, Locale faultStringLocale) { + Assert.notNull(faultCode, "No faultCode given"); + Assert.notNull(faultString, "No faultString given"); + if (!StringUtils.hasLength(faultCode.getNamespaceURI())) { + throw new IllegalArgumentException("fault code has no namespace"); + } + try { + saajBody.removeContents(); + SOAPFault saajFault; + if (faultStringLocale == null) { + saajFault = saajBody.addFault(faultCode, faultString); + } + else { + saajFault = saajBody.addFault(faultCode, faultString, faultStringLocale); + } + return new Saaj13Soap11Fault(saajFault); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public SoapFault addMustUnderstandFault(String faultString, Locale locale) { + return addStandardFault("MustUnderstand", faultString, locale); + } + + public SoapFault addClientOrSenderFault(String faultString, Locale locale) { + return addStandardFault("Client", faultString, locale); + } + + public SoapFault addServerOrReceiverFault(String faultString, Locale locale) { + return addStandardFault("Server", faultString, locale); + } + + public SoapFault addVersionMismatchFault(String faultString, Locale locale) { + return addStandardFault("VersionMismatch", faultString, locale); + + } + + private Soap11Fault addStandardFault(String localName, String faultString, Locale faultStringLocale) { + try { + QName faultCode = QNameUtils.createQName(saajBody.getElementQName().getNamespaceURI(), + localName, + QNameUtils.getPrefix(saajBody.getElementQName())); + saajBody.removeContents(); + SOAPFault saajFault = faultStringLocale == null ? saajBody.addFault(faultCode, faultString) : + saajBody.addFault(faultCode, faultString, faultStringLocale); + return new Saaj12Soap11Fault(saajFault); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public SoapFault getFault() { + return new Saaj13Soap11Fault(saajBody.getFault()); + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap11Fault.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap11Fault.java new file mode 100644 index 00000000..f483eea3 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap11Fault.java @@ -0,0 +1,92 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.soap.Detail; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.soap11.Soap11Fault; + +/** + * Internal class that uses SAAJ 1.3 to implement the Soap11Fault interface. + * + * @author Arjen Poutsma + */ +class Saaj13Soap11Fault implements Soap11Fault { + + private final SOAPFault saajFault; + + Saaj13Soap11Fault(SOAPFault saajFault) { + Assert.notNull(saajFault, "No saajFault given"); + this.saajFault = saajFault; + } + + public Locale getFaultStringLocale() { + return saajFault.getFaultStringLocale(); + } + + public String getFaultString() { + return saajFault.getFaultString(); + } + + public Source getSource() { + return new DOMSource(saajFault); + } + + public QName getName() { + return saajFault.getElementQName(); + } + + public SoapFaultDetail addFaultDetail() { + try { + Detail saajDetail = saajFault.addDetail(); + return saajDetail != null ? new Saaj13SoapFaultDetail(saajDetail) : null; + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public SoapFaultDetail getFaultDetail() { + Detail saajDetail = saajFault.getDetail(); + return saajDetail != null ? new Saaj13SoapFaultDetail(saajDetail) : null; + } + + public String getFaultActorOrRole() { + return saajFault.getFaultActor(); + } + + public void setFaultActorOrRole(String faultActor) { + try { + saajFault.setFaultActor(faultActor); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public QName getFaultCode() { + return saajFault.getFaultCodeAsQName(); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Body.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Body.java new file mode 100644 index 00000000..932bc9a1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Body.java @@ -0,0 +1,85 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPBody; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.soap12.Soap12Body; +import org.springframework.ws.soap.soap12.Soap12Fault; + +/** + * Internal class that uses SAAJ 1.3 to implement the Soap12Body interface. Used by + * Saaj13SoapEnvelope. + * + * @author Arjen Poutsma + */ +class Saaj13Soap12Body extends Saaj13SoapBody implements Soap12Body { + + Saaj13Soap12Body(SOAPBody saajBody) { + super(saajBody); + } + + private Soap12Fault addFault(QName code, String reason, Locale locale) { + Assert.notNull(code, "No code given"); + Assert.hasLength(reason, "reason cannot be empty"); + Assert.notNull(locale, "No locale given"); + if (!StringUtils.hasLength(code.getNamespaceURI())) { + throw new IllegalArgumentException( + "A code with namespace and local part must be specific for a custom fault code"); + } + try { + saajBody.removeContents(); + SOAPFault saajFault = saajBody.addFault(code, reason, locale); + return new Saaj13Soap12Fault(saajFault); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public SoapFault addMustUnderstandFault(String reason, Locale locale) { + return addFault(SOAPConstants.SOAP_MUSTUNDERSTAND_FAULT, reason, locale); + } + + public SoapFault addClientOrSenderFault(String reason, Locale locale) { + return addFault(SOAPConstants.SOAP_SENDER_FAULT, reason, locale); + } + + public SoapFault addServerOrReceiverFault(String reason, Locale reasonLocale) { + return addFault(SOAPConstants.SOAP_RECEIVER_FAULT, reason, reasonLocale); + } + + public SoapFault addVersionMismatchFault(String reason, Locale locale) { + return addFault(SOAPConstants.SOAP_VERSIONMISMATCH_FAULT, reason, locale); + } + + public Soap12Fault addDataEncodingUnknownFault(QName[] subcodes, String reason, Locale reasonLocale) { + return addFault(SOAPConstants.SOAP_DATAENCODINGUNKNOWN_FAULT, reason, reasonLocale); + } + + public SoapFault getFault() { + return new Saaj13Soap12Fault(saajBody.getFault()); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Fault.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Fault.java new file mode 100644 index 00000000..7da9f499 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Fault.java @@ -0,0 +1,127 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Iterator; +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.soap.Detail; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.soap12.Soap12Fault; + +/** + * Internal class that uses SAAJ 1.3 to implement the Soap12Fault interface. + * + * @author Arjen Poutsma + */ +class Saaj13Soap12Fault implements Soap12Fault { + + private final SOAPFault saajFault; + + Saaj13Soap12Fault(SOAPFault saajFault) { + this.saajFault = saajFault; + } + + public QName getName() { + return saajFault.getElementQName(); + } + + public Source getSource() { + return new DOMSource(saajFault); + } + + public QName getFaultCode() { + return saajFault.getFaultCodeAsQName(); + } + + public SoapFaultDetail getFaultDetail() { + Detail saajDetail = saajFault.getDetail(); + return saajDetail != null ? new Saaj13SoapFaultDetail(saajDetail) : null; + } + + public SoapFaultDetail addFaultDetail() { + try { + Detail saajDetail = saajFault.addDetail(); + return saajDetail != null ? new Saaj13SoapFaultDetail(saajDetail) : null; + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public Iterator getFaultSubcodes() { + return saajFault.getFaultSubcodes(); + } + + public void addFaultSubcode(QName subcode) { + try { + saajFault.appendFaultSubcode(subcode); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public String getFaultActorOrRole() { + return saajFault.getFaultRole(); + } + + public void setFaultActorOrRole(String uri) { + try { + saajFault.setFaultRole(uri); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public String getFaultNode() { + return saajFault.getFaultNode(); + } + + public void setFaultNode(String uri) { + try { + saajFault.setFaultNode(uri); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public void setFaultReasonText(Locale locale, String text) { + try { + saajFault.addFaultReasonText(text, locale); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public String getFaultReasonText(Locale locale) { + try { + return saajFault.getFaultReasonText(locale); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Header.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Header.java new file mode 100644 index 00000000..12d5ed17 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13Soap12Header.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPHeader; +import javax.xml.soap.SOAPHeaderElement; + +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.soap12.Soap12Header; + +/** + * Internal class that uses SAAJ 1.3 to implement the Soap12Header interface. + * + * @author Arjen Poutsma + */ +class Saaj13Soap12Header extends Saaj13SoapHeader implements Soap12Header { + + Saaj13Soap12Header(SOAPHeader saajHeader) { + super(saajHeader); + } + + public SoapHeaderElement addNotUnderstoodHeaderElement(QName headerName) { + try { + SOAPHeaderElement saajHeaderElement = saajHeader.addNotUnderstoodHeaderElement(headerName); + return new Saaj13SoapHeaderElement(saajHeaderElement); + } + catch (SOAPException ex) { + throw new SaajSoapHeaderException(ex); + } + } + + public SoapHeaderElement addUpgradeHeaderElement(String[] supportedSoapUris) { + try { + SOAPHeaderElement saajHeaderElement = saajHeader.addUpgradeHeaderElement(supportedSoapUris); + return new Saaj13SoapHeaderElement(saajHeaderElement); + } + catch (SOAPException ex) { + throw new SaajSoapHeaderException(ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapBody.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapBody.java new file mode 100644 index 00000000..47293ac6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapBody.java @@ -0,0 +1,84 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPBody; +import javax.xml.soap.SOAPBodyElement; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapBody; + +/** + * * Internal class that uses SAAJ 1.3 to implement the SoapBody interface. Used by + * Saaj13SoapEnvelope. + * + * @author Arjen Poutsma + */ +abstract class Saaj13SoapBody implements SoapBody { + + protected final SOAPBody saajBody; + + Saaj13SoapBody(SOAPBody saajBody) { + Assert.notNull(saajBody, "No saajBody given"); + this.saajBody = saajBody; + } + + public final Source getPayloadSource() { + SOAPBodyElement payloadElement = getPayloadElement(); + return payloadElement != null ? new DOMSource(payloadElement) : null; + } + + public final Result getPayloadResult() { + saajBody.removeContents(); + return new DOMResult(saajBody); + } + + public final boolean hasFault() { + return saajBody.hasFault(); + } + + public final QName getName() { + return saajBody.getElementQName(); + } + + public final Source getSource() { + return new DOMSource(saajBody); + } + + /** + * Retrieves the payload of the wrapped SAAJ message as a single DOM element. The payload of a message is the + * contents of the SOAP body. + * + * @return the message payload, or null if none is set. + */ + private SOAPBodyElement getPayloadElement() { + for (Iterator iterator = saajBody.getChildElements(); iterator.hasNext();) { + Object child = iterator.next(); + if (child instanceof SOAPBodyElement) { + return (SOAPBodyElement) child; + } + } + return null; + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapEnvelope.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapEnvelope.java new file mode 100644 index 00000000..655d249f --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapEnvelope.java @@ -0,0 +1,110 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPBody; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPHeader; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.SoapHeader; + +/** + * Internal class that uses SAAJ 1.3 to implement the SoapEnvelope interface. Used by + * SaajSoapMessage. + * + * @author Arjen Poutsma + */ +class Saaj13SoapEnvelope implements SoapEnvelope { + + private final SOAPEnvelope saajEnvelope; + + private Saaj13SoapHeader header; + + private Saaj13SoapBody body; + + Saaj13SoapEnvelope(SOAPEnvelope saajEnvelope) { + Assert.notNull(saajEnvelope, "No saajEnvelope given"); + this.saajEnvelope = saajEnvelope; + } + + public QName getName() { + return saajEnvelope.getElementQName(); + } + + public Source getSource() { + return new DOMSource(saajEnvelope); + } + + public SoapHeader getHeader() { + if (header == null) { + try { + if (saajEnvelope.getHeader() == null) { + return null; + } + else { + SOAPHeader saajHeader = saajEnvelope.getHeader(); + String namespaceURI = saajEnvelope.getElementQName().getNamespaceURI(); + if (SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE.equals(namespaceURI)) { + header = new Saaj13SoapHeader(saajHeader); + } + else if (SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE.equals(namespaceURI)) { + header = new Saaj13Soap12Header(saajHeader); + } + else { + throw new SaajSoapEnvelopeException("Unknown SOAP namespace \"" + namespaceURI + "\""); + } + } + } + catch (SOAPException ex) { + throw new SaajSoapHeaderException(ex); + } + } + return header; + } + + public SoapBody getBody() { + if (body == null) { + try { + SOAPBody saajBody = saajEnvelope.getBody(); + String namespaceURI = saajEnvelope.getElementQName().getNamespaceURI(); + if (SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE.equals(namespaceURI)) { + body = new Saaj13Soap11Body(saajBody); + } + else if (SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE.equals(namespaceURI)) { + body = new Saaj13Soap12Body(saajBody); + } + else { + throw new SaajSoapEnvelopeException("Unknown SOAP namespace \"" + namespaceURI + "\""); + } + + } + catch (SOAPException ex) { + throw new SaajSoapBodyException(ex); + } + } + return body; + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapFaultDetail.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapFaultDetail.java new file mode 100644 index 00000000..20663941 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapFaultDetail.java @@ -0,0 +1,122 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.soap.Detail; +import javax.xml.soap.DetailEntry; +import javax.xml.soap.SOAPException; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.SoapFaultDetailElement; + +/** + * @author Arjen Poutsma + */ +class Saaj13SoapFaultDetail implements SoapFaultDetail { + + private Detail saajDetail; + + Saaj13SoapFaultDetail(Detail saajDetail) { + Assert.notNull(saajDetail, "No saajDetail given"); + this.saajDetail = saajDetail; + } + + public QName getName() { + return saajDetail.getElementQName(); + } + + public Source getSource() { + return new DOMSource(saajDetail); + } + + public SoapFaultDetailElement addFaultDetailElement(QName name) { + try { + DetailEntry saajDetailEntry = saajDetail.addDetailEntry(name); + return new Saaj13SoapFaultDetailElement(saajDetailEntry); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public Iterator getDetailEntries() { + return new Saaj13SoapFaultDetailIterator(saajDetail.getDetailEntries()); + } + + private static class Saaj13SoapFaultDetailElement implements SoapFaultDetailElement { + + private DetailEntry saajDetailEntry; + + private Saaj13SoapFaultDetailElement(DetailEntry saajDetailEntry) { + Assert.notNull(saajDetailEntry, "No saajDetailEntry given"); + this.saajDetailEntry = saajDetailEntry; + } + + public Result getResult() { + return new DOMResult(saajDetailEntry); + } + + public void addText(String text) { + try { + saajDetailEntry.addTextNode(text); + } + catch (SOAPException ex) { + throw new SaajSoapFaultException(ex); + } + } + + public QName getName() { + return saajDetailEntry.getElementQName(); + } + + public Source getSource() { + return new DOMSource(saajDetailEntry); + } + } + + private static class Saaj13SoapFaultDetailIterator implements Iterator { + + private final Iterator saajIterator; + + public Saaj13SoapFaultDetailIterator(Iterator saajIterator) { + Assert.notNull(saajIterator, "No saajIterator given"); + this.saajIterator = saajIterator; + } + + public boolean hasNext() { + return saajIterator.hasNext(); + } + + public Object next() { + DetailEntry saajDetailEntry = (DetailEntry) saajIterator.next(); + return new Saaj13SoapFaultDetailElement(saajDetailEntry); + } + + public void remove() { + saajIterator.remove(); + } + + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapHeader.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapHeader.java new file mode 100644 index 00000000..7b75c592 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapHeader.java @@ -0,0 +1,92 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPHeader; +import javax.xml.soap.SOAPHeaderElement; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.SoapHeaderElement; + +/** + * Internal class that uses SAAJ 1.3 to implement the SoapHeader interface. + * + * @author Arjen Poutsma + */ +class Saaj13SoapHeader implements SoapHeader { + + protected final SOAPHeader saajHeader; + + Saaj13SoapHeader(SOAPHeader saajHeader) { + Assert.notNull(saajHeader, "No saajHeader given"); + this.saajHeader = saajHeader; + } + + public QName getName() { + return saajHeader.getElementQName(); + } + + public Source getSource() { + return new DOMSource(saajHeader); + } + + public SoapHeaderElement addHeaderElement(QName name) { + try { + SOAPHeaderElement saajHeaderElement = saajHeader.addHeaderElement(name); + return new Saaj13SoapHeaderElement(saajHeaderElement); + } + catch (SOAPException ex) { + throw new SaajSoapHeaderException(ex); + } + } + + public Iterator examineMustUnderstandHeaderElements(String role) { + return new SaajSoapHeaderElementIterator(saajHeader.examineMustUnderstandHeaderElements(role)); + } + + public Iterator examineAllHeaderElements() { + return new SaajSoapHeaderElementIterator(saajHeader.examineAllHeaderElements()); + } + + private static class SaajSoapHeaderElementIterator implements Iterator { + + private final Iterator saajIterator; + + private SaajSoapHeaderElementIterator(Iterator saajIterator) { + this.saajIterator = saajIterator; + } + + public boolean hasNext() { + return saajIterator.hasNext(); + } + + public Object next() { + SOAPHeaderElement saajHeaderElement = (SOAPHeaderElement) saajIterator.next(); + return new Saaj13SoapHeaderElement(saajHeaderElement); + } + + public void remove() { + saajIterator.remove(); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapHeaderElement.java b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapHeaderElement.java new file mode 100644 index 00000000..0ac92c45 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/Saaj13SoapHeaderElement.java @@ -0,0 +1,82 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPHeaderElement; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapHeaderException; + +/** + * Internal class that uses SAAJ 1.3 to implement the SoapHeaderElement interface. + * + * @author Arjen Poutsma + */ +class Saaj13SoapHeaderElement implements SoapHeaderElement { + + private final SOAPHeaderElement saajHeaderElement; + + Saaj13SoapHeaderElement(SOAPHeaderElement saajHeaderElement) { + Assert.notNull(saajHeaderElement, "No saajHeaderElement given"); + this.saajHeaderElement = saajHeaderElement; + } + + public QName getName() { + return saajHeaderElement.getElementQName(); + } + + public Source getSource() { + return new DOMSource(saajHeaderElement); + } + + public String getActorOrRole() { + return saajHeaderElement.getActor(); + } + + public void setActorOrRole(String role) { + saajHeaderElement.setActor(role); + } + + public boolean getMustUnderstand() { + return saajHeaderElement.getMustUnderstand(); + } + + public void setMustUnderstand(boolean mustUnderstand) { + saajHeaderElement.setMustUnderstand(mustUnderstand); + } + + public Result getResult() { + return new DOMResult(saajHeaderElement); + } + + public void addAttribute(QName name, String value) throws SoapHeaderException { + try { + saajHeaderElement.addAttribute(name, value); + } + catch (SOAPException ex) { + throw new SaajSoapHeaderException(ex); + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajAttachmentException.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajAttachmentException.java new file mode 100644 index 00000000..55197419 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajAttachmentException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import org.springframework.ws.soap.AttachmentException; + +/** + * @author Arjen Poutsma + */ +public class SaajAttachmentException extends AttachmentException { + + public SaajAttachmentException(String msg) { + super(msg); + } + + public SaajAttachmentException(String msg, Throwable ex) { + super(msg, ex); + } + + public SaajAttachmentException(Throwable ex) { + super(ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajMessageFactoryBean.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajMessageFactoryBean.java new file mode 100644 index 00000000..e8b016ef --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajMessageFactoryBean.java @@ -0,0 +1,49 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * Spring FactoryBean for SAAJ MessageFactory objects. + * + * @author Arjen Poutsma + * @see javax.xml.soap.MessageFactory + */ +public class SaajMessageFactoryBean implements FactoryBean, InitializingBean { + + private MessageFactory messageFactory; + + public Object getObject() throws Exception { + return messageFactory; + } + + public Class getObjectType() { + return MessageFactory.class; + } + + public boolean isSingleton() { + return true; + } + + public void afterPropertiesSet() throws Exception { + messageFactory = MessageFactory.newInstance(); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapBodyException.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapBodyException.java new file mode 100644 index 00000000..c961e340 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapBodyException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.saaj; + +import org.springframework.ws.soap.SoapEnvelopeException; + +/** + * @author Arjen Poutsma + */ +public class SaajSoapBodyException extends SoapEnvelopeException { + + public SaajSoapBodyException(String msg) { + super(msg); + } + + public SaajSoapBodyException(String msg, Throwable ex) { + super(msg, ex); + } + + public SaajSoapBodyException(Throwable ex) { + super(ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapEnvelopeException.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapEnvelopeException.java new file mode 100644 index 00000000..b1929797 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapEnvelopeException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import org.springframework.ws.soap.SoapEnvelopeException; + +/** + * @author Arjen Poutsma + */ +public class SaajSoapEnvelopeException extends SoapEnvelopeException { + + public SaajSoapEnvelopeException(String msg) { + super(msg); + } + + public SaajSoapEnvelopeException(String msg, Throwable ex) { + super(msg, ex); + } + + public SaajSoapEnvelopeException(Throwable ex) { + super(ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapFaultException.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapFaultException.java new file mode 100644 index 00000000..c4d0ae96 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapFaultException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.saaj; + +import org.springframework.ws.soap.SoapFaultException; + +/** + * @author Arjen Poutsma + */ +public class SaajSoapFaultException extends SoapFaultException { + + public SaajSoapFaultException(String msg) { + super(msg); + } + + public SaajSoapFaultException(String msg, Throwable ex) { + super(msg, ex); + } + + public SaajSoapFaultException(Throwable ex) { + super(ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapHeaderException.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapHeaderException.java new file mode 100644 index 00000000..2ce77106 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapHeaderException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.saaj; + +import org.springframework.ws.soap.SoapHeaderException; + +/** + * @author Arjen Poutsma + */ +public class SaajSoapHeaderException extends SoapHeaderException { + + public SaajSoapHeaderException(String msg) { + super(msg); + } + + public SaajSoapHeaderException(String msg, Throwable ex) { + super(msg, ex); + } + + public SaajSoapHeaderException(Throwable ex) { + super(ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessage.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessage.java new file mode 100644 index 00000000..6c2c7d0e --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessage.java @@ -0,0 +1,246 @@ +/* + * Copyright 2005, 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.activation.FileDataSource; +import javax.xml.soap.AttachmentPart; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; + +import org.springframework.core.io.InputStreamSource; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.ws.soap.AbstractSoapMessage; +import org.springframework.ws.soap.Attachment; +import org.springframework.ws.soap.AttachmentException; +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.saaj.support.SaajUtils; + +/** + * SAAJ-specific implementation of the SoapMessage interface. Accessed via the + * SaajSoapMessageContext. + * + * @author Arjen Poutsma + * @see javax.xml.soap.SOAPMessage + * @see SaajSoapMessageContext + */ +public class SaajSoapMessage extends AbstractSoapMessage { + + private final SOAPMessage saajMessage; + + private SoapEnvelope envelope; + + /** + * Create a new SaajSoapMessage based on the given SAAJ SOAPMessage. + * + * @param soapMessage the SAAJ SOAPMessage + */ + public SaajSoapMessage(SOAPMessage soapMessage) { + saajMessage = soapMessage; + } + + /** + * Return the SAAJ SOAPMessage that this SaajSoapMessage is based on. + */ + public final SOAPMessage getSaajMessage() { + return saajMessage; + } + + public SoapEnvelope getEnvelope() { + if (envelope == null) { + try { + SOAPEnvelope saajEnvelope = saajMessage.getSOAPPart().getEnvelope(); + if (SaajUtils.getSaajVersion() == SaajUtils.SAAJ_12) { + envelope = new Saaj12SoapEnvelope(saajEnvelope); + } + else { + envelope = new Saaj13SoapEnvelope(saajEnvelope); + } + } + catch (SOAPException ex) { + throw new SaajSoapEnvelopeException(ex); + } + } + return envelope; + } + + public void writeTo(OutputStream outputStream) throws IOException { + try { + if (saajMessage.saveRequired()) { + saajMessage.saveChanges(); + } + saajMessage.writeTo(outputStream); + } + catch (SOAPException ex) { + throw new SaajSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex); + } + } + + public Attachment getAttachment(String contentId) { + MimeHeaders mimeHeaders = new MimeHeaders(); + mimeHeaders.addHeader("Content-Id", contentId); + Iterator iterator = saajMessage.getAttachments(mimeHeaders); + if (!iterator.hasNext()) { + return null; + } + else { + AttachmentPart saajAttachment = (AttachmentPart) iterator.next(); + return new SaajAttachment(saajAttachment); + } + } + + public Iterator getAttachments() { + Iterator saajAttachmentIterator = saajMessage.getAttachments(); + return new SaajAttachmentIterator(saajAttachmentIterator); + } + + public Attachment addAttachment(File file) throws AttachmentException { + Assert.notNull(file, "File must not be null"); + FileDataSource dataSource = new FileDataSource(file); + AttachmentPart saajAttachment = saajMessage.createAttachmentPart(new DataHandler(dataSource)); + saajMessage.addAttachmentPart(saajAttachment); + return new SaajAttachment(saajAttachment); + } + + public Attachment addAttachment(InputStreamSource inputStreamSource, String contentType) { + Assert.notNull(inputStreamSource, "InputStreamSource must not be null"); + if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) { + throw new IllegalArgumentException("Passed-in Resource contains an open stream: invalid argument. " + + "SAAJ requires an InputStreamSource that creates a fresh stream for every call."); + } + DataSource dataSource = createDataSource(inputStreamSource, contentType); + AttachmentPart saajAttachment = saajMessage.createAttachmentPart(new DataHandler(dataSource)); + saajMessage.addAttachmentPart(saajAttachment); + return new SaajAttachment(saajAttachment); + } + + /** + * Create an Activation Framework DataSource for the given InputStreamSource. + * + * @param inputStreamSource the InputStreamSource (typically a Spring Resource) + * @param contentType the content type + * @return the Activation Framework DataSource + */ + private DataSource createDataSource(final InputStreamSource inputStreamSource, final String contentType) { + return new DataSource() { + public InputStream getInputStream() throws IOException { + return inputStreamSource.getInputStream(); + } + + public OutputStream getOutputStream() { + throw new UnsupportedOperationException("Read-only javax.activation.DataSource"); + } + + public String getContentType() { + return contentType; + } + + public String getName() { + throw new UnsupportedOperationException("DataSource name not available"); + } + }; + } + + public SoapVersion getVersion() { + if (SaajUtils.getSaajVersion() == SaajUtils.SAAJ_12) { + return SoapVersion.SOAP_11; + } + else { + return super.getVersion(); + } + } + + /** + * SAAJ-specific implementation of org.springframework.ws.soap.Attachment + */ + private static class SaajAttachment implements Attachment { + + private final AttachmentPart saajAttachment; + + public SaajAttachment(AttachmentPart saajAttachment) { + this.saajAttachment = saajAttachment; + } + + public String getId() { + return saajAttachment.getContentId(); + } + + public void setId(String id) { + saajAttachment.setContentId(id); + } + + public String getContentType() { + return saajAttachment.getContentType(); + } + + public InputStream getInputStream() throws IOException { + try { + return saajAttachment.getDataHandler().getInputStream(); + } + catch (SOAPException e) { + return new ByteArrayInputStream(new byte[0]); + } + } + + public long getSize() { + try { + int result = saajAttachment.getSize(); + // SAAJ returns -1 when the size cannot be determined + return result != -1 ? result : 0; + } + catch (SOAPException ex) { + throw new SaajAttachmentException(ex); + } + } + + } + + private static class SaajAttachmentIterator implements Iterator { + + private final Iterator saajIterator; + + public SaajAttachmentIterator(Iterator saajIterator) { + this.saajIterator = saajIterator; + } + + public boolean hasNext() { + return saajIterator.hasNext(); + } + + public Object next() { + AttachmentPart saajAttachment = (AttachmentPart) saajIterator.next(); + return new SaajAttachment(saajAttachment); + } + + public void remove() { + saajIterator.remove(); + } + } + + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageContext.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageContext.java new file mode 100644 index 00000000..4f1c4058 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageContext.java @@ -0,0 +1,127 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.io.IOException; +import java.util.Iterator; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeader; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapMessageCreationException; +import org.springframework.ws.soap.context.AbstractSoapMessageContext; +import org.springframework.ws.transport.TransportRequest; +import org.springframework.ws.transport.TransportResponse; + +/** + * SAAJ-specific implementation of the SoapMessageContext interface. Created by the + * SaajSoapMessageContextFactory. + * + * @author Arjen Poutsma + * @see SaajSoapMessageContextFactory + */ +public class SaajSoapMessageContext extends AbstractSoapMessageContext { + + private final MessageFactory messageFactory; + + /** + * Creates a new instance based on the given SAAJ request message, and a message factory. + * + * @param request the request message + * @param transportRequest the transport request + * @param messageFactory the message factory used for creating a response + */ + public SaajSoapMessageContext(SOAPMessage request, + TransportRequest transportRequest, + MessageFactory messageFactory) { + super(new SaajSoapMessage(request), transportRequest); + Assert.notNull(messageFactory); + this.messageFactory = messageFactory; + } + + /** + * Returns the request as a SAAJ SOAP message. + */ + public SOAPMessage getSaajRequest() { + return ((SaajSoapMessage) getSoapRequest()).getSaajMessage(); + } + + /** + * Sets the request to the given SAAJ SOAP message. + */ + public void setSaajRequest(SOAPMessage request) { + setRequest(new SaajSoapMessage(request)); + } + + /** + * Returns the response as a SAAJ SOAP message. + */ + public SOAPMessage getSaajResponse() { + return ((SaajSoapMessage) getSoapResponse()).getSaajMessage(); + } + + /** + * Sets the response to the given SAAJ SOAP message. + */ + public void setSaajResponse(SOAPMessage response) { + setResponse(new SaajSoapMessage(response)); + } + + public void sendResponse(TransportResponse transportResponse) throws IOException { + if (hasResponse()) { + SOAPMessage response = getSaajResponse(); + try { + if (response.saveRequired()) { + response.saveChanges(); + } + // some SAAJ implementations (Axis 1) do not have a Content-Type header by default + MimeHeaders headers = response.getMimeHeaders(); + if (ObjectUtils.isEmpty(headers.getHeader("Content-Type"))) { + headers.addHeader("Content-Type", getSoapResponse().getVersion().getContentType()); + if (response.saveRequired()) { + response.saveChanges(); + } + } + for (Iterator iterator = headers.getAllHeaders(); iterator.hasNext();) { + MimeHeader mimeHeader = (MimeHeader) iterator.next(); + transportResponse.addHeader(mimeHeader.getName(), mimeHeader.getValue()); + } + response.writeTo(transportResponse.getOutputStream()); + } + catch (SOAPException ex) { + throw new SaajSoapMessageException("Could not write message to TransportResponse: " + ex.getMessage(), + ex); + } + + } + } + + protected SoapMessage createResponseSoapMessage() { + try { + SOAPMessage saajMessage = messageFactory.createMessage(); + return new SaajSoapMessage(saajMessage); + } + catch (SOAPException ex) { + throw new SoapMessageCreationException("Could not create message: " + ex.toString(), ex); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageContextFactory.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageContextFactory.java new file mode 100644 index 00000000..92c06afb --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageContextFactory.java @@ -0,0 +1,124 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.io.IOException; +import java.util.Iterator; +import java.util.StringTokenizer; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.StringUtils; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.soap.SoapMessageCreationException; +import org.springframework.ws.soap.saaj.support.SaajUtils; +import org.springframework.ws.transport.TransportContext; +import org.springframework.ws.transport.TransportRequest; + +/** + * SAAJ-specific implementation of the MessageContextFactory interface. Creates a + * SaajSoapMessageContext. This factory will use SAAJ 1.3 when found, or fall back to SAAJ 1.2. + * + * @author Arjen Poutsma + * @see SaajSoapMessageContext + */ +public class SaajSoapMessageContextFactory implements MessageContextFactory, InitializingBean { + + private static final Log logger = LogFactory.getLog(SaajSoapMessageContextFactory.class); + + private MessageFactory messageFactory; + + private String messageFactoryProtocol; + + public void setMessageFactory(MessageFactory messageFactory) { + this.messageFactory = messageFactory; + } + + /** + * Sets the protocol for the MessageFactory. Only used for SAAJ 1.3+, defaults to + * SOAPConstants.DEFAULT_SOAP_PROTOCOL (i.e. SOAP 1.1). + * + * @see MessageFactory#newInstance(String) + * @see SOAPConstants#DEFAULT_SOAP_PROTOCOL + * @see SOAPConstants#SOAP_1_1_PROTOCOL + * @see SOAPConstants#SOAP_1_2_PROTOCOL + * @see SOAPConstants#DYNAMIC_SOAP_PROTOCOL + */ + public void setSoapProtocol(String messageFactoryProtocol) { + this.messageFactoryProtocol = messageFactoryProtocol; + } + + public void afterPropertiesSet() throws Exception { + if (messageFactory == null) { + try { + if (SaajUtils.getSaajVersion() >= SaajUtils.SAAJ_13) { + if (!StringUtils.hasLength(messageFactoryProtocol)) { + messageFactoryProtocol = SOAPConstants.DEFAULT_SOAP_PROTOCOL; + } + if (logger.isInfoEnabled()) { + logger.info("Creating SAAJ 1.3 MessageFactory with " + messageFactoryProtocol); + } + messageFactory = MessageFactory.newInstance(messageFactoryProtocol); + } + else if (SaajUtils.getSaajVersion() == SaajUtils.SAAJ_12) { + if (logger.isInfoEnabled()) { + logger.info("Creating SAAJ 1.2 MessageFactory"); + } + messageFactory = MessageFactory.newInstance(); + } + else { + throw new IllegalStateException("SaajSoapMessageContextFactory requires SAAJ 1.2, which was not" + + "found on the classpath"); + } + } + catch (SOAPException ex) { + throw new SoapMessageCreationException("Could not create MessageFactory: " + ex.getMessage(), ex); + } + } + } + + public MessageContext createContext(TransportContext transportContext) throws IOException { + TransportRequest transportRequest = transportContext.getTransportRequest(); + MimeHeaders mimeHeaders = new MimeHeaders(); + for (Iterator headerNames = transportRequest.getHeaderNames(); headerNames.hasNext();) { + String headerName = (String) headerNames.next(); + for (Iterator headerValues = transportRequest.getHeaders(headerName); headerValues.hasNext();) { + String headerValue = (String) headerValues.next(); + StringTokenizer tokenizer = new StringTokenizer(headerValue, ","); + while (tokenizer.hasMoreTokens()) { + mimeHeaders.addHeader(headerName, tokenizer.nextToken().trim()); + } + } + } + try { + SOAPMessage requestMessage = messageFactory.createMessage(mimeHeaders, transportRequest.getInputStream()); + return new SaajSoapMessageContext(requestMessage, transportRequest, messageFactory); + } + catch (SOAPException ex) { + throw new SoapMessageCreationException("Could not create message from TransportRequest: " + ex.getMessage(), + ex); + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageCreationException.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageCreationException.java new file mode 100644 index 00000000..e3337825 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageCreationException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.saaj; + +import org.springframework.ws.soap.SoapMessageCreationException; + +/** + * @author Arjen Poutsma + */ +public class SaajSoapMessageCreationException extends SoapMessageCreationException { + + public SaajSoapMessageCreationException(String msg) { + super(msg); + } + + public SaajSoapMessageCreationException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageException.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageException.java new file mode 100644 index 00000000..f827a97a --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import org.springframework.ws.soap.SoapMessageException; + +/** + * @author Arjen Poutsma + */ +public class SaajSoapMessageException extends SoapMessageException { + + public SaajSoapMessageException(String msg) { + super(msg); + } + + public SaajSoapMessageException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/package.html b/core/src/main/java/org/springframework/ws/soap/saaj/package.html new file mode 100644 index 00000000..01e8ac8d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/package.html @@ -0,0 +1,5 @@ + + +SOAP with Attachments API for Java (SAAJ) support for Spring-WS' soap message infrastructure. + + diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/support/SaajUtils.java b/core/src/main/java/org/springframework/ws/soap/saaj/support/SaajUtils.java new file mode 100644 index 00000000..b1cb8fa6 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/support/SaajUtils.java @@ -0,0 +1,165 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj.support; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPElement; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; + +import org.springframework.core.io.Resource; +import org.springframework.util.StringUtils; +import org.springframework.xml.namespace.QNameUtils; +import org.w3c.dom.Element; + +/** + * Collection of generic utility methods to work with SAAJ. Includes conversion from Names to + * QNames and vice-versa, and SAAJ version checking. + * + * @author Arjen Poutsma + * @see Name + * @see QName + */ +public abstract class SaajUtils { + + public static final int SAAJ_11 = 0; + + public static final int SAAJ_12 = 1; + + public static final int SAAJ_13 = 2; + + private static final String SAAJ_13_CLASS_NAME = "javax.xml.soap.SAAJMetaFactory"; + + private static int saajVersion = SAAJ_12; + + static { + try { + Class.forName(SAAJ_13_CLASS_NAME); + saajVersion = SAAJ_13; + } + catch (ClassNotFoundException ex) { + if (Element.class.isAssignableFrom(SOAPElement.class)) { + saajVersion = SAAJ_12; + } + else { + saajVersion = SAAJ_11; + } + } + } + + /** + * Gets the SAAJ version. + * + * @return a code comparable to the SAAJ_XX codes in this class + * @see #SAAJ_12 + * @see #SAAJ_13 + */ + public static int getSaajVersion() { + return saajVersion; + } + + /** + * Converts a javax.xml.namespace.QName to a javax.xml.soap.Name. A + * SOAPEnvelope is required to create the name. + * + * @param qName the QName to convert + * @param resolveElement a SOAPElement used to resolve namespaces to prefixes + * @param envelope a SOAPEnvelope necessary to create the Name + * @return the converted SAAJ Name + * @throws SOAPException if conversion is unsuccessful + * @throws IllegalArgumentException if qName is not fully qualified + */ + public static Name toName(QName qName, SOAPElement resolveElement, SOAPEnvelope envelope) throws SOAPException { + String qNamePrefix = QNameUtils.getPrefix(qName); + if (StringUtils.hasLength(qName.getNamespaceURI()) && StringUtils.hasLength(qNamePrefix)) { + return envelope.createName(qName.getLocalPart(), qNamePrefix, qName.getNamespaceURI()); + } + else if (StringUtils.hasLength(qName.getNamespaceURI())) { + Iterator prefixes = resolveElement.getVisibleNamespacePrefixes(); + while (prefixes.hasNext()) { + String prefix = (String) prefixes.next(); + if (qName.getNamespaceURI().equals(resolveElement.getNamespaceURI(prefix))) { + return envelope.createName(qName.getLocalPart(), prefix, qName.getNamespaceURI()); + } + } + throw new IllegalArgumentException("Could not resolve namespace of QName [" + qName + "]"); + } + else { + return envelope.createName(qName.getLocalPart()); + } + } + + /** + * Converts a javax.xml.soap.Name to a javax.xml.namespace.QName. + * + * @param name the Name to convert + * @return the converted QName + */ + public static QName toQName(Name name) { + if (StringUtils.hasLength(name.getURI()) && StringUtils.hasLength(name.getPrefix())) { + return QNameUtils.createQName(name.getURI(), name.getLocalName(), name.getPrefix()); + } + else if (StringUtils.hasLength(name.getURI())) { + return new QName(name.getURI(), name.getLocalName()); + } + else { + return new QName(name.getLocalName()); + } + } + + /** + * Loads a SAAJ SOAPMessage from the given resource. + * + * @param resource the resource to read from + * @return the loaded SAAJ message + * @throws SOAPException if the message cannot be constructed + * @throws IOException if the input stream resource cannot be loaded + */ + public static SOAPMessage loadMessage(Resource resource) throws SOAPException, IOException { + return loadMessage(resource, MessageFactory.newInstance()); + } + + /** + * Loads a SAAJ SOAPMessage from the given resource with a given message factory. + * + * @param resource the resource to read from + * @param messageFactory SAAJ message factory used to construct the message + * @return the loaded SAAJ message + * @throws SOAPException if the message cannot be constructed + * @throws IOException if the input stream resource cannot be loaded + */ + public static SOAPMessage loadMessage(Resource resource, MessageFactory messageFactory) + throws SOAPException, IOException { + InputStream is = resource.getInputStream(); + try { + MimeHeaders mimeHeaders = new MimeHeaders(); + mimeHeaders.addHeader("Content-Type", "text/xml"); + mimeHeaders.addHeader("Content-Length", Long.toString(resource.getFile().length())); + return messageFactory.createMessage(mimeHeaders, is); + } + finally { + is.close(); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/support/package.html b/core/src/main/java/org/springframework/ws/soap/saaj/support/package.html new file mode 100644 index 00000000..164f83c1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/saaj/support/package.html @@ -0,0 +1,5 @@ + + +Support classes for working with the SOAP with Attachments API for Java (SAAJ). + + diff --git a/core/src/main/java/org/springframework/ws/soap/soap11/Soap11Body.java b/core/src/main/java/org/springframework/ws/soap/soap11/Soap11Body.java new file mode 100644 index 00000000..03f8446a --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/soap11/Soap11Body.java @@ -0,0 +1,46 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap11; + +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapFaultException; + +/** + * Subinterface of SoapBody that exposes SOAP 1.1 functionality. Necessary because SOAP 1.1 differs from + * SOAP 1.2 with respect to SOAP Faults. + * + * @author Arjen Poutsma + * @see Soap11Fault + */ +public interface Soap11Body extends SoapBody { + + /** + * Adds a SOAP 1.1 Fault to the body with a localized message. Adding a fault removes the + * current content of the body. + * + * @param faultCode the fully qualified fault faultCode + * @param faultString the faultString + * @param faultStringLocale the faultString locale. May be null + * @return the added Soap11Fault + * @throws IllegalArgumentException if the fault faultCode is not fully qualified + */ + Soap11Fault addFault(QName faultCode, String faultString, Locale faultStringLocale) throws SoapFaultException; + +} diff --git a/core/src/main/java/org/springframework/ws/soap/soap11/Soap11Fault.java b/core/src/main/java/org/springframework/ws/soap/soap11/Soap11Fault.java new file mode 100644 index 00000000..d64814ba --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/soap11/Soap11Fault.java @@ -0,0 +1,41 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap11; + +import java.util.Locale; + +import org.springframework.ws.soap.SoapFault; + +/** + * Subinterface of SoapFault that exposes SOAP 1.1 functionality. Necessary because SOAP 1.1 differs from + * SOAP 1.2 with respect to SOAP Faults. + * + * @author Arjen Poutsma + */ +public interface Soap11Fault extends SoapFault { + + /** + * Returns the fault string. + */ + String getFaultString(); + + /** + * Returns the locale of the fault string. + */ + Locale getFaultStringLocale(); + +} diff --git a/core/src/main/java/org/springframework/ws/soap/soap11/package.html b/core/src/main/java/org/springframework/ws/soap/soap11/package.html new file mode 100644 index 00000000..34036f12 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/soap11/package.html @@ -0,0 +1,5 @@ + + +Contains interfaces specific to SOAP 1.1. + + diff --git a/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Body.java b/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Body.java new file mode 100644 index 00000000..e3c6f4f5 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Body.java @@ -0,0 +1,43 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap12; + +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.SoapBody; + +/** + * Subinterface of SoapBody that exposes SOAP 1.2 functionality. Necessary because SOAP 1.1 differs from + * SOAP 1.2 with respect to SOAP Faults. + * + * @author Arjen Poutsma + */ +public interface Soap12Body extends SoapBody { + + /** + * Adds a DataEncodingUnknown fault to the body. + *

+ * Adding a fault removes the current content of the body. + * + * @param subcodes the optional fully qualified fault subcodes + * @param reason the fault reason + * @param locale the language of the fault reason + * @return the created SoapFault + */ + Soap12Fault addDataEncodingUnknownFault(QName[] subcodes, String reason, Locale locale); +} diff --git a/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Fault.java b/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Fault.java new file mode 100644 index 00000000..59a9f254 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Fault.java @@ -0,0 +1,67 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap12; + +import java.util.Iterator; +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.SoapFault; + +/** + * Subinterface of SoapFault that exposes SOAP 1.2 functionality. Necessary because SOAP 1.1 differs from + * SOAP 1.2 with respect to SOAP Faults. + * + * @author Arjen Poutsma + */ +public interface Soap12Fault extends SoapFault { + + /** + * Returns an iteration over the fault subcodes. The subcodes are returned in order: from top to bottom. + * + * @return an Iterator that contains QNames representing the fault subcodes + */ + Iterator getFaultSubcodes(); + + /** + * Adds a fault subcode this fault. + * + * @param subcode the qualified name of the subcode + */ + void addFaultSubcode(QName subcode); + + /** + * Returns the fault node. Optional. + */ + String getFaultNode(); + + /** + * Sets the fault node. + */ + void setFaultNode(String uri); + + /** + * Sets the specified fault reason text. + */ + void setFaultReasonText(Locale locale, String text); + + /** + * Returns the reason associated with the given language. + */ + String getFaultReasonText(Locale locale); + +} diff --git a/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Header.java b/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Header.java new file mode 100644 index 00000000..982d5e12 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/soap12/Soap12Header.java @@ -0,0 +1,51 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap12; + +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.SoapHeaderElement; + +/** + * Subinterface of SoapHeader that exposes SOAP 1.2 functionality. + * + * @author Arjen Poutsma + */ +public interface Soap12Header extends SoapHeader { + + /** + * Adds a new NotUnderstood SoapHeaderElement this header. + * + * @param headerName the qualified name of the header that was not understood + * @return the created SoapHeaderElement + * @throws org.springframework.ws.soap.SoapHeaderException + * if the header cannot be created + */ + SoapHeaderElement addNotUnderstoodHeaderElement(QName headerName); + + /** + * Adds a new Upgrade SoapHeaderElement this header. + * + * @param supportedSoapUris an array of the URIs of SOAP versions supported + * @return the created SoapHeaderElement + * @throws org.springframework.ws.soap.SoapHeaderException + * if the header cannot be created + */ + SoapHeaderElement addUpgradeHeaderElement(java.lang.String[] supportedSoapUris); + +} diff --git a/core/src/main/java/org/springframework/ws/soap/soap12/package.html b/core/src/main/java/org/springframework/ws/soap/soap12/package.html new file mode 100644 index 00000000..4a1ba153 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/soap/soap12/package.html @@ -0,0 +1,5 @@ + + +Contains interfaces specific to SOAP 1.2. + + diff --git a/core/src/main/java/org/springframework/ws/transport/TransportContext.java b/core/src/main/java/org/springframework/ws/transport/TransportContext.java new file mode 100644 index 00000000..4bc488cc --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/TransportContext.java @@ -0,0 +1,15 @@ +package org.springframework.ws.transport; + +/** + * Defines the contract for Web service request that come in via a transport. Exposes headers and the inputstream to + * read from. + * + * @author Arjen Poutsma + */ +public interface TransportContext { + + TransportRequest getTransportRequest() throws TransportException; + + TransportResponse getTransportResponse() throws TransportException; + +} diff --git a/core/src/main/java/org/springframework/ws/transport/TransportException.java b/core/src/main/java/org/springframework/ws/transport/TransportException.java new file mode 100644 index 00000000..99a28925 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/TransportException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport; + +import org.springframework.ws.WebServiceException; + +/** + * Abstract base class for exceptions related to the transport layer. + * + * @author Arjen Poutsma + */ +public abstract class TransportException extends WebServiceException { + + protected TransportException(String msg) { + super(msg); + } + + protected TransportException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/core/src/main/java/org/springframework/ws/transport/TransportRequest.java b/core/src/main/java/org/springframework/ws/transport/TransportRequest.java new file mode 100644 index 00000000..6d0efbfd --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/TransportRequest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + +/** + * Defines the contract for Web service request that come in via a transport. Exposes headers and the inputstream to + * read from. + * + * @author Arjen Poutsma + */ +public interface TransportRequest { + + /** + * Returns an iteratoion over all the header names this request contains. Returns an empty Iterator if + * the request has no headers, this method . + */ + Iterator getHeaderNames() throws TransportException; + + /** + * Returns an iteration over all the string values of the specified request header. Returns an empty + * Iterator if the request did not include any headers of the specified name. + */ + Iterator getHeaders(String name) throws TransportException; + + /** + * Returns the contents of the request as a InputStream. + */ + InputStream getInputStream() throws TransportException, IOException; + + /** + * Return a URL handle for this request. + */ + String getUrl() throws TransportException; + +} diff --git a/core/src/main/java/org/springframework/ws/transport/TransportResponse.java b/core/src/main/java/org/springframework/ws/transport/TransportResponse.java new file mode 100644 index 00000000..740ddce8 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/TransportResponse.java @@ -0,0 +1,34 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Defines the contract for Web service request that come in via a transport. Exposes headers and the inputstream to + * read from. + * + * @author Arjen Poutsma + */ +public interface TransportResponse { + + void addHeader(String name, String value); + + OutputStream getOutputStream() throws IOException; + +} diff --git a/core/src/main/java/org/springframework/ws/transport/http/HttpTransportContext.java b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportContext.java new file mode 100644 index 00000000..c6f5a250 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportContext.java @@ -0,0 +1,56 @@ +package org.springframework.ws.transport.http; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.util.Assert; +import org.springframework.ws.transport.TransportContext; +import org.springframework.ws.transport.TransportRequest; +import org.springframework.ws.transport.TransportResponse; + +/** + * HTTP-specific implementation of the TransportContext interface. Exposes the + * HttpServletRequest and HttpServletResponse. + * + * @author Arjen Poutsma + */ +public class HttpTransportContext implements TransportContext { + + private final HttpTransportRequest transportRequest; + + private final HttpTransportResponse transportResponse; + + /** + * Constructs a new instance of the HttpTransportContext with the given HttpServletRequest + * and HttpServletResponse + */ + public HttpTransportContext(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { + Assert.notNull(httpServletRequest, "No httpServletRequest given"); + Assert.notNull(httpServletResponse, "No httpServletResponse given"); + transportRequest = new HttpTransportRequest(httpServletRequest); + transportResponse = new HttpTransportResponse(httpServletResponse); + } + + public TransportRequest getTransportRequest() { + return transportRequest; + } + + public TransportResponse getTransportResponse() { + return transportResponse; + } + + /** + * Returns the wrapped HttpServletRequest. + */ + public HttpServletRequest getHttpServletRequest() { + return transportRequest.getHttpServletRequest(); + } + + /** + * Returns the wrapped HttpServletResponse. + */ + public HttpServletResponse getHttpServletRespo() { + return transportResponse.getHttpServletResponse(); + } + +} diff --git a/core/src/main/java/org/springframework/ws/transport/http/HttpTransportRequest.java b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportRequest.java new file mode 100644 index 00000000..21de9eb9 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportRequest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport.http; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Iterator; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.util.Assert; +import org.springframework.ws.transport.TransportRequest; + +/** + * HTTP-specific implementation of the TransportRequest interface. Exposes the + * HttpServletRequest + * + * @author Arjen Poutsma + */ +public class HttpTransportRequest implements TransportRequest { + + private final HttpServletRequest request; + + /** + * Constructs a new instance of the HttpTransportRequest with the given + * HttpServletRequest. + */ + public HttpTransportRequest(HttpServletRequest request) { + Assert.notNull(request, "request is required"); + this.request = request; + } + + /** + * Returns the wrapped HttpServletRequest. + */ + public HttpServletRequest getHttpServletRequest() { + return request; + } + + public Iterator getHeaders(String name) { + return new EnumerationIterator(request.getHeaders(name)); + } + + public InputStream getInputStream() throws IOException { + return request.getInputStream(); + } + + public Iterator getHeaderNames() { + return new EnumerationIterator(request.getHeaderNames()); + } + + public String getUrl() { + StringBuffer url = new StringBuffer(request.getScheme()); + url.append("://").append(request.getServerName()).append(':').append(request.getServerPort()); + url.append(request.getRequestURI()); + return url.toString(); + } + + /** + * Private static class that adapts a header enumeration provided by the HttpServletRequest and provides it as an + * iterator. + */ + private static class EnumerationIterator implements Iterator { + + private final Enumeration enumeration; + + public EnumerationIterator(Enumeration enumeration) { + this.enumeration = enumeration; + } + + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + + public Object next() { + return enumeration.nextElement(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/transport/http/HttpTransportResponse.java b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportResponse.java new file mode 100644 index 00000000..64ce7f99 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportResponse.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport.http; + +import java.io.IOException; +import java.io.OutputStream; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.ws.transport.TransportResponse; + +/** + * HTTP-specific implementation of the TransportResponse interface. Exposes the + * HttpServletResponse + * + * @author Arjen Poutsma + */ +public class HttpTransportResponse implements TransportResponse { + + private final HttpServletResponse response; + + /** + * Constructs a new instance of the HttpTransportResponse with the given + * HttpServletResponse. + */ + public HttpTransportResponse(HttpServletResponse response) { + this.response = response; + } + + /** + * Returns the wrapped HttpServletResponse. + */ + public HttpServletResponse getHttpServletResponse() { + return response; + } + + public void addHeader(String name, String value) { + response.addHeader(name, value); + } + + public OutputStream getOutputStream() throws IOException { + return response.getOutputStream(); + } +} diff --git a/core/src/main/java/org/springframework/ws/transport/http/MessageEndpointHandlerAdapter.java b/core/src/main/java/org/springframework/ws/transport/http/MessageEndpointHandlerAdapter.java new file mode 100644 index 00000000..8fe08703 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/http/MessageEndpointHandlerAdapter.java @@ -0,0 +1,111 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport.http; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.ws.NoEndpointFoundException; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.endpoint.MessageEndpoint; +import org.springframework.ws.soap.SoapMessage; + +/** + * Adapter to use the MessageEndpoint interface with the generic DispatcherServlet. Requires a + * MessageContextFactory, which is used to convert the incoming HttpServletRequest into a + * MessageContext, and passes that context to the mapped MessageEndpoint. If a response is + * created, that is sent via the HttpServletResponse. + *

+ * Note that the MessageDispatcher implements the MessageEndpoint interface, enabling this + * adapter to function as a gateway to further message handling logic. + * + * @author Arjen Poutsma + * @see org.springframework.ws.endpoint.MessageEndpoint + * @see MessageContextFactory + * @see org.springframework.ws.MessageDispatcher + */ +public class MessageEndpointHandlerAdapter implements HandlerAdapter, InitializingBean { + + private static final Log logger = LogFactory.getLog(MessageEndpointHandlerAdapter.class); + + private MessageContextFactory messageContextFactory; + + public void setMessageContextFactory(MessageContextFactory messageContextFactory) { + this.messageContextFactory = messageContextFactory; + } + + public long getLastModified(HttpServletRequest request, Object handler) { + return -1L; + } + + public ModelAndView handle(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, + Object handler) throws Exception { + if ("POST".equals(httpServletRequest.getMethod())) { + handlePost(httpServletRequest, (MessageEndpoint) handler, httpServletResponse); + return null; + } + else { + throw new ServletException("Request method '" + httpServletRequest.getMethod() + "' not supported"); + } + } + + public boolean supports(Object handler) { + return handler instanceof MessageEndpoint; + } + + public final void afterPropertiesSet() throws Exception { + Assert.notNull(messageContextFactory, "messageContextFactory is required"); + logger.info("Using message context factory " + messageContextFactory); + } + + private void handlePost(HttpServletRequest httpServletRequest, + MessageEndpoint endpoint, + HttpServletResponse httpServletResponse) throws Exception { + HttpTransportContext transportContext = new HttpTransportContext(httpServletRequest, httpServletResponse); + MessageContext messageContext = messageContextFactory.createContext(transportContext); + try { + endpoint.invoke(messageContext); + if (!messageContext.hasResponse()) { + httpServletResponse.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + else { + WebServiceMessage webServiceResponse = messageContext.getResponse(); + if (webServiceResponse instanceof SoapMessage && + ((SoapMessage) webServiceResponse).getSoapBody().hasFault()) { + httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + else { + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + } + messageContext.sendResponse(new HttpTransportResponse(httpServletResponse)); + } + } + catch (NoEndpointFoundException ex) { + httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } +} diff --git a/core/src/main/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapter.java b/core/src/main/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapter.java new file mode 100644 index 00000000..f576db74 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapter.java @@ -0,0 +1,208 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport.http; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.ws.endpoint.TransformerObjectSupport; +import org.springframework.ws.wsdl.WsdlDefinition; +import org.springframework.xml.xpath.XPathExpression; +import org.springframework.xml.xpath.XPathExpressionFactory; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * Adapter to use the WsdlDefinition interface with the generic DispatcherServlet. Reads the + * source from the mapped WsdlDefinition implementation, and writes that as result to the + * HttpServletResponse. + *

+ * If the property transformLocations is set to true, this adapter will change + * location attributes in the WSDL definition to reflect the URL of the incoming request. If the location + * field in the original WSDL is an absolute path, the scheme, hostname, and port will be changed. If the location is a + * relative path, the scheme, hostname, port, and context path will be prepended. This behavior can be customized by + * overriding the transformLocation() method. + *

+ * For instance, if the location attribute defined in the WSDL is http://localhost:8080/context/services/myService, + * and the request URI for the WSDL is http://example.com/context/myService.wsdl, the location will be + * changed to http://example.com/context/services/myService. + *

+ * If the location attribute defined in the WSDL is /services/myService, and the request URI for the WSDL + * is http://example.com:8080/context/myService.wsdl, the location will be changed to + * http://example.com:8080/context/services/myService. + *

+ * When transformLocations is enabled, all location attributes found in the WSDL definition + * are changed by default. This behavior can be customized by changing the locationExpression property, + * which is an XPath expression that matches the attributes to change. + * + * @author Arjen Poutsma + * @see WsdlDefinition + * @see #setTransformLocations(boolean) + * @see #setLocationExpression(String) + * @see #transformLocation(String, javax.servlet.http.HttpServletRequest) + */ +public class WsdlDefinitionHandlerAdapter extends TransformerObjectSupport implements HandlerAdapter, InitializingBean { + + /** + * Default XPath expression used for extracting all location attributes from the WSDL definition. + */ + public static final String DEFAULT_LOCATION_EXPRESSION = "//@location"; + + private static final String CONTENT_TYPE = "text/xml"; + + private static final Log logger = LogFactory.getLog(WsdlDefinitionHandlerAdapter.class); + + private Properties expressionNamespaces = new Properties(); + + private String locationExpression = DEFAULT_LOCATION_EXPRESSION; + + private XPathExpression locationXPathExpression; + + private boolean transformLocations = false; + + /** + * Sets the XPath expression used for extracting the location attributes from the WSDL 1.1 definition. + * Defaults to DEFAULT_LOCATION_EXPRESSION. + * + * @see #DEFAULT_LOCATION_EXPRESSION + */ + public void setLocationExpression(String locationExpression) { + this.locationExpression = locationExpression; + } + + /** + * Sets whether relative address locations in the WSDL are to be transformed using the request URI of the incoming + * HttpServletRequest. Defaults to false. + */ + public void setTransformLocations(boolean transformLocations) { + this.transformLocations = transformLocations; + } + + public long getLastModified(HttpServletRequest request, Object handler) { + return -1; + } + + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + if (!"GET".equals(request.getMethod())) { + throw new ServletException("Request method '" + request.getMethod() + "' not supported"); + } + response.setContentType(CONTENT_TYPE); + Transformer transformer = createTransformer(); + WsdlDefinition definition = (WsdlDefinition) handler; + Source definitionSource = definition.getSource(); + if (transformLocations) { + DOMResult domResult = new DOMResult(); + transformer.transform(definitionSource, domResult); + Document definitionDocument = (Document) domResult.getNode(); + transformLocations(definitionDocument, request); + definitionSource = new DOMSource(definitionDocument); + } + StreamResult responseResult = new StreamResult(response.getOutputStream()); + transformer.transform(definitionSource, responseResult); + return null; + } + + public boolean supports(Object handler) { + return handler instanceof WsdlDefinition; + } + + public void afterPropertiesSet() throws Exception { + locationXPathExpression = + XPathExpressionFactory.createXPathExpression(locationExpression, expressionNamespaces); + } + + /** + * Transform the given location string to reflect the given request. If the given location is a full url, the + * scheme, server name, and port are changed. If it is a relative url, the scheme, server name, and port are + * prepended. Can be overridden in subclasses to change this behavior. + *

+ * For instance, if the location attribute defined in the WSDL is http://localhost:8080/context/services/myService, + * and the request URI for the WSDL is http://example.com:8080/context/myService.wsdl, the location + * will be changed to http://example.com:8080/context/services/myService. + *

+ * If the location attribute defined in the WSDL is /services/myService, and the request URI for the + * WSDL is http://example.com:8080/context/myService.wsdl, the location will be changed to + * http://example.com:8080/context/services/myService. + *

+ * This method is only called when the transformLocations property is true. + */ + protected String transformLocation(String location, HttpServletRequest request) { + try { + if (location.startsWith("/")) { + // a relative path, prepend the context path + URL newLocation = new URL(request.getScheme(), + request.getServerName(), + request.getServerPort(), + request.getContextPath() + location); + return newLocation.toString(); + } + else { + // a full url + URL oldLocation = new URL(location); + URL newLocation = new URL(request.getScheme(), + request.getServerName(), + request.getServerPort(), + oldLocation.getFile()); + return newLocation.toString(); + } + } + catch (MalformedURLException e) { + return location; + // fall though to the default return value + } + } + + /** + * Transforms all location attributes to reflect the server name given HttpServletRequest. + * Determines the suitable attributes by evaluating the defined XPath expression, and delegates to + * transformLocation to do the transformation for all attributes that match. + *

+ * This method is only called when the transformLocations property is true. + * + * @see #setLocationExpression(String) + * @see #setTransformLocations(boolean) + * @see #transformLocation(String, javax.servlet.http.HttpServletRequest) + */ + protected void transformLocations(Document definitionDocument, HttpServletRequest request) throws Exception { + Node[] locationNodes = locationXPathExpression.evaluateAsNodes(definitionDocument); + for (int i = 0; i < locationNodes.length; i++) { + Attr location = (Attr) locationNodes[i]; + if (location != null && StringUtils.hasLength(location.getValue())) { + String newLocation = transformLocation(location.getValue(), request); + logger.debug("Transforming [" + location.getValue() + "] to [" + newLocation + "]"); + location.setValue(newLocation); + } + } + } +} diff --git a/core/src/main/java/org/springframework/ws/transport/http/package.html b/core/src/main/java/org/springframework/ws/transport/http/package.html new file mode 100644 index 00000000..bd11fcac --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/http/package.html @@ -0,0 +1,5 @@ + + +Package providing support for handling messages via HTTP. + + diff --git a/core/src/main/java/org/springframework/ws/transport/package.html b/core/src/main/java/org/springframework/ws/transport/package.html new file mode 100644 index 00000000..0252161c --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/package.html @@ -0,0 +1,5 @@ + + +Contains the TransportRequest and TransportResponse interfaces. + + diff --git a/core/src/main/java/org/springframework/ws/wsdl/WsdlDefinition.java b/core/src/main/java/org/springframework/ws/wsdl/WsdlDefinition.java new file mode 100644 index 00000000..c5b82668 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/WsdlDefinition.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.wsdl; + +import javax.xml.transform.Source; + +/** + * Represents an abstraction for WSDL definitions. + * + * @author Arjen Poutsma + */ +public interface WsdlDefinition { + + /** + * Returns the Source of the definition. + * + * @return the Source of this WSDL definition + */ + Source getSource(); + +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/WsdlDefinitionException.java b/core/src/main/java/org/springframework/ws/wsdl/WsdlDefinitionException.java new file mode 100644 index 00000000..f8e3b2c1 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/WsdlDefinitionException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.wsdl; + +import org.springframework.ws.WebServiceException; + +/** + * Base class for all WSDL definition exceptions. + * + * @author Arjen Poutsma + */ +public class WsdlDefinitionException extends WebServiceException { + + public WsdlDefinitionException(String reason) { + super(reason); + } + + public WsdlDefinitionException(String reason, Throwable throwable) { + super(reason, throwable); + } +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/package.html b/core/src/main/java/org/springframework/ws/wsdl/package.html new file mode 100644 index 00000000..3cb79118 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/package.html @@ -0,0 +1,6 @@ + + +Provides the WSDL functionality of the Spring Web Services framework. Contains the WsdlDefinition and related +interfaces. + + diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11Definition.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11Definition.java new file mode 100644 index 00000000..d58d37ca --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11Definition.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.wsdl.wsdl11; + +import java.io.IOException; +import javax.xml.transform.Source; +import javax.xml.transform.sax.SAXSource; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.ws.wsdl.WsdlDefinitionException; +import org.springframework.xml.sax.SaxUtils; + +/** + * Default implementation of the Wsdl11Definition interface. Allows a WSDL to be set by the + * wsdl property. + * + * @author Arjen Poutsma + * @see #setWsdl(org.springframework.core.io.Resource) + */ +public class SimpleWsdl11Definition implements Wsdl11Definition, InitializingBean { + + private Resource wsdlResource; + + public void setWsdl(Resource wsdlResource) { + this.wsdlResource = wsdlResource; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(wsdlResource, "wsdlResource is required"); + Assert.isTrue(wsdlResource.exists(), "wsdl \"" + wsdlResource + "\" does not exit"); + } + + public Source getSource() { + try { + return new SAXSource(SaxUtils.createInputSource(wsdlResource)); + } + catch (IOException ex) { + throw new WsdlDefinitionException("Could not create source from " + wsdlResource, ex); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/Wsdl11Definition.java b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/Wsdl11Definition.java new file mode 100644 index 00000000..08f3ac70 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/Wsdl11Definition.java @@ -0,0 +1,26 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.wsdl.wsdl11; + +import org.springframework.ws.wsdl.WsdlDefinition; + +/** + * @author Arjen Poutsma + */ +public interface Wsdl11Definition extends WsdlDefinition { + +} diff --git a/core/src/main/java/org/springframework/ws/wsdl/wsdl11/package.html b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/package.html new file mode 100644 index 00000000..9774cc37 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/wsdl/wsdl11/package.html @@ -0,0 +1,5 @@ + + +Contains interfaces specific to WSDL 1.1. + + diff --git a/core/src/main/resources/org/springframework/ws/MessageDispatcher.properties b/core/src/main/resources/org/springframework/ws/MessageDispatcher.properties new file mode 100644 index 00000000..7e807c98 --- /dev/null +++ b/core/src/main/resources/org/springframework/ws/MessageDispatcher.properties @@ -0,0 +1,5 @@ +# Default implementation classes for MessageDispatcher's strategy interfaces. +# Used as fallback when no matching beans are found in the MessageDispatcher context. +# Not meant to be customized by application developers +org.springframework.ws.EndpointAdapter=org.springframework.ws.endpoint.MessageEndpointAdapter,\ + org.springframework.ws.endpoint.PayloadEndpointAdapter \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/AbstractWebServiceMessageTestCase.java b/core/src/test/java/org/springframework/ws/AbstractWebServiceMessageTestCase.java new file mode 100644 index 00000000..9645a1f3 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/AbstractWebServiceMessageTestCase.java @@ -0,0 +1,163 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.custommonkey.xmlunit.XMLUnit; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.util.FileCopyUtils; +import org.springframework.xml.sax.SaxUtils; +import org.springframework.xml.transform.StaxResult; +import org.springframework.xml.transform.StaxSource; +import org.springframework.xml.transform.StringResult; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +public abstract class AbstractWebServiceMessageTestCase extends XMLTestCase { + + protected Transformer transformer; + + protected WebServiceMessage webServiceMessage; + + private Resource payload; + + private String getExpectedString() throws IOException { + StringWriter expectedWriter = new StringWriter(); + FileCopyUtils.copy(new InputStreamReader(payload.getInputStream(), "UTF-8"), expectedWriter); + return expectedWriter.toString(); + } + + protected final void setUp() throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + webServiceMessage = createWebServiceMessage(); + payload = new ClassPathResource("payload.xml", AbstractWebServiceMessageTestCase.class); + XMLUnit.setIgnoreWhitespace(true); + } + + public void testDomPayload() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document payloadDocument = documentBuilder.parse(SaxUtils.createInputSource(payload)); + DOMSource domSource = new DOMSource(payloadDocument); + transformer.transform(domSource, webServiceMessage.getPayloadResult()); + Document resultDocument = documentBuilder.newDocument(); + DOMResult domResult = new DOMResult(resultDocument); + transformer.transform(webServiceMessage.getPayloadSource(), domResult); + assertXMLEqual(payloadDocument, resultDocument); + validateMessage(); + } + + public void testEventReaderPayload() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader = inputFactory.createXMLEventReader(payload.getInputStream()); + StaxSource staxSource = new StaxSource(eventReader); + transformer.transform(staxSource, webServiceMessage.getPayloadResult()); + StringWriter stringWriter = new StringWriter(); + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(stringWriter); + StaxResult staxResult = new StaxResult(eventWriter); + transformer.transform(webServiceMessage.getPayloadSource(), staxResult); + eventWriter.flush(); + assertXMLEqual(getExpectedString(), stringWriter.toString()); + validateMessage(); + } + + public void testReaderPayload() throws Exception { + Reader reader = new InputStreamReader(payload.getInputStream(), "UTF-8"); + StreamSource streamSource = new StreamSource(reader, payload.getURL().toString()); + transformer.transform(streamSource, webServiceMessage.getPayloadResult()); + StringWriter resultWriter = new StringWriter(); + StreamResult streamResult = new StreamResult(resultWriter); + transformer.transform(webServiceMessage.getPayloadSource(), streamResult); + assertXMLEqual(getExpectedString(), resultWriter.toString()); + } + + public void testSaxPayload() throws Exception { + SAXSource saxSource = new SAXSource(SaxUtils.createInputSource(payload)); + transformer.transform(saxSource, webServiceMessage.getPayloadResult()); + StringResult stringResult = new StringResult(); + transformer.transform(webServiceMessage.getPayloadSource(), stringResult); + assertXMLEqual(getExpectedString(), stringResult.toString()); + validateMessage(); + } + + public void testStreamPayload() throws Exception { + StreamSource streamSource = new StreamSource(payload.getInputStream(), payload.getURL().toString()); + transformer.transform(streamSource, webServiceMessage.getPayloadResult()); + ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); + StreamResult streamResult = new StreamResult(resultStream); + transformer.transform(webServiceMessage.getPayloadSource(), streamResult); + ByteArrayOutputStream expectedStream = new ByteArrayOutputStream(); + FileCopyUtils.copy(payload.getInputStream(), expectedStream); + assertXMLEqual(expectedStream.toString("UTF-8"), resultStream.toString("UTF-8")); + validateMessage(); + } + + public void testStreamReaderPayload() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(payload.getInputStream()); + StaxSource staxSource = new StaxSource(streamReader); + transformer.transform(staxSource, webServiceMessage.getPayloadResult()); + StringWriter stringWriter = new StringWriter(); + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(stringWriter); + StaxResult staxResult = new StaxResult(streamWriter); + transformer.transform(webServiceMessage.getPayloadSource(), staxResult); + streamWriter.flush(); + assertXMLEqual(getExpectedString(), stringWriter.toString()); + validateMessage(); + } + + private void validateMessage() throws Exception { + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + xmlReader.setContentHandler(new DefaultHandler()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + webServiceMessage.writeTo(os); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + xmlReader.parse(new InputSource(is)); + } + + protected abstract WebServiceMessage createWebServiceMessage() throws Exception; +} diff --git a/core/src/test/java/org/springframework/ws/MessageDispatcherTest.java b/core/src/test/java/org/springframework/ws/MessageDispatcherTest.java new file mode 100644 index 00000000..a621fcb1 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/MessageDispatcherTest.java @@ -0,0 +1,275 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws; + +import java.util.Collections; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.mock.MockMessageContext; + +public class MessageDispatcherTest extends TestCase { + + private MessageDispatcher dispatcher; + + private MockMessageContext messageContext; + + protected void setUp() throws Exception { + dispatcher = new MessageDispatcher(); + dispatcher.setApplicationContext(new StaticApplicationContext()); + dispatcher.initApplicationContext(); + messageContext = new MockMessageContext(); + } + + public void testGetEndpoint() throws Exception { + MockControl mappingControl = MockControl.createControl(EndpointMapping.class); + EndpointMapping mappingMock = (EndpointMapping) mappingControl.getMock(); + dispatcher.setEndpointMappings(Collections.singletonList(mappingMock)); + + EndpointInvocationChain chain = new EndpointInvocationChain(new Object()); + + mappingControl.expectAndReturn(mappingMock.getEndpoint(messageContext), chain); + + mappingControl.replay(); + EndpointInvocationChain result = dispatcher.getEndpoint(messageContext); + mappingControl.verify(); + assertEquals("getEndpoint returns invalid EndpointInvocationChain", chain, result); + } + + public void testGetEndpointAdapterSupportedEndpoint() throws Exception { + MockControl adapterControl = MockControl.createControl(EndpointAdapter.class); + EndpointAdapter adapterMock = (EndpointAdapter) adapterControl.getMock(); + dispatcher.setEndpointAdapters(Collections.singletonList(adapterMock)); + + Object endpoint = new Object(); + adapterControl.expectAndReturn(adapterMock.supports(endpoint), true); + adapterControl.replay(); + EndpointAdapter result = dispatcher.getEndpointAdapter(endpoint); + adapterControl.verify(); + assertEquals("getEnpointAdapter returns invalid EndpointAdapter", adapterMock, result); + } + + public void testGetEndpointAdapterUnsupportedEndpoint() throws Exception { + MockControl adapterControl = MockControl.createControl(EndpointAdapter.class); + EndpointAdapter adapterMock = (EndpointAdapter) adapterControl.getMock(); + dispatcher.setEndpointAdapters(Collections.singletonList(adapterMock)); + + Object endpoint = new Object(); + adapterControl.expectAndReturn(adapterMock.supports(endpoint), false); + adapterControl.replay(); + try { + dispatcher.getEndpointAdapter(endpoint); + fail("getEndpointAdapter does not throw IllegalStateException for unsupported endpoint"); + } + catch (IllegalStateException ex) { + // Expected + } + adapterControl.verify(); + } + + public void testProcessEndpointExceptionReturnsResponse() throws Exception { + + final Object endpoint = new Object(); + final Exception ex = new Exception(); + EndpointExceptionResolver resolver = new EndpointExceptionResolver() { + + public boolean resolveException(MessageContext givenMessageContext, + Object givenEndpoint, + Exception givenException) { + assertEquals("Invalid message context", messageContext, givenMessageContext); + assertEquals("Invalid endpoint", endpoint, givenEndpoint); + assertEquals("Invalid exception", ex, givenException); + givenMessageContext.getResponse(); + return true; + } + + }; + dispatcher.setEndpointExceptionResolvers(Collections.singletonList(resolver)); + + dispatcher.processEndpointException(messageContext, endpoint, ex); + assertNotNull("processEndpointException sets no response", messageContext.getResponse()); + } + + public void testProcessUnsupportedEndpointException() throws Exception { + MockControl resolverControl = MockControl.createControl(EndpointExceptionResolver.class); + EndpointExceptionResolver resolverMock = (EndpointExceptionResolver) resolverControl.getMock(); + dispatcher.setEndpointExceptionResolvers(Collections.singletonList(resolverMock)); + + Object endpoint = new Object(); + Exception ex = new Exception(); + + resolverControl.expectAndReturn(resolverMock.resolveException(messageContext, endpoint, ex), false); + + resolverControl.replay(); + try { + dispatcher.processEndpointException(messageContext, endpoint, ex); + } + catch (Exception result) { + assertEquals("processEndpointException throws invalid exception", ex, result); + } + resolverControl.verify(); + } + + public void testNormalFlow() throws Exception { + MockControl adapterControl = MockControl.createControl(EndpointAdapter.class); + EndpointAdapter adapterMock = (EndpointAdapter) adapterControl.getMock(); + dispatcher.setEndpointAdapters(Collections.singletonList(adapterMock)); + + Object endpoint = new Object(); + adapterControl.expectAndReturn(adapterMock.supports(endpoint), true); + + MockControl mappingControl = MockControl.createControl(EndpointMapping.class); + EndpointMapping mappingMock = (EndpointMapping) mappingControl.getMock(); + dispatcher.setEndpointMappings(Collections.singletonList(mappingMock)); + + MockControl interceptorControl = MockControl.createStrictControl(EndpointInterceptor.class); + EndpointInterceptor interceptorMock1 = (EndpointInterceptor) interceptorControl.getMock(); + EndpointInterceptor interceptorMock2 = (EndpointInterceptor) interceptorControl.getMock(); + + interceptorControl.expectAndReturn(interceptorMock1.handleRequest(messageContext, endpoint), true); + interceptorControl.expectAndReturn(interceptorMock2.handleRequest(messageContext, endpoint), true); + adapterMock.invoke(messageContext, endpoint); + interceptorControl.expectAndReturn(interceptorMock2.handleResponse(messageContext, endpoint), true); + interceptorControl.expectAndReturn(interceptorMock1.handleResponse(messageContext, endpoint), true); + + EndpointInvocationChain chain = + new EndpointInvocationChain(endpoint, new EndpointInterceptor[]{interceptorMock1, interceptorMock2}); + + mappingControl.expectAndReturn(mappingMock.getEndpoint(messageContext), chain); + + mappingControl.replay(); + interceptorControl.replay(); + adapterControl.replay(); + // response required for interceptor invocation + messageContext.getResponse(); + dispatcher.dispatch(messageContext); + + mappingControl.verify(); + interceptorControl.verify(); + adapterControl.verify(); + } + + public void testFlowNoResponse() throws Exception { + MockControl adapterControl = MockControl.createControl(EndpointAdapter.class); + EndpointAdapter adapterMock = (EndpointAdapter) adapterControl.getMock(); + dispatcher.setEndpointAdapters(Collections.singletonList(adapterMock)); + + Object endpoint = new Object(); + adapterControl.expectAndReturn(adapterMock.supports(endpoint), true); + + MockControl mappingControl = MockControl.createControl(EndpointMapping.class); + EndpointMapping mappingMock = (EndpointMapping) mappingControl.getMock(); + dispatcher.setEndpointMappings(Collections.singletonList(mappingMock)); + + MockControl interceptorControl = MockControl.createStrictControl(EndpointInterceptor.class); + EndpointInterceptor interceptorMock1 = (EndpointInterceptor) interceptorControl.getMock(); + EndpointInterceptor interceptorMock2 = (EndpointInterceptor) interceptorControl.getMock(); + + EndpointInvocationChain chain = + new EndpointInvocationChain(endpoint, new EndpointInterceptor[]{interceptorMock1, interceptorMock2}); + mappingControl.expectAndReturn(mappingMock.getEndpoint(messageContext), chain); + + interceptorControl.expectAndReturn(interceptorMock1.handleRequest(messageContext, endpoint), true); + interceptorControl.expectAndReturn(interceptorMock2.handleRequest(messageContext, endpoint), true); + adapterMock.invoke(messageContext, endpoint); + + mappingControl.replay(); + interceptorControl.replay(); + adapterControl.replay(); + + dispatcher.dispatch(messageContext); + + mappingControl.verify(); + interceptorControl.verify(); + adapterControl.verify(); + } + + public void testInterceptedRequestFlow() throws Exception { + MockControl adapterControl = MockControl.createControl(EndpointAdapter.class); + EndpointAdapter adapterMock = (EndpointAdapter) adapterControl.getMock(); + dispatcher.setEndpointAdapters(Collections.singletonList(adapterMock)); + + MockControl mappingControl = MockControl.createControl(EndpointMapping.class); + EndpointMapping mappingMock = (EndpointMapping) mappingControl.getMock(); + dispatcher.setEndpointMappings(Collections.singletonList(mappingMock)); + + MockControl interceptorControl = MockControl.createStrictControl(EndpointInterceptor.class); + EndpointInterceptor interceptorMock1 = (EndpointInterceptor) interceptorControl.getMock(); + EndpointInterceptor interceptorMock2 = (EndpointInterceptor) interceptorControl.getMock(); + + Object endpoint = new Object(); + interceptorControl.expectAndReturn(interceptorMock1.handleRequest(messageContext, endpoint), false); + interceptorControl.expectAndReturn(interceptorMock1.handleResponse(messageContext, endpoint), true); + + EndpointInvocationChain chain = + new EndpointInvocationChain(endpoint, new EndpointInterceptor[]{interceptorMock1, interceptorMock2}); + + mappingControl.expectAndReturn(mappingMock.getEndpoint(messageContext), chain); + + mappingControl.replay(); + interceptorControl.replay(); + adapterControl.replay(); + + // response required for interceptor invocation + messageContext.getResponse(); + + dispatcher.dispatch(messageContext); + + mappingControl.verify(); + interceptorControl.verify(); + adapterControl.verify(); + } + + public void testInterceptedResponseFlow() throws Exception { + MockControl adapterControl = MockControl.createControl(EndpointAdapter.class); + EndpointAdapter adapterMock = (EndpointAdapter) adapterControl.getMock(); + dispatcher.setEndpointAdapters(Collections.singletonList(adapterMock)); + + MockControl mappingControl = MockControl.createControl(EndpointMapping.class); + EndpointMapping mappingMock = (EndpointMapping) mappingControl.getMock(); + dispatcher.setEndpointMappings(Collections.singletonList(mappingMock)); + + MockControl interceptorControl = MockControl.createStrictControl(EndpointInterceptor.class); + EndpointInterceptor interceptorMock1 = (EndpointInterceptor) interceptorControl.getMock(); + EndpointInterceptor interceptorMock2 = (EndpointInterceptor) interceptorControl.getMock(); + + Object endpoint = new Object(); + interceptorControl.expectAndReturn(interceptorMock1.handleRequest(messageContext, endpoint), true); + interceptorControl.expectAndReturn(interceptorMock2.handleRequest(messageContext, endpoint), false); + interceptorControl.expectAndReturn(interceptorMock2.handleResponse(messageContext, endpoint), false); + + EndpointInvocationChain chain = + new EndpointInvocationChain(endpoint, new EndpointInterceptor[]{interceptorMock1, interceptorMock2}); + + mappingControl.expectAndReturn(mappingMock.getEndpoint(messageContext), chain); + + mappingControl.replay(); + interceptorControl.replay(); + adapterControl.replay(); + // response required for interceptor invocation + messageContext.getResponse(); + + dispatcher.dispatch(messageContext); + + mappingControl.verify(); + interceptorControl.verify(); + adapterControl.verify(); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/endpoint/AbstractEndpointTestCase.java b/core/src/test/java/org/springframework/ws/endpoint/AbstractEndpointTestCase.java new file mode 100644 index 00000000..df7afb21 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/AbstractEndpointTestCase.java @@ -0,0 +1,91 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +import org.springframework.xml.transform.StaxSource; + +public abstract class AbstractEndpointTestCase extends XMLTestCase { + + protected final static String NAMESPACE_URI = "http://springframework.org/ws"; + + protected final static String REQUEST_ELEMENT = "request"; + + protected final static String RESPONSE_ELEMENT = "response"; + + protected final static String REQUEST = "<" + REQUEST_ELEMENT + " xmlns=\"" + NAMESPACE_URI + "\"/>"; + + protected final static String RESPONSE = "<" + RESPONSE_ELEMENT + " xmlns=\"" + NAMESPACE_URI + "\"/>"; + + public void testDomSource() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document requestDocument = documentBuilder.parse(new InputSource(new StringReader(REQUEST))); + testSource(new DOMSource(requestDocument.getDocumentElement())); + } + + public void testSaxSource() throws Exception { + XMLReader reader = XMLReaderFactory.createXMLReader(); + InputSource inputSource = new InputSource(new StringReader(REQUEST)); + testSource(new SAXSource(reader, inputSource)); + } + + public void testStaxSourceEventReader() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(REQUEST)); + testSource(new StaxSource(eventReader)); + } + + public void testStaxSourceStreamReader() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(new StringReader(REQUEST)); + testSource(new StaxSource(streamReader)); + } + + public void testStreamSourceInputStream() throws Exception { + InputStream is = new ByteArrayInputStream(REQUEST.getBytes("UTF-8")); + testSource(new StreamSource(is)); + } + + public void testStreamSourceReader() throws Exception { + Reader reader = new StringReader(REQUEST); + testSource(new StreamSource(reader)); + } + + protected abstract void testSource(Source requestSource) throws Exception; +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/AbstractMessageEndpointTestCase.java b/core/src/test/java/org/springframework/ws/endpoint/AbstractMessageEndpointTestCase.java new file mode 100644 index 00000000..f3b9c619 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/AbstractMessageEndpointTestCase.java @@ -0,0 +1,52 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Source; + +import org.springframework.ws.mock.MockMessageContext; +import org.springframework.ws.mock.MockWebServiceMessage; +import org.springframework.xml.transform.StringSource; + +public abstract class AbstractMessageEndpointTestCase extends AbstractEndpointTestCase { + + private MessageEndpoint endpoint; + + protected final void setUp() throws Exception { + endpoint = createResponseEndpoint(); + } + + public void testNoResponse() throws Exception { + endpoint = createNoResponseEndpoint(); + StringSource requestSource = new StringSource(REQUEST); + MockMessageContext context = new MockMessageContext(new MockWebServiceMessage(requestSource)); + endpoint.invoke(context); + assertFalse("Response message created", context.hasResponse()); + } + + protected final void testSource(Source requestSource) throws Exception { + MockMessageContext context = new MockMessageContext(new MockWebServiceMessage(requestSource)); + endpoint.invoke(context); + assertTrue("No response message created", context.hasResponse()); + assertXMLEqual(RESPONSE, ((MockWebServiceMessage) context.getResponse()).getPayloadAsString()); + } + + protected abstract MessageEndpoint createNoResponseEndpoint(); + + protected abstract MessageEndpoint createResponseEndpoint(); + +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/AbstractPayloadEndpointTestCase.java b/core/src/test/java/org/springframework/ws/endpoint/AbstractPayloadEndpointTestCase.java new file mode 100644 index 00000000..a7aed7b9 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/AbstractPayloadEndpointTestCase.java @@ -0,0 +1,55 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public abstract class AbstractPayloadEndpointTestCase extends AbstractEndpointTestCase { + + private PayloadEndpoint endpoint; + + private Transformer transformer; + + protected final void setUp() throws Exception { + endpoint = createResponseEndpoint(); + transformer = TransformerFactory.newInstance().newTransformer(); + } + + public void testNoResponse() throws Exception { + endpoint = createNoResponseEndpoint(); + StringSource requestSource = new StringSource(REQUEST); + Source resultSource = endpoint.invoke(requestSource); + assertNull("Response source returned", resultSource); + } + + protected final void testSource(Source requestSource) throws Exception { + Source responseSource = endpoint.invoke(requestSource); + assertNotNull("No response source returned", responseSource); + StringResult result = new StringResult(); + transformer.transform(responseSource, result); + assertXMLEqual(RESPONSE, result.toString()); + } + + protected abstract PayloadEndpoint createNoResponseEndpoint() throws Exception; + + protected abstract PayloadEndpoint createResponseEndpoint() throws Exception; +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/Dom4jPayloadEndpointTest.java b/core/src/test/java/org/springframework/ws/endpoint/Dom4jPayloadEndpointTest.java new file mode 100644 index 00000000..64e347b3 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/Dom4jPayloadEndpointTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import org.dom4j.Document; +import org.dom4j.Element; + +public class Dom4jPayloadEndpointTest extends AbstractPayloadEndpointTestCase { + + protected PayloadEndpoint createResponseEndpoint() { + return new AbstractDom4jPayloadEndpoint() { + + protected Element invokeInternal(Element requestElement, Document responseDocument) throws Exception { + assertNotNull("No requestElement passed", requestElement); + assertNotNull("No responseDocument passed", responseDocument); + assertEquals("Invalid request element", REQUEST_ELEMENT, requestElement.getName()); + assertEquals("Invalid request element", NAMESPACE_URI, requestElement.getNamespaceURI()); + return responseDocument.addElement(RESPONSE_ELEMENT, NAMESPACE_URI); + } + }; + } + + protected PayloadEndpoint createNoResponseEndpoint() throws Exception { + return new AbstractDom4jPayloadEndpoint() { + + protected Element invokeInternal(Element requestElement, Document responseDocument) throws Exception { + return null; + } + }; + } + + +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/DomPayloadEndpointTest.java b/core/src/test/java/org/springframework/ws/endpoint/DomPayloadEndpointTest.java new file mode 100644 index 00000000..f9303fa1 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/DomPayloadEndpointTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class DomPayloadEndpointTest extends AbstractPayloadEndpointTestCase { + + protected PayloadEndpoint createNoResponseEndpoint() throws Exception { + return new AbstractDomPayloadEndpoint() { + + protected Element invokeInternal(Element requestElement, Document document) throws Exception { + return null; + } + }; + } + + protected PayloadEndpoint createResponseEndpoint() throws Exception { + return new AbstractDomPayloadEndpoint() { + + protected Element invokeInternal(Element requestElement, Document responseDocument) throws Exception { + assertNotNull("No requestElement passed", requestElement); + assertNotNull("No responseDocument passed", responseDocument); + assertEquals("Invalid request element", REQUEST_ELEMENT, requestElement.getLocalName()); + assertEquals("Invalid request element", NAMESPACE_URI, requestElement.getNamespaceURI()); + return responseDocument.createElementNS(NAMESPACE_URI, RESPONSE_ELEMENT); + } + }; + } + + +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/JDomPayloadEndpointTest.java b/core/src/test/java/org/springframework/ws/endpoint/JDomPayloadEndpointTest.java new file mode 100644 index 00000000..5ff35a00 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/JDomPayloadEndpointTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import org.jdom.Element; +import org.jdom.Namespace; + +public class JDomPayloadEndpointTest extends AbstractPayloadEndpointTestCase { + + protected PayloadEndpoint createNoResponseEndpoint() throws Exception { + return new AbstractJDomPayloadEndpoint() { + + protected Element invokeInternal(Element requestElement) throws Exception { + return null; + } + }; + } + + protected PayloadEndpoint createResponseEndpoint() throws Exception { + return new AbstractJDomPayloadEndpoint() { + + protected Element invokeInternal(Element requestElement) throws Exception { + assertNotNull("No requestElement passed", requestElement); + assertEquals("Invalid request element", REQUEST_ELEMENT, requestElement.getName()); + assertEquals("Invalid request element", NAMESPACE_URI, requestElement.getNamespaceURI()); + return new Element(RESPONSE_ELEMENT, Namespace.getNamespace("tns", NAMESPACE_URI)); + } + }; + } +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/MarshallingPayloadEndpointTest.java b/core/src/test/java/org/springframework/ws/endpoint/MarshallingPayloadEndpointTest.java new file mode 100644 index 00000000..188f9811 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/MarshallingPayloadEndpointTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.custommonkey.xmlunit.XMLTestCase; + +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.Unmarshaller; +import org.springframework.oxm.XmlMappingException; +import org.springframework.ws.mock.MockMessageContext; +import org.springframework.ws.mock.MockWebServiceMessage; + +public class MarshallingPayloadEndpointTest extends XMLTestCase { + + public void testInvoke() throws Exception { + MockWebServiceMessage request = new MockWebServiceMessage(""); + final Transformer transformer = TransformerFactory.newInstance().newTransformer(); + + Unmarshaller unmarshaller = new Unmarshaller() { + public Object unmarshal(Source source) throws XmlMappingException { + try { + StringWriter writer = new StringWriter(); + transformer.transform(source, new StreamResult(writer)); + assertXMLEqual("Invalid source", "", writer.toString()); + return new Long(42); + } + catch (Exception e) { + fail(e.getMessage()); + return null; + } + } + }; + Marshaller marshaller = new Marshaller() { + public void marshal(Object graph, Result result) throws XmlMappingException { + assertEquals("Invalid graph", "result", graph); + try { + transformer.transform(new StreamSource(new StringReader("")), result); + } + catch (TransformerException e) { + fail(e.getMessage()); + } + } + }; + AbstractMarshallingPayloadEndpoint endpoint = new AbstractMarshallingPayloadEndpoint() { + protected Object invokeInternal(Object requestObject) throws Exception { + assertEquals("Invalid request object", new Long(42), requestObject); + return "result"; + } + }; + endpoint.setMarshaller(marshaller); + endpoint.setUnmarshaller(unmarshaller); + endpoint.afterPropertiesSet(); + + MockMessageContext context = new MockMessageContext(request); + endpoint.invoke(context); + MockWebServiceMessage response = (MockWebServiceMessage) context.getResponse(); + assertNotNull("Invalid result", response); + assertXMLEqual("Invalid response", "", response.getPayloadAsString()); + } + + public void testInvokeNullResponse() throws Exception { + MockWebServiceMessage request = new MockWebServiceMessage(""); + final Transformer transformer = TransformerFactory.newInstance().newTransformer(); + + Unmarshaller unmarshaller = new Unmarshaller() { + public Object unmarshal(Source source) throws XmlMappingException { + try { + StringWriter writer = new StringWriter(); + transformer.transform(source, new StreamResult(writer)); + assertXMLEqual("Invalid source", "", writer.toString()); + return new Long(42); + } + catch (Exception e) { + fail(e.getMessage()); + return null; + } + } + }; + Marshaller marshaller = new Marshaller() { + public void marshal(Object graph, Result result) throws XmlMappingException { + fail("marshal not expected"); + } + }; + AbstractMarshallingPayloadEndpoint endpoint = new AbstractMarshallingPayloadEndpoint() { + protected Object invokeInternal(Object requestObject) throws Exception { + assertEquals("Invalid request object", new Long(42), requestObject); + return null; + } + }; + endpoint.setMarshaller(marshaller); + endpoint.setUnmarshaller(unmarshaller); + endpoint.afterPropertiesSet(); + MockMessageContext context = new MockMessageContext(request); + endpoint.invoke(context); + assertFalse("Response created", context.hasResponse()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/MessageEndpointAdapterTest.java b/core/src/test/java/org/springframework/ws/endpoint/MessageEndpointAdapterTest.java new file mode 100644 index 00000000..17b3a17b --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/MessageEndpointAdapterTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import junit.framework.TestCase; +import org.easymock.MockControl; + +import org.springframework.ws.mock.MockMessageContext; + +public class MessageEndpointAdapterTest extends TestCase { + + private MessageEndpointAdapter adapter; + + private MockControl endpointControl; + + private MessageEndpoint endpointMock; + + protected void setUp() throws Exception { + adapter = new MessageEndpointAdapter(); + endpointControl = MockControl.createControl(MessageEndpoint.class); + endpointMock = (MessageEndpoint) endpointControl.getMock(); + } + + public void testSupports() throws Exception { + assertTrue("MessageEndpointAdapter does not support MessageEndpoint", adapter.supports(endpointMock)); + } + + public void testInvoke() throws Exception { + MockMessageContext context = new MockMessageContext(); + + endpointMock.invoke(context); + endpointControl.replay(); + adapter.invoke(context, endpointMock); + endpointControl.verify(); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/endpoint/PayloadEndpointAdapterTest.java b/core/src/test/java/org/springframework/ws/endpoint/PayloadEndpointAdapterTest.java new file mode 100644 index 00000000..ec707e11 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/PayloadEndpointAdapterTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.easymock.MockControl; + +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.mock.MockMessageContext; +import org.springframework.ws.mock.MockWebServiceMessage; + +public class PayloadEndpointAdapterTest extends XMLTestCase { + + private PayloadEndpointAdapter adapter; + + private PayloadEndpoint endpointMock; + + private MockControl endpointControl; + + protected void setUp() throws Exception { + adapter = new PayloadEndpointAdapter(); + endpointControl = MockControl.createControl(PayloadEndpoint.class); + endpointMock = (PayloadEndpoint) endpointControl.getMock(); + } + + public void testSupports() throws Exception { + assertTrue("PayloadEndpointAdapter does not support PayloadEndpoint", adapter.supports(endpointMock)); + } + + public void testInvoke() throws Exception { + MockWebServiceMessage request = new MockWebServiceMessage(""); + final Transformer transformer = TransformerFactory.newInstance().newTransformer(); + PayloadEndpoint endpoint = new PayloadEndpoint() { + public Source invoke(Source request) throws Exception { + StringWriter writer = new StringWriter(); + transformer.transform(request, new StreamResult(writer)); + assertXMLEqual("Invalid request", "", writer.toString()); + return new StreamSource(new StringReader("")); + } + }; + endpoint.invoke(request.getPayloadSource()); + MessageContext messageContext = new MockMessageContext(request); + adapter.invoke(messageContext, endpoint); + MockWebServiceMessage response = (MockWebServiceMessage) messageContext.getResponse(); + assertNotNull("No response created", response); + assertXMLEqual("Invalid payload", "", response.getPayloadAsString()); + } + + public void testInvokeNoResponse() throws Exception { + MessageContext messageContext = new MockMessageContext(); + endpointMock.invoke(messageContext.getRequest().getPayloadSource()); + endpointControl.setMatcher(MockControl.ALWAYS_MATCHER); + endpointControl.setReturnValue(null); + endpointControl.replay(); + adapter.invoke(messageContext, endpointMock); + endpointControl.verify(); + assertFalse("Response created", messageContext.hasResponse()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/SaxPayloadEndpointTest.java b/core/src/test/java/org/springframework/ws/endpoint/SaxPayloadEndpointTest.java new file mode 100644 index 00000000..3c8785f4 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/SaxPayloadEndpointTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.transform.Source; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.springframework.xml.transform.StringSource; + +public class SaxPayloadEndpointTest extends AbstractPayloadEndpointTestCase { + + protected PayloadEndpoint createNoResponseEndpoint() throws Exception { + return new AbstractSaxPayloadEndpoint() { + + protected Source getResponse(ContentHandler contentHandler) { + return null; + } + + protected ContentHandler createContentHandler() { + return new DefaultHandler(); + } + }; + } + + protected PayloadEndpoint createResponseEndpoint() throws Exception { + return new AbstractSaxPayloadEndpoint() { + + protected ContentHandler createContentHandler() { + return new TestContentHandler(); + } + + protected Source getResponse(ContentHandler contentHandler) { + return new StringSource(RESPONSE); + } + }; + } + + private static class TestContentHandler extends DefaultHandler { + + public void endElement(String uri, String localName, String qName) throws SAXException { + assertEquals("Invalid local name", REQUEST_ELEMENT, localName); + assertEquals("Invalid qName", REQUEST_ELEMENT, localName); + assertEquals("Invalid namespace", NAMESPACE_URI, uri); + } + + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + assertEquals("Invalid local name", REQUEST_ELEMENT, localName); + assertEquals("Invalid qName", REQUEST_ELEMENT, localName); + assertEquals("Invalid namespace", NAMESPACE_URI, uri); + } + } +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/StaxEventPayloadEndpointTest.java b/core/src/test/java/org/springframework/ws/endpoint/StaxEventPayloadEndpointTest.java new file mode 100644 index 00000000..8e65a28a --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/StaxEventPayloadEndpointTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import java.util.Collections; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Namespace; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; + +/** + * Test case for AbstractStaxEventPayloadEndpoint. + * + * @see AbstractStaxEventPayloadEndpoint + */ +public class StaxEventPayloadEndpointTest extends AbstractMessageEndpointTestCase { + + protected MessageEndpoint createNoResponseEndpoint() { + return new AbstractStaxEventPayloadEndpoint() { + protected void invokeInternal(XMLEventReader eventReader, + XMLEventConsumer eventWriter, + XMLEventFactory eventFactory) throws Exception { + } + }; + } + + protected MessageEndpoint createResponseEndpoint() { + return new AbstractStaxEventPayloadEndpoint() { + + protected void invokeInternal(XMLEventReader eventReader, + XMLEventConsumer eventWriter, + XMLEventFactory eventFactory) throws XMLStreamException { + assertNotNull("eventReader not given", eventReader); + assertNotNull("eventWriter not given", eventWriter); + assertNotNull("eventFactory not given", eventFactory); + assertTrue("eventReader has not next element", eventReader.hasNext()); + XMLEvent event = eventReader.nextEvent(); + assertTrue("Not a start document", event.isStartDocument()); + event = eventReader.nextEvent(); + assertTrue("Not a start element", event.isStartElement()); + assertEquals("Invalid start event local name", REQUEST_ELEMENT, + event.asStartElement().getName().getLocalPart()); + assertEquals("Invalid start event namespace", NAMESPACE_URI, + event.asStartElement().getName().getNamespaceURI()); + assertTrue("eventReader has not next element", eventReader.hasNext()); + event = eventReader.nextEvent(); + assertTrue("Not a end element", event.isEndElement()); + assertEquals("Invalid end event local name", REQUEST_ELEMENT, + event.asEndElement().getName().getLocalPart()); + assertEquals("Invalid end event namespace", NAMESPACE_URI, + event.asEndElement().getName().getNamespaceURI()); + Namespace namespace = eventFactory.createNamespace(NAMESPACE_URI); + QName name = new QName(NAMESPACE_URI, RESPONSE_ELEMENT); + eventWriter + .add(eventFactory.createStartElement(name, null, Collections.singleton(namespace).iterator())); + eventWriter.add(eventFactory.createEndElement(name, Collections.singleton(namespace).iterator())); + eventWriter.add(eventFactory.createEndDocument()); + } + }; + } + + +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/StaxStreamPayloadEndpointTest.java b/core/src/test/java/org/springframework/ws/endpoint/StaxStreamPayloadEndpointTest.java new file mode 100644 index 00000000..0c246af4 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/StaxStreamPayloadEndpointTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.mock.MockTransportRequest; +import org.springframework.ws.soap.axiom.AxiomSoapMessage; +import org.springframework.ws.soap.axiom.AxiomSoapMessageContext; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +/** + * Test case for AbstractStaxStreamPayloadEndpoint. + * + * @see AbstractStaxStreamPayloadEndpoint + */ +public class StaxStreamPayloadEndpointTest extends AbstractMessageEndpointTestCase { + + protected MessageEndpoint createNoResponseEndpoint() { + return new AbstractStaxStreamPayloadEndpoint() { + protected void invokeInternal(XMLStreamReader streamReader, XMLStreamWriter streamWriter) throws Exception { + } + }; + } + + protected MessageEndpoint createResponseEndpoint() { + return new AbstractStaxStreamPayloadEndpoint() { + protected void invokeInternal(XMLStreamReader streamReader, XMLStreamWriter streamWriter) throws Exception { + assertNotNull("No streamReader passed", streamReader); + assertNotNull("No streamWriter passed", streamReader); + assertEquals("Not a start element", XMLStreamConstants.START_ELEMENT, streamReader.next()); + assertEquals("Invalid start event local name", REQUEST_ELEMENT, streamReader.getLocalName()); + assertEquals("Invalid start event namespace", NAMESPACE_URI, streamReader.getNamespaceURI()); + assertTrue("streamReader has no next element", streamReader.hasNext()); + assertEquals("Not a end element", XMLStreamConstants.END_ELEMENT, streamReader.next()); + assertEquals("Invalid end event local name", REQUEST_ELEMENT, streamReader.getLocalName()); + assertEquals("Invalid end event namespace", NAMESPACE_URI, streamReader.getNamespaceURI()); + streamWriter.writeEmptyElement(NAMESPACE_URI, RESPONSE_ELEMENT); + streamWriter.flush(); + streamWriter.close(); + } + + protected XMLOutputFactory createXmlOutputFactory() { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + outputFactory.setProperty("javax.xml.stream.isRepairingNamespaces", Boolean.TRUE); + return outputFactory; + } + }; + } + + public void testAxiomResponse() throws Exception { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory); + transformer.transform(new StringSource(REQUEST), request.getPayloadResult()); + AxiomSoapMessageContext context = new AxiomSoapMessageContext(request, new MockTransportRequest()); + + MessageEndpoint endpoint = createResponseEndpoint(); + endpoint.invoke(context); + assertTrue("context has not response", context.hasResponse()); + StringResult stringResult = new StringResult(); + transformer.transform(context.getResponse().getPayloadSource(), stringResult); + assertXMLEqual(RESPONSE, stringResult.toString()); + } + + public void testAxiomNoResponse() throws Exception { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage request = new AxiomSoapMessage(axiomFactory); + transformer.transform(new StringSource(REQUEST), request.getPayloadResult()); + AxiomSoapMessageContext context = new AxiomSoapMessageContext(request, new MockTransportRequest()); + + MessageEndpoint endpoint = createNoResponseEndpoint(); + endpoint.invoke(context); + assertFalse("context has response", context.hasResponse()); + } + + +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/endpoint/XomPayloadEndpointTest.java b/core/src/test/java/org/springframework/ws/endpoint/XomPayloadEndpointTest.java new file mode 100644 index 00000000..b0863298 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/XomPayloadEndpointTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint; + +import nu.xom.Element; + +public class XomPayloadEndpointTest extends AbstractPayloadEndpointTestCase { + + protected PayloadEndpoint createNoResponseEndpoint() throws Exception { + return new AbstractXomPayloadEndpoint() { + + protected Element invokeInternal(Element requestElement) throws Exception { + return null; + } + }; + } + + protected PayloadEndpoint createResponseEndpoint() throws Exception { + return new AbstractXomPayloadEndpoint() { + + protected Element invokeInternal(Element requestElement) throws Exception { + assertNotNull("No requestElement passed", requestElement); + assertEquals("Invalid request element", REQUEST_ELEMENT, requestElement.getLocalName()); + assertEquals("Invalid request element", NAMESPACE_URI, requestElement.getNamespaceURI()); + return new Element(RESPONSE_ELEMENT, NAMESPACE_URI); + } + }; + } + + public void testStaxSourceEventReader() throws Exception { + // Unfortutately, XOM does not support these, hence the override here + } + + public void testStaxSourceStreamReader() throws Exception { + // Unfortutately, XOM does not support these, hence the override here + } +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadLoggingInterceptorTest.java b/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadLoggingInterceptorTest.java new file mode 100644 index 00000000..82e5e994 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadLoggingInterceptorTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint.interceptor; + +import junit.framework.TestCase; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.springframework.ws.mock.MockMessageContext; +import org.springframework.ws.mock.MockWebServiceMessage; + +public class PayloadLoggingInterceptorTest extends TestCase { + + private PayloadLoggingInterceptor interceptor; + + private CountingAppender appender; + + private MockMessageContext messageContext; + + protected void setUp() throws Exception { + interceptor = new PayloadLoggingInterceptor(); + appender = new CountingAppender(); + BasicConfigurator.configure(appender); + Logger.getRootLogger().setLevel(Level.DEBUG); + MockWebServiceMessage request = new MockWebServiceMessage(""); + messageContext = new MockMessageContext(request); + appender.reset(); + } + + public void testHandleRequestDisabled() throws Exception { + interceptor.setLogRequest(false); + int eventCount = appender.getCount(); + interceptor.handleRequest(messageContext, null); + assertEquals("PayloadLoggingInterceptor logged when disabled", appender.getCount(), eventCount); + } + + public void testHandleRequestEnabled() throws Exception { + int eventCount = appender.getCount(); + interceptor.handleRequest(messageContext, null); + assertTrue("PayloadLoggingInterceptor did not log", appender.getCount() > eventCount); + } + + public void testHandleResponseDisabled() throws Exception { + MockWebServiceMessage response = (MockWebServiceMessage) messageContext.getResponse(); + response.setPayload(""); + interceptor.setLogResponse(false); + int eventCount = appender.getCount(); + interceptor.handleResponse(messageContext, null); + assertEquals("PayloadLoggingInterceptor logged when disabled", appender.getCount(), eventCount); + } + + public void testHandleResponseEnabled() throws Exception { + MockWebServiceMessage response = (MockWebServiceMessage) messageContext.getResponse(); + response.setPayload(""); + int eventCount = appender.getCount(); + interceptor.handleResponse(messageContext, null); + assertTrue("PayloadLoggingInterceptor did not log", appender.getCount() > eventCount); + } + + private static class CountingAppender extends AppenderSkeleton { + + private int count; + + public int getCount() { + return count; + } + + public void reset() { + count = 0; + } + + protected void append(LoggingEvent loggingEvent) { + count++; + } + + public boolean requiresLayout() { + return false; + } + + public void close() { + } + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadTransformingInterceptorTest.java b/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadTransformingInterceptorTest.java new file mode 100644 index 00000000..836db781 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadTransformingInterceptorTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint.interceptor; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXSource; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.custommonkey.xmlunit.XMLUnit; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.ws.mock.MockMessageContext; +import org.springframework.ws.mock.MockWebServiceMessage; +import org.springframework.xml.sax.SaxUtils; +import org.springframework.xml.transform.StringResult; + +public class PayloadTransformingInterceptorTest extends XMLTestCase { + + private PayloadTransformingInterceptor interceptor; + + private Transformer transformer; + + private Resource input; + + private Resource output; + + private Resource xslt; + + protected void setUp() throws Exception { + interceptor = new PayloadTransformingInterceptor(); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + input = new ClassPathResource("transformInput.xml", getClass()); + output = new ClassPathResource("transformOutput.xml", getClass()); + xslt = new ClassPathResource("transformation.xslt", getClass()); + XMLUnit.setIgnoreWhitespace(true); + } + + public void testHandleRequest() throws Exception { + interceptor.setRequestXslt(xslt); + interceptor.afterPropertiesSet(); + MockWebServiceMessage request = new MockWebServiceMessage(input); + MockMessageContext context = new MockMessageContext(request); + + boolean result = interceptor.handleRequest(context, null); + assertTrue("Invalid interceptor result", result); + StringResult stringResult = new StringResult(); + transformer.transform(new SAXSource(SaxUtils.createInputSource(output)), stringResult); + assertXMLEqual(stringResult.toString(), request.getPayloadAsString()); + } + + public void testHandleRequestNoXslt() throws Exception { + interceptor.setResponseXslt(xslt); + interceptor.afterPropertiesSet(); + MockWebServiceMessage request = new MockWebServiceMessage(input); + MockMessageContext context = new MockMessageContext(request); + + boolean result = interceptor.handleRequest(context, null); + assertTrue("Invalid interceptor result", result); + StringResult stringResult = new StringResult(); + transformer.transform(new SAXSource(SaxUtils.createInputSource(input)), stringResult); + assertXMLEqual(stringResult.toString(), request.getPayloadAsString()); + } + + public void testHandleResponse() throws Exception { + interceptor.setResponseXslt(xslt); + interceptor.afterPropertiesSet(); + MockWebServiceMessage request = new MockWebServiceMessage(input); + MockMessageContext context = new MockMessageContext(request); + MockWebServiceMessage response = (MockWebServiceMessage) context.getResponse(); + response.setPayload(input); + + boolean result = interceptor.handleResponse(context, null); + assertTrue("Invalid interceptor result", result); + StringResult stringResult = new StringResult(); + transformer.transform(new SAXSource(SaxUtils.createInputSource(output)), stringResult); + assertXMLEqual(stringResult.toString(), response.getPayloadAsString()); + } + + public void testHandleResponseNoXslt() throws Exception { + interceptor.setRequestXslt(xslt); + interceptor.afterPropertiesSet(); + MockWebServiceMessage request = new MockWebServiceMessage(input); + MockMessageContext context = new MockMessageContext(request); + MockWebServiceMessage response = (MockWebServiceMessage) context.getResponse(); + response.setPayload(input); + + boolean result = interceptor.handleResponse(context, null); + assertTrue("Invalid interceptor result", result); + StringResult stringResult = new StringResult(); + transformer.transform(new SAXSource(SaxUtils.createInputSource(input)), stringResult); + assertXMLEqual(stringResult.toString(), response.getPayloadAsString()); + } + + public void testNoStylesheetsSet() throws Exception { + try { + interceptor.afterPropertiesSet(); + fail("Should have thrown an Exception"); + } + catch (IllegalArgumentException ex) { + } + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadValidatingInterceptorTest.java b/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadValidatingInterceptorTest.java new file mode 100644 index 00000000..e9cf703f --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/interceptor/PayloadValidatingInterceptorTest.java @@ -0,0 +1,237 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.endpoint.interceptor; + +import java.io.InputStream; +import java.util.Locale; +import javax.xml.XMLConstants; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPMessage; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.stream.StreamSource; + +import junit.framework.TestCase; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.ws.mock.MockMessageContext; +import org.springframework.ws.mock.MockTransportRequest; +import org.springframework.ws.mock.MockWebServiceMessage; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.saaj.SaajSoapMessageContext; +import org.springframework.ws.soap.saaj.support.SaajUtils; +import org.springframework.ws.soap.soap11.Soap11Fault; +import org.springframework.ws.soap.soap12.Soap12Fault; + +public class PayloadValidatingInterceptorTest extends TestCase { + + private PayloadValidatingInterceptor interceptor; + + private MockWebServiceMessage request; + + private MockMessageContext messageContext; + + protected void setUp() throws Exception { + interceptor = new PayloadValidatingInterceptor(); + interceptor.setSchema(new ClassPathResource("schema.xsd", getClass())); + interceptor.setValidateRequest(true); + interceptor.setValidateResponse(true); + interceptor.afterPropertiesSet(); + request = new MockWebServiceMessage(); + messageContext = new MockMessageContext(request); + } + + public void testHandleInvalidRequestSoap11() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage invalidMessage = messageFactory.createMessage(); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + InputStream inputStream = getClass().getResourceAsStream("invalidMessage.xml"); + transformer.transform(new StreamSource(inputStream), new DOMResult(invalidMessage.getSOAPBody())); + SaajSoapMessageContext context = + new SaajSoapMessageContext(invalidMessage, new MockTransportRequest(), messageFactory); + + boolean result = interceptor.handleRequest(context, null); + assertFalse("Invalid response from interceptor", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = context.getSoapResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", + SoapVersion.SOAP_11.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", + PayloadValidatingInterceptor.DEFAULT_FAULTSTRING_OR_REASON, + fault.getFaultString()); + assertNotNull("No Detail on fault", fault.getFaultDetail()); + } + + public void testHandleInvalidRequestSoap12() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage invalidMessage = messageFactory.createMessage(); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + InputStream inputStream = getClass().getResourceAsStream("invalidMessage.xml"); + transformer.transform(new StreamSource(inputStream), new DOMResult(invalidMessage.getSOAPBody())); + SaajSoapMessageContext context = + new SaajSoapMessageContext(invalidMessage, new MockTransportRequest(), messageFactory); + + boolean result = interceptor.handleRequest(context, null); + assertFalse("Invalid response from interceptor", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = context.getSoapResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap12Fault fault = (Soap12Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", + SoapVersion.SOAP_12.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", + PayloadValidatingInterceptor.DEFAULT_FAULTSTRING_OR_REASON, + fault.getFaultReasonText(Locale.ENGLISH)); + assertNotNull("No Detail on fault", fault.getFaultDetail()); + } + + public void testHandleInvalidRequestOverridenProperties() throws Exception { + String faultString = "fout"; + Locale locale = new Locale("nl"); + interceptor.setFaultStringOrReason(faultString); + interceptor.setFaultStringOrReasonLocale(locale); + interceptor.setAddValidationErrorDetail(false); + + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage invalidMessage = messageFactory.createMessage(); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + InputStream inputStream = getClass().getResourceAsStream("invalidMessage.xml"); + transformer.transform(new StreamSource(inputStream), new DOMResult(invalidMessage.getSOAPBody())); + SaajSoapMessageContext context = + new SaajSoapMessageContext(invalidMessage, new MockTransportRequest(), messageFactory); + + boolean result = interceptor.handleRequest(context, null); + assertFalse("Invalid response from interceptor", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = context.getSoapResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", + SoapVersion.SOAP_11.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", faultString, fault.getFaultString()); + assertEquals("Invalid fault string locale on fault", locale, fault.getFaultStringLocale()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testHandlerInvalidRequest() throws Exception { + request.setPayload(new ClassPathResource("invalidMessage.xml", getClass())); + boolean result = interceptor.handleRequest(messageContext, null); + assertFalse("Invalid response from interceptor", result); + } + + public void testHandleValidRequest() throws Exception { + request.setPayload(new ClassPathResource("validMessage.xml", getClass())); + boolean result = interceptor.handleRequest(messageContext, null); + assertTrue("Invalid response from interceptor", result); + assertFalse("Response set", messageContext.hasResponse()); + } + + public void testHandleInvalidResponse() throws Exception { + MockWebServiceMessage response = (MockWebServiceMessage) messageContext.getResponse(); + response.setPayload(new ClassPathResource("invalidMessage.xml", getClass())); + boolean result = interceptor.handleResponse(messageContext, null); + assertFalse("Invalid response from interceptor", result); + } + + public void testHandleValidResponse() throws Exception { + MockWebServiceMessage response = (MockWebServiceMessage) messageContext.getResponse(); + response.setPayload(new ClassPathResource("validMessage.xml", getClass())); + boolean result = interceptor.handleResponse(messageContext, null); + assertTrue("Invalid response from interceptor", result); + } + + public void testNamespacesInType() throws Exception { + // Make sure we use Xerces for this testcase: the JAXP implementation used internally by JDK 1.5 has a bug + // See http://opensource.atlassian.com/projects/spring/browse/SWS-35 + System.setProperty("javax.xml.validation.SchemaFactory:" + XMLConstants.W3C_XML_SCHEMA_NS_URI, + "org.apache.xerces.jaxp.validation.XMLSchemaFactory"); + try { + interceptor.setSchema(new ClassPathResource("schema2.xsd", PayloadValidatingInterceptorTest.class)); + interceptor.afterPropertiesSet(); + MessageFactory messageFactory = MessageFactory.newInstance(); + SOAPMessage saajMessage = + SaajUtils.loadMessage(new ClassPathResource("validSoapMessage.xml", getClass()), messageFactory); + SaajSoapMessageContext soapContext = + new SaajSoapMessageContext(saajMessage, new MockTransportRequest(), messageFactory); + boolean result = interceptor.handleRequest(soapContext, null); + assertTrue("Invalid response from interceptor", result); + assertFalse("Response set", soapContext.hasResponse()); + } + finally { + // Reset the property + System.setProperty("javax.xml.validation.SchemaFactory:" + XMLConstants.W3C_XML_SCHEMA_NS_URI, ""); + } + } + + public void testNonExistingSchema() throws Exception { + try { + interceptor.setSchema(new ClassPathResource("invalid")); + interceptor.afterPropertiesSet(); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException ex) { + // expected + } + } + + public void testHandlerInvalidRequestMultipleSchemas() throws Exception { + interceptor.setSchemas(new Resource[]{new ClassPathResource("productSchema.xsd", getClass()), + new ClassPathResource("sizeSchema.xsd", getClass())}); + interceptor.afterPropertiesSet(); + request.setPayload(new ClassPathResource("invalidMessage.xml", getClass())); + boolean result = interceptor.handleRequest(messageContext, null); + assertFalse("Invalid response from interceptor", result); + } + + public void testHandleValidRequestMultipleSchemas() throws Exception { + interceptor.setSchemas(new Resource[]{new ClassPathResource("productSchema.xsd", getClass()), + new ClassPathResource("sizeSchema.xsd", getClass())}); + interceptor.afterPropertiesSet(); + request.setPayload(new ClassPathResource("validMessage.xml", getClass())); + boolean result = interceptor.handleRequest(messageContext, null); + assertTrue("Invalid response from interceptor", result); + assertFalse("Response set", messageContext.hasResponse()); + } + + public void testHandleInvalidResponseMultipleSchemas() throws Exception { + interceptor.setSchemas(new Resource[]{new ClassPathResource("productSchema.xsd", getClass()), + new ClassPathResource("sizeSchema.xsd", getClass())}); + interceptor.afterPropertiesSet(); + MockWebServiceMessage response = (MockWebServiceMessage) messageContext.getResponse(); + response.setPayload(new ClassPathResource("invalidMessage.xml", getClass())); + boolean result = interceptor.handleResponse(messageContext, null); + assertFalse("Invalid response from interceptor", result); + } + + public void testHandleValidResponseMultipleSchemas() throws Exception { + interceptor.setSchemas(new Resource[]{new ClassPathResource("productSchema.xsd", getClass()), + new ClassPathResource("sizeSchema.xsd", getClass())}); + interceptor.afterPropertiesSet(); + MockWebServiceMessage response = (MockWebServiceMessage) messageContext.getResponse(); + response.setPayload(new ClassPathResource("validMessage.xml", getClass())); + boolean result = interceptor.handleResponse(messageContext, null); + assertTrue("Invalid response from interceptor", result); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/endpoint/mapping/EndpointMappingTest.java b/core/src/test/java/org/springframework/ws/endpoint/mapping/EndpointMappingTest.java new file mode 100644 index 00000000..dd0c29ca --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/mapping/EndpointMappingTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint.mapping; + +import junit.framework.TestCase; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.ws.EndpointInterceptor; +import org.springframework.ws.EndpointInvocationChain; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.endpoint.interceptor.EndpointInterceptorAdapter; +import org.springframework.ws.mock.MockMessageContext; + +public class EndpointMappingTest extends TestCase { + + public void testDefaultEndpoint() throws Exception { + final MockMessageContext context = new MockMessageContext(); + Object defaultEndpoint = new Object(); + AbstractEndpointMapping mapping = new AbstractEndpointMapping() { + protected Object getEndpointInternal(MessageContext givenRequest) throws Exception { + assertEquals("Invalid request passed", context, givenRequest); + return null; + } + }; + mapping.setDefaultEndpoint(defaultEndpoint); + + EndpointInvocationChain result = mapping.getEndpoint(context); + assertNotNull("No EndpointInvocatioChain returned", result); + assertEquals("Default Endpoint not returned", defaultEndpoint, result.getEndpoint()); + } + + public void testEndpoint() throws Exception { + final MockMessageContext context = new MockMessageContext(); + final Object endpoint = new Object(); + AbstractEndpointMapping mapping = new AbstractEndpointMapping() { + protected Object getEndpointInternal(MessageContext givenRequest) throws Exception { + assertEquals("Invalid request passed", context, givenRequest); + return endpoint; + } + }; + + EndpointInvocationChain result = mapping.getEndpoint(context); + assertNotNull("No EndpointInvocatioChain returned", result); + assertEquals("Unexpected Endpoint returned", endpoint, result.getEndpoint()); + } + + public void testEndpointInterceptors() throws Exception { + final MockMessageContext context = new MockMessageContext(); + final Object endpoint = new Object(); + EndpointInterceptor interceptor = new EndpointInterceptorAdapter(); + AbstractEndpointMapping mapping = new AbstractEndpointMapping() { + protected Object getEndpointInternal(MessageContext givenRequest) throws Exception { + assertEquals("Invalid request passed", context, givenRequest); + return endpoint; + } + }; + mapping.setInterceptors(new EndpointInterceptor[]{interceptor}); + EndpointInvocationChain result = mapping.getEndpoint(context); + assertEquals("Unexpected amount of EndpointInterceptors returned", 1, result.getInterceptors().length); + assertEquals("Unexpected EndpointInterceptor returned", interceptor, result.getInterceptors()[0]); + } + + public void testEndpointBeanName() throws Exception { + final MockMessageContext context = new MockMessageContext(); + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.registerSingleton("endpoint", Object.class); + + AbstractEndpointMapping mapping = new AbstractEndpointMapping() { + + protected Object getEndpointInternal(MessageContext message) throws Exception { + assertEquals("Invalid request", context, message); + return "endpoint"; + } + }; + mapping.setApplicationContext(applicationContext); + + EndpointInvocationChain result = mapping.getEndpoint(context); + assertNotNull("No endpoint returned", result); + } + + public void testEndpointInvalidBeanName() throws Exception { + final MockMessageContext context = new MockMessageContext(); + + StaticApplicationContext applicationContext = new StaticApplicationContext(); + applicationContext.registerSingleton("endpoint", Object.class); + + AbstractEndpointMapping mapping = new AbstractEndpointMapping() { + + protected Object getEndpointInternal(MessageContext message) throws Exception { + assertEquals("Invalid request", context, message); + return "noSuchBean"; + } + }; + mapping.setApplicationContext(applicationContext); + + EndpointInvocationChain result = mapping.getEndpoint(context); + + assertNull("No endpoint returned", result); + } + + +} diff --git a/core/src/test/java/org/springframework/ws/endpoint/mapping/MapBasedSoapEndpointMappingTest.java b/core/src/test/java/org/springframework/ws/endpoint/mapping/MapBasedSoapEndpointMappingTest.java new file mode 100644 index 00000000..5239c198 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/mapping/MapBasedSoapEndpointMappingTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint.mapping; + +import java.util.Arrays; +import java.util.Map; +import java.util.TreeMap; + +import junit.framework.TestCase; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.ws.context.MessageContext; + +/** + * Test case for AbstractMapBasedEndpointMapping. + */ +public class MapBasedSoapEndpointMappingTest extends TestCase { + + public void testBeanNames() throws Exception { + StaticApplicationContext context = new StaticApplicationContext(); + context.registerSingleton("endpointMapping", MyMapBasedEndpointMapping.class); + context.registerSingleton("endpoint", Object.class); + context.registerAlias("endpoint", "alias"); + MyMapBasedEndpointMapping mapping = new MyMapBasedEndpointMapping(); + mapping.setValidKeys(new String[]{"endpoint", "alias"}); + + mapping.setRegisterBeanNames(true); + mapping.setApplicationContext(context); + + // try bean + mapping.setKey("endpoint"); + assertNotNull("No endpoint returned", mapping.getEndpointInternal(null)); + + // try alias + mapping.setKey("alias"); + assertNotNull("No endpoint returned", mapping.getEndpointInternal(null)); + + // try non-mapped values + mapping.setKey("endpointMapping"); + assertNull("Endpoint returned", mapping.getEndpointInternal(null)); + + } + + public void testDisabledBeanNames() throws Exception { + StaticApplicationContext context = new StaticApplicationContext(); + context.registerSingleton("endpoint", Object.class); + + MyMapBasedEndpointMapping mapping = new MyMapBasedEndpointMapping(); + + mapping.setRegisterBeanNames(true); + mapping.setApplicationContext(context); + + mapping.setKey("endpoint"); + assertNull("Endpoint returned", mapping.getEndpointInternal(null)); + } + + public void testEndpointMap() throws Exception { + Map endpointMap = new TreeMap(); + Object endpoint1 = new Object(); + Object endpoint2 = new Object(); + endpointMap.put("endpoint1", endpoint1); + endpointMap.put("endpoint2", endpoint2); + + MyMapBasedEndpointMapping mapping = new MyMapBasedEndpointMapping(); + mapping.setValidKeys(new String[]{"endpoint1", "endpoint2"}); + + mapping.setEndpointMap(endpointMap); + mapping.initApplicationContext(); + + // try endpoint1 + mapping.setKey("endpoint1"); + assertNotNull("No endpoint returned", mapping.getEndpointInternal(null)); + + // try endpoint2 + mapping.setKey("endpoint2"); + assertNotNull("No endpoint returned", mapping.getEndpointInternal(null)); + + // try non-mapped values + mapping.setKey("endpoint3"); + assertNull("Endpoint returned", mapping.getEndpointInternal(null)); + } + + private static class MyMapBasedEndpointMapping extends AbstractMapBasedEndpointMapping { + + private String key; + + private String[] validKeys = new String[0]; + + public void setKey(String key) { + this.key = key; + } + + public void setValidKeys(String[] validKeys) { + this.validKeys = validKeys; + Arrays.sort(this.validKeys); + } + + protected boolean validateLookupKey(String key) { + return Arrays.binarySearch(validKeys, key) >= 0; + } + + protected String getLookupKeyForMessage(MessageContext messageContext) throws Exception { + return key; + } + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/endpoint/mapping/PayloadRootQNameEndpointMappingTest.java b/core/src/test/java/org/springframework/ws/endpoint/mapping/PayloadRootQNameEndpointMappingTest.java new file mode 100644 index 00000000..875c59e6 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/endpoint/mapping/PayloadRootQNameEndpointMappingTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.endpoint.mapping; + +import javax.xml.namespace.QName; + +import junit.framework.TestCase; +import org.springframework.ws.mock.MockMessageContext; + +public class PayloadRootQNameEndpointMappingTest extends TestCase { + + private PayloadRootQNameEndpointMapping mapping; + + protected void setUp() throws Exception { + mapping = new PayloadRootQNameEndpointMapping(); + mapping.afterPropertiesSet(); + } + + public void testResolveQNames() throws Exception { + MockMessageContext context = new MockMessageContext(""); + + QName qName = mapping.resolveQName(context); + assertNotNull("mapping returns null", qName); + assertEquals("mapping returns invalid qualified name", new QName("root"), qName); + } + + public void testGetQNameNameNamespace() throws Exception { + MockMessageContext context = new MockMessageContext(""); + + QName qName = mapping.resolveQName(context); + assertNotNull("mapping returns null", qName); + assertEquals("mapping returns invalid method name", new QName("namespace", "localname", "prefix"), qName); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/mock/MockMessageContext.java b/core/src/test/java/org/springframework/ws/mock/MockMessageContext.java new file mode 100644 index 00000000..208e00b7 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/mock/MockMessageContext.java @@ -0,0 +1,59 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.mock; + +import java.io.IOException; + +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.context.AbstractMessageContext; +import org.springframework.ws.transport.TransportContext; +import org.springframework.ws.transport.TransportResponse; + +/** + * Mock implementation of the MessageContext interface. + * + * @author Arjen Poutsma + */ +public class MockMessageContext extends AbstractMessageContext { + + private TransportResponse transportResponse; + + public MockMessageContext() { + super(new MockWebServiceMessage(), new MockTransportRequest()); + } + + public MockMessageContext(MockWebServiceMessage request) { + super(request, new MockTransportRequest()); + } + + public MockMessageContext(MockWebServiceMessage request, TransportContext transportContext) { + super(request, transportContext.getTransportRequest()); + transportResponse = transportContext.getTransportResponse(); + } + + public MockMessageContext(String content) { + super(new MockWebServiceMessage(content), new MockTransportRequest()); + } + + protected WebServiceMessage createResponseMessage() { + return new MockWebServiceMessage(); + } + + public void sendResponse(TransportResponse transportResponse) throws IOException { + getResponse().writeTo(this.transportResponse.getOutputStream()); + } +} diff --git a/core/src/test/java/org/springframework/ws/mock/MockTransportContext.java b/core/src/test/java/org/springframework/ws/mock/MockTransportContext.java new file mode 100644 index 00000000..d4aca745 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/mock/MockTransportContext.java @@ -0,0 +1,55 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.mock; + +import org.springframework.ws.transport.TransportContext; +import org.springframework.ws.transport.TransportRequest; +import org.springframework.ws.transport.TransportResponse; + +/** + * Mock implementation of the TransportContext interface. + * + * @author Arjen Poutsma + */ +public class MockTransportContext implements TransportContext { + + private MockTransportRequest request; + + private MockTransportResponse response; + + public MockTransportContext() { + request = new MockTransportRequest(); + response = new MockTransportResponse(); + } + + public MockTransportContext(MockTransportRequest request) { + this.request = request; + } + + public MockTransportContext(MockTransportRequest request, MockTransportResponse response) { + this.request = request; + this.response = response; + } + + public TransportRequest getTransportRequest() { + return request; + } + + public TransportResponse getTransportResponse() { + return response; + } +} diff --git a/core/src/test/java/org/springframework/ws/mock/MockTransportRequest.java b/core/src/test/java/org/springframework/ws/mock/MockTransportRequest.java new file mode 100644 index 00000000..85d897f5 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/mock/MockTransportRequest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.mock; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Iterator; +import java.util.Properties; + +import org.springframework.util.Assert; +import org.springframework.ws.transport.TransportRequest; + +/** + * Mock implementation of the TransportRequest interface. + * + * @author Arjen Poutsma + */ +public class MockTransportRequest implements TransportRequest { + + private byte[] contents; + + private Properties headers; + + private String url; + + public MockTransportRequest() { + headers = new Properties(); + contents = new byte[0]; + } + + public MockTransportRequest(Properties headers, byte[] contents) { + Assert.notNull(headers, "headers must not be null"); + Assert.notNull(contents, "contents must not be null"); + this.headers = headers; + this.contents = contents; + } + + public MockTransportRequest(byte[] contents) { + Assert.notNull(contents, "contents must not be null"); + this.contents = contents; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUrl() { + return url; + } + + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(contents); + } + + public Iterator getHeaderNames() { + return headers.keySet().iterator(); + } + + public Iterator getHeaders(String name) { + String value = headers.getProperty(name); + return value != null ? Collections.singletonList(value).iterator() : Collections.EMPTY_LIST.iterator(); + } + + public void addHeader(String name, String value) { + headers.setProperty(name, value); + } +} diff --git a/core/src/test/java/org/springframework/ws/mock/MockTransportResponse.java b/core/src/test/java/org/springframework/ws/mock/MockTransportResponse.java new file mode 100644 index 00000000..e6a4771d --- /dev/null +++ b/core/src/test/java/org/springframework/ws/mock/MockTransportResponse.java @@ -0,0 +1,62 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.mock; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Properties; + +import org.springframework.ws.transport.TransportResponse; + +/** + * Mock implementation of the TransportResponse interface. + * + * @author Arjen Poutsma + */ +public class MockTransportResponse implements TransportResponse { + + private Properties headers = new Properties(); + + private ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + public void addHeader(String name, String value) { + String currentValue = headers.getProperty(name); + if (currentValue != null) { + value = currentValue + "," + value; + } + headers.setProperty(name, value); + } + + public Properties getHeaders() { + return headers; + } + + public String getContents() { + try { + return new String(outputStream.toByteArray(), "UTF-8"); + } + catch (UnsupportedEncodingException e) { + return ""; + } + } + + public OutputStream getOutputStream() throws IOException { + return outputStream; + } +} diff --git a/core/src/test/java/org/springframework/ws/mock/MockWebServiceMessage.java b/core/src/test/java/org/springframework/ws/mock/MockWebServiceMessage.java new file mode 100644 index 00000000..8210aec7 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/mock/MockWebServiceMessage.java @@ -0,0 +1,143 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.mock; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; + +import org.springframework.core.io.InputStreamSource; +import org.springframework.core.io.Resource; +import org.springframework.util.FileCopyUtils; +import org.springframework.ws.WebServiceMessage; +import org.springframework.xml.sax.SaxUtils; +import org.springframework.xml.transform.StringSource; + +/** + * Mock implementation of the WebServiceMessage interface. + * + * @author Arjen Poutsma + */ +public class MockWebServiceMessage implements WebServiceMessage { + + private final StringBuffer content; + + public MockWebServiceMessage() { + content = new StringBuffer(); + } + + public MockWebServiceMessage(Source source) throws TransformerException { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + content = new StringBuffer(); + transformer.transform(source, getPayloadResult()); + } + + public MockWebServiceMessage(Resource resource) throws IOException, TransformerException { + this(new SAXSource(SaxUtils.createInputSource(resource))); + } + + public MockWebServiceMessage(StringBuffer content) { + this.content = content; + } + + public MockWebServiceMessage(String content) { + this.content = new StringBuffer(content); + } + + public String getPayloadAsString() { + return content.toString(); + } + + public void setPayload(InputStreamSource inputStreamSource) throws IOException { + InputStream is = null; + try { + is = inputStreamSource.getInputStream(); + Reader reader = new InputStreamReader(is, "UTF-8"); + content.replace(0, content.length(), FileCopyUtils.copyToString(reader)); + } + finally { + if (is != null) { + is.close(); + } + } + } + + public void setPayload(String content) { + this.content.replace(0, this.content.length(), content); + } + + public Result getPayloadResult() { + content.setLength(0); + return new StreamResult(new StringBufferWriter()); + } + + public Source getPayloadSource() { + return new StringSource(content.toString()); + } + + public void writeTo(OutputStream outputStream) throws IOException { + PrintWriter writer = new PrintWriter(outputStream); + writer.write(content.toString()); + } + + private class StringBufferWriter extends Writer { + + private StringBufferWriter() { + super(content); + } + + public void write(String str) { + content.append(str); + } + + public void write(int c) { + content.append((char) c); + } + + public void write(String str, int off, int len) { + content.append(str.substring(off, off + len)); + } + + public void close() throws IOException { + } + + public void flush() { + } + + public void write(char cbuf[], int off, int len) { + if (off < 0 || off > cbuf.length || len < 0 || off + len > cbuf.length || off + len < 0) { + throw new IndexOutOfBoundsException(); + } + else if (len == 0) { + return; + } + content.append(cbuf, off, len); + } + } +} diff --git a/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageContextFactoryTest.java b/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageContextFactoryTest.java new file mode 100644 index 00000000..b308d52b --- /dev/null +++ b/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageContextFactoryTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.dom; + +import junit.framework.TestCase; +import org.springframework.ws.mock.MockTransportContext; +import org.springframework.ws.mock.MockTransportRequest; + +public class DomPoxMessageContextFactoryTest extends TestCase { + + private DomPoxMessageContextFactory contextFactory; + + protected void setUp() throws Exception { + contextFactory = new DomPoxMessageContextFactory(); + contextFactory.afterPropertiesSet(); + } + + public void testCreateContext() throws Exception { + String content = ""; + MockTransportRequest transportRequest = new MockTransportRequest(content.getBytes("UTF-8")); + MockTransportContext transportContext = new MockTransportContext(transportRequest); + DomPoxMessageContext context = (DomPoxMessageContext) contextFactory.createContext(transportContext); + assertNotNull("No context returned", context); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageContextTest.java b/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageContextTest.java new file mode 100644 index 00000000..35599158 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageContextTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.dom; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.mock.MockTransportRequest; +import org.springframework.ws.mock.MockTransportResponse; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class DomPoxMessageContextTest extends XMLTestCase { + + private DomPoxMessageContext context; + + private Transformer transformer; + + protected void setUp() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.newDocument(); + Element element = document.createElementNS("http://springframework.org/spring-ws", "element"); + document.appendChild(element); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + MockTransportRequest transportRequest = new MockTransportRequest(); + context = new DomPoxMessageContext(document, transportRequest, documentBuilder, transformer); + } + + public void testGetRequest() throws Exception { + WebServiceMessage message = context.getRequest(); + assertNotNull("No request returned", message); + StringResult result = new StringResult(); + transformer.transform(message.getPayloadSource(), result); + assertXMLEqual("", result.toString()); + } + + public void testGetResponse() throws Exception { + WebServiceMessage message = context.getResponse(); + assertNotNull("No request returned", message); + } + + public void testSendResponse() throws Exception { + WebServiceMessage message = context.getResponse(); + String content = ""; + StringSource source = new StringSource(content); + transformer.transform(source, message.getPayloadResult()); + MockTransportResponse transportResponse = new MockTransportResponse(); + context.sendResponse(transportResponse); + assertXMLEqual(transportResponse.getContents(), content); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageTest.java b/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageTest.java new file mode 100644 index 00000000..94038d4c --- /dev/null +++ b/core/src/test/java/org/springframework/ws/pox/dom/DomPoxMessageTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.pox.dom; + +import java.io.ByteArrayOutputStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; +import org.w3c.dom.Document; + +public class DomPoxMessageTest extends XMLTestCase { + + public static final String NAMESPACE = "http://www.springframework.org/spring-ws"; + + private DomPoxMessage message; + + private Transformer transformer; + + protected void setUp() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.newDocument(); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + message = new DomPoxMessage(document, transformer); + } + + public void testGetPayload() throws Exception { + String content = "" + ""; + StringSource source = new StringSource(content); + transformer.transform(source, message.getPayloadResult()); + StringResult stringResult = new StringResult(); + transformer.transform(message.getPayloadSource(), stringResult); + assertXMLEqual(content, stringResult.toString()); + } + + public void testWriteTo() throws Exception { + String content = "" + ""; + StringSource source = new StringSource(content); + transformer.transform(source, message.getPayloadResult()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + message.writeTo(os); + assertXMLEqual(content, os.toString("UTF-8")); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/AbstractSoapBodyTestCase.java b/core/src/test/java/org/springframework/ws/soap/AbstractSoapBodyTestCase.java new file mode 100644 index 00000000..2f5c30cd --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/AbstractSoapBodyTestCase.java @@ -0,0 +1,65 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import java.util.Locale; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public abstract class AbstractSoapBodyTestCase extends XMLTestCase { + + protected SoapBody soapBody; + + protected Transformer transformer; + + protected final void setUp() throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + soapBody = createSoapBody(); + } + + protected abstract SoapBody createSoapBody() throws Exception; + + public void testPayload() throws Exception { + String payload = ""; + StringSource contents = new StringSource(payload); + transformer.transform(contents, soapBody.getPayloadResult()); + assertPayloadEqual(payload); + } + + public void testNoFault() throws Exception { + assertFalse("body has fault", soapBody.hasFault()); + } + + public void testAddFaultWithExistingPayload() throws Exception { + StringSource contents = new StringSource(""); + transformer.transform(contents, soapBody.getPayloadResult()); + soapBody.addMustUnderstandFault("faultString", Locale.ENGLISH); + assertTrue("Body has no fault", soapBody.hasFault()); + } + + protected void assertPayloadEqual(String expectedPayload) throws Exception { + StringResult result = new StringResult(); + transformer.transform(soapBody.getPayloadSource(), result); + assertXMLEqual("Invalid payload contents", expectedPayload, result.toString()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/AbstractSoapEnvelopeTestCase.java b/core/src/test/java/org/springframework/ws/soap/AbstractSoapEnvelopeTestCase.java new file mode 100644 index 00000000..1c01634a --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/AbstractSoapEnvelopeTestCase.java @@ -0,0 +1,47 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; + +public abstract class AbstractSoapEnvelopeTestCase extends XMLTestCase { + + protected SoapEnvelope soapEnvelope; + + protected Transformer transformer; + + protected final void setUp() throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + soapEnvelope = createSoapEnvelope(); + } + + protected abstract SoapEnvelope createSoapEnvelope() throws Exception; + + public void testGetHeader() throws Exception { + SoapHeader header = soapEnvelope.getHeader(); + assertNotNull("No header returned", header); + } + + public void testGetBody() throws Exception { + SoapBody body = soapEnvelope.getBody(); + assertNotNull("No body returned", body); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/AbstractSoapHeaderTestCase.java b/core/src/test/java/org/springframework/ws/soap/AbstractSoapHeaderTestCase.java new file mode 100644 index 00000000..cb92e194 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/AbstractSoapHeaderTestCase.java @@ -0,0 +1,100 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public abstract class AbstractSoapHeaderTestCase extends XMLTestCase { + + protected SoapHeader soapHeader; + + protected Transformer transformer; + + protected final void setUp() throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + soapHeader = createSoapHeader(); + } + + protected abstract SoapHeader createSoapHeader() throws Exception; + + public void testAddHeaderElement() throws Exception { + QName qName = new QName("http://www.springframework.org", "localName", "spring"); + SoapHeaderElement headerElement = soapHeader.addHeaderElement(qName); + assertEquals("Invalid qName for element", qName, headerElement.getName()); + String payload = ""; + assertNotNull("No SoapHeaderElement returned", headerElement); + transformer.transform(new StringSource(payload), headerElement.getResult()); + assertHeaderElementEqual(headerElement, + ""); + } + + public void testExamineAllHeaderElement() throws Exception { + QName qName = new QName("http://www.springframework.org", "localName", "spring"); + SoapHeaderElement headerElement = soapHeader.addHeaderElement(qName); + assertEquals("Invalid qName for element", qName, headerElement.getName()); + String payload = ""; + assertNotNull("No SoapHeaderElement returned", headerElement); + transformer.transform(new StringSource(payload), headerElement.getResult()); + Iterator iterator = soapHeader.examineAllHeaderElements(); + assertNotNull("header element iterator is null", iterator); + assertTrue("header element iterator has no elements", iterator.hasNext()); + headerElement = (SoapHeaderElement) iterator.next(); + assertEquals("Invalid qName for element", qName, headerElement.getName()); + StringResult result = new StringResult(); + transformer.transform(headerElement.getSource(), result); + assertXMLEqual("Invalid contents of header element", + "", + result.toString()); + assertFalse("header element iterator has too many elements", iterator.hasNext()); + } + + public void testExamineMustUnderstandHeaderElements() throws Exception { + QName qName1 = new QName("http://www.springframework.org", "localName1", "spring"); + SoapHeaderElement headerElement1 = soapHeader.addHeaderElement(qName1); + headerElement1.setMustUnderstand(true); + headerElement1.setActorOrRole("role1"); + QName qName2 = new QName("http://www.springframework.org", "localName2", "spring"); + SoapHeaderElement headerElement2 = soapHeader.addHeaderElement(qName2); + headerElement2.setMustUnderstand(true); + headerElement2.setActorOrRole("role2"); + Iterator iterator = soapHeader.examineMustUnderstandHeaderElements("role1"); + assertNotNull("header element iterator is null", iterator); + assertTrue("header element iterator has no elements", iterator.hasNext()); + SoapHeaderElement headerElement = (SoapHeaderElement) iterator.next(); + assertEquals("Invalid name on header element", qName1, headerElement.getName()); + assertTrue("MustUnderstand not set on header element", headerElement.getMustUnderstand()); + assertEquals("Invalid role on header element", "role1", headerElement.getActorOrRole()); + assertFalse("header element iterator has too many elements", iterator.hasNext()); + } + + protected void assertHeaderElementEqual(SoapHeaderElement headerElement, String expected) throws Exception { + StringResult result = new StringResult(); + transformer.transform(headerElement.getSource(), result); + assertXMLEqual("Invalid contents of header element", + "", + result.toString()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/AbstractSoapMessageTestCase.java b/core/src/test/java/org/springframework/ws/soap/AbstractSoapMessageTestCase.java new file mode 100644 index 00000000..97e5178c --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/AbstractSoapMessageTestCase.java @@ -0,0 +1,82 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import java.io.ByteArrayOutputStream; +import java.util.Iterator; + +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.InputStreamSource; +import org.springframework.core.io.Resource; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; +import org.springframework.ws.AbstractWebServiceMessageTestCase; +import org.springframework.ws.WebServiceMessage; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.validation.XmlValidator; +import org.springframework.xml.validation.XmlValidatorFactory; +import org.xml.sax.SAXParseException; + +public abstract class AbstractSoapMessageTestCase extends AbstractWebServiceMessageTestCase { + + protected SoapMessage soapMessage; + + protected final WebServiceMessage createWebServiceMessage() throws Exception { + soapMessage = createSoapMessage(); + return soapMessage; + } + + protected abstract SoapMessage createSoapMessage() throws Exception; + + public void testAttachments() throws Exception { + String contents = "contents"; + String contentType = "text/plain"; + InputStreamSource inputStreamSource = new ByteArrayResource(contents.getBytes("UTF-8")); + soapMessage.addAttachment(inputStreamSource, contentType); + Iterator iterator = soapMessage.getAttachments(); + assertNotNull("Attachment iterator is null", iterator); + assertTrue("Attachment iterator has no elements", iterator.hasNext()); + Attachment attachment = (Attachment) iterator.next(); + assertEquals("Invalid conent-id", contentType, attachment.getContentType()); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + FileCopyUtils.copy(attachment.getInputStream(), os); + String result = new String(os.toByteArray(), "UTF-8"); + assertEquals("Invalid contents", contents, result); + assertFalse("Attachment iterator has too many elements", iterator.hasNext()); + } + + public void testWriteTo() throws Exception { + StringResult stringResult = new StringResult(); + transformer.transform(soapMessage.getEnvelope().getSource(), stringResult); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + soapMessage.writeTo(os); + assertXMLEqual(stringResult.toString(), os.toString("UTF-8")); + } + + public void testValidate() throws Exception { + XmlValidator validator = + XmlValidatorFactory.createValidator(getSoapSchemas(), XmlValidatorFactory.SCHEMA_W3C_XML); + SAXParseException[] errors = validator.validate(soapMessage.getEnvelope().getSource()); + if (errors.length > 0) { + fail(StringUtils.arrayToCommaDelimitedString(errors)); + } + } + + protected abstract Resource[] getSoapSchemas(); + + +} diff --git a/core/src/test/java/org/springframework/ws/soap/SoapMessageDispatcherTest.java b/core/src/test/java/org/springframework/ws/soap/SoapMessageDispatcherTest.java new file mode 100644 index 00000000..235bca62 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/SoapMessageDispatcherTest.java @@ -0,0 +1,230 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap; + +import java.util.Iterator; +import java.util.Locale; +import javax.xml.namespace.QName; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPHeaderElement; +import javax.xml.soap.SOAPMessage; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.springframework.ws.mock.MockTransportRequest; +import org.springframework.ws.soap.saaj.SaajSoapMessageContext; +import org.springframework.ws.soap.soap11.Soap11Fault; +import org.springframework.ws.soap.soap12.Soap12Fault; + +public class SoapMessageDispatcherTest extends TestCase { + + private SoapMessageDispatcher dispatcher; + + private MockControl interceptorControl; + + private SoapEndpointInterceptor interceptorMock; + + protected void setUp() throws Exception { + interceptorControl = MockControl.createControl(SoapEndpointInterceptor.class); + interceptorMock = (SoapEndpointInterceptor) interceptorControl.getMock(); + dispatcher = new SoapMessageDispatcher(); + } + + public void testProcessMustUnderstandHeadersUnderstoodSoap11() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage request = messageFactory.createMessage(); + SOAPHeaderElement header = + request.getSOAPHeader().addHeaderElement(new QName("http://www.springframework.org", "Header")); + header.setActor(SOAPConstants.URI_SOAP_ACTOR_NEXT); + header.setMustUnderstand(true); + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new MockTransportRequest(), messageFactory); + interceptorMock.understands(null); + interceptorControl.setMatcher(MockControl.ALWAYS_MATCHER); + interceptorControl.setReturnValue(true); + interceptorControl.replay(); + + SoapEndpointInvocationChain chain = + new SoapEndpointInvocationChain(new Object(), new SoapEndpointInterceptor[]{interceptorMock}); + + boolean result = dispatcher.handleRequest(chain, context); + assertTrue("Header not understood", result); + interceptorControl.verify(); + } + + public void testProcessMustUnderstandHeadersUnderstoodSoap12() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage request = messageFactory.createMessage(); + SOAPHeaderElement header = + request.getSOAPHeader().addHeaderElement(new QName("http://www.springframework.org", "Header")); + header.setMustUnderstand(true); + header.setRole(SOAPConstants.URI_SOAP_1_2_ROLE_NEXT); + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new MockTransportRequest(), messageFactory); + interceptorMock.understands(null); + interceptorControl.setMatcher(MockControl.ALWAYS_MATCHER); + interceptorControl.setReturnValue(true); + interceptorControl.replay(); + + SoapEndpointInvocationChain chain = + new SoapEndpointInvocationChain(new Object(), new SoapEndpointInterceptor[]{interceptorMock}); + + boolean result = dispatcher.handleRequest(chain, context); + assertTrue("Header not understood", result); + interceptorControl.verify(); + } + + public void testProcessMustUnderstandHeadersNotUnderstoodSoap11() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage request = messageFactory.createMessage(); + SOAPHeaderElement header = request.getSOAPHeader() + .addHeaderElement(new QName("http://www.springframework.org", "Header", "spring-ws")); + header.setActor(SOAPConstants.URI_SOAP_ACTOR_NEXT); + header.setMustUnderstand(true); + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new MockTransportRequest(), messageFactory); + interceptorMock.understands(null); + interceptorControl.setMatcher(MockControl.ALWAYS_MATCHER); + interceptorControl.setReturnValue(false); + interceptorControl.replay(); + + SoapEndpointInvocationChain chain = + new SoapEndpointInvocationChain(new Object(), new SoapEndpointInterceptor[]{interceptorMock}); + + boolean result = dispatcher.handleRequest(chain, context); + assertFalse("Header understood", result); + assertTrue("Context has no response", context.hasResponse()); + SoapBody responseBody = context.getSoapResponse().getSoapBody(); + assertTrue("Response body has no fault", responseBody.hasFault()); + Soap11Fault fault = (Soap11Fault) responseBody.getFault(); + assertEquals("Invalid fault code", + new QName(SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE, "MustUnderstand"), + fault.getFaultCode()); + assertEquals("Invalid fault string", + SoapMessageDispatcher.DEFAULT_MUST_UNDERSTAND_FAULT, + fault.getFaultString()); + assertEquals("Invalid fault string locale", Locale.ENGLISH, fault.getFaultStringLocale()); + assertEquals("Invalid fault actor", SOAPConstants.URI_SOAP_ACTOR_NEXT, fault.getFaultActorOrRole()); + interceptorControl.verify(); + } + + public void testProcessMustUnderstandHeadersNotUnderstoodSoap12() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage request = messageFactory.createMessage(); + SOAPHeaderElement header = request.getSOAPHeader() + .addHeaderElement(new QName("http://www.springframework.org", "Header", "spring-ws")); + header.setMustUnderstand(true); + header.setRole(SOAPConstants.URI_SOAP_1_2_ROLE_NEXT); + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new MockTransportRequest(), messageFactory); + interceptorMock.understands(null); + interceptorControl.setMatcher(MockControl.ALWAYS_MATCHER); + interceptorControl.setReturnValue(false); + interceptorControl.replay(); + + SoapEndpointInvocationChain chain = + new SoapEndpointInvocationChain(new Object(), new SoapEndpointInterceptor[]{interceptorMock}); + + boolean result = dispatcher.handleRequest(chain, context); + assertFalse("Header understood", result); + assertTrue("Context has no response", context.hasResponse()); + SoapBody responseBody = context.getSoapResponse().getSoapBody(); + assertTrue("Response body has no fault", responseBody.hasFault()); + Soap12Fault fault = (Soap12Fault) responseBody.getFault(); + assertEquals("Invalid fault code", + new QName(SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE, "MustUnderstand"), + fault.getFaultCode()); + assertEquals("Invalid fault string", + SoapMessageDispatcher.DEFAULT_MUST_UNDERSTAND_FAULT, + fault.getFaultReasonText(Locale.ENGLISH)); + assertEquals("Invalid fault actor", SOAPConstants.URI_SOAP_1_2_ROLE_NEXT, fault.getFaultActorOrRole()); + SoapHeader responseHeader = context.getSoapResponse().getSoapHeader(); + Iterator iterator = responseHeader.examineAllHeaderElements(); + assertTrue("Response header has no elements", iterator.hasNext()); + SoapHeaderElement headerElement = (SoapHeaderElement) iterator.next(); + assertEquals("No NotUnderstood header", + new QName(SOAPConstants.URI_NS_SOAP_1_2_ENVELOPE, "NotUnderstood"), + headerElement.getName()); + interceptorControl.verify(); + } + + public void testProcessMustUnderstandHeadersForActorSoap11() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage request = messageFactory.createMessage(); + SOAPHeaderElement header = request.getSOAPHeader() + .addHeaderElement(new QName("http://www.springframework.org", "Header", "spring-ws")); + String headerActor = "http://www/springframework.org/role"; + header.setActor(headerActor); + header.setMustUnderstand(true); + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new MockTransportRequest(), messageFactory); + interceptorMock.understands(null); + interceptorControl.setMatcher(MockControl.ALWAYS_MATCHER); + interceptorControl.setReturnValue(true); + interceptorControl.replay(); + + SoapEndpointInvocationChain chain = new SoapEndpointInvocationChain(new Object(), + new SoapEndpointInterceptor[]{interceptorMock}, + new String[]{headerActor}); + + boolean result = dispatcher.handleRequest(chain, context); + assertTrue("actor-specific header not understood", result); + interceptorControl.verify(); + } + + public void testProcessMustUnderstandHeadersForRoleSoap11() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage request = messageFactory.createMessage(); + SOAPHeaderElement header = request.getSOAPHeader() + .addHeaderElement(new QName("http://www.springframework.org", "Header", "spring-ws")); + String headerRole = "http://www/springframework.org/role"; + header.setRole(headerRole); + header.setMustUnderstand(true); + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new MockTransportRequest(), messageFactory); + interceptorMock.understands(null); + interceptorControl.setMatcher(MockControl.ALWAYS_MATCHER); + interceptorControl.setReturnValue(true); + interceptorControl.replay(); + + SoapEndpointInvocationChain chain = new SoapEndpointInvocationChain(new Object(), + new SoapEndpointInterceptor[]{interceptorMock}, + new String[]{headerRole}); + + boolean result = dispatcher.handleRequest(chain, context); + assertTrue("role-specific header not understood", result); + interceptorControl.verify(); + } + + public void testProcessNoHeader() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage request = messageFactory.createMessage(); + request.getSOAPHeader().detachNode(); + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new MockTransportRequest(), messageFactory); + interceptorControl.replay(); + + SoapEndpointInvocationChain chain = new SoapEndpointInvocationChain(new Object(), + new SoapEndpointInterceptor[]{interceptorMock}, + new String[]{"role"}); + + boolean result = dispatcher.handleRequest(chain, context); + assertTrue("Invalid result", result); + interceptorControl.verify(); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11BodyTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11BodyTest.java new file mode 100644 index 00000000..cad003ef --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11BodyTest.java @@ -0,0 +1,35 @@ +package org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.soap11.AbstractSoap11BodyTestCase; + +public class AxiomSoap11BodyTest extends AbstractSoap11BodyTestCase { + + protected SoapBody createSoapBody() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + return axiomSoapMessage.getSoapBody(); + } + + public void testAddMustUnderstandFault() throws Exception { + // Overriden because of http://issues.apache.org/jira/browse/WSCOMMONS-38 + } + + public void testAddClientFault() throws Exception { + // Overriden because of http://issues.apache.org/jira/browse/WSCOMMONS-38 + } + + public void testAddServerFault() throws Exception { + // Overriden because of http://issues.apache.org/jira/browse/WSCOMMONS-38 + } + + public void testAddFault() throws Exception { + // Overriden because of http://issues.apache.org/jira/browse/WSCOMMONS-38 + } + + public void testAddFaultWithDetail() throws Exception { + // Overriden because of http://issues.apache.org/jira/browse/WSCOMMONS-38 + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11EnvelopeTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11EnvelopeTest.java new file mode 100644 index 00000000..0fe68786 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11EnvelopeTest.java @@ -0,0 +1,15 @@ +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.AbstractSoap11EnvelopeTestCase; + +public class AxiomSoap11EnvelopeTest extends AbstractSoap11EnvelopeTestCase { + + protected SoapEnvelope createSoapEnvelope() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + return axiomSoapMessage.getEnvelope(); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11HeaderTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11HeaderTest.java new file mode 100644 index 00000000..172bc045 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11HeaderTest.java @@ -0,0 +1,16 @@ +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.AbstractSoap11HeaderTestCase; + +public class AxiomSoap11HeaderTest extends AbstractSoap11HeaderTestCase { + + protected SoapHeader createSoapHeader() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + return axiomSoapMessage.getSoapHeader(); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageContextFactoryTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageContextFactoryTest.java new file mode 100644 index 00000000..cf6491cd --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageContextFactoryTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.soap.context.AbstractSoap11MessageContextFactoryTestCase; + +public class AxiomSoap11MessageContextFactoryTest extends AbstractSoap11MessageContextFactoryTestCase { + + protected MessageContextFactory createSoapMessageContextFactory() { + return new AxiomSoapMessageContextFactory(); + } + + public void testCreateMessageFromHttpServletRequest12WithAttachment() throws Exception { + // Axiom does not support SwA with SOAP 1.2 + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageTest.java new file mode 100644 index 00000000..027429d4 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap11MessageTest.java @@ -0,0 +1,22 @@ +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.AbstractSoap11MessageTestCase; + +public class AxiomSoap11MessageTest extends AbstractSoap11MessageTestCase { + + protected SoapMessage createSoapMessage() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP11Factory(); + return new AxiomSoapMessage(axiomFactory); + } + + public void testAttachments() throws Exception { + // Attachment support not supported + } + + public void testWriteToTransportResponseAttachment() throws Exception { + // Attachment support not supported + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12BodyTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12BodyTest.java new file mode 100644 index 00000000..451e8fbc --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12BodyTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom; + +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.soap.SOAPFactory; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.soap12.AbstractSoap12BodyTestCase; + +public class AxiomSoap12BodyTest extends AbstractSoap12BodyTestCase { + + protected SoapBody createSoapBody() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP12Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + return axiomSoapMessage.getSoapBody(); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12EnvelopeTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12EnvelopeTest.java new file mode 100644 index 00000000..d6f051ea --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12EnvelopeTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2006 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 + * + * 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 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.AbstractSoap12EnvelopeTestCase; + +public class AxiomSoap12EnvelopeTest extends AbstractSoap12EnvelopeTestCase { + + protected SoapEnvelope createSoapEnvelope() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP12Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + return axiomSoapMessage.getEnvelope(); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12HeaderTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12HeaderTest.java new file mode 100644 index 00000000..0241de06 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12HeaderTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2006 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 + * + * 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 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.AbstractSoap12HeaderTestCase; + +public class AxiomSoap12HeaderTest extends AbstractSoap12HeaderTestCase { + + protected SoapHeader createSoapHeader() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP12Factory(); + AxiomSoapMessage axiomSoapMessage = new AxiomSoapMessage(axiomFactory); + return axiomSoapMessage.getSoapHeader(); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageContextFactoryTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageContextFactoryTest.java new file mode 100644 index 00000000..659982b5 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageContextFactoryTest.java @@ -0,0 +1,15 @@ +package org.springframework.ws.soap.axiom; + +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.soap.context.AbstractSoap12MessageContextFactoryTestCase; + +public class AxiomSoap12MessageContextFactoryTest extends AbstractSoap12MessageContextFactoryTestCase { + + protected MessageContextFactory createSoapMessageContextFactory() { + return new AxiomSoapMessageContextFactory(); + } + + public void testCreateContextAttachments() throws Exception { + // Axiom does not support SwA with SOAP 1.2 + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageTest.java new file mode 100644 index 00000000..4e3bb02e --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/AxiomSoap12MessageTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 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 + * + * 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 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.AbstractSoap12MessageTestCase; + +public class AxiomSoap12MessageTest extends AbstractSoap12MessageTestCase { + + protected SoapMessage createSoapMessage() throws Exception { + SOAPFactory axiomFactory = OMAbstractFactory.getSOAP12Factory(); + return new AxiomSoapMessage(axiomFactory); + } + + public void testAttachments() throws Exception { + // Attachments not supported + } + + public void testWriteToTransportResponseAttachment() throws Exception { + // Attachments not supported + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/axiom/support/AxiomUtilsTest.java b/core/src/test/java/org/springframework/ws/soap/axiom/support/AxiomUtilsTest.java new file mode 100644 index 00000000..3e7988ba --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/axiom/support/AxiomUtilsTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.axiom.support; + +import java.util.Locale; +import javax.xml.namespace.QName; + +import junit.framework.TestCase; +import org.apache.axiom.om.OMAbstractFactory; +import org.apache.axiom.om.OMElement; +import org.apache.axiom.om.OMFactory; +import org.apache.axiom.om.OMNamespace; + +public class AxiomUtilsTest extends TestCase { + + private OMElement element; + + protected void setUp() throws Exception { + OMFactory factory = OMAbstractFactory.getOMFactory(); + OMNamespace namespace = factory.createOMNamespace("http://www.springframework.org", "prefix"); + element = factory.createOMElement("element", namespace); + } + + public void testToNamespaceDeclared() throws Exception { + QName qName = new QName(element.getNamespace().getName(), "localPart"); + OMNamespace namespace = AxiomUtils.toNamespace(qName, element); + assertNotNull("Invalid namespace", namespace); + assertEquals("Invalid namespace", qName.getNamespaceURI(), namespace.getName()); + } + + public void testToNamespaceUndeclared() throws Exception { + QName qName = new QName("http://www.example.com", "localPart"); + OMNamespace namespace = AxiomUtils.toNamespace(qName, element); + assertNotNull("Invalid namespace", namespace); + assertEquals("Invalid namespace", qName.getNamespaceURI(), namespace.getName()); + assertFalse("Invalid prefix", "prefix".equals(namespace.getPrefix())); + } + + public void testToNamespacePrefixDeclared() throws Exception { + QName qName = new QName(element.getNamespace().getName(), "localPart", "prefix"); + OMNamespace namespace = AxiomUtils.toNamespace(qName, element); + assertNotNull("Invalid namespace", namespace); + assertEquals("Invalid namespace", qName.getNamespaceURI(), namespace.getName()); + assertEquals("Invalid prefix", "prefix", namespace.getPrefix()); + } + + public void testToNamespacePrefixUndeclared() throws Exception { + QName qName = new QName("http://www.example.com", "localPart", "otherPrefix"); + OMNamespace namespace = AxiomUtils.toNamespace(qName, element); + assertNotNull("Invalid namespace", namespace); + assertEquals("Invalid namespace", qName.getNamespaceURI(), namespace.getName()); + assertEquals("Invalid prefix", qName.getPrefix(), namespace.getPrefix()); + } + + public void testToLanguage() throws Exception { + assertEquals("Invalid conversion", "fr-CA", AxiomUtils.toLanguage(Locale.CANADA_FRENCH)); + assertEquals("Invalid conversion", "en", AxiomUtils.toLanguage(Locale.ENGLISH)); + } + + public void testToLocale() throws Exception { + assertEquals("Invalid conversion", Locale.CANADA_FRENCH, AxiomUtils.toLocale("fr-CA")); + assertEquals("Invalid conversion", Locale.ENGLISH, AxiomUtils.toLocale("en")); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap11MessageContextFactoryTestCase.java b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap11MessageContextFactoryTestCase.java new file mode 100644 index 00000000..e709633a --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap11MessageContextFactoryTestCase.java @@ -0,0 +1,54 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.context; + +import java.util.Properties; + +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.mock.MockTransportContext; +import org.springframework.ws.soap.Attachment; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapVersion; + +public abstract class AbstractSoap11MessageContextFactoryTestCase extends AbstractSoapMessageContextFactoryTestCase { + + public void testCreateContextNoAttachment() throws Exception { + Properties headers = new Properties(); + headers.setProperty("Content-Type", "text/xml"); + headers.setProperty("SOAPAction", "\"Some-URI\""); + MockTransportContext transportContext = createTransportContext(headers, "soap11.xml"); + + MessageContext messageContext = contextFactory.createContext(transportContext); + SoapMessage requestMessage = (SoapMessage) messageContext.getRequest(); + assertNotNull("Request null", requestMessage); + assertEquals("Invalid soap version", SoapVersion.SOAP_11, requestMessage.getVersion()); + } + + public void testCreateContextAttachment() throws Exception { + Properties headers = new Properties(); + headers.setProperty("Content-Type", + "multipart/related; type=\"text/xml\"; boundary=\"----=_Part_0_11416420.1149699787554\""); + MockTransportContext transportContext = createTransportContext(headers, "soap11-attachment.bin"); + + MessageContext messageContext = contextFactory.createContext(transportContext); + SoapMessage requestMessage = (SoapMessage) messageContext.getRequest(); + assertEquals("Invalid soap version", SoapVersion.SOAP_11, requestMessage.getVersion()); + Attachment attachment = requestMessage.getAttachment("interface21"); + assertNotNull("No attachment read", attachment); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap11MessageContextTestCase.java b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap11MessageContextTestCase.java new file mode 100644 index 00000000..adef6c55 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap11MessageContextTestCase.java @@ -0,0 +1,44 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.context; + +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.InputStreamSource; +import org.springframework.ws.soap.SoapVersion; + +public abstract class AbstractSoap11MessageContextTestCase extends AbstractSoapMessageContextTestCase { + + public void testWriteToTransportResponse() throws Exception { + messageContext.getResponse(); // create response + messageContext.sendResponse(transportResponse); + assertXMLEqual("

", + transportResponse.getContents()); + assertTrue("Invalid Content-Type set", transportResponse.getHeaders().getProperty("Content-Type") + .indexOf(SoapVersion.SOAP_11.getContentType()) != -1); + } + + public void testWriteToTransportResponseAttachment() throws Exception { + InputStreamSource inputStreamSource = new ByteArrayResource("contents".getBytes("UTF-8")); + messageContext.getSoapResponse().addAttachment(inputStreamSource, "text/plain"); + messageContext.sendResponse(transportResponse); + assertTrue("Invalid Content-Type set", + transportResponse.getHeaders().getProperty("Content-Type").indexOf("multipart/related") != -1); + assertTrue("Invalid Content-Type set", transportResponse.getHeaders().getProperty("Content-Type") + .indexOf(SoapVersion.SOAP_11.getContentType()) != -1); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap12MessageContextFactoryTestCase.java b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap12MessageContextFactoryTestCase.java new file mode 100644 index 00000000..7816b419 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap12MessageContextFactoryTestCase.java @@ -0,0 +1,38 @@ +package org.springframework.ws.soap.context; + +import java.util.Properties; + +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.mock.MockTransportContext; +import org.springframework.ws.soap.Attachment; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapVersion; + +public abstract class AbstractSoap12MessageContextFactoryTestCase extends AbstractSoapMessageContextFactoryTestCase { + + public void testCreateContextNoAttachment() throws Exception { + Properties headers = new Properties(); + headers.setProperty("Content-Type", "application/soap+xml"); + headers.setProperty("SOAPAction", "\"Some-URI\""); + MockTransportContext transportContext = createTransportContext(headers, "soap12.xml"); + + MessageContext messageContext = contextFactory.createContext(transportContext); + SoapMessage requestMessage = (SoapMessage) messageContext.getRequest(); + assertNotNull("Request null", requestMessage); + assertEquals("Invalid soap version", SoapVersion.SOAP_12, requestMessage.getVersion()); + } + + public void testCreateContextAttachments() throws Exception { + Properties headers = new Properties(); + headers.setProperty("Content-Type", + "multipart/related; type=\"application/soap+xml\"; boundary=\"----=_Part_0_11416420.1149699787554\""); + MockTransportContext transportContext = createTransportContext(headers, "soap12-attachment.bin"); + + MessageContext messageContext = contextFactory.createContext(transportContext); + SoapMessage requestMessage = (SoapMessage) messageContext.getRequest(); + assertEquals("Invalid soap version", SoapVersion.SOAP_12, requestMessage.getVersion()); + Attachment attachment = requestMessage.getAttachment("interface21"); + assertNotNull("No attachment read", attachment); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap12MessageContextTestCase.java b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap12MessageContextTestCase.java new file mode 100644 index 00000000..08bc6801 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoap12MessageContextTestCase.java @@ -0,0 +1,44 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.context; + +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.InputStreamSource; +import org.springframework.ws.soap.SoapVersion; + +public abstract class AbstractSoap12MessageContextTestCase extends AbstractSoapMessageContextTestCase { + + public void testWriteToTransportResponse() throws Exception { + messageContext.getResponse(); // create response + messageContext.sendResponse(null); + assertXMLEqual("
", + transportResponse.getContents()); + assertTrue("Invalid Content-Type set", transportResponse.getHeaders().getProperty("Content-Type") + .indexOf(SoapVersion.SOAP_12.getContentType()) != -1); + } + + public void testWriteToTransportResponseAttachment() throws Exception { + InputStreamSource inputStreamSource = new ByteArrayResource("contents".getBytes("UTF-8")); + messageContext.getSoapResponse().addAttachment(inputStreamSource, "text/plain"); + messageContext.sendResponse(null); + assertTrue("Invalid Content-Type set", + transportResponse.getHeaders().getProperty("Content-Type").indexOf("multipart/related") != -1); + assertTrue("Invalid Content-Type set", transportResponse.getHeaders().getProperty("Content-Type") + .indexOf(SoapVersion.SOAP_12.getContentType()) != -1); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/context/AbstractSoapMessageContextFactoryTestCase.java b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoapMessageContextFactoryTestCase.java new file mode 100644 index 00000000..efe10902 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoapMessageContextFactoryTestCase.java @@ -0,0 +1,32 @@ +package org.springframework.ws.soap.context; + +import java.io.IOException; +import java.util.Properties; + +import junit.framework.TestCase; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.FileCopyUtils; +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.mock.MockTransportContext; +import org.springframework.ws.mock.MockTransportRequest; + +public abstract class AbstractSoapMessageContextFactoryTestCase extends TestCase { + + protected MessageContextFactory contextFactory; + + protected final void setUp() throws Exception { + contextFactory = createSoapMessageContextFactory(); + if (contextFactory instanceof InitializingBean) { + ((InitializingBean) contextFactory).afterPropertiesSet(); + } + } + + protected MockTransportContext createTransportContext(Properties headers, String requestFile) throws IOException { + byte[] contents = FileCopyUtils + .copyToByteArray(AbstractSoapMessageContextFactoryTestCase.class.getResourceAsStream(requestFile)); + return new MockTransportContext(new MockTransportRequest(headers, contents)); + } + + protected abstract MessageContextFactory createSoapMessageContextFactory() throws Exception; + +} diff --git a/core/src/test/java/org/springframework/ws/soap/context/AbstractSoapMessageContextTestCase.java b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoapMessageContextTestCase.java new file mode 100644 index 00000000..ecbedbb9 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/context/AbstractSoapMessageContextTestCase.java @@ -0,0 +1,46 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.context; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.springframework.ws.mock.MockTransportRequest; +import org.springframework.ws.mock.MockTransportResponse; +import org.springframework.ws.transport.TransportRequest; + +public abstract class AbstractSoapMessageContextTestCase extends XMLTestCase { + + protected SoapMessageContext messageContext; + + protected Transformer transformer; + + protected MockTransportResponse transportResponse; + + protected final void setUp() throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + TransportRequest transportRequest = new MockTransportRequest(); + transportResponse = new MockTransportResponse(); + messageContext = createMessageContext(transportRequest); + } + + protected abstract SoapMessageContext createMessageContext(TransportRequest transportRequest) throws Exception; + + +} diff --git a/core/src/test/java/org/springframework/ws/soap/endpoint/SimpleSoapExceptionResolverTest.java b/core/src/test/java/org/springframework/ws/soap/endpoint/SimpleSoapExceptionResolverTest.java new file mode 100644 index 00000000..7f04feee --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/endpoint/SimpleSoapExceptionResolverTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.endpoint; + +import java.util.Locale; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.context.SoapMessageContext; +import org.springframework.ws.soap.soap11.Soap11Body; + +public class SimpleSoapExceptionResolverTest extends TestCase { + + private SimpleSoapExceptionResolver exceptionResolver; + + private MockControl contextControl; + + private SoapMessageContext contextMock; + + private MockControl messageControl; + + private SoapMessage messageMock; + + private MockControl bodyControl; + + private Soap11Body bodyMock; + + protected void setUp() throws Exception { + exceptionResolver = new SimpleSoapExceptionResolver(); + contextControl = MockControl.createControl(SoapMessageContext.class); + contextMock = (SoapMessageContext) contextControl.getMock(); + messageControl = MockControl.createControl(SoapMessage.class); + messageMock = (SoapMessage) messageControl.getMock(); + bodyControl = MockControl.createControl(Soap11Body.class); + bodyControl.setDefaultMatcher(MockControl.ARRAY_MATCHER); + bodyMock = (Soap11Body) bodyControl.getMock(); + } + + public void testResolveExceptionInternal() throws Exception { + Exception exception = new Exception("message"); + contextControl.expectAndReturn(contextMock.getSoapResponse(), messageMock); + messageControl.expectAndReturn(messageMock.getSoapBody(), bodyMock); + bodyControl.expectAndReturn(bodyMock.addServerOrReceiverFault(exception.getMessage(), Locale.ENGLISH), null); + contextControl.replay(); + messageControl.replay(); + bodyControl.replay(); + boolean result = exceptionResolver.resolveExceptionInternal(contextMock, null, exception); + assertTrue("Invalid result", result); + contextControl.verify(); + messageControl.verify(); + bodyControl.verify(); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/endpoint/SoapFaultDefinitionEditorTest.java b/core/src/test/java/org/springframework/ws/soap/endpoint/SoapFaultDefinitionEditorTest.java new file mode 100644 index 00000000..6d2b2ca3 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/endpoint/SoapFaultDefinitionEditorTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.endpoint; + +import java.util.Locale; +import javax.xml.namespace.QName; + +import junit.framework.TestCase; + +public class SoapFaultDefinitionEditorTest extends TestCase { + + private SoapFaultDefinitionEditor editor; + + protected void setUp() throws Exception { + editor = new SoapFaultDefinitionEditor(); + } + + public void testSetAsTextNoLocale() throws Exception { + editor.setAsText("Server, Server error"); + SoapFaultDefinition definition = (SoapFaultDefinition) editor.getValue(); + assertNotNull("fault not set", definition); + assertEquals("Invalid fault code", new QName("Server"), definition.getFaultCode()); + assertEquals("Invalid fault string", "Server error", definition.getFaultStringOrReason()); + assertEquals("Invalid fault string locale", Locale.ENGLISH, definition.getLocale()); + } + + public void testSetAsTextLocale() throws Exception { + editor.setAsText("Server, Server error, nl"); + SoapFaultDefinition definition = (SoapFaultDefinition) editor.getValue(); + assertNotNull("fault not set", definition); + assertEquals("Invalid fault code", new QName("Server"), definition.getFaultCode()); + assertEquals("Invalid fault string", "Server error", definition.getFaultStringOrReason()); + assertEquals("Invalid fault string locale", new Locale("nl"), definition.getLocale()); + } + + public void testSetAsTextSender() throws Exception { + editor.setAsText("SENDER, Server error"); + SoapFaultDefinition definition = (SoapFaultDefinition) editor.getValue(); + assertNotNull("fault not set", definition); + assertEquals("Invalid fault code", SoapFaultDefinition.SENDER, definition.getFaultCode()); + assertEquals("Invalid fault string", "Server error", definition.getFaultStringOrReason()); + } + + public void testSetAsTextReceiver() throws Exception { + editor.setAsText("RECEIVER, Server error"); + SoapFaultDefinition definition = (SoapFaultDefinition) editor.getValue(); + assertNotNull("fault not set", definition); + assertEquals("Invalid fault code", SoapFaultDefinition.RECEIVER, definition.getFaultCode()); + assertEquals("Invalid fault string", "Server error", definition.getFaultStringOrReason()); + } + + public void testSetAsTextIllegalArgument() throws Exception { + try { + editor.setAsText("SOAP-ENV:Server"); + } + catch (IllegalArgumentException ex) { + } + } + + public void testSetAsTextEmpty() throws Exception { + editor.setAsText(""); + assertNull("definition not set to null", editor.getValue()); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/endpoint/SoapFaultMappingExceptionResolverTest.java b/core/src/test/java/org/springframework/ws/soap/endpoint/SoapFaultMappingExceptionResolverTest.java new file mode 100644 index 00000000..11af2477 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/endpoint/SoapFaultMappingExceptionResolverTest.java @@ -0,0 +1,175 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.endpoint; + +import java.util.Locale; +import java.util.Properties; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPMessage; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.springframework.ws.mock.MockTransportRequest; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.SoapMessageException; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.ws.soap.saaj.SaajSoapMessageContext; +import org.springframework.ws.soap.soap11.Soap11Fault; +import org.springframework.ws.soap.soap12.Soap12Fault; + +public class SoapFaultMappingExceptionResolverTest extends XMLTestCase { + + private SoapFaultMappingExceptionResolver resolver; + + protected void setUp() throws Exception { + resolver = new SoapFaultMappingExceptionResolver(); + } + + public void testGetDepth() throws Exception { + assertEquals("Invalid depth for Exception", 0, resolver.getDepth("java.lang.Exception", new Exception())); + assertEquals("Invalid depth for IllegalArgumentException", + 2, + resolver.getDepth("java.lang.Exception", new IllegalArgumentException())); + assertEquals("Invalid depth for IllegalStateException", + -1, + resolver.getDepth("IllegalArgumentException", new IllegalStateException())); + } + + public void testResolveExceptionClientSoap11() throws Exception { + Properties mappings = new Properties(); + mappings.setProperty(Exception.class.getName(), "SERVER, Server error"); + mappings.setProperty(RuntimeException.class.getName(), "CLIENT, Client error"); + resolver.setExceptionMappings(mappings); + + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage message = messageFactory.createMessage(); + SaajSoapMessageContext context = + new SaajSoapMessageContext(message, new MockTransportRequest(), messageFactory); + + boolean result = resolver.resolveException(context, null, new IllegalArgumentException("bla")); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = context.getSoapResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", + SoapVersion.SOAP_11.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Client error", fault.getFaultString()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionSenderSoap12() throws Exception { + Properties mappings = new Properties(); + mappings.setProperty(Exception.class.getName(), "RECEIVER, Receiver error, en"); + mappings.setProperty(RuntimeException.class.getName(), "SENDER, Sender error, en"); + resolver.setExceptionMappings(mappings); + + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage message = messageFactory.createMessage(); + SaajSoapMessageContext context = + new SaajSoapMessageContext(message, new MockTransportRequest(), messageFactory); + + boolean result = resolver.resolveException(context, null, new IllegalArgumentException("bla")); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = context.getSoapResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap12Fault fault = (Soap12Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", + SoapVersion.SOAP_12.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Sender error", fault.getFaultReasonText(Locale.ENGLISH)); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionServerSoap11() throws Exception { + Properties mappings = new Properties(); + mappings.setProperty(Exception.class.getName(), "CLIENT, Client error"); + mappings.setProperty(RuntimeException.class.getName(), "SERVER, Server error"); + resolver.setExceptionMappings(mappings); + + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage message = messageFactory.createMessage(); + SaajSoapMessageContext context = + new SaajSoapMessageContext(message, new MockTransportRequest(), messageFactory); + + boolean result = resolver.resolveException(context, null, new IllegalArgumentException("bla")); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = context.getSoapResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", + SoapVersion.SOAP_11.getServerOrReceiverFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Server error", fault.getFaultString()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionReceiverSoap12() throws Exception { + Properties mappings = new Properties(); + mappings.setProperty(Exception.class.getName(), "SENDER, Sender error"); + mappings.setProperty(RuntimeException.class.getName(), "RECEIVER, Receiver error"); + resolver.setExceptionMappings(mappings); + + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage message = messageFactory.createMessage(); + SaajSoapMessageContext context = + new SaajSoapMessageContext(message, new MockTransportRequest(), messageFactory); + + boolean result = resolver.resolveException(context, null, new IllegalArgumentException("bla")); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = context.getSoapResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap12Fault fault = (Soap12Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", + SoapVersion.SOAP_12.getServerOrReceiverFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "Receiver error", fault.getFaultReasonText(Locale.ENGLISH)); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + public void testResolveExceptionDefault() throws Exception { + Properties mappings = new Properties(); + mappings.setProperty(SoapMessageException.class.getName(), "SERVER,Server error"); + resolver.setExceptionMappings(mappings); + SoapFaultDefinition defaultFault = new SoapFaultDefinition(); + defaultFault.setFaultCode(SoapFaultDefinition.CLIENT); + defaultFault.setFaultStringOrReason("faultstring"); + resolver.setDefaultFault(defaultFault); + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + SOAPMessage message = messageFactory.createMessage(); + SaajSoapMessageContext context = + new SaajSoapMessageContext(message, new MockTransportRequest(), messageFactory); + + boolean result = resolver.resolveException(context, null, new IllegalArgumentException("bla")); + assertTrue("resolveException returns false", result); + assertTrue("Context has no response", context.hasResponse()); + SoapMessage response = context.getSoapResponse(); + assertTrue("Resonse has no fault", response.getSoapBody().hasFault()); + Soap11Fault fault = (Soap11Fault) response.getSoapBody().getFault(); + assertEquals("Invalid fault code on fault", + SoapVersion.SOAP_11.getClientOrSenderFaultName(), + fault.getFaultCode()); + assertEquals("Invalid fault string on fault", "faultstring", fault.getFaultString()); + assertNull("Detail on fault", fault.getFaultDetail()); + } + + +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/endpoint/interceptor/SoapEnvelopeLoggingInterceptorTest.java b/core/src/test/java/org/springframework/ws/soap/endpoint/interceptor/SoapEnvelopeLoggingInterceptorTest.java new file mode 100644 index 00000000..83539e57 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/endpoint/interceptor/SoapEnvelopeLoggingInterceptorTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.endpoint.interceptor; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPMessage; + +import junit.framework.TestCase; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.springframework.ws.mock.MockTransportRequest; +import org.springframework.ws.soap.saaj.SaajSoapMessageContext; + +public class SoapEnvelopeLoggingInterceptorTest extends TestCase { + + private SoapEnvelopeLoggingInterceptor interceptor; + + private CountingAppender appender; + + private SaajSoapMessageContext messageContext; + + protected void setUp() throws Exception { + interceptor = new SoapEnvelopeLoggingInterceptor(); + appender = new SoapEnvelopeLoggingInterceptorTest.CountingAppender(); + BasicConfigurator.configure(appender); + Logger.getRootLogger().setLevel(Level.DEBUG); + MessageFactory messageFactory = MessageFactory.newInstance(); + SOAPMessage saajMessage = messageFactory.createMessage(); + messageContext = new SaajSoapMessageContext(saajMessage, new MockTransportRequest(), messageFactory); + appender.reset(); + } + + public void testHandleRequestDisabled() throws Exception { + interceptor.setLogRequest(false); + int eventCount = appender.getCount(); + interceptor.handleRequest(messageContext, null); + assertEquals("interceptor logged when disabled", appender.getCount(), eventCount); + } + + public void testHandleRequestEnabled() throws Exception { + int eventCount = appender.getCount(); + interceptor.handleRequest(messageContext, null); + assertTrue("interceptor did not log", appender.getCount() > eventCount); + } + + public void testHandleResponseDisabled() throws Exception { + messageContext.getResponse(); + interceptor.setLogResponse(false); + int eventCount = appender.getCount(); + interceptor.handleResponse(messageContext, null); + assertEquals("interceptor logged when disabled", appender.getCount(), eventCount); + } + + public void testHandleResponseEnabled() throws Exception { + messageContext.getResponse(); + int eventCount = appender.getCount(); + interceptor.handleResponse(messageContext, null); + assertTrue("interceptor did not log", appender.getCount() > eventCount); + } + + public void testHandleFaultDisabled() throws Exception { + messageContext.getResponse(); + interceptor.setLogFault(false); + int eventCount = appender.getCount(); + interceptor.handleFault(messageContext, null); + assertEquals("interceptor logged when disabled", appender.getCount(), eventCount); + } + + public void testHandleFaultEnabled() throws Exception { + messageContext.getResponse(); + int eventCount = appender.getCount(); + interceptor.handleResponse(messageContext, null); + assertTrue("interceptor did not log", appender.getCount() > eventCount); + } + + private static class CountingAppender extends AppenderSkeleton { + + private int count; + + public int getCount() { + return count; + } + + public void reset() { + count = 0; + } + + protected void append(LoggingEvent loggingEvent) { + count++; + } + + public boolean requiresLayout() { + return false; + } + + public void close() { + } + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/endpoint/mapping/DelegatingSoapEndpointMappingTest.java b/core/src/test/java/org/springframework/ws/soap/endpoint/mapping/DelegatingSoapEndpointMappingTest.java new file mode 100644 index 00000000..d15ea3b0 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/endpoint/mapping/DelegatingSoapEndpointMappingTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.endpoint.mapping; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.springframework.ws.EndpointInvocationChain; +import org.springframework.ws.EndpointMapping; +import org.springframework.ws.mock.MockMessageContext; +import org.springframework.ws.soap.SoapEndpointInvocationChain; + +public class DelegatingSoapEndpointMappingTest extends TestCase { + + private DelegatingSoapEndpointMapping endpointMapping; + + private MockControl control; + + private EndpointMapping mock; + + protected void setUp() throws Exception { + endpointMapping = new DelegatingSoapEndpointMapping(); + control = MockControl.createControl(EndpointMapping.class); + mock = (EndpointMapping) control.getMock(); + endpointMapping.setDelegate(mock); + } + + public void testGetEndpointMapping() throws Exception { + String role = "http://www.springframework.org/spring-ws/role"; + endpointMapping.setActorOrRole(role); + MockMessageContext context = new MockMessageContext(); + EndpointInvocationChain delegateChain = new EndpointInvocationChain(new Object()); + control.expectAndReturn(mock.getEndpoint(context), delegateChain); + control.replay(); + SoapEndpointInvocationChain resultChain = (SoapEndpointInvocationChain) endpointMapping.getEndpoint(context); + assertNotNull("No chain returned", resultChain); + assertEquals("Invalid ampount of roles returned", 1, resultChain.getActorsOrRoles().length); + assertEquals("Invalid role returned", role, resultChain.getActorsOrRoles()[0]); + control.verify(); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/endpoint/mapping/SoapActionEndpointMappingTest.java b/core/src/test/java/org/springframework/ws/soap/endpoint/mapping/SoapActionEndpointMappingTest.java new file mode 100644 index 00000000..693ad8b0 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/endpoint/mapping/SoapActionEndpointMappingTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.endpoint.mapping; + +import junit.framework.TestCase; +import org.springframework.ws.mock.MockMessageContext; +import org.springframework.ws.mock.MockTransportContext; +import org.springframework.ws.mock.MockTransportRequest; +import org.springframework.ws.mock.MockWebServiceMessage; + +public class SoapActionEndpointMappingTest extends TestCase { + + private SoapActionEndpointMapping mapping; + + private MockTransportRequest request; + + private MockMessageContext context; + + protected void setUp() throws Exception { + request = new MockTransportRequest(); + MockTransportContext transportContext = new MockTransportContext(request); + context = new MockMessageContext(new MockWebServiceMessage(), transportContext); + mapping = new SoapActionEndpointMapping(); + } + + public void testGetLookupKeyForMessage() throws Exception { + String soapAction = "http://springframework.org/spring-ws/SoapAction"; + request.addHeader(SoapActionEndpointMapping.SOAP_ACTION_HEADER, soapAction); + assertEquals("Invalid lookup key", soapAction, mapping.getLookupKeyForMessage(context)); + } + + public void testGetLookupKeyForMessageQuoted() throws Exception { + String soapAction = "http://springframework.org/spring-ws/SoapAction"; + request.addHeader(SoapActionEndpointMapping.SOAP_ACTION_HEADER, "\"" + soapAction + "\""); + assertEquals("Invalid lookup key", soapAction, mapping.getLookupKeyForMessage(context)); + } + + public void testValidateLookupKey() throws Exception { + mapping.validateLookupKey("SoapAction"); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajAttachmentTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajAttachmentTest.java new file mode 100644 index 00000000..e88cb174 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajAttachmentTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import java.io.IOException; +import java.io.InputStream; +import javax.xml.soap.AttachmentPart; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; + +import junit.framework.TestCase; +import org.springframework.util.FileCopyUtils; +import org.springframework.ws.soap.Attachment; + +/** + * @author Arjen Poutsma + */ +public class SaajAttachmentTest extends TestCase { + + private static final String CONTENTS = "attachment contents"; + + private static final String CONTENT_ID = "contentId"; + + private AttachmentPart saajAttachment; + + private Attachment attachment; + + protected void setUp() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(); + SOAPMessage saajMessage = messageFactory.createMessage(); + saajAttachment = saajMessage.createAttachmentPart(CONTENTS, "text/plain"); + saajAttachment.setContentId(CONTENT_ID); + saajMessage.addAttachmentPart(saajAttachment); + SaajSoapMessage saajSoapMessage = new SaajSoapMessage(saajMessage); + attachment = saajSoapMessage.getAttachment(CONTENT_ID); + } + + public void testGetSize() throws SOAPException { + assertEquals("Invalid size", saajAttachment.getSize(), attachment.getSize()); + } + + public void testContentId() { + assertEquals("Invalid content id", CONTENT_ID, attachment.getId()); + String id = "123"; + attachment.setId(id); + assertEquals("Invalid content id", id, attachment.getId()); + } + + public void testGetContentType() { + assertEquals("Invalid content type", "text/plain", attachment.getContentType()); + } + + public void testGetInputStream() throws IOException { + InputStream inputStream = attachment.getInputStream(); + assertNotNull("Invalid input stream returned", inputStream); + byte[] bytes = FileCopyUtils.copyToByteArray(inputStream); + assertEquals("Invalid size", CONTENTS.getBytes().length, bytes.length); + } + + public void testGetEmptyInputStream() throws IOException { + saajAttachment.clearContent(); + InputStream inputStream = attachment.getInputStream(); + assertNotNull("Invalid input stream returned", inputStream); + byte[] bytes = FileCopyUtils.copyToByteArray(inputStream); + assertEquals("Invalid size", 0, bytes.length); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11BodyTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11BodyTest.java new file mode 100644 index 00000000..12bbaab5 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11BodyTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPMessage; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.soap11.AbstractSoap11BodyTestCase; + +public class SaajSoap11BodyTest extends AbstractSoap11BodyTestCase { + + protected SoapBody createSoapBody() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(); + SOAPMessage saajMessage = messageFactory.createMessage(); + SaajSoapMessage saajSoapMessage = new SaajSoapMessage(saajMessage); + return saajSoapMessage.getSoapBody(); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11EnvelopeTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11EnvelopeTest.java new file mode 100644 index 00000000..8a5e47dc --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11EnvelopeTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPMessage; + +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.soap11.AbstractSoap11EnvelopeTestCase; + +public class SaajSoap11EnvelopeTest extends AbstractSoap11EnvelopeTestCase { + + protected SoapEnvelope createSoapEnvelope() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(); + SOAPMessage saajMessage = messageFactory.createMessage(); + SaajSoapMessage saajSoapMessage = new SaajSoapMessage(saajMessage); + return saajSoapMessage.getEnvelope(); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11HeaderTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11HeaderTest.java new file mode 100644 index 00000000..0cb04fac --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11HeaderTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPMessage; + +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.soap11.AbstractSoap11HeaderTestCase; + +public class SaajSoap11HeaderTest extends AbstractSoap11HeaderTestCase { + + protected SoapHeader createSoapHeader() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(); + SOAPMessage saajMessage = messageFactory.createMessage(); + SaajSoapMessage saajSoapMessage = new SaajSoapMessage(saajMessage); + return saajSoapMessage.getSoapHeader(); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageContextFactoryTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageContextFactoryTest.java new file mode 100644 index 00000000..ad954d37 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageContextFactoryTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.saaj; + +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.soap.context.AbstractSoap11MessageContextFactoryTestCase; + +public class SaajSoap11MessageContextFactoryTest extends AbstractSoap11MessageContextFactoryTestCase { + + protected MessageContextFactory createSoapMessageContextFactory() { + return new SaajSoapMessageContextFactory(); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageContextTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageContextTest.java new file mode 100644 index 00000000..8301b402 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageContextTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPException; + +import org.springframework.ws.soap.context.AbstractSoap11MessageContextTestCase; +import org.springframework.ws.soap.context.SoapMessageContext; +import org.springframework.ws.transport.TransportRequest; + +public class SaajSoap11MessageContextTest extends AbstractSoap11MessageContextTestCase { + + protected SoapMessageContext createMessageContext(TransportRequest transportRequest) throws SOAPException { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); + return new SaajSoapMessageContext(messageFactory.createMessage(), transportRequest, messageFactory); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageTest.java new file mode 100644 index 00000000..cc70b0bb --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap11MessageTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPMessage; +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.soap11.AbstractSoap11MessageTestCase; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public class SaajSoap11MessageTest extends AbstractSoap11MessageTestCase { + + private SOAPMessage saajMessage; + + protected SoapMessage createSoapMessage() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(); + saajMessage = messageFactory.createMessage(); + return new SaajSoapMessage(saajMessage); + } + + public void testGetPayloadSource() throws Exception { + saajMessage.getSOAPBody().addChildElement("child"); + Source source = soapMessage.getPayloadSource(); + StringResult result = new StringResult(); + transformer.transform(source, result); + assertXMLEqual("Invalid source", "", result.toString()); + } + + public void testGetPayloadSourceText() throws Exception { + saajMessage.getSOAPBody().addTextNode(" "); + saajMessage.getSOAPBody().addChildElement("child"); + Source source = soapMessage.getPayloadSource(); + StringResult result = new StringResult(); + transformer.transform(source, result); + assertXMLEqual("Invalid source", "", result.toString()); + } + + public void testGetPayloadResult() throws Exception { + StringSource source = new StringSource(""); + Result result = soapMessage.getPayloadResult(); + transformer.transform(source, result); + assertTrue("No child nodes created", saajMessage.getSOAPBody().hasChildNodes()); + assertEquals("Invalid child node created", "child", saajMessage.getSOAPBody().getFirstChild().getLocalName()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12BodyTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12BodyTest.java new file mode 100644 index 00000000..fad7ee40 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12BodyTest.java @@ -0,0 +1,20 @@ +package org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPMessage; + +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.soap12.AbstractSoap12BodyTestCase; + +public class SaajSoap12BodyTest extends AbstractSoap12BodyTestCase { + + protected SoapBody createSoapBody() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage saajMessage = messageFactory.createMessage(); + saajMessage.getSOAPPart().getEnvelope().setPrefix("soapenv"); + SaajSoapMessage saajSoapMessage = new SaajSoapMessage(saajMessage); + return saajSoapMessage.getSoapBody(); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12EnvelopeTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12EnvelopeTest.java new file mode 100644 index 00000000..ae671dbf --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12EnvelopeTest.java @@ -0,0 +1,18 @@ +package org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPMessage; + +import org.springframework.ws.soap.SoapEnvelope; +import org.springframework.ws.soap.soap12.AbstractSoap12EnvelopeTestCase; + +public class SaajSoap12EnvelopeTest extends AbstractSoap12EnvelopeTestCase { + + protected SoapEnvelope createSoapEnvelope() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage saajMessage = messageFactory.createMessage(); + SaajSoapMessage saajSoapMessage = new SaajSoapMessage(saajMessage); + return saajSoapMessage.getEnvelope(); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12HeaderTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12HeaderTest.java new file mode 100644 index 00000000..fe5d8aff --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12HeaderTest.java @@ -0,0 +1,18 @@ +package org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPMessage; + +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.soap12.AbstractSoap12HeaderTestCase; + +public class SaajSoap12HeaderTest extends AbstractSoap12HeaderTestCase { + + protected SoapHeader createSoapHeader() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage saajMessage = messageFactory.createMessage(); + SaajSoapMessage saajSoapMessage = new SaajSoapMessage(saajMessage); + return saajSoapMessage.getSoapHeader(); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12MessageContextFactoryTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12MessageContextFactoryTest.java new file mode 100644 index 00000000..91ddd5ee --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12MessageContextFactoryTest.java @@ -0,0 +1,16 @@ +package org.springframework.ws.soap.saaj; + +import javax.xml.soap.SOAPConstants; + +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.soap.context.AbstractSoap12MessageContextFactoryTestCase; + +public class SaajSoap12MessageContextFactoryTest extends AbstractSoap12MessageContextFactoryTestCase { + + protected MessageContextFactory createSoapMessageContextFactory() throws Exception { + SaajSoapMessageContextFactory contextFactory = new SaajSoapMessageContextFactory(); + contextFactory.setSoapProtocol(SOAPConstants.SOAP_1_2_PROTOCOL); + return contextFactory; + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12MessageTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12MessageTest.java new file mode 100644 index 00000000..7edf10bc --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/SaajSoap12MessageTest.java @@ -0,0 +1,49 @@ +package org.springframework.ws.soap.saaj; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPMessage; +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.soap12.AbstractSoap12MessageTestCase; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public class SaajSoap12MessageTest extends AbstractSoap12MessageTestCase { + + private SOAPMessage saajMessage; + + protected SoapMessage createSoapMessage() throws Exception { + MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + saajMessage = messageFactory.createMessage(); + return new SaajSoapMessage(saajMessage); + } + + public void testGetPayloadSource() throws Exception { + saajMessage.getSOAPBody().addChildElement("child"); + Source source = soapMessage.getPayloadSource(); + StringResult result = new StringResult(); + transformer.transform(source, result); + assertXMLEqual("Invalid source", "", result.toString()); + } + + public void testGetPayloadSourceText() throws Exception { + saajMessage.getSOAPBody().addTextNode(" "); + saajMessage.getSOAPBody().addChildElement("child"); + Source source = soapMessage.getPayloadSource(); + StringResult result = new StringResult(); + transformer.transform(source, result); + assertXMLEqual("Invalid source", "", result.toString()); + } + + public void testGetPayloadResult() throws Exception { + StringSource source = new StringSource(""); + Result result = soapMessage.getPayloadResult(); + transformer.transform(source, result); + assertTrue("No child nodes created", saajMessage.getSOAPBody().hasChildNodes()); + assertEquals("Invalid child node created", "child", saajMessage.getSOAPBody().getFirstChild().getLocalName()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/saaj/support/SaajUtilsTest.java b/core/src/test/java/org/springframework/ws/soap/saaj/support/SaajUtilsTest.java new file mode 100644 index 00000000..223ad9b5 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/saaj/support/SaajUtilsTest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.saaj.support; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPMessage; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.StringUtils; +import org.w3c.dom.Document; + +public class SaajUtilsTest extends XMLTestCase { + + private MessageFactory messageFactory; + + protected void setUp() throws Exception { + messageFactory = MessageFactory.newInstance(); + } + + public void testToName() throws Exception { + SOAPMessage message = messageFactory.createMessage(); + QName qName = new QName("localPart"); + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name name = SaajUtils.toName(qName, envelope, envelope); + assertNotNull("Invalid name", name); + assertEquals("Invalid local part", qName.getLocalPart(), name.getLocalName()); + assertFalse("Invalid prefix", StringUtils.hasLength(name.getPrefix())); + assertFalse("Invalid namespace", StringUtils.hasLength(name.getURI())); + } + + public void testToNameNamespace() throws Exception { + SOAPMessage message = messageFactory.createMessage(); + QName qName = new QName("namespace", "localPart"); + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + envelope.addNamespaceDeclaration("prefix", "namespace"); + Name name = SaajUtils.toName(qName, envelope, envelope); + assertNotNull("Invalid name", name); + assertEquals("Invalid namespace", qName.getNamespaceURI(), name.getURI()); + assertEquals("Invalid local part", qName.getLocalPart(), name.getLocalName()); + assertEquals("Invalid prefix", "prefix", name.getPrefix()); + } + + public void testToNameNamespacePrefix() throws Exception { + SOAPMessage message = messageFactory.createMessage(); + QName qName = new QName("namespace", "localPart", "prefix"); + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name name = SaajUtils.toName(qName, envelope, envelope); + assertNotNull("Invalid name", name); + assertEquals("Invalid namespace", qName.getNamespaceURI(), name.getURI()); + assertEquals("Invalid local part", qName.getLocalPart(), name.getLocalName()); + assertEquals("Invalid prefix", qName.getPrefix(), name.getPrefix()); + } + + public void testToQName() throws Exception { + SOAPMessage message = messageFactory.createMessage(); + Name name = message.getSOAPPart().getEnvelope().createName("localPart", null, null); + QName qName = SaajUtils.toQName(name); + assertNotNull("Invalid qName", qName); + assertEquals("Invalid namespace", name.getURI(), qName.getNamespaceURI()); + assertFalse("Invalid prefix", StringUtils.hasLength(qName.getPrefix())); + assertFalse("Invalid namespace", StringUtils.hasLength(qName.getNamespaceURI())); + } + + public void testToQNameNamespace() throws Exception { + SOAPMessage message = messageFactory.createMessage(); + Name name = message.getSOAPPart().getEnvelope().createName("localPart", null, "namespace"); + QName qName = SaajUtils.toQName(name); + assertNotNull("Invalid qName", qName); + assertEquals("Invalid namespace", name.getURI(), qName.getNamespaceURI()); + assertEquals("Invalid local part", name.getLocalName(), qName.getLocalPart()); + assertFalse("Invalid prefix", StringUtils.hasLength(qName.getPrefix())); + } + + public void testToQNamePrefixNamespace() throws Exception { + SOAPMessage message = messageFactory.createMessage(); + Name name = message.getSOAPPart().getEnvelope().createName("localPart", "prefix", "namespace"); + QName qName = SaajUtils.toQName(name); + assertNotNull("Invalid qName", qName); + assertEquals("Invalid namespace", name.getURI(), qName.getNamespaceURI()); + assertEquals("Invalid local part", name.getLocalName(), qName.getLocalPart()); + assertEquals("Invalid prefix", name.getPrefix(), qName.getPrefix()); + } + + public void testLoadMessage() throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(getClass().getResourceAsStream("soapMessage.xml")); + SOAPMessage soapMessage = + SaajUtils.loadMessage(new ClassPathResource("soapMessage.xml", getClass()), messageFactory); + assertXMLEqual(soapMessage.getSOAPPart(), document); + } + + public void testGetSaajVersion() throws Exception { + assertEquals("Invalid SAAJ version", SaajUtils.SAAJ_13, SaajUtils.getSaajVersion()); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11BodyTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11BodyTestCase.java new file mode 100644 index 00000000..4c6c9315 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11BodyTestCase.java @@ -0,0 +1,108 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap11; + +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.AbstractSoapBodyTestCase; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.SoapFaultDetailElement; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public abstract class AbstractSoap11BodyTestCase extends AbstractSoapBodyTestCase { + + public void testGetName() throws Exception { + assertEquals("Invalid qualified name", SoapVersion.SOAP_11.getBodyName(), soapBody.getName()); + } + + public void testGetSource() throws Exception { + StringResult result = new StringResult(); + transformer.transform(soapBody.getSource(), result); + assertXMLEqual("Invalid contents of body", + "", + result.toString()); + } + + public void testAddMustUnderstandFault() throws Exception { + SoapFault fault = soapBody.addMustUnderstandFault("SOAP Must Understand Error", null); + assertEquals("Invalid fault code", + new QName("http://schemas.xmlsoap.org/soap/envelope/", "MustUnderstand"), + fault.getFaultCode()); + assertPayloadEqual("" + + "SOAP-ENV:MustUnderstand" + + "SOAP Must Understand Error"); + } + + public void testAddClientFault() throws Exception { + SoapFault fault = soapBody.addClientOrSenderFault("faultString", null); + assertEquals("Invalid fault code", + new QName("http://schemas.xmlsoap.org/soap/envelope/", "Client"), + fault.getFaultCode()); + assertPayloadEqual("" + + "SOAP-ENV:Client" + "faultString" + + ""); + } + + public void testAddServerFault() throws Exception { + SoapFault fault = soapBody.addServerOrReceiverFault("faultString", null); + assertEquals("Invalid fault code", + new QName("http://schemas.xmlsoap.org/soap/envelope/", "Server"), + fault.getFaultCode()); + assertPayloadEqual("" + + "SOAP-ENV:Server" + "faultString" + + ""); + } + + public void testAddFault() throws Exception { + QName faultCode = new QName("http://www.springframework.org", "fault", "spring"); + String faultString = "faultString"; + Soap11Fault fault = ((Soap11Body) soapBody).addFault(faultCode, "faultString", Locale.ENGLISH); + assertNotNull("Null returned", fault); + assertTrue("SoapBody has no fault", soapBody.hasFault()); + assertNotNull("SoapBody has no fault", soapBody.getFault()); + assertEquals("Invalid fault code", faultCode, fault.getFaultCode()); + assertEquals("Invalid fault string", faultString, fault.getFaultString()); + assertEquals("Invalid fault string locale", Locale.ENGLISH, fault.getFaultStringLocale()); + String actor = "http://www.springframework.org/actor"; + fault.setFaultActorOrRole(actor); + assertEquals("Invalid fault actor", actor, fault.getFaultActorOrRole()); + assertPayloadEqual("" + "spring:fault" + + "" + faultString + "" + "" + actor + + "" + ""); + } + + public void testAddFaultWithDetail() throws Exception { + SoapFault fault = ((Soap11Body) soapBody) + .addFault(new QName("namespace", "localPart", "prefix"), "Fault", null); + SoapFaultDetail detail = fault.addFaultDetail(); + SoapFaultDetailElement detailElement = + detail.addFaultDetailElement(new QName("namespace", "localPart", "prefix")); + StringSource detailContents = new StringSource(""); + transformer.transform(detailContents, detailElement.getResult()); + assertPayloadEqual("" + + "prefix:localPart" + + "Fault" + + "" + + ""); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11EnvelopeTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11EnvelopeTestCase.java new file mode 100644 index 00000000..1b3e5564 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11EnvelopeTestCase.java @@ -0,0 +1,40 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap11; + +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.AbstractSoapEnvelopeTestCase; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.xml.transform.StringResult; + +public abstract class AbstractSoap11EnvelopeTestCase extends AbstractSoapEnvelopeTestCase { + + public void testGetName() throws Exception { + assertEquals("Invalid qualified name", + new QName(SoapVersion.SOAP_11.getEnvelopeNamespaceUri(), "Envelope"), + soapEnvelope.getName()); + } + + public void testGetContent() throws Exception { + StringResult result = new StringResult(); + transformer.transform(soapEnvelope.getSource(), result); + assertXMLEqual("
", + result.toString()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11HeaderTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11HeaderTestCase.java new file mode 100644 index 00000000..75a6fa6d --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11HeaderTestCase.java @@ -0,0 +1,41 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap11; + +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.AbstractSoapHeaderTestCase; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.xml.transform.StringResult; + +public abstract class AbstractSoap11HeaderTestCase extends AbstractSoapHeaderTestCase { + + public void testGetName() throws Exception { + assertEquals("Invalid qualified name", + new QName(SoapVersion.SOAP_11.getEnvelopeNamespaceUri(), "Header"), + soapHeader.getName()); + } + + public void testGetSource() throws Exception { + StringResult result = new StringResult(); + transformer.transform(soapHeader.getSource(), result); + assertXMLEqual("Invalid contents of header", + "", + result.toString()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11MessageTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11MessageTestCase.java new file mode 100644 index 00000000..69c4dca6 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11MessageTestCase.java @@ -0,0 +1,43 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap11; + +import java.io.ByteArrayOutputStream; + +import junit.framework.Assert; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.ws.soap.AbstractSoapMessageTestCase; +import org.springframework.ws.soap.SoapVersion; + +public abstract class AbstractSoap11MessageTestCase extends AbstractSoapMessageTestCase { + + protected final Resource[] getSoapSchemas() { + return new Resource[]{new ClassPathResource("soap11.xsd", AbstractSoap11MessageTestCase.class)}; + } + + public void testGetVersion() throws Exception { + Assert.assertEquals("Invalid SOAP version", SoapVersion.SOAP_11, soapMessage.getVersion()); + } + + public void testWriteToOutputStream() throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + soapMessage.writeTo(outputStream); + assertXMLEqual("
", + new String(outputStream.toByteArray(), "UTF-8")); + } +} diff --git a/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12BodyTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12BodyTestCase.java new file mode 100644 index 00000000..f2a96d15 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12BodyTestCase.java @@ -0,0 +1,129 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap12; + +import java.util.Iterator; +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.AbstractSoapBodyTestCase; +import org.springframework.ws.soap.SoapFault; +import org.springframework.ws.soap.SoapFaultDetail; +import org.springframework.ws.soap.SoapFaultDetailElement; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public abstract class AbstractSoap12BodyTestCase extends AbstractSoapBodyTestCase { + + public void testGetName() throws Exception { + assertEquals("Invalid qualified name", new QName(SoapVersion.SOAP_12.getEnvelopeNamespaceUri(), "Body"), + soapBody.getName()); + } + + public void testGetSource() throws Exception { + StringResult result = new StringResult(); + transformer.transform(soapBody.getSource(), result); + assertXMLEqual("Invalid contents of body", "", + result.toString()); + } + + public void testAddMustUnderstandFault() throws Exception { + SoapFault fault = soapBody.addMustUnderstandFault("SOAP Must Understand Error", Locale.ENGLISH); + assertEquals("Invalid fault code", new QName("http://www.w3.org/2003/05/soap-envelope", "MustUnderstand"), + fault.getFaultCode()); + StringResult result = new StringResult(); + transformer.transform(fault.getSource(), result); + assertXMLEqual("Invalid contents of body", + "" + + "" + soapBody.getName().getPrefix() + + ":MustUnderstand" + + "SOAP Must Understand Error" + + "", result.toString()); + } + + public void testAddSenderFault() throws Exception { + SoapFault fault = soapBody.addClientOrSenderFault("faultString", Locale.ENGLISH); + assertEquals("Invalid fault code", new QName("http://www.w3.org/2003/05/soap-envelope", "Sender"), + fault.getFaultCode()); + StringResult result = new StringResult(); + transformer.transform(fault.getSource(), result); + assertXMLEqual("Invalid contents of body", + "" + + "" + soapBody.getName().getPrefix() + + ":Sender" + + "faultString" + + "", result.toString()); + } + + public void testAddReceiverFault() throws Exception { + SoapFault fault = soapBody.addServerOrReceiverFault("faultString", Locale.ENGLISH); + assertEquals("Invalid fault code", new QName("http://www.w3.org/2003/05/soap-envelope", "Receiver"), + fault.getFaultCode()); + StringResult result = new StringResult(); + transformer.transform(fault.getSource(), result); + assertXMLEqual("Invalid contents of body", + "" + + "" + soapBody.getName().getPrefix() + + ":Receiver" + + "faultString" + + "", result.toString()); + } + + public void testAddFaultWithDetail() throws Exception { + SoapFault fault = soapBody.addServerOrReceiverFault("faultString", Locale.ENGLISH); + SoapFaultDetail detail = fault.addFaultDetail(); + SoapFaultDetailElement detailElement = + detail.addFaultDetailElement(new QName("namespace", "localPart", "prefix")); + StringSource detailContents = new StringSource(""); + transformer.transform(detailContents, detailElement.getResult()); + StringResult result = new StringResult(); + transformer.transform(fault.getSource(), result); + assertXMLEqual("Invalid source for body", + "" + + "" + soapBody.getName().getPrefix() + ":Receiver" + + "" + + "faultString" + + "" + + "", result.toString()); + } + + public void testAddFaultWithSubcode() throws Exception { + Soap12Fault fault = (Soap12Fault) soapBody.addServerOrReceiverFault("faultString", Locale.ENGLISH); + QName subcode1 = new QName("http://www.springframework.org", "Subcode1", "spring-ws"); + fault.addFaultSubcode(subcode1); + QName subcode2 = new QName("http://www.springframework.org", "Subcode2", "spring-ws"); + fault.addFaultSubcode(subcode2); + Iterator iterator = fault.getFaultSubcodes(); + assertTrue("No subcode found", iterator.hasNext()); + assertEquals("Invalid subcode", subcode1, iterator.next()); + assertTrue("No subcode found", iterator.hasNext()); + assertEquals("Invalid subcode", subcode2, iterator.next()); + assertFalse("Subcode found", iterator.hasNext()); + StringResult result = new StringResult(); + transformer.transform(fault.getSource(), result); + assertXMLEqual("Invalid source for body", + "" + + "" + soapBody.getName().getPrefix() + ":Receiver" + + "spring-ws:Subcode1" + + "spring-ws:Subcode2" + + "" + + "faultString" + + "", result.toString()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12EnvelopeTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12EnvelopeTestCase.java new file mode 100644 index 00000000..97430457 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12EnvelopeTestCase.java @@ -0,0 +1,39 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap12; + +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.AbstractSoapEnvelopeTestCase; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.xml.transform.StringResult; + +public abstract class AbstractSoap12EnvelopeTestCase extends AbstractSoapEnvelopeTestCase { + + public void testGetName() throws Exception { + assertEquals("Invalid qualified name", new QName(SoapVersion.SOAP_12.getEnvelopeNamespaceUri(), "Envelope"), + soapEnvelope.getName()); + } + + public void testGetSource() throws Exception { + StringResult result = new StringResult(); + transformer.transform(soapEnvelope.getSource(), result); + assertXMLEqual("
" + "", + result.toString()); + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12HeaderTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12HeaderTestCase.java new file mode 100644 index 00000000..18a6e3a0 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12HeaderTestCase.java @@ -0,0 +1,70 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap12; + +import javax.xml.namespace.QName; + +import org.springframework.ws.soap.AbstractSoapHeaderTestCase; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapVersion; +import org.springframework.xml.transform.StringResult; + +public abstract class AbstractSoap12HeaderTestCase extends AbstractSoapHeaderTestCase { + + public void testGetName() throws Exception { + assertEquals("Invalid qualified name", new QName(SoapVersion.SOAP_12.getEnvelopeNamespaceUri(), "Header"), + soapHeader.getName()); + } + + public void testGetSource() throws Exception { + StringResult result = new StringResult(); + transformer.transform(soapHeader.getSource(), result); + assertXMLEqual("Invalid contents of header", "
", + result.toString()); + } + + public void testAddNotUnderstood() throws Exception { + Soap12Header soap12Header = (Soap12Header) soapHeader; + QName headerName = new QName("http://www.springframework.org", "NotUnderstood", "spring-ws"); + soap12Header.addNotUnderstoodHeaderElement(headerName); + StringResult result = new StringResult(); + transformer.transform(soapHeader.getSource(), result); + assertXMLEqual("Invalid contents of header", "
" + + "" + + "
", result.toString()); + } + + public void testAddUpgrade() throws Exception { + String[] supportedUris = + new String[]{"http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2003/05/soap-envelope"}; + Soap12Header soap12Header = (Soap12Header) soapHeader; + SoapHeaderElement header = soap12Header.addUpgradeHeaderElement(supportedUris); + StringResult result = new StringResult(); + transformer.transform(soapHeader.getSource(), result); + assertEquals("Invalid name", header.getName(), new QName("http://www.w3.org/2003/05/soap-envelope", "Upgrade")); + // XMLUnit can't test this: +/* + assertXMLEqual("Invalid contents of header", "
" + + "" + + "" + + "" + + "" + + "
", result.toString()); +*/ + } + +} diff --git a/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12MessageTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12MessageTestCase.java new file mode 100644 index 00000000..410842b3 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12MessageTestCase.java @@ -0,0 +1,46 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.soap12; + +import java.io.ByteArrayOutputStream; + +import junit.framework.Assert; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.ws.soap.AbstractSoapMessageTestCase; +import org.springframework.ws.soap.SoapVersion; + +public abstract class AbstractSoap12MessageTestCase extends AbstractSoapMessageTestCase { + + public void testGetVersion() throws Exception { + Assert.assertEquals("Invalid SOAP version", SoapVersion.SOAP_12, soapMessage.getVersion()); + } + + public void testWriteTo() throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + soapMessage.writeTo(outputStream); + assertXMLEqual("
", + new String(outputStream.toByteArray(), "UTF-8")); + } + + protected final Resource[] getSoapSchemas() { + return new Resource[]{new ClassPathResource("xml.xsd", AbstractSoap12MessageTestCase.class), + new ClassPathResource("soap12.xsd", AbstractSoap12MessageTestCase.class)}; + } + + +} diff --git a/core/src/test/java/org/springframework/ws/transport/http/HttpTransportRequestTest.java b/core/src/test/java/org/springframework/ws/transport/http/HttpTransportRequestTest.java new file mode 100644 index 00000000..94c66c24 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/transport/http/HttpTransportRequestTest.java @@ -0,0 +1,59 @@ +package org.springframework.ws.transport.http; + +import java.util.Iterator; + +import junit.framework.TestCase; +import org.springframework.mock.web.MockHttpServletRequest; + +public class HttpTransportRequestTest extends TestCase { + + private HttpTransportRequest transportRequest; + + private MockHttpServletRequest mockRequest; + + protected void setUp() throws Exception { + mockRequest = new MockHttpServletRequest(); + transportRequest = new HttpTransportRequest(mockRequest); + } + + public void testGetHttpServletRequest() throws Exception { + assertEquals("Invalid request", mockRequest, transportRequest.getHttpServletRequest()); + } + + public void testGetHeaderNames() throws Exception { + mockRequest.addHeader("header1", "value11"); + mockRequest.addHeader("header1", "value12"); + mockRequest.addHeader("header2", "value2"); + Iterator headers = transportRequest.getHeaderNames(); + assertTrue("Invalid amount of header names", headers.hasNext()); + String header = (String) headers.next(); + assertTrue("Invalid header", "header1".equals(header) || "header2".equals(header)); + assertTrue("Invalid amount of header names", headers.hasNext()); + header = (String) headers.next(); + assertTrue("Invalid header", "header1".equals(header) || "header2".equals(header)); + assertFalse("Invalid amount of header names", headers.hasNext()); + } + + public void testGetHeaders() throws Exception { + mockRequest.addHeader("header", "value1"); + mockRequest.addHeader("header", "value2"); + Iterator values = transportRequest.getHeaders("header"); + assertTrue("Invalid amount of header names", values.hasNext()); + String value = (String) values.next(); + assertTrue("Invalid value", "value1".equals(value) || "value2".equals(value)); + assertTrue("Invalid amount of header names", values.hasNext()); + value = (String) values.next(); + assertTrue("Invalid value", "value1".equals(value) || "value2".equals(value)); + assertFalse("Invalid amount of header names", values.hasNext()); + } + + public void testGetURL() throws Exception { + mockRequest.setScheme("http"); + mockRequest.setServerName("www.example.com"); + mockRequest.setServerPort(8080); + mockRequest.setRequestURI("/services/Service"); + String url = transportRequest.getUrl(); + assertNotNull("No url returned", url); + assertEquals("Invalid url returned", "http://www.example.com:8080/services/Service", url); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/transport/http/MessageEndpointHandlerAdapterTest.java b/core/src/test/java/org/springframework/ws/transport/http/MessageEndpointHandlerAdapterTest.java new file mode 100644 index 00000000..c71ed509 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/transport/http/MessageEndpointHandlerAdapterTest.java @@ -0,0 +1,208 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport.http; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.ws.NoEndpointFoundException; +import org.springframework.ws.context.MessageContextFactory; +import org.springframework.ws.endpoint.MessageEndpoint; +import org.springframework.ws.mock.MockTransportContext; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.context.SoapMessageContext; + +public class MessageEndpointHandlerAdapterTest extends TestCase { + + private static final String REQUEST = " \n" + " \n" + + " \n" + " DIS\n" + + " \n" + " \n" + ""; + + private MessageEndpointHandlerAdapter adapter; + + private MockHttpServletRequest httpRequest; + + private MockHttpServletResponse httpResponse; + + private MockControl endpointControl; + + private MessageEndpoint endpointMock; + + private MockControl factoryControl; + + private MessageContextFactory factoryMock; + + private MockControl contextControl; + + private SoapMessageContext contextMock; + + private MockControl messageControl; + + private SoapMessage messageMock; + + private MockControl bodyControl; + + private SoapBody bodyMock; + + protected void setUp() throws Exception { + adapter = new MessageEndpointHandlerAdapter(); + httpRequest = new MockHttpServletRequest(); + httpResponse = new MockHttpServletResponse(); + endpointControl = MockControl.createControl(MessageEndpoint.class); + endpointMock = (MessageEndpoint) endpointControl.getMock(); + factoryControl = MockControl.createControl(MessageContextFactory.class); + factoryMock = (MessageContextFactory) factoryControl.getMock(); + adapter.setMessageContextFactory(factoryMock); + contextControl = MockControl.createControl(SoapMessageContext.class); + contextMock = (SoapMessageContext) contextControl.getMock(); + messageControl = MockControl.createControl(SoapMessage.class); + messageMock = (SoapMessage) messageControl.getMock(); + bodyControl = MockControl.createControl(SoapBody.class); + bodyMock = (SoapBody) bodyControl.getMock(); + } + + public void testHandleNonPost() throws Exception { + httpRequest.setMethod("GET"); + endpointControl.replay(); + try { + adapter.handle(httpRequest, httpResponse, endpointMock); + fail("ServletException expected"); + } + catch (ServletException ex) { + // expected + } + endpointControl.verify(); + } + + public void testHandlePostNoResponse() throws Exception { + httpRequest.setMethod("POST"); + httpRequest.setContent(MessageEndpointHandlerAdapterTest.REQUEST.getBytes("UTF-8")); + httpRequest.setContentType("text/xml; charset=\"utf-8\""); + httpRequest.setCharacterEncoding("UTF-8"); + endpointMock.invoke(null); + endpointControl.setMatcher(MockControl.ALWAYS_MATCHER); + factoryMock.createContext(new MockTransportContext()); + factoryControl.setMatcher(MockControl.ALWAYS_MATCHER); + factoryControl.setReturnValue(contextMock); + contextControl.expectAndReturn(contextMock.hasResponse(), false); + + replayMockControls(); + + adapter.handle(httpRequest, httpResponse, endpointMock); + + assertEquals("Invalid status code on response", HttpServletResponse.SC_NO_CONTENT, httpResponse.getStatus()); + assertEquals("Response written", 0, httpResponse.getContentAsString().length()); + verifyMockControls(); + } + + public void testHandlePostResponse() throws Exception { + httpRequest.setMethod("POST"); + httpRequest.setContent(MessageEndpointHandlerAdapterTest.REQUEST.getBytes("UTF-8")); + httpRequest.setContentType("text/xml; charset=\"utf-8\""); + httpRequest.setCharacterEncoding("UTF-8"); + endpointMock.invoke(null); + endpointControl.setMatcher(MockControl.ALWAYS_MATCHER); + factoryMock.createContext(new MockTransportContext()); + factoryControl.setMatcher(MockControl.ALWAYS_MATCHER); + factoryControl.setReturnValue(contextMock); + contextControl.expectAndReturn(contextMock.hasResponse(), true); + contextControl.expectAndReturn(contextMock.getResponse(), messageMock); + messageControl.expectAndReturn(messageMock.getSoapBody(), bodyMock); + bodyControl.expectAndReturn(bodyMock.hasFault(), false); + contextMock.sendResponse(new HttpTransportResponse(httpResponse)); + contextControl.setMatcher(MockControl.ALWAYS_MATCHER); + + replayMockControls(); + + adapter.handle(httpRequest, httpResponse, endpointMock); + + assertEquals("Invalid status code on response", HttpServletResponse.SC_OK, httpResponse.getStatus()); + verifyMockControls(); + } + + public void testHandlePostFault() throws Exception { + httpRequest.setMethod("POST"); + httpRequest.setContent(MessageEndpointHandlerAdapterTest.REQUEST.getBytes("UTF-8")); + httpRequest.setContentType("text/xml; charset=\"utf-8\""); + httpRequest.setCharacterEncoding("UTF-8"); + endpointMock.invoke(null); + endpointControl.setMatcher(MockControl.ALWAYS_MATCHER); + factoryMock.createContext(new MockTransportContext()); + factoryControl.setMatcher(MockControl.ALWAYS_MATCHER); + factoryControl.setReturnValue(contextMock); + contextControl.expectAndReturn(contextMock.hasResponse(), true); + contextControl.expectAndReturn(contextMock.getResponse(), messageMock); + messageControl.expectAndReturn(messageMock.getSoapBody(), bodyMock); + bodyControl.expectAndReturn(bodyMock.hasFault(), true); + contextMock.sendResponse(null); + contextControl.setMatcher(MockControl.ALWAYS_MATCHER); + + replayMockControls(); + + adapter.handle(httpRequest, httpResponse, endpointMock); + + assertEquals("Invalid status code on response", + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + httpResponse.getStatus()); + verifyMockControls(); + } + + public void testHandleNotFound() throws Exception { + httpRequest.setMethod("POST"); + httpRequest.setContent(MessageEndpointHandlerAdapterTest.REQUEST.getBytes("UTF-8")); + httpRequest.setContentType("text/xml; charset=\"utf-8\""); + httpRequest.setCharacterEncoding("UTF-8"); + endpointMock.invoke(null); + endpointControl.setMatcher(MockControl.ALWAYS_MATCHER); + endpointControl.setThrowable(new NoEndpointFoundException(null)); + factoryMock.createContext(new MockTransportContext()); + factoryControl.setMatcher(MockControl.ALWAYS_MATCHER); + factoryControl.setReturnValue(contextMock); + + replayMockControls(); + + adapter.handle(httpRequest, httpResponse, endpointMock); + assertEquals("No 404 returned", HttpServletResponse.SC_NOT_FOUND, httpResponse.getStatus()); + + verifyMockControls(); + + } + + private void replayMockControls() { + endpointControl.replay(); + factoryControl.replay(); + contextControl.replay(); + messageControl.replay(); + bodyControl.replay(); + } + + private void verifyMockControls() { + endpointControl.verify(); + factoryControl.verify(); + contextControl.verify(); + messageControl.verify(); + bodyControl.verify(); + } + +} diff --git a/core/src/test/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapterTest.java b/core/src/test/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapterTest.java new file mode 100644 index 00000000..9628b258 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/transport/http/WsdlDefinitionHandlerAdapterTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.transport.http; + +import java.net.URL; +import javax.servlet.ServletException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.easymock.MockControl; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.ws.wsdl.WsdlDefinition; +import org.springframework.xml.transform.StringSource; +import org.w3c.dom.Document; + +public class WsdlDefinitionHandlerAdapterTest extends XMLTestCase { + + private WsdlDefinitionHandlerAdapter adapter; + + private MockControl definitionControl; + + private WsdlDefinition definitionMock; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + protected void setUp() throws Exception { + adapter = new WsdlDefinitionHandlerAdapter(); + definitionControl = MockControl.createControl(WsdlDefinition.class); + definitionMock = (WsdlDefinition) definitionControl.getMock(); + adapter.afterPropertiesSet(); + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + } + + public void testHandleGet() throws Exception { + request.setMethod("GET"); + String definition = ""; + definitionControl.expectAndReturn(definitionMock.getSource(), new StringSource(definition)); + definitionControl.replay(); + adapter.handle(request, response, definitionMock); + assertXMLEqual(definition, response.getContentAsString()); + definitionControl.verify(); + } + + public void testHandleNonGet() throws Exception { + request.setMethod("POST"); + definitionControl.replay(); + try { + adapter.handle(request, response, definitionMock); + fail("ServletException expected"); + } + catch (ServletException ex) { + // expected + } + definitionControl.verify(); + } + + public void testTransformLocations() throws Exception { + adapter.setTransformLocations(true); + request.setMethod("GET"); + request.setScheme("http"); + request.setServerName("example.com"); + request.setServerPort(8080); + request.setContextPath("/context"); + request.setServletPath("/service.wsdl"); + request.setPathInfo(null); + request.setRequestURI("/context/service.wsdl"); + definitionControl.replay(); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document result = documentBuilder.parse(getClass().getResourceAsStream("wsdl11-input.wsdl")); + adapter.transformLocations(result, request); + Document expectedDocument = documentBuilder.parse(getClass().getResourceAsStream("wsdl11-expected.wsdl")); + assertXMLEqual("Invalid result", expectedDocument, result); + + definitionControl.verify(); + } + + public void testTransformLocationFullUrl() throws Exception { + request.setScheme("http"); + request.setServerName("example.com"); + request.setServerPort(8080); + request.setContextPath("/context"); + request.setPathInfo("/service.wsdl"); + request.setRequestURI("/context/service.wsdl"); + String oldLocation = "http://localhost:8080/context/service"; + + String result = adapter.transformLocation(oldLocation, request); + assertNotNull("No result", result); + assertEquals("Invalid result", new URL("http://example.com:8080/context/service"), new URL(result)); + } + + public void testTransformLocationEmptyContextFullUrl() throws Exception { + request.setScheme("http"); + request.setServerName("example.com"); + request.setServerPort(8080); + request.setContextPath(""); + request.setRequestURI("/service.wsdl"); + String oldLocation = "http://localhost:8080/service"; + + String result = adapter.transformLocation(oldLocation, request); + assertNotNull("No result", result); + assertEquals("Invalid result", new URL("http://example.com:8080/service"), new URL(result)); + } + + public void testTransformLocationRelativeUrl() throws Exception { + request.setScheme("http"); + request.setServerName("example.com"); + request.setServerPort(8080); + request.setContextPath("/context"); + request.setPathInfo("/service.wsdl"); + request.setRequestURI("/context/service.wsdl"); + String oldLocation = "/service"; + + String result = adapter.transformLocation(oldLocation, request); + assertNotNull("No result", result); + assertEquals("Invalid result", new URL("http://example.com:8080/context/service"), new URL(result)); + } + + public void testTransformLocationEmptyContextRelativeUrl() throws Exception { + request.setScheme("http"); + request.setServerName("example.com"); + request.setServerPort(8080); + request.setContextPath(""); + request.setRequestURI("/service.wsdl"); + String oldLocation = "/service"; + + String result = adapter.transformLocation(oldLocation, request); + assertNotNull("No result", result); + assertEquals("Invalid result", new URL("http://example.com:8080/service"), new URL(result)); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11DefinitionTest.java b/core/src/test/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11DefinitionTest.java new file mode 100644 index 00000000..63ffd842 --- /dev/null +++ b/core/src/test/java/org/springframework/ws/wsdl/wsdl11/SimpleWsdl11DefinitionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.wsdl.wsdl11; + +import junit.framework.TestCase; +import org.springframework.core.io.ClassPathResource; + +public class SimpleWsdl11DefinitionTest extends TestCase { + + private SimpleWsdl11Definition definition; + + protected void setUp() throws Exception { + definition = new SimpleWsdl11Definition(); + definition.setWsdl(new ClassPathResource("complete.wsdl", getClass())); + definition.afterPropertiesSet(); + } + + public void testGetSource() throws Exception { + assertNotNull("No source returned", definition.getSource()); + } +} \ No newline at end of file diff --git a/doc/pom.xml b/doc/pom.xml new file mode 100644 index 00000000..91221b43 --- /dev/null +++ b/doc/pom.xml @@ -0,0 +1,54 @@ + + + + spring-ws + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + spring-ws-doc + pom + Spring WS Documentation + + + agilejava + http://agilejava.com/maven/ + + + + + + com.agilejava.docbkx + docbkx-maven-plugin + 1.69.1.7 + + + + generate-html + generate-pdf + + generate-sources + + + + + org.docbook + docbook-xml + 4.4 + runtime + + + + index.xml + true + + + version + ${version} + + + + + + + diff --git a/doc/src/docbkx/bibliography.xml b/doc/src/docbkx/bibliography.xml new file mode 100644 index 00000000..3b8c5bf0 --- /dev/null +++ b/doc/src/docbkx/bibliography.xml @@ -0,0 +1,33 @@ + + Bibliography + + + + JimWaldo + + + AnnWollrath + + + SamKendall + + + A Note on Distributed Computing + Springer Verlag + 1994 + + + + + SteveLoughran + + + EdmundSmith + + + Rethinking the Java SOAP Stack + May 17, 2005 + 2005 + IEEE Telephone Laboratories, Inc. + + \ No newline at end of file diff --git a/doc/src/docbkx/contract-first.xml b/doc/src/docbkx/contract-first.xml new file mode 100644 index 00000000..93bf463c --- /dev/null +++ b/doc/src/docbkx/contract-first.xml @@ -0,0 +1,365 @@ + + + Writing Contract-first Web Services + +
+ Introduction + + In this chapter, we will give an overall tutorial on how to approach Web services development in + contract-first style, i.e. starting with the XML Schema/WSDL contract instead of Java code. Spring-WS + focusses on this development style, and this chapter might help you get started. Note that this chapter + contains almost no Spring-WS specific information: it is mostly about XML, schema's, and WSDL. + + + In this tutorial, we will define a Web service that can be used for Human Resources. Clients can send + holiday request forms to this service to book a holiday. It is based on a metaphor for Service Oriented + Architectures originally though of by Dan + North. + + + The most important thing when doing contract-first Web service development is to try and think in terms of + XML. This means that Java-language concepts are of lesser importance. It is the XML that is sent across the + wire, and you should focus on that. + +
+
+ The Messages + + In this section, we will focus on the actual XML messages that are sent to and from the service. We will + start out by determining what these messages look like. + +
+ Holiday + + In the scenario, we have to deal with holiday request, so it makes sense to determine what a holiday + looks like: + + + 2006-07-03 + 2006-07-07 +]]> + + + A holiday consists of a start date and an end date. We decided to use the standard + ISO 8601 date + format for the dates, because that will save a lot of parsing hassle. We also added a namespace to the + element, to make sure our elements can used within other XML documents. + +
+
+ Employee + + There is also the notion of an employee in the scenario. Here's what it looks like: + + + 42 + Arjen + Poutsma +]]> + + + We have used the same namespace as before. If this employee element could be used in other scenarios, + it might make sense to use a different namespace, such as + "http://mycompany.com/employees/schemas". + +
+
+ HolidayRequest + + Both the holiday and employee element can be put in a HolidayRequest: + + + + 2006-07-03 + 2006-07-07 + + + 42 + Arjen + Poutsma + +]]> + + + The order of the two element does not matter: Employee could have been the first + element just as well. As long as all the data is there; that's what is important. + In fact, the data is the only thing that is important: we are taking a data-driven + approach. + +
+
+
+ The Schema + + Now that we have seen some examples of the XML data that we will use, it makes sense to formalize this into + a schema. Basically, there are four different ways of defining a grammar for XML: + + DTDs + + XML Schema (XSD) + + + RELAX NG + + + Schematron + + + + + DTDs have limited namespaces support, so they are not suitable for Web services. Relax NG and Schematron are + certainly easier than XSDs. Unfortunately, they are not so widely supported across platforms. We will use + XML Schema. + + + By far the easiest way to create a XSD is to infer it from sample documents. Any good XML editor or Java IDE + offers this functionality. Basically, these tools use some sample XML documents, and generate a schema from it + that validates them all. The end result certainly needs to be polished up, but it's a great starting + point. + + + Using the sample described in , we end up with the following + generated schema: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + The generated schema can obviously be improved. The first thing to notice is that everything is a root-level + element. This means that the Web service should be able to accept all of these elements as data. This is not + desirable: we only want to accept a HolidayRequest. By removing the wrapping element tags + (thus keeping the types), and inlining the results, we can accomplish this. + +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:holidays="http://mycompany.com/holidays/schemas" + elementFormDefault="qualified" + targetNamespace="http://mycompany.com/holidays/schemas"> + <xs:element name="HolidayRequest"> + <xs:complexType> + <xs:sequence> + <xs:element name="Holiday" type="holidays:HolidayType"/> + <xs:element name="Employee" type="holidays:EmployeeType"/> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:complexType name="HolidayType"> + <xs:sequence> + <xs:element name="StartDate" type="xs:NMTOKEN"/> + <xs:element name="EndDate" type="xs:NMTOKEN"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="EmployeeType"> + <xs:sequence> + <xs:element name="Number" type="xs:integer"/> + <xs:element name="FirstName" type="xs:NCName"/> + <xs:element name="LastName" type="xs:NCName"/> + </xs:sequence> + </xs:complexType> +</xs:schema> + + + The schema still has one problem: with a schema like this, you can expect the following messages to + validate: + + + + this is not a date + neither is this + + ... +]]> + + + Clearly, we must make sure that the start and end date are really dates. XML Schema has an excellent built-in + date type which we can use. We also change the NCNames to + strings. Finally, we change the sequence in + HolidayRequest to all. This tells the XML parser that the order of + Holiday and Employee is not significant. Our final XSD looks like this: + +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:holidays="http://mycompany.com/holidays/schemas" + elementFormDefault="qualified" + targetNamespace="http://mycompany.com/holidays/schemas"> + <xs:element name="HolidayRequest"> + <xs:complexType> + <xs:all> + <xs:element name="Holiday" type="holidays:HolidayType"/> + <xs:element name="Employee" type="holidays:EmployeeType"/> + </xs:all> + </xs:complexType> + </xs:element> + <xs:complexType name="HolidayType"> + <xs:sequence> + <xs:element name="StartDate" type="xs:date"/> + <xs:element name="EndDate" type="xs:date"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="EmployeeType"> + <xs:sequence> + <xs:element name="Number" type="xs:integer"/> + <xs:element name="FirstName" type="xs:string"/> + <xs:element name="LastName" type="xs:string"/> + </xs:sequence> + </xs:complexType> +</xs:schema> + + + We can store this file with a convenient name such as holidays.xsd. + +
+
+ The WSDL + + Which leaves the WSDL. We start our WSDL with the standard preamble, and by importing our existing XSD. To + separate the schema from the definition, we will use a separate namespace for the WSDL definitions: + "http://mycompany.com/holidays/definitions". + +<wsdl:definitions name="HumanResources" + targetNamespace="http://mycompany.com/holidays/definitions" + xmlns:tns="http://mycompany.com/holidays/definitions" + xmlns:types="http://mycompany.com/holidays/schemas" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> + <wsdl:types> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:import namespace="http://mycompany.com/holidays/schemas" + schemaLocation="holidays.xsd"/> + </xsd:schema> + </wsdl:types> +</wsdl:definitions> + + + Next, we define our messages based on the written schema. We only have one message: one with the + HolidayRequest we put in the schema: + +<wsdl:definitions name="HumanResources" + targetNamespace="http://mycompany.com/holidays/definitions" + xmlns:tns="http://mycompany.com/holidays/definitions" + xmlns:types="http://mycompany.com/holidays/schemas" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> + <wsdl:types> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:import namespace="http://mycompany.com/holidays/schemas" + schemaLocation="holidays.xsd"/> + </xsd:schema> + </wsdl:types> + <wsdl:message name="RequestHolidayInput"> + <wsdl:part name="body" element="types:HolidayRequest" /> + </wsdl:message> +</wsdl:definitions> + + + We add the messages to a port type as operations: + +<wsdl:definitions name="HumanResources" + targetNamespace="http://mycompany.com/holidays/definitions" + xmlns:tns="http://mycompany.com/holidays/definitions" + xmlns:types="http://mycompany.com/holidays/schemas" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> + <wsdl:types> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:import namespace="http://mycompany.com/holidays/schemas" + schemaLocation="holidays.xsd"/> + </xsd:schema> + </wsdl:types> + <wsdl:message name="RequestHolidayInput"> + <wsdl:part name="body" element="types:HolidayRequest" /> + </wsdl:message> + <wsdl:portType name="HumanResourcesPortType"> + <wsdl:operation name="RequestHoliday"> + <wsdl:input message="tns:RequestHolidayInput" /> + </wsdl:operation> + </wsdl:portType> +</wsdl:definitions> + + + That finished the abstract part of the WSDL (the interface, as it were), and leaves the concrete part. This + part consists of a binding, which tells the client how to invoke the + operations you've just defined; and a service, which tells it where + to invoke it. + + + Adding a concrete part is pretty standard: just refer to the abstract part you defined previously, make sure + you use document/literal for the <soap:binding> elements + (anything else is not interoperable), pick a soapAction (in this case + http://example.com/RequestHoliday, but any URI will do), and determine the + location URL where you want request to come in (in this case + http://mycompany.com/humanresources): + +<wsdl:definitions name="HumanResources" + targetNamespace="http://mycompany.com/holidays/definitions" + xmlns:tns="http://mycompany.com/holidays/definitions" + xmlns:types="http://mycompany.com/holidays/schemas" + xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> + <wsdl:types> + <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <xsd:import namespace="http://mycompany.com/holidays/schemas" + schemaLocation="holidays.xsd"/> + </xsd:schema> + </wsdl:types> + <wsdl:message name="RequestHolidayInput"> + <wsdl:part name="body" element="types:HolidayRequest" /> + </wsdl:message> + <wsdl:portType name="HumanResourcesPortType"> + <wsdl:operation name="RequestHoliday"> + <wsdl:input message="tns:RequestHolidayInput" /> + </wsdl:operation> + </wsdl:portType> + <wsdl:binding name="HumanResourcesBinding" type="tns:HumanResourcesPortType"> + <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> + <wsdl:operation name="RequestHoliday"> + <soap:operation soapAction="http://example.com/RequestHoliday" /> + <wsdl:input> + <soap:body use="literal" /> + </wsdl:input> + </wsdl:operation> + </wsdl:binding> + <wsdl:service name="HumanResourcesService"> + <wsdl:port name="HumanResourcesPort" binding="tns:HumanResourcesBinding"> + <soap:address location="http://mycompany.com/humanresources" /> + </wsdl:port> + </wsdl:service> +</wsdl:definitions> + + + This is the final WSDL. We will describe how to implement the resulting schema and WSDL in the next chapter. + +
+
\ No newline at end of file diff --git a/doc/src/docbkx/images/oxm-exceptions.png b/doc/src/docbkx/images/oxm-exceptions.png new file mode 100644 index 00000000..9503d600 Binary files /dev/null and b/doc/src/docbkx/images/oxm-exceptions.png differ diff --git a/doc/src/docbkx/images/oxm-exceptions.svg b/doc/src/docbkx/images/oxm-exceptions.svg new file mode 100644 index 00000000..e33c67ae --- /dev/null +++ b/doc/src/docbkx/images/oxm-exceptions.svg @@ -0,0 +1,48 @@ + + + + + +G +c1 + +XmlMappingException + +c2 + +GenericMarshallingFailureException + +c1->c2 + + + +c5 + +ValidationFailureException + +c1->c5 + + + +c3 + +MarshallingFailureException + +c2->c3 + + + +c4 + +UnmarshallingFailureException + +c2->c4 + + + + + diff --git a/doc/src/docbkx/index.xml b/doc/src/docbkx/index.xml new file mode 100644 index 00000000..7a949f5f --- /dev/null +++ b/doc/src/docbkx/index.xml @@ -0,0 +1,49 @@ + + + + + + + + +]> + + + + Spring Web Services + + Reference Documentation + + Version &version; + (Work in progress) + + + + Arjen + Poutsma + + + + Copies of this document may be made for your own use and for distribution to others, provided that + you do not charge any fee for such copies and further provided that each copy contains this Copyright + Notice, whether distributed in print or electronically. + + + + + &preface; + + &overview; + + &contract-first; + + &ws; + + &security; + + &oxm; + + &bibliography; + + diff --git a/doc/src/docbkx/overview.xml b/doc/src/docbkx/overview.xml new file mode 100644 index 00000000..504ef4cb --- /dev/null +++ b/doc/src/docbkx/overview.xml @@ -0,0 +1,140 @@ + + + Introduction + +
+ Overview + + Spring-WS consists of three separate modules. This chapter discusses each of the modules in turn. + + + The Core package is the central part of the Web services functionality. It + provides the central WebServiceMessage and SoapMessage + interfaces, the powerful message dispatching, and the various support classes for implementing Web service + endpoints. + + + The Security package provides a WS-Security implementation that integrates + with the core Web service package. It allows you to add principal tokens, sign, and decrypt and encrypt SOAP + messages. Addtionally, it allows you to leverage your existing Acegi security implementation for + authentication and authorization. + + + The OXM package provides integration for popular XML marshalling APIs, including + JAXB 1 and 2. Using the OXM package means that you benefit from a unified exception hierarchy, and can wire + up your favorite XML marshalling technology easily. + +
+ + +
+ Why Spring Web Services? + + There are various other SOAP stacks available, why and where should you use Spring-WS? This section answers + that question by showing what the focus of Spring-WS is. + +
+ Spring-WS is meant for Public Web Services + + One can distinguish between two different sorts of Web services. Private Web services are not used + outside your application domain. They might form a part of your Enterprise Service Bus, or used as a + means to communicate between a fat .NET client and a J2EE server. When the two sides of the spectrum + (client and server) are under your control, you can easily expose (existing) methods, since you can + (re)generate client code easily. + + + Public Web services provide a separate interface to your application. They are often used by clients + that are outside of your reach. When developing a public Web service, you should really think about the + interface you are providing: it is probably going to be around for a while, and you cannot change it + that often. As such, it is a good idea to place the Web service in a separate layer, thus hiding the + inner workings of the application. As a result, you can change the Web service and the rest of the + appliciation seperately. + +
+
+ Spring-WS makes Web Services First Class Citizens of the Architecture + + Web Services deserve a proper place in an application architecture. Often, they exist as an afterthought + in the application architecture, mostly because existing Java business interfaces are exposed as SOAP + services. One could say that they are "SOAPified". Spring-WS provides a MVC-like framework for + developing a Web service application layer, just like you would develop a layer especially for a Web + user interface using Spring-MVC. Spring-WS also provides useful integration points with you existing + Spring application architecture, such as the Acegi integration. + +
+
+ Spring-WS is Data-Driven + + When Web Services started making their way into the Enterprise Computing world, developers considered + Web Services just another, XML-based remoting protocol. Such remoting frameworks can be used with + relative ease: on the server-side, one simply implements a specific interface such as + java.rmi.Remote, and on the client side, a dynamic proxy is used. + Unfortunately, because of this simplicity, remoting architectures have some issues: + + + + They pretend there is no latency between the client and the server, + while in fact there is both network and application latency, + + + + + They pretend that client and server have shared memory access, while in + fact data must be both marshalled and unmarshalled, + + + + + They ignore the possibility of a request or response not reaching its + destination, + + + + + They enforce a non-concurrent programming model, while in fact a + concurrent approach seems more in place, + + + + + They enforce a tightly coupled architecture, where changes on the + server-side result in changes on the client-side. + + + + It is not without reason that Gregor Hohpe calls a distributed architecture a fairy tale + architecture: one is made to believe things that simply are not true. To quote : +
+ + Objects that interact in a distributed system need to be dealt with in ways that are + intrinsically different from objects that interact in a single address space. + +
+
+ + Instead of being behavior-driven, Spring-WS is data-driven: it focusses on the data being sent, not on a + particular method being invoked. + +
+
+ Spring-WS Focusses on Contract-first Development + + SOAP services are defined in two contracts: the data contract (the XSD schema), and the service contract + (the WSDL). Generating these contracts from Java-code is called contract-last + development identifies some problems with this approach, most + importantly: +
+ + There is no way to ensure that a service’s published interface remains constant over time. + Every redeployment of the service may change the classes, and hence the contract. + +
+ The alternative of contract-last development is contract-first development. + Using this approach, the service and data contract are leading. Spring-WS focusses on contract-first Web + service development, because is considered to be a best practice. After all, the actual XML that is sent + across the wire is more important than the Java code that is used to implement it. +
+
+
+
diff --git a/doc/src/docbkx/oxm.xml b/doc/src/docbkx/oxm.xml new file mode 100644 index 00000000..78ae2f81 --- /dev/null +++ b/doc/src/docbkx/oxm.xml @@ -0,0 +1,617 @@ + + + Marshalling XML using O/X Mappers + +
+ Introduction + + In this chapter, we will describe Spring's Object/XML Mapping support. Object/XML Mapping, or O/X mapping + for short, is the act of converting an XML document to and from an object. This conversion process is also + known as XML Marshalling, or XML Serialization. This chapter uses these terms interchangeably. + + + Within the field of O/X mapping, a marshaller is responsible for serializing an object + (graph) to XML. In similar fashion, an unmarshaller deserializes the XML to an object + graph. This XML can take the form of a DOM document, an input or output stream, or a SAX handler. + + + Some of the benefits of using Spring for your O/X mapping needs are: + + + Ease of configuration + + Spring's bean factory makes it easy to configure marshallers, without needing to construct JAXB context, + JiBX binding factories, etc. The marshallers can be configured as any other bean in your application + context. + + + + Consistent Interfaces + + Spring's O/X mapping operates through two global interfaces: the + Marshaller + and + Unmarshaller + interface. These abstractions allow you to switch O/X mapping + frameworks with relative ease, with little or no changes required on the classes that do the + marshalling. This approach has the additional benefit of making it possible to do XML marshalling with a + mix-and-match approach (e.g. some marshalling performed using JAXB, other using XMLBeans) in a + non-intrusive fashion, leveraging the strength of each technology. + + + + Consistent Exception Hierarchy + + Spring provides a conversion from exceptions from the underlying O/X mapping tool to its own + exception hierarchy with the + XmlMappingException + as the root exception. As can be + expected, these runtime exceptions wrap the original exception so no information is lost. + + +
+
+ Marshaller and Unmarshaller + + As stated in the introduction, a + marshaller + serializes an object to XML, and an + unmarshaller + deserializes XML stream to an object. In this section, we will describe + the two Spring interfaces used for this purpose. + +
+ Marshaller + + Spring abstracts all marshalling operations behind the + org.springframework.oxm.Marshaller + interface, which is listed below. + + The + Marshaller + interface has just one method, which marshals the given + object to a given + javax.xml.transform.Result + . Result is a tagging interface that + basically represents an XML output abstraction: concrete implementations wrap various XML + representations, as indicated in the table below. + + + + + + javax.xml.transform.Result + implementation + + Wraps XML representation + + + + + + javax.xml.transform.dom.DOMResult + + + org.w3c.dom.Node + + + + + javax.xml.transform.sax.SAXResult + + + org.xml.sax.ContentHandler + + + + + javax.xml.transform.stream.StreamResult + + + java.io.File + , + java.io.OutputStream + , + or + java.io.Writer + + + + + + + + + Although the + marshal + method accepts a plain object as its first parameter, + most + Marshaller + implementations cannot handle arbitrary objects. Instead, an + object class must be mapped in a mapping file, registered with the marshaller, or have a common base + class. Refer to the further sections in this chapter to determine how your O/X technology of choice + manages this. + + +
+
+ Unmarshaller + + Similar to the + Marshaller + , there is the + org.springframework.oxm.Unmarshaller + interface. + + This interface also has one method, which reads from the given + javax.xml.transform.Source + (an XML input abstraction), and returns the + object read. As with Result, Source is a tagging interface that has three concrete implementations. Each + wraps a different XML representation, as indicated in the table below. + + + + + + javax.xml.transform.Source + implementation + + Wraps XML representation + + + + + + javax.xml.transform.dom.DOMSource + + + org.w3c.dom.Node + + + + + javax.xml.transform.sax.SAXSource + + + org.xml.sax.InputSource + and + org.xml.sax.XMLReader + + + + + javax.xml.transform.stream.StreamSource + + + java.io.File + , + java.io.InputStream + , + or + java.io.Reader + + + + + + +
+ + Even though there are two separate marshalling interfaces ( + Marshaller + and + Unmarshaller + ), most implementations found in Spring-WS implement both in one class. + This means that you can wire up one marshaller class and refer to it as marshaller and unmarshaller in your + applicationContext.xml + . + +
+ XmlMappingException + + Spring converts exceptions from the underlying O/X mapping tool to its own exception hierarchy with the + XmlMappingException + as the root exception. As can be expected, these runtime + exceptions wrap the original exception so no information will be lost. + + + Additionally, the + MarshallingFailureException + and + UnmarshallingFailureException + provide a distinction between marshalling and + unmarshalling operations, even though the underlying O/X mapping tool does not do so. + + + The O/X Mapping exception hierarchy is shown in the following figure: + + + + + + + + + + O/X Mapping exception hierarchy + + + + +
+
+
+ Using Marshaller and Unmarshaller + + Spring's OXM can be used for a wide variety of situations. In the following example, we will use it to + marshal the settings of a Spring-managed application as an XML file. We will use a simple JavaBean to + represent the settings: + + + + The application class uses this bean to store its settings. Besides a main method, the class has two + methods: + saveSettings + saves the settings bean to a file named + settings.xml + , and + loadSettings + loads these settings again. A + main + method constructs a Spring application context, and calls these two methods. + + The + Application + requires both a + marshaller + and + unmarshaller + property to be set. We can do so using the following + applicationContext.xml + : + + + + + + + + +]]> + This application context uses Castor, but we could have used any of the other marshaller instances described + later in this chapter. Note that Castor does not require any further configuration by default, so the bean + definition is rather simple. Also note that the + CastorMarshaller + implements both + Marshaller + and + Unmarshaller + , so we can refer to the + castorMarshaller + bean in both the + marshaller + and + unmarshaller + property of the application. + + + This sample application produces the following + settings.xml + file: + + +]]> + +
+
+ JAXB + + The JAXB binding compiler translates a W3C XML Schema into one or more Java classes, a + jaxb.properties + file, and possibly other files, depending on the specific implementation of JAXB. Alternatively, JAXB2 + offers a way to generate a schema from annotated Java classes. + + + Spring supports both the JAXB 1.0 as the JAXB 2.0 API as XML marshalling strategy, following the + Marshaller and Unmarshaller interfaces described in + . The corresponding integration classes reside in the + org.springframework.oxm.jaxb + package. + +
+ Jaxb1Marshaller + + The Jaxb1Marshaller class implements both the Spring + Marshaller and Unmarshaller interface. It requires a + context path to operate, which you can set using the contextPath property. The + context path is a list of colon (:) separated Java package names that contain schema derived classes. + The marshaller has an additional validating property which defines whether to + validate invoming XML. + + + The next sample bean configuration shows how to configure a + JaxbMarshaller using the classes generated to + org.springframework.ws.samples.airline.schema. + + + + + + + ... + +]]> +
+
+ Jaxb2Marshaller + + The Jaxb2Marshaller can be configured using the same contextPath property as the Jaxb1Marshaller. However, it also offers a classesToBeBound property, which allows you to set an array of classes to be supported by the marshaller. Schema validation is performed by specifying one or more schema resource to the bean, like so: + + + + + + + org.springframework.oxm.jaxb.Flight + org.springframework.oxm.jaxb.Flights + + + + + ... + +]]> +
+
+
+ Castor + + Castor XML mapping is an open source XML binding framework. It allows you to transform the data contained in + a java object model into/from an XML document. By default, it does not require any further configuration, + though a mapping file can be used to have more control over the behavior of Castor. + + + For more information on Castor, refer to the + Castor web site. The Spring integration classes reside in the + org.springframework.oxm.castor package. + +
+ CastorMarshaller + + As with JAXB, the CastorMarshaller implements both the + Marshaller and Unmarshaller interface. It can be wired up + as follows: + + + + + ... + +]]> +
+
+ Mapping + + Although it is possible to rely on Castor's default marshalling behavior, it might be necessary to have + more control over it. This can be accomplished using a Castor mapping file. For more information, refer + to Castor XML Mapping. + + + The mapping can be set using the mappingLocation resource property, indicated + below with a classpath resource. + + + + + + +]]> +
+
+ +
+ XMLBeans + + XMLBeans is an XML binding tool that has full XML Schema support, and offers full XML Infoset fidelity. It + takes a different approach that most other O/X mapping frameworks, in that all classes that are + generated from an XML Schema are all derived from XmlObject, and contain XML binding + information in them. + + + For more information on XMLBeans, refer to the XMLBeans + web site . The Spring-WS integration classes reside in the + org.springframework.oxm.xmlbeans package. + +
+ XmlBeansMarshaller + + The XmlBeansMarshaller implements both the Marshaller and + Unmarshaller interface. It can be wired up as follows: + + + + + ... + +]]> + + + Note that the XmlBeansMarshaller can only marshal objects of type + XmlObject, and not every java.lang.Object. + + +
+ + +
+ +
+ JiBX + + The JiBX framework offers a solution similar to JDO does for ORM: a binding definition defines the + rules for how your Java objects are converted to or from XML. After preparing the binding and compiling the + classes, a JiBX binding compiler enhances the class files, and adds code to handle converting instances of + the classes from or to XML. + + + For more information on JiBX, refer to the JiBX web + site. The Spring integration classes reside in the + org.springframework.oxm.jibx package. + +
+ JibxMarshaller + + The JibxMarshaller class implements both the Marshaller + and Unmarshaller interface. To operate, it requires the name of the class to + marshall in, which you can set using the targetClass property. Optionally, you can + set the binding name using the bindingName property. In the next sample, we refer to + the flightsBindingFactory defined in the previous bean definition. + + + + + org.springframework.oxm.jibx.Flights + + + ... +]]> + + + A JibxMarshaller is configured for a single class. If you want to marshal + multiple classes, you have to configure multiple JibxMarshallers with + different targetClasses. + + +
+
+
+ XStream + + XStream is a simple library to serialize objects to XML and back again. It does not require any mapping, and + generates clean XML. + + + For more information on XStream, refer to the XStream + web site. The Spring integration classes reside in the + org.springframework.oxm.xstream package. + +
+ XStreamMarshaller + + The XStreamMarshaller does not require any configuration, and can be configured + in an application context directly. To further customize the XML, you can set an + alias map, which consists of string aliases mapped to classes: + + + + + + + org.springframework.oxm.xstream.Flight + + + + ... + +]]> +
+ + + Note that XStream is an XML serialization library, not a data binding library. Therefore, it has + limited namespace support. As such, it is rather unsuitable for usage within Web services. + + +
+
\ No newline at end of file diff --git a/doc/src/docbkx/preface.xml b/doc/src/docbkx/preface.xml new file mode 100644 index 00000000..c4248d0b --- /dev/null +++ b/doc/src/docbkx/preface.xml @@ -0,0 +1,20 @@ + + + Preface + + Web services development is a complicated affair. There is SOAP, there is REST. There is SOAP 1.1, and there is + SOAP 1.2. There is rpc/encoded, and there is document/literal. There is WS-Addressing, WS-Security, WS-Policy, + and various other Web service specifications. Implementing Web services using abstractions that magically turn + Java into XML, but turn out to be leaky abstractions makes it even harder. Spring Web Services provides a + solution for building interoperable Web services, while making it clear what XML is received and sent across the + wire. Spring-WS provides a powerful message dispatching framework, various XML marshalling techniques that can be used outside a Web service environment, and a WS-Security solution that integrates with your existing application security + solution. + + + This document provides a reference guide to Spring-WS's features. Since this document is still a + work-in-progress, if you have any requests or comments, please post them on the support forums at . + + \ No newline at end of file diff --git a/doc/src/docbkx/security.xml b/doc/src/docbkx/security.xml new file mode 100644 index 00000000..3920b434 --- /dev/null +++ b/doc/src/docbkx/security.xml @@ -0,0 +1,858 @@ + + + Securing your Web services with Spring-WS + +
+ Introduction + + In this chapter, we will show you how to add WS-Security aspects to your Web services. We + will focus on the three different areas of WS-Security, namely: + + + Authentication + + This is the process of determining whether a principal is who they claim to be. + In this context, a "principal" generally means a user, device or some other system which can perform an + action in your application. + + + + Digital signatures + + The digital signature of a message is a piece of information based on both the document and the signer's + private key. It is created through the use of a hash function and a private signing function + (encrypting with the signer's private key). + + + + Encryption and Decryption + + Encryption is the process of transforming data into a form that is impossible to + read without the appropriate key. It is mainly used to keep information hidden from anyone for whom it + is not intended. Decryption is the reverse of encryption; it is the process of + transforming of encrypted data back into an readable form. + + + + All of these three areas are implemented using the XwsSecurityInterceptor, which we + will describe in + + + + Note that WS-Security (especially encryption and signing) requires substantial amounts of memory, + and will also decrease performance. If performance is important to you, you might want to consider using + not using WS-Security. + + +
+
+ XwsSecurityInterceptor + + The XwsSecurityInterceptor is an EndpointInterceptor (see + ) that is based on SUN's XML and Web Services Security package + (XWSS). This WS-Security implementation is part of the Java Web Services Developer Pack (Java WSDP). + + + Like any other endpoint interceptor, it is defined in the endpoint mapping (see ). This means that you can be selective about adding WS-Security support: + some endpoint mappings require it, while others do not. + + + The XwsSecurityInterceptor requires a security policy file to + operate. This XML file tells the interceptor what security aspects to require from incoming SOAP messages, + and what aspects to add to outgoing messages. The basic format of the policy file will be explained in the + following sections, but you can find a more in-depth tutorial + here. You can set the policy with the + policyConfiguration property, which requires a Spring resource. + The policy file can contain multiple elements, e.g. require a username + token on incoming messages, and sign all outgoing messages. It contains a + SecurityConfiguration element as root (not a JAXRPCSecurity + element). + + + Additionally, the security interceptor requires one or more CallbackHandlers to + operate. These handlers are used to retrieve certificates, private keys, validate user credentials, etc. + Spring-WS offers handlers for most common security concerns, e.g. authenticating against a Acegi + authentication manager, signing outgoing messages based on a X509 certificate. The following sections + will indicate what callback handler to use for which security concern. You can set the callback handlers + using the callbackHandler or callbackHandlers property. + + + Here is an example that shows how to wire the XwsSecurityInterceptor up: + + + + + + + + + + + ... + +]]> + This interceptor is configured using the securityPolicy.xml file on the classpath. It + uses two callback handlers which are defined further on in the file. + +
+ +
+ Key stores + + For most cryptographic operations, you will use standard java.security.KeyStore + objects. This includes certificate verification, message signing, signature verification, encryption, but + excludes username and time-stamp verification. This section aims to give you some background knowledge on + key stores, and the Java tools that you can use to store keys and certificates in a key store file. This + information is mostly not related to Spring-WS, but to the general cryptographic features of Java. + + + The java.security.KeyStore class represents a storage facility for cryptographic keys + and certificates. It can contain three different sort of elements: + + + Private Keys + + These keys are used for self-authentication. The private key is accompanied by certificate chain for + the corresponding public key. Within the field of WS-Security, this accounts to message signing and + message decryption. + + + + Symmetric Keys + + Symmetric (or secret) keys are used for message encryption and decryption as well. The difference + being that both sides (sender and recipient) share the same, secret key. + + + + Trusted certificates + + These X509 certificates are called a trusted certificate because the keystore owner + trusts that the public key in the certificates indeed belong to the owner of the certificate. Within + WS-Security, these certificates are used for certificate validation, signature verification, and + encryption. + + +
+ KeyTool + Supplied with your Java Virtual Machine is the keytool, a key and certificate management + utility. You can use this tool to create new key stores, add new private keys and certificates to them, etc. + It is beyond the scope of this document to provide a full reference of the keytool + command, but you can find a reference + here, or by giving the command keytool -help on the command + line. +
+
+ KeyStoreFactoryBean + + To easily load a key store using Spring configuration, you can use the + KeyStoreFactoryBean. It has a resource location property, which you can set to + point to the path of the key store to load. A password may be given to check the integrity of the + key store data. If a password is not given, integrity checking is not performed. + + + + +]]> + + + If you don't specify the location property, a new, empty key store will be created, which is most + likely not what you want. + + +
+
+ KeyStoreCallbackHandler + + To use the key stores within a XwsSecurityInterceptor, you will need to define a + KeyStoreCallbackHandler. This callback has three properties with type key store: + (keyStore, trustStore, and + symmetricStore). The exact stores used by the handler depend on the + cryptographic operations that are to be performed by this handler. For private key operation, the + keyStore is used, for symmetric key operations the + symmetricStore, and for determining trust relationships, the + trustStore. The following table indicates this: + + + + + Cryptographic operation + Key store used + + + + + Certificate validation + + first the keyStore, then the + trustStore + + + + Decryption based on private key + keyStore + + + Decryption based on symmetric key + symmetricStore + + + Encryption based on public key certificate + trustStore + + + Encryption based on symmetric key + symmetricStore + + + Signing + keyStore + + + Signature verification + trustStore + + + + + Additionally, the KeyStoreCallbackHandler has a + privateKeyPassword property, which should be set to unlock the private key(s) + contained in the keyStore. + + + If the symmetricStore is not set, it will default to the + keyStore. If the key or trust store is not set, the callback handler will use + the standard Java mechanism to load or create it. Refer to the JavaDoc of the + KeyStoreCallbackHandler to know how this mechanism works. + + + For instance, if you want to use the KeyStoreCallbackHandler to validate incoming + certificates or signatures, you would use a trust store, like so: + + + + + + + + + +]]> + If you want to use it to decrypt incoming certificates or sign outgoing messages, you would use a key + store, like so: + + + + + + + + + + +]]> + + + The following sections will indicate where the KeyStoreCallbackHandler can be + used, and which properties to set for particular cryptographic operations. + +
+
+ +
+ Authentication + + As stated in the introduction, authentication is the task of determining whether a + principal is who they claim to be. Within WS-Security, authentication can take two forms: using a username + and password token (using either a plain text password or a password digest), or using a X509 certificate. + +
+ Plain Text Username Authentication + + The simplest form of username authentication uses plain text passwords. In this + scenario, the SOAP message will contain a UsernameToken element, which itself + contains a Username element and a Password element which contains + the plain text password. Plain text authentication can be compared to the Basic Authentication provided + by HTTP servers. + + + + Note that plain text passwords are not very secure. Therefore, you should always add additional + security measures to your transport layer if you are using them (using HTTPS instead of plain HTTP, + for instance). + + + + To require that every incoming message contains a UsernameToken with a plain + text password, the security policy file should contain a RequireUsernameToken + element, with the passwordDigestRequired attribute set to false. + You can find a reference of possible child elements + here. + + + ... + + ... +]]> + + If the username token is not present, the XwsSecurityInterceptor will return a + SOAP Fault to the sender. If it is present, it will fire a + PasswordValidationCallback with a PlainTextPasswordRequest + to the registered handlers. Within Spring-WS, there are three classes which handle this particular + callback. + +
+ SimplePasswordValidationCallbackHandler + + The simplest password validation handler is the + SimplePasswordValidationCallbackHandler. This handler validates passwords + against a in-memory Properties object, which you can specify using the + users property, like so: + + + + + Ernie + + +]]> + + In this case, we are only allowing the user "Bert" to log in using the password "Ernie". + +
+
+ AcegiPlainTextPasswordValidationCallbackHandler + + The AcegiPlainTextPasswordValidationCallbackHandler uses the excellent Acegi Security Framework to + authenticate users. It is beyond the scope of this document to describe Acegi, but suffice it to say + that Acegi is a full-fledged security framework. You can read more about Acegi in the Acegi reference + documentation. + + + The AcegiPlainTextPasswordValidationCallbackHandler requires an Acegi + AuthenticationManager to operate. It uses this manager to authenticate against a + UsernamePasswordAuthenticationToken that it creates. If authentication is + successful, the token is stored in the SecurityContextHolder. You can set the + authentication manager using the authenticationManager property: + + + + + + + + + + + + + + + + ... +]]> +
+
+ JaasPlainTextPasswordValidationCallbackHandler + + The JaasPlainTextPasswordValidationCallbackHandler is based on the standard + Java Authentication and Authorization + Service. It is beyond the scope of this document to provide a full + introduction into JAAS, but there is a + good tutorial available. + + + The JaasPlainTextPasswordValidationCallbackHandler only requires a + loginContextName to operate. It creates a new JAAS + LoginContext using this name, and handles the standard JAAS + NameCallback and PasswordCallback using the username + and password provided in the SOAP message. This means that this callback handler + integrates with any JAAS + LoginModule that fires these callbacks during the + login() phase, which is standard behavior. + + + You can wire up a JaasPlainTextPasswordValidationCallbackHandler as follows: + + + +]]> + + In this case, the callback handler uses the LoginContext named + "MyLoginModule". This module should be defined in your jaas.config file, as + explained in the abovementioned tutorial. + +
+
+ +
+ Digest Username Authentication + + When using password digests, the SOAP message also contain a UsernameToken element, + which itself contains a Username element and a Password element. + The difference is that the password is not sent as plain text, but as a digest.The + recipient compares this digest to the digest he calculated from the known password of the user, and if + they are the same, the user is authenticated. It can be compared to the Digest + Authentication provided by HTTP servers. + + + To require that every incoming message contains a UsernameToken element with a + password digest, the security policy file should contain a RequireUsernameToken + element, with the passwordDigestRequired attribute set to true. + Additionally, the nonceRequired should be set to true: + You can find a reference of possible child elements + here. + + + ... + + ... +]]> + + If the username token is not present, the XwsSecurityInterceptor will return a + SOAP Fault to the sender. If it is present, it will fire a + PasswordValidationCallback with a DigestPasswordRequest + to the registered handlers. Within Spring-WS, there are two classes which handle this particular + callback. + +
+ SimplePasswordValidationCallbackHandler + + The SimplePasswordValidationCallbackHandler can handle both plain text + passwords as well as password digests. It is described in . + +
+
+ AcegiDigestPasswordValidationCallbackHandler + + The AcegiPlainTextPasswordValidationCallbackHandler requires an Acegi + UserDetailService to operate. It uses this service to retrieve the password + of the user specified in the token. The digest of the password contained in this details object is + then compared with the digest in the message. If they are equal, the user has succesfully + authenticated, and a UsernamePasswordAuthenticationToken is stored in the + SecurityContextHolder. You can set the service using the + userDetailsService. Additionally, you can set a + userCache property, to cache loaded user details. + + + + + + + + ... +]]> +
+
+ +
+ Certificate Authentication + + A more secure way of authentication uses X509 certificates. In this scenerario, the SOAP message + contains a BinarySecurityToken, which contains a Base 64-encoded version of a X509 + certificate. The recipient is used by the recipient to authenticate. The certificate stored in the + message is also used to sign the message (see ). + + + To make sure that all incoming SOAP messages carry a BinarySecurityToken, the + security policy file should contain a RequireSignature element. This element can + carry further other elements, which will be covered in . + You can find a reference of possible child elements + here. + + + ... + + ... +]]> + + When a message arrives that carries no certificate, the XwsSecurityInterceptor + will return a SOAP Fault to the sender. If it is present, it will fire a + CertificateValidationCallback. There are three handlers within Spring-WS + which handle this callback for authentication purposes. + + + + In most cases, certificate authentication should be preceded by certificate + validation, since you only want authenticate against valid certificates. + Invalid certificates such as certificates for which the expiration date has passed, or which are not + in your store of trusted certificates, should be ignored. + + + In Spring-WS terms, this means that the + AcegiCertificateValidationCallbackHandler or + JaasCertificateValidationCallbackHandler should be preceded by + KeyStoreCallbackHandler. This can be accomplished by setting the order of the + callbackHandlers property in the configuration of the + XwsSecurityInterceptor: + + + + + + + + + +]]> + Using this setup, the interceptor will first determine if the certificate in the message is valid + using the keystore, and then authenticate against it. + + +
+ KeyStoreCallbackHandler + + The KeyStoreCallbackHandler uses a standard Java key store to validate + certificates. This certificate validation process consists of the following steps: + + + + First, the handler will check whether the certificate is in the private + keyStore. If it is, it is valid. + + + + + If the certificate is not in the private key store, the handler will check whether the + the current date and time are within the validity period given in the certificate. + If they are not, the certificate is invalid; if it is, it will continue with the final + step. + + + + + Finally, a certification path for the certificate is created. This + basically means that the handler will determine whether the certificate has been issued + by any of the certificate authorities in the trustStore. If it + a certification path can be built succesfully, the certificate is valid. Otherwise, it + is not. + + + + + + To use the KeyStoreCallbackHandler for certificate validation purposes, you + will most likely only set the trustStore property: + + + + + + + + + +]]> + Using this setup, the certificate that is to be validated must either be in the trust store itself, + or the trust store must contain a certificate authority that issued the certificate. + +
+
+ AcegiCertificateValidationCallbackHandler + + The AcegiCertificateValidationCallbackHandler requires an Acegi + AuthenticationManager to operate. It uses this manager authenticate against a + X509AuthenticationToken that it creates. The configured authentication + manager is expected to supply a provider which can handle this token (usually an instance of + X509AuthenticationProvider). If authentication is succesfull, the token is + stored in the SecurityContextHolder. You can set the authentication manager + using the authenticationManager property: + + + + + + + + + + + + + + + + + + + + ... +]]> + + In this case, we are using a custom user details service to obtain authentication details based on + the certificate. Refer to the Acegi reference + documentation for more information about authentication against X509 + certificates. + +
+
+ JaasCertificateValidationCallbackHandler + + The JaasCertificateValidationCallbackHandler requires a + loginContextName to operate. It creates a new JAAS + LoginContext using this name and with the + X500Principal of the certificate. This means that this callback handler + integrates with any JAAS LoginModule that handles X500 principals. + + + You can wire up a JaasCertificateValidationCallbackHandler as follows: + + + MyLoginModule +]]> + + In this case, the callback handler uses the LoginContext named + "MyLoginModule". This module should be defined in your jaas.config file, and + should be able to authenticate against X500 principals. + +
+
+ +
+ +
+ Digital Signatures + + The digital signature of a message is a piece of information based on both the document + and the signer's private key. There are two main tasks related to signatures in WS-Security: verifying + signatures and signing messages. + +
+ Verifying Signatures + + Just like certificate-based authentication, + a signed message contains a BinarySecurityToken, which contains the certificate used + to sign the message. Additionally, it contains a SignedInfo block, which indicates + what part of the message was signed. + + + To make sure that all incoming SOAP messages carry a BinarySecurityToken, the + security policy file should contain a RequireSignature element. + It can also contain a SignatureTarget element, which specifies the target message + part which was expected to be signed, and various other subelements. You can also define the private key + alias to use, whether to use a symmetric instead of a private key, and many other properties. You can + find a reference of possible child elements + here. + + + +]]> + + If the signature is not present, the XwsSecurityInterceptor will return a + SOAP Fault to the sender. If it is present, it will fire a + SignatureVerificationKeyCallback to the registered handlers. Within Spring-WS, + there are is one class which handles this particular callback: the + KeyStoreCallbackHandler. + + +
+ KeyStoreCallbackHandler + + As described in , the + KeyStoreCallbackHandler uses a java.security.KeyStore + for handling various cryptographic callbacks, including signature verification. For signature + verification, the handler uses the trustStore property: + + + + + + + + + + +]]> +
+
+ +
+ Signing Messages + + When signing a message, the XwsSecurityInterceptor adds the + BinarySecurityToken to the message, and a SignedInfo block, which + indicates what part of the message was signed. + + + To sign all outgoing SOAP messages, the + security policy file should contain a Sign element. + It can also contain a SignatureTarget element, which specifies the target message + part which was expected to be signed, and various other subelements. You can also define the private key + alias to use, whether to use a symmetric instead of a private key, and many other properties. You can + find a reference of possible child elements + here. + + + +]]> + + The XwsSecurityInterceptor will fire a + SignatureKeyCallback to the registered handlers. Within Spring-WS, + there are is one class which handles this particular callback: the + KeyStoreCallbackHandler. + +
+ KeyStoreCallbackHandler + + As described in , the + KeyStoreCallbackHandler uses a java.security.KeyStore + for handling various cryptographic callbacks, including signing messages. For adding signatures, + the handler uses the keyStore property. Additionally, you must set + the privateKeyPassword property to unlock the private key used for signing. + + + + + + + + + + + +]]> +
+
+
+ +
+ Encryption and Decryption + + When encrypting, the message is transformed into a form that can only be read with the + appropriate key. The message can be decrypted to reveal the original, readable message. + +
+ Decryption + + To decrypt incoming SOAP messages, the security policy file should contain a + RequireEncryption element. This element can further carry a + EncryptionTarget element which indicates which part of the message should be + encrypted, a SymmetricKey to indicate that a shared secret instead of the regular + private key should be used to decrypt the message. You can read a description of the other elements + + here. + + + +]]> + + If an incoming message is not encrypted, the XwsSecurityInterceptor will return a + SOAP Fault to the sender. If it is present, it will fire a DecryptionKeyCallback + to the registered handlers. Within Spring-WS, there is one class which handled this particular callback: + the KeyStoreCallbackHandler. + +
+ KeyStoreCallbackHandler + + As described in , the + KeyStoreCallbackHandler uses a java.security.KeyStore + for handling various cryptographic callbacks, including decryption. For decryption, + the handler uses the keyStore property. Additionally, you must set + the privateKeyPassword property to unlock the private key used for + decryption. For decryption based on symmetric keys, it will use the + symmetricStore. + + + + + + + + + + + +]]> +
+
+ +
+ Encryption + + To encrypt outgoing SOAP messages, the security policy file should contain a Encrypt + element. This element can further carry a EncryptionTarget element which indicates + which part of the message should be encrypted, a SymmetricKey to indicate that a + shared secret instead of the regular private key should be used to decrypt the message. You can read a + description of the other elements + here. + + + +]]> + + The XwsSecurityInterceptor will fire a + EncryptionKeyCallback to the registered handlers in order to retrieve the + encryption information. Within Spring-WS, there is one class which handled this particular callback: the + KeyStoreCallbackHandler. + +
+ KeyStoreCallbackHandler + + As described in , the + KeyStoreCallbackHandler uses a java.security.KeyStore + for handling various cryptographic callbacks, including encryption. For encryption based on public + keys, the handler uses the trustStore property. For encryption based on + symmetric keys, it will use the symmetricStore. + + + + + + + + + + +]]> +
+
+ +
+
+ diff --git a/doc/src/docbkx/ws.xml b/doc/src/docbkx/ws.xml new file mode 100644 index 00000000..09eb51e3 --- /dev/null +++ b/doc/src/docbkx/ws.xml @@ -0,0 +1,193 @@ + + + Document-driven Web services with Spring-WS + +
+ Introduction + + Spring's Web service framework is designed around + +
+
+ MessageDispatcher + + Spring-WS is designed around a central dispatching mechanism, which forwards + +
+
+ Endpoints +
+
+ Endpoint mappings + + The endpoint mapping is responsible for mapping incoming messages to appropriate endpoints. It does this by + delivering a EndpointInterceptorChain, which consists of the endpoint that matches + the incoming request, and an optional list of endpoint interceptors. When a message is received by the + MessageDispatcher, it will ask the registered endpoint mappings to come up with a + appropriate HandlerExecutionChain. After that, the + MessageDispatcher will invoke the endpoint and interceptors in the chain. + + + Most endpoint mappings inherit from the AbstractEndpointMapping, which offers the + following properties: + + + + + + interceptors + + + the list of interceptors use. EndpointInterceptors are + discussed in . + + + + + defaultHandler + + + the default handler to use. This endpoint will be returned if no specific mapping was + found. + + + + + + + + + + + + +
+ SoapActionEndpointMapping +
+
+ PayloadRootQNameEndpointMapping +
+
+ Adding <interfacename>EndpointInterceptors</interfacename> +
+
+ Handling Exceptions + + Spring-WS provides + EndpointExceptionResolvers + to ease the pain of unexpected + exceptions occurring while your message is being processed by an endpoint which matched the request. + EndpointExceptionResolver + s somewhat resemble the exception mappings that can be + defined in the web application descriptor + web.xml + . + Rather than expose the innards of your application by giving a client a full stack trace, you can handle + the exception any way you want, e.g. return a SOAP fault with a specific fault code and string. + Furthermore, a programmatic way of handling exceptions gives you many more options for how to respond + appropriately. + + + Besides implementing the + HandlerExceptionResolver + interface, which is only a + matter of implementing the + resolveException(MessageContext, endpoint, Exception) + method and returning a + boolean, you may also use the + SoapFaultMappingExceptionResolver + . + This resolver enables you to take the class name of any exception that might be thrown and map it to a + SOAP Fault, like so: + + + + + + + SENDER,Invalid request + + + + +]]> + This configuration will map exceptions of type + ValidationFailureException + to a + sender side SOAP Fault with a fault string "Invalid request". + If any other exception occurs, it will return the default fault: a server side fault with fault string + "Server error". + Refer to the Javadoc of + SoapFaultDefinitionEditor + to read more about the exact + notation of the faults. + +
+
+ +
+ Similarities between Spring-MVC and Spring-WS + + Spring-WS has the same basic architecture as Spring's Web MVC framework. + The table below shows some of the core concepts of Spring Web MVC, and the corresponding class in Spring-WS. + + + + + Spring Web MVC + Spring Web Services + + + + + + DispatcherServlet + + + MessageDispatcher + + + + handler + endpoint + + + + HandlerAdapter + + + EndpointAdapter + + + + + HandlerMapping + + + EndpointMapping + + + + + HandlerInterceptor + + + EndpointInterceptor + + + + + HandlerExceptionResolver + + + EndpointExceptionResolver + + + + + + +
+
\ No newline at end of file diff --git a/oxm-tiger/build-maven2.xml b/oxm-tiger/build-maven2.xml new file mode 100644 index 00000000..4fd4ce2d --- /dev/null +++ b/oxm-tiger/build-maven2.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/oxm-tiger/pom.xml b/oxm-tiger/pom.xml new file mode 100644 index 00000000..593986f8 --- /dev/null +++ b/oxm-tiger/pom.xml @@ -0,0 +1,75 @@ + + + spring-ws + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + spring-oxm-tiger + Spring O/X Mapping - Java 5 + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + org.apache.maven.plugins + maven-antrun-plugin + + + generate-test-sources + + + + + + + ${project.build.directory}/generated-sources/test/java + + + run + + + + + + + + + + org.springframework.ws + spring-oxm + + + + woodstox + wstx-asl + test + + + + javax.xml.bind + jaxb-api + true + 2.0 + + + com.sun.xml.bind + jaxb-impl + 2.0.1 + test + + + com.sun.xml.bind + jaxb-xjc + 2.0.1 + test + + + + \ No newline at end of file diff --git a/oxm-tiger/src/etc/test-resources/log4j.properties b/oxm-tiger/src/etc/test-resources/log4j.properties new file mode 100644 index 00000000..5645c165 --- /dev/null +++ b/oxm-tiger/src/etc/test-resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootCategory=INFO, stdout +log4j.logger.org.springframework.oxm=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/oxm-tiger/src/etc/test-resources/org/springframework/oxm/flight.xsd b/oxm-tiger/src/etc/test-resources/org/springframework/oxm/flight.xsd new file mode 100644 index 00000000..5f46e0b9 --- /dev/null +++ b/oxm-tiger/src/etc/test-resources/org/springframework/oxm/flight.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java new file mode 100644 index 00000000..3213f768 --- /dev/null +++ b/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -0,0 +1,276 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jaxb; + +import java.io.IOException; +import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.validation.Schema; + +import org.springframework.core.io.Resource; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.xml.transform.StaxResult; +import org.springframework.xml.transform.StaxSource; +import org.springframework.xml.validation.SchemaLoaderUtils; +import org.xml.sax.SAXException; + +/** + * Implementation of the Marshaller interface for JAXB 2.0. + *

+ * The typical usage will be to set either the contextPath or the classesToBeBound property on + * this bean, possibly customize the marshaller and unmarshaller by setting properties, schemas, adapters, and + * listeners, and to refer to it. + * + * @author Arjen Poutsma + * @see #setContextPath(String) + * @see #setClassesToBeBound(Class[]) + * @see #setJaxbContextProperties(java.util.Map) + * @see #setMarshallerProperties(java.util.Map) + * @see #setUnmarshallerProperties(java.util.Map) + * @see #setSchema(org.springframework.core.io.Resource) + * @see #setSchemas(org.springframework.core.io.Resource[]) + * @see #setMarshallerListener(javax.xml.bind.Marshaller.Listener) + * @see #setUnmarshallerListener(javax.xml.bind.Unmarshaller.Listener) + * @see #setAdapters(javax.xml.bind.annotation.adapters.XmlAdapter[]) + */ +public class Jaxb2Marshaller extends AbstractJaxbMarshaller { + + private Resource[] schemaResources; + + private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI; + + private Marshaller.Listener marshallerListener; + + private Unmarshaller.Listener unmarshallerListener; + + private XmlAdapter[] adapters; + + private Schema schema; + + private Class[] classesToBeBound; + + private Map jaxbContextProperties; + + /** + * Sets the XmlAdapters to be registered with the JAXB Marshaller and + * Unmarshaller + */ + public void setAdapters(XmlAdapter[] adapters) { + this.adapters = adapters; + } + + /** + * Sets the list of java classes to be recognized by a newly created JAXBContext. Setting this property or + * contextPath is required. + * + * @see #setContextPath(String) + */ + public void setClassesToBeBound(Class[] classesToBeBound) { + this.classesToBeBound = classesToBeBound; + } + + /** + * Sets the JAXBContext properties. These implementation-specific properties will be set on the + * JAXBContext. + */ + public void setJaxbContextProperties(Map jaxbContextProperties) { + this.jaxbContextProperties = jaxbContextProperties; + } + + /** + * Sets the Marshaller.Listener to be registered with the JAXB Marshaller. + */ + public void setMarshallerListener(Marshaller.Listener marshallerListener) { + this.marshallerListener = marshallerListener; + } + + /** + * Sets the schema resource to use for validation. + */ + public void setSchema(Resource schemaResource) { + this.schemaResources = new Resource[]{schemaResource}; + } + + /** + * Sets the schema language. Default is the W3C XML Schema: http://www.w3.org/2001/XMLSchema". + * + * @see XMLConstants#W3C_XML_SCHEMA_NS_URI + * @see XMLConstants#RELAXNG_NS_URI + */ + public void setSchemaLanguage(String schemaLanguage) { + this.schemaLanguage = schemaLanguage; + } + + /** + * Sets the schema resources to use for validation. + */ + public void setSchemas(Resource[] schemaResources) { + this.schemaResources = schemaResources; + } + + /** + * Sets the Unmarshaller.Listener to be registered with the JAXB Unmarshaller. + */ + public void setUnmarshallerListener(Unmarshaller.Listener unmarshallerListener) { + this.unmarshallerListener = unmarshallerListener; + } + + public void marshal(Object graph, Result result) { + try { + if (result instanceof StaxResult) { + marshalStaxResult(graph, (StaxResult) result); + } + else { + createMarshaller().marshal(graph, result); + } + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } + + public Object unmarshal(Source source) { + try { + if (source instanceof StaxSource) { + return unmarshalStaxSource((StaxSource) source); + } + else { + return createUnmarshaller().unmarshal(source); + } + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } + + protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException { + if (schema != null) { + marshaller.setSchema(schema); + } + if (marshallerListener != null) { + marshaller.setListener(marshallerListener); + } + if (adapters != null) { + for (int i = 0; i < adapters.length; i++) { + marshaller.setAdapter(adapters[i]); + } + } + } + + protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException { + if (schema != null) { + unmarshaller.setSchema(schema); + } + if (unmarshallerListener != null) { + unmarshaller.setListener(unmarshallerListener); + } + if (adapters != null) { + for (int i = 0; i < adapters.length; i++) { + unmarshaller.setAdapter(adapters[i]); + } + } + } + + protected JAXBContext createJaxbContext() throws Exception { + if (JaxbUtils.getJaxbVersion() < JaxbUtils.JAXB_2) { + throw new IllegalStateException( + "Cannot use Jaxb2Marshaller in combination with JAXB 1.0. Use Jaxb1Marshaller instead."); + } + if (StringUtils.hasLength(getContextPath()) && !ObjectUtils.isEmpty(classesToBeBound)) { + throw new IllegalArgumentException("specify either contextPath or classesToBeBound property; not both"); + } + loadSchema(); + if (StringUtils.hasLength(getContextPath())) { + return createJaxbContextFromContextPath(); + } + else if (!ObjectUtils.isEmpty(classesToBeBound)) { + return createJaxbContextFromClasses(); + } + else { + throw new IllegalArgumentException("setting either contextPath or classesToBeBound is required"); + } + } + + private JAXBContext createJaxbContextFromContextPath() throws JAXBException { + if (logger.isInfoEnabled()) { + logger.info("Creating JAXBContext with context path [" + getContextPath() + "]"); + } + if (jaxbContextProperties != null) { + return JAXBContext + .newInstance(getContextPath(), ClassUtils.getDefaultClassLoader(), jaxbContextProperties); + } + else { + return JAXBContext.newInstance(getContextPath()); + } + } + + private JAXBContext createJaxbContextFromClasses() throws JAXBException { + if (logger.isInfoEnabled()) { + logger.info("Creating JAXBContext with classes to be bound [" + + StringUtils.arrayToCommaDelimitedString(classesToBeBound) + "]"); + } + if (jaxbContextProperties != null) { + return JAXBContext.newInstance(classesToBeBound, jaxbContextProperties); + } + else { + return JAXBContext.newInstance(classesToBeBound); + } + } + + private void loadSchema() throws IOException, SAXException { + if (!ObjectUtils.isEmpty(schemaResources)) { + if (logger.isDebugEnabled()) { + logger.debug( + "Setting validation schema to " + StringUtils.arrayToCommaDelimitedString(schemaResources)); + } + schema = SchemaLoaderUtils.loadSchema(schemaResources, schemaLanguage); + } + } + + private void marshalStaxResult(Object graph, StaxResult staxResult) throws JAXBException { + if (staxResult.getXMLStreamWriter() != null) { + createMarshaller().marshal(graph, staxResult.getXMLStreamWriter()); + } + else if (staxResult.getXMLEventWriter() != null) { + createMarshaller().marshal(graph, staxResult.getXMLEventWriter()); + } + else { + throw new IllegalArgumentException("StaxResult contains neither XMLStreamWriter nor XMLEventConsumer"); + } + } + + private Object unmarshalStaxSource(StaxSource staxSource) throws JAXBException { + if (staxSource.getXMLStreamReader() != null) { + return createUnmarshaller().unmarshal(staxSource.getXMLStreamReader()); + } + else if (staxSource.getXMLEventReader() != null) { + return createUnmarshaller().unmarshal(staxSource.getXMLEventReader()); + } + else { + throw new IllegalArgumentException("StaxSource contains neither XMLStreamReader nor XMLEventReader"); + } + } +} diff --git a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java new file mode 100644 index 00000000..ac6eabc7 --- /dev/null +++ b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java @@ -0,0 +1,188 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jaxb; + +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; +import java.util.Collections; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Result; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamResult; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.easymock.MockControl; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; +import org.xml.sax.ContentHandler; + +import org.springframework.oxm.XmlMappingException; +import org.springframework.oxm.jaxb2.FlightType; +import org.springframework.oxm.jaxb2.Flights; +import org.springframework.xml.transform.StaxResult; + +public class Jaxb2MarshallerTest extends XMLTestCase { + + private static final String CONTEXT_PATH = "org.springframework.oxm.jaxb2"; + + private static final String EXPECTED_STRING = + "" + + "42"; + + private Jaxb2Marshaller marshaller; + + private Flights flights; + + protected void setUp() throws Exception { + marshaller = new Jaxb2Marshaller(); + marshaller.setContextPath(CONTEXT_PATH); + marshaller.afterPropertiesSet(); + FlightType flight = new FlightType(); + flight.setNumber(42L); + flights = new Flights(); + flights.getFlight().add(flight); + } + + public void testMarshalDOMResult() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); + Document document = builder.newDocument(); + DOMResult domResult = new DOMResult(document); + marshaller.marshal(flights, domResult); + Document expected = builder.newDocument(); + Element flightsElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:flights"); + expected.appendChild(flightsElement); + Element flightElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:flight"); + flightsElement.appendChild(flightElement); + Element numberElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:number"); + flightElement.appendChild(numberElement); + Text text = expected.createTextNode("42"); + numberElement.appendChild(text); + assertXMLEqual("Marshaller writes invalid DOMResult", expected, document); + } + + public void testMarshalStreamResultWriter() throws Exception { + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + marshaller.marshal(flights, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } + + public void testMarshalStreamResultOutputStream() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + StreamResult result = new StreamResult(os); + marshaller.marshal(flights, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, + new String(os.toByteArray(), "UTF-8")); + } + + public void testMarshalStaxResultXMLStreamWriter() throws Exception { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + StringWriter writer = new StringWriter(); + XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(writer); + StaxResult result = new StaxResult(streamWriter); + marshaller.marshal(flights, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } + + public void testMarshalStaxResultXMLEventWriter() throws Exception { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + StringWriter writer = new StringWriter(); + XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer); + StaxResult result = new StaxResult(eventWriter); + marshaller.marshal(flights, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } + + public void testProperties() throws Exception { + Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); + marshaller.setContextPath(CONTEXT_PATH); + marshaller.setMarshallerProperties( + Collections.singletonMap(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE)); + marshaller.afterPropertiesSet(); + } + + public void testNoContextPathOrClassesToBeBound() throws Exception { + try { + Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); + marshaller.afterPropertiesSet(); + fail("Should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + } + } + + public void testInvalidContextPath() throws Exception { + try { + Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); + marshaller.setContextPath("ab"); + marshaller.afterPropertiesSet(); + fail("Should have thrown an XmlMappingException"); + } + catch (XmlMappingException ex) { + } + } + + public void testMarshalInvalidClass() throws Exception { + Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); + marshaller.setClassesToBeBound(new Class[]{FlightType.class}); + marshaller.afterPropertiesSet(); + Result result = new StreamResult(new StringWriter()); + Flights flights = new Flights(); + try { + marshaller.marshal(flights, result); + fail("Should have thrown an MarshallingFailureException"); + } + catch (XmlMappingException ex) { + // expected + } + } + + public void testMarshalSaxResult() throws Exception { + MockControl handlerControl = MockControl.createStrictControl(ContentHandler.class); + ContentHandler handlerMock = (ContentHandler) handlerControl.getMock(); + handlerMock.setDocumentLocator(null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startDocument(); + handlerMock.startPrefixMapping("", "http://samples.springframework.org/flight"); + handlerMock.startElement("http://samples.springframework.org/flight", "flights", "flights", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startElement("http://samples.springframework.org/flight", "flight", "flight", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startElement("http://samples.springframework.org/flight", "number", "number", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.characters(new char[]{'4', '2'}, 0, 2); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.endElement("http://samples.springframework.org/flight", "number", "number"); + handlerMock.endElement("http://samples.springframework.org/flight", "flight", "flight"); + handlerMock.endElement("http://samples.springframework.org/flight", "flights", "flights"); + handlerMock.endPrefixMapping(""); + handlerMock.endDocument(); + + handlerControl.replay(); + SAXResult result = new SAXResult(handlerMock); + marshaller.marshal(flights, result); + handlerControl.verify(); + } +} diff --git a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTest.java b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTest.java new file mode 100644 index 00000000..63bbd874 --- /dev/null +++ b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jaxb; + +import java.io.StringReader; +import java.io.ByteArrayInputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLEventReader; + +import junit.framework.TestCase; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.oxm.jaxb2.FlightType; +import org.springframework.oxm.jaxb2.Flights; +import org.springframework.xml.transform.StaxSource; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; +import org.xml.sax.XMLReader; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.XMLReaderFactory; + +public class Jaxb2UnmarshallerTest extends TestCase { + + private static final String INPUT_STRING = "" + + "42"; + + private Jaxb2Marshaller unmarshaller; + + protected void setUp() throws Exception { + unmarshaller = new Jaxb2Marshaller(); + unmarshaller.setContextPath("org.springframework.oxm.jaxb2"); + unmarshaller.setSchema(new ClassPathResource("org/springframework/oxm/flight.xsd")); + unmarshaller.afterPropertiesSet(); + } + + public void testUnmarshalDomSource() throws Exception { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = builder.newDocument(); + Element flightsElement = document.createElementNS("http://samples.springframework.org/flight", "tns:flights"); + document.appendChild(flightsElement); + Element flightElement = document.createElementNS("http://samples.springframework.org/flight", "tns:flight"); + flightsElement.appendChild(flightElement); + Element numberElement = document.createElementNS("http://samples.springframework.org/flight", "tns:number"); + flightElement.appendChild(numberElement); + Text text = document.createTextNode("42"); + numberElement.appendChild(text); + DOMSource source = new DOMSource(document); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalStreamSourceReader() throws Exception { + StreamSource source = new StreamSource(new StringReader(INPUT_STRING)); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalStreamSourceInputStream() throws Exception { + StreamSource source = new StreamSource(new ByteArrayInputStream(INPUT_STRING.getBytes("UTF-8"))); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalSAXSource() throws Exception { + XMLReader reader = XMLReaderFactory.createXMLReader(); + SAXSource source = new SAXSource(reader, new InputSource(new StringReader(INPUT_STRING))); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalStaxSourceXmlStreamReader() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(new StringReader(INPUT_STRING)); + StaxSource source = new StaxSource(streamReader); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalStaxSourceXmlEventReader() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(INPUT_STRING)); + StaxSource source = new StaxSource(eventReader); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + private void testFlights(Object o) { + Flights flights = (Flights) o; + assertNotNull("Flights is null", flights); + assertEquals("Invalid amount of flight elements", 1, flights.getFlight().size()); + FlightType flight = (FlightType) flights.getFlight().get(0); + assertNotNull("Flight is null", flight); + assertEquals("Number is invalid", 42L, flight.getNumber()); + } + +} diff --git a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/JaxbUtilsTest.java b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/JaxbUtilsTest.java new file mode 100644 index 00000000..393770fb --- /dev/null +++ b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/JaxbUtilsTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.bind.JAXBException; +import javax.xml.bind.MarshalException; +import javax.xml.bind.UnmarshalException; +import javax.xml.bind.ValidationException; + +import junit.framework.TestCase; + +public class JaxbUtilsTest extends TestCase { + + public void testGetJaxbVersion() throws Exception { + assertEquals("Invalid JAXB version", JaxbUtils.JAXB_2, JaxbUtils.getJaxbVersion()); + } +} diff --git a/oxm/build-maven2.xml b/oxm/build-maven2.xml new file mode 100644 index 00000000..1bee0c78 --- /dev/null +++ b/oxm/build-maven2.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/oxm/pom.xml b/oxm/pom.xml new file mode 100644 index 00000000..c3c723f9 --- /dev/null +++ b/oxm/pom.xml @@ -0,0 +1,149 @@ + + + + spring-ws + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + spring-oxm + jar + Spring O/X Mapping + Spring Object/XML Mapping abstraction + + + jibx + JiBX Maven Repository + http://jibx.sourceforge.net/maven2/ + default + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + generate-test-sources + generate-test-sources + + + + + + + ${project.build.directory}/generated-sources/test/java + + + run + + + + test-compile + test-compile + + + + + + + + + run + + + + + + + + + + org.springframework.ws + spring-xml + + + + stax + stax-api + true + + + woodstox + wstx-asl + test + + + xpp3 + xpp3 + test + + + xerces + xercesImpl + test + + + + org.codehaus.castor + castor + true + + + + javax.xml.bind + jaxb-api + true + + + com.sun.xml.bind + jaxb-impl + test + + + com.sun.xml.bind + jaxb-libs + test + + + com.sun.xml.bind + jaxb-xjc + test + + + com.sun.msv.datatype.xsd + xsdlib + test + + + + xmlbeans + xbean + true + + + + org.jibx + jibx-run + true + + + org.jibx + jibx-bind + test + + + bcel + bcel + test + + + + xstream + xstream + true + + + diff --git a/oxm/src/etc/test-resources/log4j.properties b/oxm/src/etc/test-resources/log4j.properties new file mode 100644 index 00000000..5645c165 --- /dev/null +++ b/oxm/src/etc/test-resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootCategory=INFO, stdout +log4j.logger.org.springframework.oxm=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/oxm/src/etc/test-resources/org/springframework/oxm/castor/mapping.xml b/oxm/src/etc/test-resources/org/springframework/oxm/castor/mapping.xml new file mode 100644 index 00000000..e8597125 --- /dev/null +++ b/oxm/src/etc/test-resources/org/springframework/oxm/castor/mapping.xml @@ -0,0 +1,31 @@ + + + + Castor generated mapping file + + + Default mapping for class + org.springframework.oxm.castor.Flights + + + + + + + + + Default mapping for class + org.springframework.oxm.castor.Flight + + + + + + + \ No newline at end of file diff --git a/oxm/src/etc/test-resources/org/springframework/oxm/flight.xsd b/oxm/src/etc/test-resources/org/springframework/oxm/flight.xsd new file mode 100644 index 00000000..5f46e0b9 --- /dev/null +++ b/oxm/src/etc/test-resources/org/springframework/oxm/flight.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/oxm/src/etc/test-resources/org/springframework/oxm/jibx/binding.xml b/oxm/src/etc/test-resources/org/springframework/oxm/jibx/binding.xml new file mode 100644 index 00000000..415021ce --- /dev/null +++ b/oxm/src/etc/test-resources/org/springframework/oxm/jibx/binding.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/oxm/src/main/java/org/springframework/oxm/AbstractMarshaller.java b/oxm/src/main/java/org/springframework/oxm/AbstractMarshaller.java new file mode 100644 index 00000000..75a614e8 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/AbstractMarshaller.java @@ -0,0 +1,504 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.xml.transform.StaxResult; +import org.springframework.xml.transform.StaxSource; +import org.w3c.dom.Node; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * Abstract implementation of the Marshaller and Unmarshaller interface. This implementation + * inspects the given Source or Result, and defers further handling to overridable template + * methods. + * + * @author Arjen Poutsma + */ +public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { + + /** + * Logger available to subclasses. + */ + protected final Log logger = LogFactory.getLog(getClass()); + + private boolean validating = false; + + private boolean namespaceAware = true; + + private DocumentBuilderFactory documentBuilderFactory; + + /** + * Set whether or not the XML parser should be XML namespace aware. Default is true. + */ + public void setNamespaceAware(boolean namespaceAware) { + this.namespaceAware = namespaceAware; + } + + /** + * Set if the XML parser should validate the document. Default is false. + */ + public void setValidating(boolean validating) { + this.validating = validating; + } + + /** + * Marshals the object graph with the given root into the provided javax.xml.transform.Result. + *

+ * This implementation inspects the given result, and calls marshalDomResult, + * marshalSaxResult, or marshalStreamResult. + * + * @param graph the root of the object graph to marshal + * @param result the result to marshal to + * @throws XmlMappingException if the given object cannot be marshalled to the result + * @throws IOException if an I/O exception occurs + * @throws IllegalArgumentException if result if neither a DOMResult, + * SAXResult, StreamResult + * @see #marshalDomResult(Object, javax.xml.transform.dom.DOMResult) + * @see #marshalSaxResult(Object, javax.xml.transform.sax.SAXResult) + * @see #marshalStreamResult(Object, javax.xml.transform.stream.StreamResult) + */ + public final void marshal(Object graph, Result result) throws XmlMappingException, IOException { + if (result instanceof DOMResult) { + marshalDomResult(graph, (DOMResult) result); + } + else if (result instanceof StaxResult) { + marshalStaxResult(graph, (StaxResult) result); + } + else if (result instanceof SAXResult) { + marshalSaxResult(graph, (SAXResult) result); + } + else if (result instanceof StreamResult) { + marshalStreamResult(graph, (StreamResult) result); + } + else { + throw new IllegalArgumentException("Unknown Result type: " + result.getClass()); + } + } + + /** + * Unmarshals the given provided javax.xml.transform.Source into an object graph. + *

+ * This implementation inspects the given result, and calls unmarshalDomSource, + * unmarshalSaxSource, or unmarshalStreamSource. + * + * @param source the source to marshal from + * @return the object graph + * @throws XmlMappingException if the given source cannot be mapped to an object + * @throws IOException if an I/O Exception occurs + * @throws IllegalArgumentException if source is neither a DOMSource, a + * SAXSource, nor a StreamSource + * @see #unmarshalDomSource(javax.xml.transform.dom.DOMSource) + * @see #unmarshalSaxSource(javax.xml.transform.sax.SAXSource) + * @see #unmarshalStreamSource(javax.xml.transform.stream.StreamSource) + */ + public final Object unmarshal(Source source) throws XmlMappingException, IOException { + if (source instanceof DOMSource) { + return unmarshalDomSource((DOMSource) source); + } + else if (source instanceof StaxSource) { + return unmarshalStaxSource((StaxSource) source); + } + else if (source instanceof SAXSource) { + return unmarshalSaxSource((SAXSource) source); + } + else if (source instanceof StreamSource) { + return unmarshalStreamSource((StreamSource) source); + } + else { + throw new IllegalArgumentException("Unknown Source type: " + source.getClass()); + } + } + + /** + * Create a DocumentBuilder that this marshaller will use for creating DOM documents when passed an + * empty DOMSource. Can be overridden in subclasses, adding further initialization of the builder. + * + * @param factory the DocumentBuilderFactory that the DocumentBuilder should be created with + * @return the DocumentBuilder + * @throws javax.xml.parsers.ParserConfigurationException + * if thrown by JAXP methods + */ + protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory) + throws ParserConfigurationException { + return factory.newDocumentBuilder(); + } + + /** + * Create a DocumentBuilder that this marshaller will use for creating DOM documents when passed an + * empty DOMSource. The resulting DocumentBuilderFactory is cached, so this method will + * only be called once. + * + * @return the DocumentBuilderFactory + * @throws ParserConfigurationException if thrown by JAXP methods + */ + protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserConfigurationException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(validating); + factory.setNamespaceAware(namespaceAware); + return factory; + } + + /** + * Create a XMLReader that this marshaller will when passed an empty SAXSource. + * + * @return the XMLReader + * @throws SAXException if thrown by JAXP methods + */ + protected XMLReader createXmlReader() throws SAXException { + return XMLReaderFactory.createXMLReader(); + } + + // + // Marshalling + // + + /** + * Template method for handling DOMResults. This implementation defers to marshalDomNode. + * + * @param graph the root of the object graph to marshal + * @param domResult the DOMResult + * @throws XmlMappingException if the given object cannot be marshalled to the result + * @throws IllegalArgumentException if the domResult is empty + * @see #marshalDomNode(Object,org.w3c.dom.Node) + */ + protected void marshalDomResult(Object graph, DOMResult domResult) throws XmlMappingException { + Assert.notNull(domResult.getNode(), "DOMResult does not contain Node"); + marshalDomNode(graph, domResult.getNode()); + } + + /** + * Template method for handling StaxResults. This implementation defers to + * marshalXMLSteamWriter, or marshalXMLEventConsumer, depending on what is contained in + * the StaxResult. + * + * @param graph the root of the object graph to marshal + * @param staxResult the StaxResult + * @throws XmlMappingException if the given object cannot be marshalled to the result + * @throws IllegalArgumentException if the domResult is empty + * @see #marshalDomNode(Object,org.w3c.dom.Node) + */ + protected void marshalStaxResult(Object graph, StaxResult staxResult) throws XmlMappingException { + if (staxResult.getXMLStreamWriter() != null) { + marshalXmlStreamWriter(graph, staxResult.getXMLStreamWriter()); + } + else if (staxResult.getXMLEventWriter() != null) { + marshalXmlEventWriter(graph, staxResult.getXMLEventWriter()); + } + else { + throw new IllegalArgumentException("StaxResult contains neither XMLStreamWriter nor XMLEventConsumer"); + } + } + + /** + * Template method for handling SAXResults. This implementation defers to + * marshalSaxHandlers. + * + * @param graph the root of the object graph to marshal + * @param saxResult the SAXResult + * @throws XmlMappingException if the given object cannot be marshalled to the result + * @see #marshalSaxHandlers(Object, org.xml.sax.ContentHandler, org.xml.sax.ext.LexicalHandler) + */ + protected void marshalSaxResult(Object graph, SAXResult saxResult) throws XmlMappingException { + ContentHandler contentHandler = saxResult.getHandler(); + Assert.notNull(contentHandler, "ContentHandler not set on SAXResult"); + LexicalHandler lexicalHandler = saxResult.getLexicalHandler(); + marshalSaxHandlers(graph, contentHandler, lexicalHandler); + } + + /** + * Template method for handling StreamResults. This implementation defers to + * marshalOutputStream, or marshalWriter, depending on what is contained in the + * StreamResult + * + * @param graph the root of the object graph to marshal + * @param streamResult the StreamResult + * @throws IOException if an I/O Exception occurs + * @throws XmlMappingException if the given object cannot be marshalled to the result + * @throws IllegalArgumentException if streamResult contains neither OutputStream nor + * Writer. + */ + protected void marshalStreamResult(Object graph, StreamResult streamResult) + throws XmlMappingException, IOException { + if (streamResult.getOutputStream() != null) { + marshalOutputStream(graph, streamResult.getOutputStream()); + } + else if (streamResult.getWriter() != null) { + marshalWriter(graph, streamResult.getWriter()); + } + else { + throw new IllegalArgumentException("StreamResult contains neither OutputStream nor Writer"); + } + } + + // + // Unmarshalling + // + + /** + * Template method for handling DOMSources. This implementation defers to + * unmarshalDomNode. If the given source is empty, an empty source Document will be + * created as a placeholder. + * + * @param domSource the DOMSource + * @return the object graph + * @throws IllegalArgumentException if the domSource is empty + * @throws XmlMappingException if the given source cannot be mapped to an object + * @see #unmarshalDomNode(org.w3c.dom.Node) + */ + protected Object unmarshalDomSource(DOMSource domSource) throws XmlMappingException { + if (domSource.getNode() == null) { + try { + if (documentBuilderFactory == null) { + documentBuilderFactory = createDocumentBuilderFactory(); + } + DocumentBuilder documentBuilder = createDocumentBuilder(documentBuilderFactory); + domSource.setNode(documentBuilder.newDocument()); + } + catch (ParserConfigurationException ex) { + throw new UnmarshallingFailureException( + "Could not create document placeholder for DOMSource: " + ex.getMessage(), ex); + } + } + return unmarshalDomNode(domSource.getNode()); + } + + /** + * Template method for handling StaxSources. This implementation defers to + * unmarshalXmlStreamReader, or unmarshalXmlEventReader. + * + * @param staxSource the StaxSource + * @return the object graph + * @throws XmlMappingException if the given source cannot be mapped to an object + */ + protected Object unmarshalStaxSource(StaxSource staxSource) throws XmlMappingException { + if (staxSource.getXMLStreamReader() != null) { + return unmarshalXmlStreamReader(staxSource.getXMLStreamReader()); + } + else if (staxSource.getXMLEventReader() != null) { + return unmarshalXmlEventReader(staxSource.getXMLEventReader()); + } + else { + throw new IllegalArgumentException("StaxSource contains neither XMLStreamReader nor XMLEventReader"); + } + } + + /** + * Template method for handling SAXSources. This implementation defers to + * unmarshalSaxReader. + * + * @param saxSource the SAXSource + * @return the object graph + * @throws XmlMappingException if the given source cannot be mapped to an object + * @throws IOException if an I/O Exception occurs + * @see #unmarshalSaxReader(org.xml.sax.XMLReader, org.xml.sax.InputSource) + */ + protected Object unmarshalSaxSource(SAXSource saxSource) throws XmlMappingException, IOException { + if (saxSource.getXMLReader() == null) { + try { + saxSource.setXMLReader(createXmlReader()); + } + catch (SAXException ex) { + throw new UnmarshallingFailureException("Could not create XMLReader for SAXSource: " + ex.getMessage(), + ex); + } + } + if (saxSource.getInputSource() == null) { + saxSource.setInputSource(new InputSource()); + } + return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource()); + } + + /** + * Template method for handling StreamSources. This implementation defers to + * unmarshalInputStream, or unmarshalReader. + * + * @param streamSource the StreamSource + * @return the object graph + * @throws IOException if an I/O exception occurs + * @throws XmlMappingException if the given source cannot be mapped to an object + */ + protected Object unmarshalStreamSource(StreamSource streamSource) throws XmlMappingException, IOException { + if (streamSource.getInputStream() != null) { + return unmarshalInputStream(streamSource.getInputStream()); + } + else if (streamSource.getReader() != null) { + return unmarshalReader(streamSource.getReader()); + } + else { + throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader"); + } + } + + // + // Abstract template methods + // + + /** + * Abstract template method for marshalling the given object graph to a DOM Node. + *

+ * In practice, node is be a Document node, a DocumentFragment node, or a + * Element node. In other words, a node that accepts children. + * + * @param graph the root of the object graph to marshal + * @param node The DOM node that will contain the result tree + * @throws XmlMappingException if the given object cannot be marshalled to the DOM node + * @see org.w3c.dom.Document + * @see org.w3c.dom.DocumentFragment + * @see org.w3c.dom.Element + */ + protected abstract void marshalDomNode(Object graph, Node node) throws XmlMappingException; + + /** + * Abstract template method for marshalling the given object to a StAX XMLEventWriter. + * + * @param graph the root of the object graph to marshal + * @param eventWriter the XMLEventWriter to write to + * @throws XmlMappingException if the given object cannot be marshalled to the DOM node + */ + protected abstract void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException; + + /** + * Abstract template method for marshalling the given object to a StAX XMLStreamWriter. + * + * @param graph the root of the object graph to marshal + * @param streamWriter the XMLStreamWriter to write to + * @throws XmlMappingException if the given object cannot be marshalled to the DOM node + */ + protected abstract void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) + throws XmlMappingException; + + /** + * Abstract template method for marshalling the given object graph to a OutputStream. + * + * @param graph the root of the object graph to marshal + * @param outputStream the OutputStream to write to + * @throws XmlMappingException if the given object cannot be marshalled to the writer + * @throws IOException if an I/O exception occurs + */ + protected abstract void marshalOutputStream(Object graph, OutputStream outputStream) + throws XmlMappingException, IOException; + + /** + * Abstract template method for marshalling the given object graph to a SAX ContentHandler. + * + * @param graph the root of the object graph to marshal + * @param contentHandler the SAX ContentHandler + * @param lexicalHandler the SAX2 LexicalHandler. Can be null. + * @throws XmlMappingException if the given object cannot be marshalled to the handlers + */ + protected abstract void marshalSaxHandlers(Object graph, + ContentHandler contentHandler, + LexicalHandler lexicalHandler) throws XmlMappingException; + + /** + * Abstract template method for marshalling the given object graph to a Writer. + * + * @param graph the root of the object graph to marshal + * @param writer the Writer to write to + * @throws XmlMappingException if the given object cannot be marshalled to the writer + * @throws IOException if an I/O exception occurs + */ + protected abstract void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException; + + /** + * Abstract template method for unmarshalling from a given DOM Node. + * + * @param node The DOM node that contains the objects to be unmarshalled + * @return the object graph + * @throws XmlMappingException if the given DOM node cannot be mapped to an object + */ + protected abstract Object unmarshalDomNode(Node node) throws XmlMappingException; + + /** + * Abstract template method for unmarshalling from a given Stax XMLEventReader. + * + * @param eventReader The XMLEventReader to read from + * @return the object graph + * @throws XmlMappingException if the given event reader cannot be converted to an object + */ + protected abstract Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException; + + /** + * Abstract template method for unmarshalling from a given Stax XMLStreamReader. + * + * @param streamReader The XMLStreamReader to read from + * @return the object graph + * @throws XmlMappingException if the given stream reader cannot be converted to an object + */ + protected abstract Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException; + + /** + * Abstract template method for unmarshalling from a given InputStream. + * + * @param inputStream the InputStreamStream to read from + * @return the object graph + * @throws XmlMappingException if the given stream cannot be converted to an object + * @throws IOException if an I/O exception occurs + */ + protected abstract Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException; + + /** + * Abstract template method for unmarshalling from a given Reader. + * + * @param reader the Reader to read from + * @return the object graph + * @throws XmlMappingException if the given reader cannot be converted to an object + * @throws IOException if an I/O exception occurs + */ + protected abstract Object unmarshalReader(Reader reader) throws XmlMappingException, IOException; + + /** + * Abstract template method for unmarshalling using a given SAX XMLReader and + * InputSource. + * + * @param xmlReader the SAX XMLReader to parse with + * @param inputSource the input source to parse from + * @return the object graph + * @throws XmlMappingException if the given reader and input source cannot be converted to an object + */ + protected abstract Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) + throws XmlMappingException, IOException; +} diff --git a/oxm/src/main/java/org/springframework/oxm/GenericMarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/GenericMarshallingFailureException.java new file mode 100644 index 00000000..4e1985ec --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/GenericMarshallingFailureException.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + + +/** + * Base class for exception thrown when a marshalling or unmarshalling error occurs. + * + * @author Arjen Poutsma + * @see MarshallingFailureException + * @see UnmarshallingFailureException + */ +public abstract class GenericMarshallingFailureException extends XmlMappingException { + + /** + * Constructor for GenericMarshallingFailureException. + */ + public GenericMarshallingFailureException(String msg) { + super(msg); + } + + /** + * Constructor for GenericMarshallingFailureException. + */ + public GenericMarshallingFailureException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/Marshaller.java b/oxm/src/main/java/org/springframework/oxm/Marshaller.java new file mode 100644 index 00000000..3bb7518d --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/Marshaller.java @@ -0,0 +1,44 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + +import java.io.IOException; + +import javax.xml.transform.Result; + +/** + * Defines the contract for Object XML Mapping Marshallers. Implementations of this interface can serialize a given + * Object to a XML Stream. + *

+ * Although the marshal method accepts a java.lang.Object as its first parameter, + * most Marshaller implementations cannot handle arbitrary java.lang.Object. Instead, a + * object class must be registered with the marshaller, or have a common base class. + * + * @author Arjen Poutsma + */ +public interface Marshaller { + + /** + * Marshals the object graph with the given root into the provided javax.xml.transform.Result. + * + * @param graph the root of the object graph to marshal + * @param result the result to marshal to + * @throws XmlMappingException if the given object cannot be marshalled to the result + * @throws IOException if an I/O exception occurs + */ + void marshal(Object graph, Result result) throws XmlMappingException, IOException; + +} diff --git a/oxm/src/main/java/org/springframework/oxm/MarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/MarshallingFailureException.java new file mode 100644 index 00000000..0fc2ace5 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/MarshallingFailureException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + + +/** + * Exception thrown on marshalling failure. + * + * @author Arjen Poutsma + */ +public class MarshallingFailureException extends GenericMarshallingFailureException { + /** + * Construct a MarshallingFailureException with the specified detail message. + * + * @param msg the detail message + */ + public MarshallingFailureException(String msg) { + super(msg); + } + + /** + * Construct a MarshallingFailureException with the specified detail message and nested exception. + * + * @param msg the detail message + * @param ex the nested exception + */ + public MarshallingFailureException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/UncategorizedXmlMappingException.java b/oxm/src/main/java/org/springframework/oxm/UncategorizedXmlMappingException.java new file mode 100644 index 00000000..cea72338 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/UncategorizedXmlMappingException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + + +/** + * Superclass for exceptions that cannot be distinguished further. + * + * @author Arjen Poutsma + */ +public abstract class UncategorizedXmlMappingException extends XmlMappingException { + + /** + * Constructor for UncategorizedXmlMappingException. + */ + protected UncategorizedXmlMappingException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/Unmarshaller.java b/oxm/src/main/java/org/springframework/oxm/Unmarshaller.java new file mode 100644 index 00000000..45711b8b --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/Unmarshaller.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + +import java.io.IOException; + +import javax.xml.transform.Source; + +/** + * Defines the contract for Object XML Mapping unmarshallers. Implementations of this interface can deserialize a given + * XML Stream to an Object graph. + * + * @author Arjen Poutsma + */ +public interface Unmarshaller { + + /** + * Unmarshals the given provided javax.xml.transform.Source into an object graph. + * + * @param source the source to marshal from + * @return the object graph + * @throws XmlMappingException if the given source cannot be mapped to an object + * @throws IOException if an I/O Exception occurs + */ + Object unmarshal(Source source) throws XmlMappingException, IOException; +} diff --git a/oxm/src/main/java/org/springframework/oxm/UnmarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/UnmarshallingFailureException.java new file mode 100644 index 00000000..8c98b0b0 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/UnmarshallingFailureException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + +/** + * Exception thrown on unmarshalling failure. + * + * @author Arjen Poutsma + */ +public class UnmarshallingFailureException extends GenericMarshallingFailureException { + + /** + * Constructor for UnmarshallingFailureException. + */ + public UnmarshallingFailureException(String msg) { + super(msg); + } + + /** + * Constructor for UnmarshallingFailureException. + */ + public UnmarshallingFailureException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/ValidationFailureException.java b/oxm/src/main/java/org/springframework/oxm/ValidationFailureException.java new file mode 100644 index 00000000..1d9f1533 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/ValidationFailureException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + +/** + * Exception thrown on marshalling validation failure. + * + * @author Arjen Poutsma + */ +public class ValidationFailureException extends XmlMappingException { + + /** + * Constructor for ValidationFailureException. + */ + public ValidationFailureException(String msg) { + super(msg); + } + + /** + * Constructor for ValidationFailureException. + */ + public ValidationFailureException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/XmlMappingException.java b/oxm/src/main/java/org/springframework/oxm/XmlMappingException.java new file mode 100644 index 00000000..2b529f5c --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/XmlMappingException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + +import org.springframework.core.NestedRuntimeException; + +/** + * Root of the hierarchy of Object XML Mapping exceptions. + * + * @author Arjen Poutsma + */ +public abstract class XmlMappingException extends NestedRuntimeException { + /** + * Constructor for XmlMappingException. + */ + public XmlMappingException(String msg) { + super(msg); + } + + /** + * Constructor for XmlMappingException. + */ + public XmlMappingException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java b/oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java new file mode 100644 index 00000000..e835d996 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java @@ -0,0 +1,301 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.castor.mapping.BindingType; +import org.castor.mapping.MappingUnmarshaller; +import org.exolab.castor.mapping.Mapping; +import org.exolab.castor.mapping.MappingException; +import org.exolab.castor.mapping.MappingLoader; +import org.exolab.castor.xml.ClassDescriptorResolverFactory; +import org.exolab.castor.xml.MarshalException; +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.UnmarshalHandler; +import org.exolab.castor.xml.Unmarshaller; +import org.exolab.castor.xml.XMLClassDescriptorResolver; +import org.exolab.castor.xml.XMLException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.oxm.AbstractMarshaller; +import org.springframework.oxm.XmlMappingException; +import org.springframework.xml.stream.StaxEventContentHandler; +import org.springframework.xml.stream.StaxEventXmlReader; +import org.springframework.xml.stream.StaxStreamContentHandler; +import org.springframework.xml.stream.StaxStreamXmlReader; +import org.w3c.dom.Node; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; + +/** + * Implementation of the Marshaller interface for Castor. By default, Castor does not require any further + * configuration, though setting a target class or providing a mapping file can be used to have more control over the + * behavior of Castor. + *

+ * If a target class is specified using setTargetClass, the CastorMarshaller can only be used + * to unmarshall XML that represents that specific class. If you want to unmarshall multiple classes, you have to + * provide a mapping file using setMappingLocation. + *

+ * Due to Castor's API, it is required to set the encoding used for writing to output streams. It defaults to + * UTF-8. + * + * @author Arjen Poutsma + * @see #setEncoding(String) + * @see #setTargetClass(Class) + * @see #setMappingLocation(org.springframework.core.io.Resource) + */ +public class CastorMarshaller extends AbstractMarshaller implements InitializingBean { + + /** + * The default encoding used for stream access. + */ + public static final String DEFAULT_ENCODING = "UTF-8"; + + private Resource mappingLocation; + + private String encoding; + + private Class targetClass; + + private XMLClassDescriptorResolver classDescriptorResolver; + + /** + * Returns the encoding to be used for stream access. If this property is not set, the default encoding is used. + * + * @see #DEFAULT_ENCODING + */ + private String getEncoding() { + return encoding != null ? encoding : DEFAULT_ENCODING; + } + + /** + * Sets the encoding to be used for stream access. If this property is not set, the default encoding is used. + * + * @see #DEFAULT_ENCODING + */ + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + /** + * Sets the Castor target class. If this property is set, this CastorMarshaller is tied to this one + * specific class. Use a mapping file for unmarshalling multiple classes. + *

+ * You cannot set both this property and the mapping (location). + */ + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + /** + * Sets the location of the Castor XML Mapping file. + */ + public void setMappingLocation(Resource mappingLocation) { + this.mappingLocation = mappingLocation; + } + + public final void afterPropertiesSet() throws IOException { + if (mappingLocation != null && targetClass != null) { + throw new IllegalArgumentException("Cannot set both the 'mappingLocation' and 'targetClass' property. " + + "Set targetClass for unmarshalling a single class, and 'mappingLocation' for multiple classes'"); + } + if (logger.isInfoEnabled()) { + if (mappingLocation != null) { + logger.info("Configured using " + mappingLocation); + } + else if (targetClass != null) { + logger.info("Configured for target class [" + targetClass.getName() + "]"); + } + else { + logger.info("Using default configuration"); + } + } + try { + createClassDescriptorResolver(); + } + catch (MappingException ex) { + throw new CastorSystemException("Could not load Castor mapping: " + ex.getMessage(), ex); + } + } + + private void createClassDescriptorResolver() throws MappingException, IOException { + classDescriptorResolver = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory + .createClassDescriptorResolver(BindingType.XML); + if (mappingLocation != null) { + Mapping mapping = new Mapping(); + mapping.loadMapping(new InputSource(mappingLocation.getInputStream())); + MappingUnmarshaller mappingUnmarshaller = new MappingUnmarshaller(); + MappingLoader mappingLoader = mappingUnmarshaller.getMappingLoader(mapping, BindingType.XML); + classDescriptorResolver.setMappingLoader(mappingLoader); + classDescriptorResolver.setClassLoader(mapping.getClassLoader()); + } + else if (targetClass != null) { + classDescriptorResolver.setClassLoader(targetClass.getClassLoader()); + } + } + + /** + * Converts the given CastorException to an appropriate exception from the + * org.springframework.oxm hierarchy. + *

+ * The default implementation delegates to CastorUtils. Can be overridden in subclasses. + *

+ * A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since + * Castor itself does not make this distinction in its exception hierarchy. + * + * @param ex Castor XMLException that occured + * @param marshalling indicates whether the exception occurs during marshalling (true), or + * unmarshalling (false) + * @return the corresponding XmlMappingException + * @see CastorUtils#convertXmlException + */ + public XmlMappingException convertCastorException(XMLException ex, boolean marshalling) { + return CastorUtils.convertXmlException(ex, marshalling); + } + + private Unmarshaller createUnmarshaller() { + Unmarshaller unmarshaller = null; + if (targetClass != null) { + unmarshaller = new Unmarshaller(targetClass); + } + else { + unmarshaller = new Unmarshaller(); + } + unmarshaller.setResolver(classDescriptorResolver); + return unmarshaller; + } + + private void marshal(Object graph, Marshaller marshaller) { + try { + marshaller.setResolver(classDescriptorResolver); + marshaller.marshal(graph); + } + catch (XMLException ex) { + throw convertCastorException(ex, true); + } + } + + protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { + Marshaller marshaller = new Marshaller(node); + marshal(graph, marshaller); + } + + protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException { + ContentHandler contentHandler = new StaxEventContentHandler(eventWriter); + marshalSaxHandlers(graph, contentHandler, null); + } + + protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { + ContentHandler contentHandler = new StaxStreamContentHandler(streamWriter); + marshalSaxHandlers(graph, contentHandler, null); + } + + protected void marshalOutputStream(Object graph, OutputStream outputStream) + throws XmlMappingException, IOException { + OutputStreamWriter writer = new OutputStreamWriter(outputStream, getEncoding()); + marshalWriter(graph, writer); + } + + protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) + throws XmlMappingException { + try { + Marshaller marshaller = new Marshaller(contentHandler); + marshal(graph, marshaller); + } + catch (IOException ex) { + throw new CastorSystemException("Could not construct Castor ContentHandler Marshaller", ex); + } + } + + protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { + Marshaller marshaller = new Marshaller(writer); + marshal(graph, marshaller); + } + + protected Object unmarshalDomNode(Node node) throws XmlMappingException { + try { + return createUnmarshaller().unmarshal(node); + } + catch (XMLException ex) { + throw convertCastorException(ex, false); + } + } + + protected Object unmarshalXmlEventReader(XMLEventReader eventReader) { + XMLReader reader = new StaxEventXmlReader(eventReader); + try { + return unmarshalSaxReader(reader, new InputSource()); + } + catch (IOException ex) { + throw new CastorUnmarshallingFailureException(new MarshalException(ex)); + } + } + + protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) { + XMLReader reader = new StaxStreamXmlReader(streamReader); + try { + return unmarshalSaxReader(reader, new InputSource()); + } + catch (IOException ex) { + throw new CastorUnmarshallingFailureException(new MarshalException(ex)); + } + } + + protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) + throws XmlMappingException, IOException { + UnmarshalHandler unmarshalHandler = createUnmarshaller().createHandler(); + try { + ContentHandler contentHandler = Unmarshaller.getContentHandler(unmarshalHandler); + xmlReader.setContentHandler(contentHandler); + xmlReader.parse(inputSource); + return unmarshalHandler.getObject(); + } + catch (SAXException ex) { + throw new CastorUnmarshallingFailureException(ex); + } + } + + protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { + try { + return createUnmarshaller().unmarshal(new InputSource(inputStream)); + } + catch (XMLException ex) { + throw convertCastorException(ex, false); + } + } + + protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { + try { + return createUnmarshaller().unmarshal(new InputSource(reader)); + } + catch (XMLException ex) { + throw convertCastorException(ex, false); + } + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/castor/CastorMarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/castor/CastorMarshallingFailureException.java new file mode 100644 index 00000000..f5a08b58 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/castor/CastorMarshallingFailureException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import org.exolab.castor.xml.MarshalException; + +import org.springframework.oxm.MarshallingFailureException; + +/** + * Castor-specific subclass of MarshallingFailureException. + * + * @author Arjen Poutsma + * @see CastorUtils#convertXmlException + */ +public class CastorMarshallingFailureException extends MarshallingFailureException { + + public CastorMarshallingFailureException(MarshalException ex) { + super("Castor marshalling exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/castor/CastorSystemException.java b/oxm/src/main/java/org/springframework/oxm/castor/CastorSystemException.java new file mode 100644 index 00000000..0bc866e4 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/castor/CastorSystemException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import org.springframework.oxm.UncategorizedXmlMappingException; + +/** + * Castor-specific subclass of UncategorizedXmlMappingException, for Castor exceptions that cannot be + * distinguished further. + * + * @author Arjen Poutsma + */ +public class CastorSystemException extends UncategorizedXmlMappingException { + + public CastorSystemException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/castor/CastorUnmarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/castor/CastorUnmarshallingFailureException.java new file mode 100644 index 00000000..551389b1 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/castor/CastorUnmarshallingFailureException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import org.exolab.castor.xml.MarshalException; +import org.xml.sax.SAXException; + +import org.springframework.oxm.UnmarshallingFailureException; + +/** + * Castor-specific subclass of UnmarshallingFailureException. + * + * @author Arjen Poutsma + * @see CastorUtils#convertXmlException + */ +public class CastorUnmarshallingFailureException extends UnmarshallingFailureException { + + public CastorUnmarshallingFailureException(MarshalException ex) { + super("Castor unmarshalling exception: " + ex.getMessage(), ex); + } + + public CastorUnmarshallingFailureException(SAXException ex) { + super("Castor unmarshalling exception: " + ex.getMessage(), ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/castor/CastorUtils.java b/oxm/src/main/java/org/springframework/oxm/castor/CastorUtils.java new file mode 100644 index 00000000..c45057b6 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/castor/CastorUtils.java @@ -0,0 +1,60 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import org.exolab.castor.xml.MarshalException; +import org.exolab.castor.xml.ValidationException; +import org.exolab.castor.xml.XMLException; + +import org.springframework.oxm.XmlMappingException; + +/** + * Generic utility methods for working with Castor. Mainly for internal use within the framework. + * + * @author Arjen Poutsma + */ +public class CastorUtils { + + /** + * Converts the given XMLException to an appropriate exception from the + * org.springframework.oxm hierarchy. + *

+ * A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since + * Castor itself does not make this distinction in its exception hierarchy. + * + * @param ex Castor XMLException that occured + * @param marshalling indicates whether the exception occurs during marshalling (true), or + * unmarshalling (false) + * @return the corresponding XmlMappingException + */ + public static XmlMappingException convertXmlException(XMLException ex, boolean marshalling) { + if (ex instanceof MarshalException) { + MarshalException marshalException = (MarshalException) ex; + if (marshalling) { + return new CastorMarshallingFailureException(marshalException); + } + else { + return new CastorUnmarshallingFailureException(marshalException); + } + } + else if (ex instanceof ValidationException) { + return new CastorValidationFailureException((ValidationException) ex); + } + // fallback + return new CastorSystemException("Unknown Castor exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/castor/CastorValidationFailureException.java b/oxm/src/main/java/org/springframework/oxm/castor/CastorValidationFailureException.java new file mode 100644 index 00000000..31de863f --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/castor/CastorValidationFailureException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import org.exolab.castor.xml.ValidationException; + +import org.springframework.oxm.ValidationFailureException; + +/** + * Castor-specific subclass of MarshallingFailureException. + * + * @author Arjen Poutsma + * @see CastorUtils#convertXmlException + */ +public class CastorValidationFailureException extends ValidationFailureException { + + public CastorValidationFailureException(ValidationException ex) { + super("Castor validation exception: " + ex.getMessage(), ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/castor/package.html b/oxm/src/main/java/org/springframework/oxm/castor/package.html new file mode 100644 index 00000000..0ba0c30f --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/castor/package.html @@ -0,0 +1,6 @@ + + +Package providing integration of Castor within Springs O/X Mapping +support. + + diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/AbstractJaxbMarshaller.java b/oxm/src/main/java/org/springframework/oxm/jaxb/AbstractJaxbMarshaller.java new file mode 100644 index 00000000..efe63a01 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/AbstractJaxbMarshaller.java @@ -0,0 +1,204 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jaxb; + +import java.util.Iterator; +import java.util.Map; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.ValidationEventHandler; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.oxm.XmlMappingException; + +/** + * Abstract base class for implementations of the Marshaller and Unmarshaller interfaces that + * use JAXB. This base class is responsible for creating JAXB marshallers from a JAXBContext. + *

+ * JAXB 2.0 added breaking API changes, so specific subclasses must be used for JAXB 1.0 and 2.0 + * (Jaxb1Marshaller and Jaxb2Marshaller respectivaly). + * + * @author Arjen Poutsma + * @see Jaxb1Marshaller + * @see Jaxb2Marshaller + */ +public abstract class AbstractJaxbMarshaller + implements org.springframework.oxm.Marshaller, org.springframework.oxm.Unmarshaller, InitializingBean { + + /** + * Logger available to subclasses. + */ + protected final Log logger = LogFactory.getLog(getClass()); + + private String contextPath; + + private Map marshallerProperties; + + private Map unmarshallerProperties; + + private JAXBContext jaxbContext; + + private ValidationEventHandler validationEventHandler; + + /** + * Returns the JAXB Context path. + */ + protected String getContextPath() { + return contextPath; + } + + /** + * Sets the JAXB Context path. + */ + public void setContextPath(String contextPath) { + this.contextPath = contextPath; + } + + /** + * Sets the JAXB Marshaller properties. These properties will be set on the underlying JAXB + * Marshaller, and allow for features such as indentation. + * + * @param properties the properties + * @see javax.xml.bind.Marshaller#setProperty(String, Object) + * @see javax.xml.bind.Marshaller#JAXB_ENCODING + * @see javax.xml.bind.Marshaller#JAXB_FORMATTED_OUTPUT + * @see javax.xml.bind.Marshaller#JAXB_NO_NAMESPACE_SCHEMA_LOCATION + * @see javax.xml.bind.Marshaller#JAXB_SCHEMA_LOCATION + */ + public void setMarshallerProperties(Map properties) { + this.marshallerProperties = properties; + } + + /** + * Sets the JAXB Unmarshaller properties. These properties will be set on the underlying JAXB + * Unmarshaller. + * + * @param properties the properties + * @see javax.xml.bind.Unmarshaller#setProperty(String, Object) + */ + public void setUnmarshallerProperties(Map properties) { + this.unmarshallerProperties = properties; + } + + /** + * Sets the JAXB validation event handler. This event handler will be called by JAXB if any validation errors are + * encountered during calls to any of the marshal API's. + * + * @param validationEventHandler the event handler + */ + public void setValidationEventHandler(ValidationEventHandler validationEventHandler) { + this.validationEventHandler = validationEventHandler; + } + + public final void afterPropertiesSet() throws Exception { + try { + jaxbContext = createJaxbContext(); + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } + + /** + * Convert the given JAXBException to an appropriate exception from the + * org.springframework.oxm hierarchy. + *

+ * The default implementation delegates to JaxbUtils. Can be overridden in subclasses. + * + * @param ex JAXBException that occured + * @return the corresponding XmlMappingException instance + * @see JaxbUtils#convertJaxbException + */ + protected XmlMappingException convertJaxbException(JAXBException ex) { + return JaxbUtils.convertJaxbException(ex); + } + + /** + * Returns a newly created JAXB marshaller. JAXB marshallers are not necessarily thread safe. + */ + protected Marshaller createMarshaller() { + try { + Marshaller marshaller = jaxbContext.createMarshaller(); + if (marshallerProperties != null) { + for (Iterator iterator = marshallerProperties.keySet().iterator(); iterator.hasNext();) { + String name = (String) iterator.next(); + marshaller.setProperty(name, marshallerProperties.get(name)); + } + } + if (validationEventHandler != null) { + marshaller.setEventHandler(validationEventHandler); + } + initJaxbMarshaller(marshaller); + return marshaller; + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } + + /** + * Returns a newly created JAXB unmarshaller. JAXB unmarshallers are not necessarily thread safe. + */ + protected Unmarshaller createUnmarshaller() { + try { + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + if (unmarshallerProperties != null) { + for (Iterator iterator = unmarshallerProperties.keySet().iterator(); iterator.hasNext();) { + String name = (String) iterator.next(); + unmarshaller.setProperty(name, unmarshallerProperties.get(name)); + } + } + if (validationEventHandler != null) { + unmarshaller.setEventHandler(validationEventHandler); + } + initJaxbUnmarshaller(unmarshaller); + return unmarshaller; + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } + + /** + * Template method that can be overridden by concrete JAXB marshallers for custom initialization behavior. Gets + * called after creation of JAXB Marshaller, and after the respective properties have been set. + *

+ * Default implementation does nothing. + */ + protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException { + } + + /** + * Template method that can overridden by concrete JAXB marshallers for custom initialization behavior. Gets called + * after creation of JAXB Unmarshaller, and after the respective properties have been set. + *

+ * Default implementation does nothing. + */ + protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException { + } + + /** + * Template method that returns a newly created JAXB context. Called from afterPropertiesSet(). + */ + protected abstract JAXBContext createJaxbContext() throws Exception; +} diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb1Marshaller.java b/oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb1Marshaller.java new file mode 100644 index 00000000..56ace1ab --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb1Marshaller.java @@ -0,0 +1,81 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.Result; +import javax.xml.transform.Source; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.StringUtils; + +/** + * Implementation of the Marshaller interface for JAXB 1.0. + *

+ * The typical usage will be to set the contextPath property on this bean, possibly customize the + * marshaller and unmarshaller by setting properties, and validations, and to refer to it. + * + * @author Arjen Poutsma + * @see #setContextPath(String) + * @see #setMarshallerProperties(java.util.Map) + * @see #setUnmarshallerProperties(java.util.Map) + * @see #setValidating(boolean) + */ +public class Jaxb1Marshaller extends AbstractJaxbMarshaller implements InitializingBean { + + private boolean validating = false; + + /** + * Set if the JAXB Unmarshaller should validate the incoming document. Default is false. + */ + public void setValidating(boolean validating) { + this.validating = validating; + } + + protected final JAXBContext createJaxbContext() throws JAXBException { + if (!StringUtils.hasLength(getContextPath())) { + throw new IllegalArgumentException("contextPath is required"); + } + if (logger.isInfoEnabled()) { + logger.info("Creating JAXBContext with context path [" + getContextPath() + "]"); + } + return JAXBContext.newInstance(getContextPath()); + } + + protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException { + unmarshaller.setValidating(validating); + } + + public void marshal(Object graph, Result result) { + try { + createMarshaller().marshal(graph, result); + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } + + public Object unmarshal(Source source) { + try { + return createUnmarshaller().unmarshal(source); + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbMarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbMarshallingFailureException.java new file mode 100644 index 00000000..e5c71aa6 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbMarshallingFailureException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.bind.MarshalException; + +import org.springframework.oxm.MarshallingFailureException; + +/** + * JAXB-specific subclass of MarshallingFailureException. + * + * @author Arjen Poutsma + * @see JaxbUtils#convertJaxbException + */ +public class JaxbMarshallingFailureException extends MarshallingFailureException { + + public JaxbMarshallingFailureException(MarshalException ex) { + super("JAXB marshalling exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbSystemException.java b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbSystemException.java new file mode 100644 index 00000000..d7a2b85f --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbSystemException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.bind.JAXBException; + +import org.springframework.oxm.UncategorizedXmlMappingException; + +/** + * JAXB-specific subclass of UncategorizedXmlMappingException, for JAXBExceptions that cannot + * be distinguished further. + * + * @author Arjen Poutsma + * @see JaxbUtils#convertJaxbException(javax.xml.bind.JAXBException) + */ +public class JaxbSystemException extends UncategorizedXmlMappingException { + + public JaxbSystemException(JAXBException ex) { + super(ex.getMessage(), ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUnmarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUnmarshallingFailureException.java new file mode 100644 index 00000000..b43a0f1a --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUnmarshallingFailureException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.bind.UnmarshalException; + +import org.springframework.oxm.UnmarshallingFailureException; + +/** + * JAXB-specific subclass of UnmarshallingFailureException. + * + * @author Arjen Poutsma + */ +public class JaxbUnmarshallingFailureException extends UnmarshallingFailureException { + + public JaxbUnmarshallingFailureException(UnmarshalException ex) { + super("JAXB unmarshalling exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUtils.java b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUtils.java new file mode 100644 index 00000000..e117115e --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUtils.java @@ -0,0 +1,82 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.bind.JAXBException; +import javax.xml.bind.MarshalException; +import javax.xml.bind.UnmarshalException; +import javax.xml.bind.ValidationException; + +import org.springframework.oxm.XmlMappingException; + +/** + * Generic utility methods for working with JAXB. Mainly for internal use within the framework. + * + * @author Arjen Poutsma + */ +public abstract class JaxbUtils { + + public static final int JAXB_1 = 0; + + public static final int JAXB_2 = 1; + + private static final String JAXB_2_CLASS_NAME = "javax.xml.bind.Binder"; + + private static int jaxbVersion = JAXB_1; + + static { + try { + Class.forName(JAXB_2_CLASS_NAME); + jaxbVersion = JAXB_2; + } + catch (ClassNotFoundException ex1) { + // leave JAXB 1 as default + } + } + + /** + * Gets the major JAXB version. This means we can do things like if (getJaxbVersion() <= JAXB_2). + * + * @return a code comparable to the JAXP_XX codes in this class + * @see #JAXB_1 + * @see #JAXB_2 + */ + public static int getJaxbVersion() { + return jaxbVersion; + } + + /** + * Converts the given JAXBException to an appropriate exception from the + * org.springframework.oxm hierarchy. + * + * @param ex JAXBException that occured + * @return the corresponding XmlMappingException + */ + public static XmlMappingException convertJaxbException(JAXBException ex) { + if (ex instanceof MarshalException) { + return new JaxbMarshallingFailureException((MarshalException) ex); + } + else if (ex instanceof UnmarshalException) { + return new JaxbUnmarshallingFailureException((UnmarshalException) ex); + } + else if (ex instanceof ValidationException) { + return new JaxbValidationFailureException((ValidationException) ex); + } + // fallback + return new JaxbSystemException(ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbValidationFailureException.java b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbValidationFailureException.java new file mode 100644 index 00000000..7c96d988 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbValidationFailureException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.bind.ValidationException; + +import org.springframework.oxm.ValidationFailureException; + +/** + * JAXB-specific subclass of ValidationFailureException. + * + * @author Arjen Poutsma + * @see JaxbUtils#convertJaxbException + */ +public class JaxbValidationFailureException extends ValidationFailureException { + + public JaxbValidationFailureException(ValidationException ex) { + super("JAXB validation exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/package.html b/oxm/src/main/java/org/springframework/oxm/jaxb/package.html new file mode 100644 index 00000000..7b398a83 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/package.html @@ -0,0 +1,6 @@ + + +Package providing integration of JAXB with Springs O/X Mapping +support. + + diff --git a/oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java b/oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java new file mode 100644 index 00000000..05708a77 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java @@ -0,0 +1,289 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.jibx.runtime.BindingDirectory; +import org.jibx.runtime.IBindingFactory; +import org.jibx.runtime.IMarshallingContext; +import org.jibx.runtime.IUnmarshallingContext; +import org.jibx.runtime.IXMLReader; +import org.jibx.runtime.IXMLWriter; +import org.jibx.runtime.JiBXException; +import org.jibx.runtime.impl.MarshallingContext; +import org.jibx.runtime.impl.StAXReaderWrapper; +import org.jibx.runtime.impl.StAXWriter; +import org.jibx.runtime.impl.UnmarshallingContext; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.oxm.AbstractMarshaller; +import org.springframework.oxm.XmlMappingException; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.xml.stream.StaxEventContentHandler; +import org.springframework.xml.stream.XmlEventStreamReader; +import org.w3c.dom.Node; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; + +/** + * Implementation of the Marshaller and Unmarshaller interfaces for JiBX. + *

+ * The typical usage will be to set the targetClass and optionally the bindingName property on + * this bean, and to refer to it. + * + * @author Arjen Poutsma + * @see org.jibx.runtime.IMarshallingContext + * @see org.jibx.runtime.IUnmarshallingContext + */ +public class JibxMarshaller extends AbstractMarshaller implements InitializingBean { + + private Class targetClass; + + private String bindingName; + + private IBindingFactory bindingFactory; + + private TransformerFactory transfomerFactory; + + /** + * Sets the optional binding name for this instance. + */ + public void setBindingName(String bindingName) { + this.bindingName = bindingName; + } + + /** + * Sets the target class for this instance. This property is required. + */ + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(targetClass, "targetClass is required"); + if (logger.isInfoEnabled()) { + if (StringUtils.hasLength(bindingName)) { + logger.info("Configured for target class [" + targetClass + "] using binding [" + bindingName + "]"); + } + else { + logger.info("Configured for target class [" + targetClass + "]"); + } + } + try { + if (StringUtils.hasLength(bindingName)) { + bindingFactory = BindingDirectory.getFactory(bindingName, targetClass); + } + else { + bindingFactory = BindingDirectory.getFactory(targetClass); + } + } + catch (JiBXException ex) { + throw new JibxSystemException(ex); + } + transfomerFactory = TransformerFactory.newInstance(); + } + + /** + * Convert the given JiBXException to an appropriate exception from the + * org.springframework.oxm hierarchy. + *

+ * The default implementation delegates to JibxUtils. Can be overridden in subclasses. + *

+ * A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since JiBX + * itself does not make this distinction in its exception hierarchy. + * + * @param ex JiBXException that occured + * @param marshalling indicates whether the exception occurs during marshalling (true), or + * unmarshalling (false) + * @return the corresponding XmlMappingException instance + * @see JibxUtils#convertJibxException(org.jibx.runtime.JiBXException, boolean) + */ + public XmlMappingException convertJibxException(JiBXException ex, boolean marshalling) { + return JibxUtils.convertJibxException(ex, marshalling); + } + + protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + marshalOutputStream(graph, os); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + Transformer transformer = transfomerFactory.newTransformer(); + transformer.transform(new StreamSource(is), new DOMResult(node)); + } + catch (IOException ex) { + throw new JibxSystemException(ex); + } + catch (TransformerException ex) { + throw new JibxSystemException(ex); + } + } + + protected void marshalOutputStream(Object graph, OutputStream outputStream) + throws XmlMappingException, IOException { + try { + IMarshallingContext marshallingContext = bindingFactory.createMarshallingContext(); + marshallingContext.marshalDocument(graph, null, null, outputStream); + } + catch (JiBXException ex) { + throw convertJibxException(ex, true); + } + } + + protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) + throws XmlMappingException { + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + marshalOutputStream(graph, os); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + Transformer transformer = transfomerFactory.newTransformer(); + transformer.transform(new StreamSource(is), new SAXResult(contentHandler)); + } + catch (IOException ex) { + throw new JibxSystemException(ex); + } + catch (TransformerException ex) { + throw new JibxSystemException(ex); + } + } + + protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { + try { + IMarshallingContext marshallingContext = bindingFactory.createMarshallingContext(); + marshallingContext.marshalDocument(graph, null, null, writer); + } + catch (JiBXException ex) { + throw convertJibxException(ex, true); + } + } + + protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) { + + ContentHandler contentHandler = new StaxEventContentHandler(eventWriter); + marshalSaxHandlers(graph, contentHandler, null); + } + + protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { + try { + MarshallingContext marshallingContext = (MarshallingContext) bindingFactory.createMarshallingContext(); + IXMLWriter xmlWriter = new StAXWriter(marshallingContext.getNamespaces(), streamWriter); + marshallingContext.setXmlWriter(xmlWriter); + marshallingContext.marshalDocument(graph); + } + catch (JiBXException ex) { + throw convertJibxException(ex, false); + } + } + + protected Object unmarshalDomNode(Node node) throws XmlMappingException { + try { + Transformer transformer = transfomerFactory.newTransformer(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + transformer.transform(new DOMSource(node), new StreamResult(os)); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + return unmarshalInputStream(is); + } + catch (IOException ex) { + throw new JibxSystemException(ex); + } + catch (TransformerException ex) { + throw new JibxSystemException(ex); + } + } + + protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { + try { + IUnmarshallingContext unmarshallingContext = bindingFactory.createUnmarshallingContext(); + return unmarshallingContext.unmarshalDocument(inputStream, null); + } + catch (JiBXException ex) { + throw convertJibxException(ex, false); + } + } + + protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { + try { + IUnmarshallingContext unmarshallingContext = bindingFactory.createUnmarshallingContext(); + return unmarshallingContext.unmarshalDocument(reader); + } + catch (JiBXException ex) { + throw convertJibxException(ex, false); + } + } + + protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) + throws XmlMappingException, IOException { + try { + Transformer transformer = transfomerFactory.newTransformer(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + transformer.transform(new SAXSource(xmlReader, inputSource), new StreamResult(os)); + ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + return unmarshalInputStream(is); + } + catch (IOException ex) { + throw new JibxSystemException(ex); + } + catch (TransformerException ex) { + throw new JibxSystemException(ex); + } + } + + protected Object unmarshalXmlEventReader(XMLEventReader eventReader) { + try { + XMLStreamReader streamReader = new XmlEventStreamReader(eventReader); + return unmarshalXmlStreamReader(streamReader); + } + catch (XMLStreamException ex) { + throw new JibxSystemException(ex); + } + } + + protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) { + try { + UnmarshallingContext unmarshallingContext = + (UnmarshallingContext) bindingFactory.createUnmarshallingContext(); + IXMLReader xmlReader = new StAXReaderWrapper(streamReader, null, true); + unmarshallingContext.setDocument(xmlReader); + return unmarshallingContext.unmarshalElement(); + } + catch (JiBXException ex) { + throw convertJibxException(ex, false); + } + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshallingFailureException.java new file mode 100644 index 00000000..4fbf894d --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshallingFailureException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import org.jibx.runtime.JiBXException; + +import org.springframework.oxm.MarshallingFailureException; + +/** + * JiXB-specific subclass of MarshallingFailureException. + * + * @author Arjen Poutsma + * @see JibxUtils#convertJibxException(org.jibx.runtime.JiBXException, boolean) + */ +public class JibxMarshallingFailureException extends MarshallingFailureException { + + public JibxMarshallingFailureException(JiBXException ex) { + super("JiBX marshalling exception: " + ex.getMessage(), ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/jibx/JibxSystemException.java b/oxm/src/main/java/org/springframework/oxm/jibx/JibxSystemException.java new file mode 100644 index 00000000..0dabff7a --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jibx/JibxSystemException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import org.springframework.oxm.UncategorizedXmlMappingException; + +/** + * JiBX-specific subclass of UncategorizedXmlMappingException, for JiBXBExceptions that cannot + * be distinguished further. + * + * @author Arjen Poutsma + * @see JibxUtils#convertJibxException(org.jibx.runtime.JiBXException, boolean) + */ +public class JibxSystemException extends UncategorizedXmlMappingException { + + public JibxSystemException(Exception ex) { + super(ex.getMessage(), ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/jibx/JibxUnmarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/jibx/JibxUnmarshallingFailureException.java new file mode 100644 index 00000000..539d487c --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jibx/JibxUnmarshallingFailureException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import org.jibx.runtime.JiBXException; + +import org.springframework.oxm.UnmarshallingFailureException; + +/** + * JiXB-specific subclass of UnmarshallingFailureException. + * + * @author Arjen Poutsma + * @see JibxUtils#convertJibxException(org.jibx.runtime.JiBXException, boolean) + */ +public class JibxUnmarshallingFailureException extends UnmarshallingFailureException { + + public JibxUnmarshallingFailureException(JiBXException ex) { + super("JiBX unmarshalling exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/jibx/JibxUtils.java b/oxm/src/main/java/org/springframework/oxm/jibx/JibxUtils.java new file mode 100644 index 00000000..23bd4c6d --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jibx/JibxUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import org.jibx.runtime.JiBXException; +import org.jibx.runtime.ValidationException; + +import org.springframework.oxm.XmlMappingException; + +/** + * Generic utility methods for working with JiBX. Mainly for internal use within the framework. + * + * @author Arjen Poutsma + */ +public abstract class JibxUtils { + + /** + * Converts the given JiBXException to an appropriate exception from the + * org.springframework.oxm hierarchy. + *

+ * A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since JiBX + * itself does not make this distinction in its exception hierarchy. + * + * @param ex JiBXException that occured + * @param marshalling indicates whether the exception occurs during marshalling (true), or + * unmarshalling (false) + * @return the corresponding XmlMappingException + */ + public static XmlMappingException convertJibxException(JiBXException ex, boolean marshalling) { + if (ex instanceof ValidationException) { + return new JibxValidationFailureException((ValidationException) ex); + } + else { + if (marshalling) { + return new JibxMarshallingFailureException(ex); + } + else { + return new JibxUnmarshallingFailureException(ex); + } + } + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/jibx/JibxValidationFailureException.java b/oxm/src/main/java/org/springframework/oxm/jibx/JibxValidationFailureException.java new file mode 100644 index 00000000..7fd0034e --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jibx/JibxValidationFailureException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import org.jibx.runtime.ValidationException; + +import org.springframework.oxm.ValidationFailureException; + +/** + * JAXB-specific subclass of ValidationFailureException. + * + * @author Arjen Poutsma + * @see JibxUtils#convertJibxException(org.jibx.runtime.JiBXException, boolean) + */ +public class JibxValidationFailureException extends ValidationFailureException { + + public JibxValidationFailureException(ValidationException ex) { + super("JiBX validation exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/jibx/package.html b/oxm/src/main/java/org/springframework/oxm/jibx/package.html new file mode 100644 index 00000000..a685b60e --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/jibx/package.html @@ -0,0 +1,6 @@ + + +Package providing integration of JiBX with Springs O/X Mapping +support. + + diff --git a/oxm/src/main/java/org/springframework/oxm/package.html b/oxm/src/main/java/org/springframework/oxm/package.html new file mode 100644 index 00000000..e7fdb736 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/package.html @@ -0,0 +1,6 @@ + + +Root package for Spring's O/X Mapping integration classes. Contains generic Marshaller and Unmarshaller interfaces, +and XmlMappingExceptions related to O/X Mapping. + + diff --git a/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java new file mode 100644 index 00000000..f02c3cbd --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java @@ -0,0 +1,243 @@ +package org.springframework.oxm.xmlbeans; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.xmlbeans.XmlError; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlObject; +import org.apache.xmlbeans.XmlOptions; +import org.apache.xmlbeans.XmlSaxHandler; +import org.apache.xmlbeans.XmlValidationError; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; + +import org.springframework.oxm.AbstractMarshaller; +import org.springframework.oxm.XmlMappingException; +import org.springframework.xml.stream.StaxEventContentHandler; +import org.springframework.xml.stream.StaxEventXmlReader; +import org.springframework.xml.stream.StaxStreamContentHandler; + +/** + * Implementation of the Marshaller interface for XMLBeans. Further options can be set by setting the + * xmlOptions property. A XmlOptionsFactoryBean is provided to easily wire up + * XmlObjects instances. + *

+ * Unmarshalled objects can be validated by setting the validating property, or by calling the + * validiate() method directly. Invalid objects will result in an XmlBeansValidationFailureException. + *

+ * Note that due to the nature of XMLBeans, this marshaller requires all passed objects to be of type + * XmlObject. + * + * @author Arjen Poutsma + * @see #setXmlOptions(org.apache.xmlbeans.XmlOptions) + * @see XmlOptionsFactoryBean + * @see #setValidating(boolean) + * @see org.apache.xmlbeans.XmlObject + */ +public class XmlBeansMarshaller extends AbstractMarshaller { + + private XmlOptions xmlOptions; + + private boolean validating = false; + + /** + * Set if this marshaller should validate the incoming document. Default is false. + */ + public void setValidating(boolean validating) { + this.validating = validating; + } + + /** + * Sets the XmlOptions. + * + * @param xmlOptions the xml options + */ + public void setXmlOptions(XmlOptions xmlOptions) { + this.xmlOptions = xmlOptions; + } + + /** + * Converts the given XMLBeans exception to an appropriate exception from the org.springframework.oxm + * hierarchy. + *

+ * The default implementation delegates to XmlBeansUtils. Can be overridden in subclasses. + *

+ * A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since + * XMLBeans itself does not make this distinction in its exception hierarchy. + * + * @param ex XMLBeans Exception that occured + * @param marshalling indicates whether the exception occurs during marshalling (true), or + * unmarshalling (false) + * @return the corresponding XmlMappingException + * @see XmlBeansUtils#convertXmlBeansException(Exception, boolean) + */ + public XmlMappingException convertXmlBeansException(Exception ex, boolean marshalling) { + return XmlBeansUtils.convertXmlBeansException(ex, marshalling); + } + + protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { + Document document = node.getNodeType() == Node.DOCUMENT_NODE ? (Document) node : node.getOwnerDocument(); + Node xmlBeansNode = ((XmlObject) graph).newDomNode(xmlOptions); + NodeList xmlBeansChildNodes = xmlBeansNode.getChildNodes(); + for (int i = 0; i < xmlBeansChildNodes.getLength(); i++) { + Node xmlBeansChildNode = xmlBeansChildNodes.item(i); + Node importedNode = document.importNode(xmlBeansChildNode, true); + node.appendChild(importedNode); + } + } + + protected void marshalOutputStream(Object graph, OutputStream outputStream) + throws XmlMappingException, IOException { + ((XmlObject) graph).save(outputStream, xmlOptions); + } + + protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) + throws XmlMappingException { + try { + ((XmlObject) graph).save(contentHandler, lexicalHandler, xmlOptions); + } + catch (SAXException ex) { + throw convertXmlBeansException(ex, true); + } + } + + protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { + ((XmlObject) graph).save(writer, xmlOptions); + } + + protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) { + ContentHandler contentHandler = new StaxEventContentHandler(eventWriter); + marshalSaxHandlers(graph, contentHandler, null); + } + + protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { + ContentHandler contentHandler = new StaxStreamContentHandler(streamWriter); + marshalSaxHandlers(graph, contentHandler, null); + } + + protected Object unmarshalDomNode(Node node) throws XmlMappingException { + try { + XmlObject object = XmlObject.Factory.parse(node, xmlOptions); + validate(object); + return object; + } + catch (XmlException ex) { + throw convertXmlBeansException(ex, false); + } + } + + protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { + try { + XmlObject object = XmlObject.Factory.parse(inputStream, xmlOptions); + validate(object); + return object; + } + catch (XmlException ex) { + throw convertXmlBeansException(ex, false); + } + } + + protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { + try { + XmlObject object = XmlObject.Factory.parse(reader, xmlOptions); + validate(object); + return object; + } + catch (XmlException ex) { + throw convertXmlBeansException(ex, false); + } + } + + protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) + throws XmlMappingException, IOException { + XmlSaxHandler saxHandler = XmlObject.Factory.newXmlSaxHandler(xmlOptions); + xmlReader.setContentHandler(saxHandler.getContentHandler()); + try { + xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", saxHandler.getLexicalHandler()); + } + catch (SAXNotRecognizedException e) { + } + catch (SAXNotSupportedException e) { + } + try { + xmlReader.parse(inputSource); + XmlObject object = saxHandler.getObject(); + validate(object); + return object; + } + catch (SAXException ex) { + throw convertXmlBeansException(ex, false); + } + catch (XmlException ex) { + throw convertXmlBeansException(ex, false); + } + } + + protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException { + XMLReader reader = new StaxEventXmlReader(eventReader); + try { + return unmarshalSaxReader(reader, new InputSource()); + } + catch (IOException ex) { + throw convertXmlBeansException(ex, false); + } + } + + protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException { + try { + XmlObject object = XmlObject.Factory.parse(streamReader, xmlOptions); + validate(object); + return object; + } + catch (XmlException ex) { + throw convertXmlBeansException(ex, false); + } + } + + /** + * Validates the given XmlObject. + * + * @param object the xml object to validate + * @throws XmlBeansValidationFailureException + * if the given object is not valid + */ + public void validate(XmlObject object) throws XmlBeansValidationFailureException { + if (validating && object != null) { + // create a temporary xmlOptions just for validation + XmlOptions validateOptions = (xmlOptions != null) ? xmlOptions : new XmlOptions(); + List errorsList = new ArrayList(); + validateOptions.setErrorListener(errorsList); + if (!object.validate(validateOptions)) { + StringBuffer buffer = new StringBuffer("Could not validate XmlObject :"); + for (Iterator iterator = errorsList.iterator(); iterator.hasNext();) { + XmlError xmlError = (XmlError) iterator.next(); + if (xmlError instanceof XmlValidationError) { + buffer.append(xmlError.toString()); + } + } + XmlException ex = new XmlException(buffer.toString(), null, errorsList); + throw new XmlBeansValidationFailureException(ex); + } + } + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshallingFailureException.java new file mode 100644 index 00000000..836f8212 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshallingFailureException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import org.apache.xmlbeans.XmlException; +import org.xml.sax.SAXException; + +import org.springframework.oxm.MarshallingFailureException; + +/** + * XMLBeans-specific subclass of MarshallingFailureException. + * + * @author Arjen Poutsma + * @see XmlBeansUtils#convertXmlBeansException(Exception, boolean) + */ +public class XmlBeansMarshallingFailureException extends MarshallingFailureException { + + public XmlBeansMarshallingFailureException(XmlException ex) { + super("XMLBeans marshalling exception: " + ex.getMessage(), ex); + } + + public XmlBeansMarshallingFailureException(SAXException ex) { + super("XMLBeans marshalling exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansSystemException.java b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansSystemException.java new file mode 100644 index 00000000..2010edc8 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansSystemException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import org.springframework.oxm.UncategorizedXmlMappingException; + +/** + * XMLBeans-specific subclass of UncategorizedXmlMappingException, for XMLBeans exceptions that cannot + * be distinguished further. + * + * @author Arjen Poutsma + */ +public class XmlBeansSystemException extends UncategorizedXmlMappingException { + + public XmlBeansSystemException(Exception e) { + super(e.getMessage(), e); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansUnmarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansUnmarshallingFailureException.java new file mode 100644 index 00000000..33fbd656 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansUnmarshallingFailureException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import org.apache.xmlbeans.XmlException; +import org.xml.sax.SAXException; + +import org.springframework.oxm.UnmarshallingFailureException; + +/** + * XMLBeans-specific subclass of UnmarshallingFailureException. + * + * @author Arjen Poutsma + * @see XmlBeansUtils#convertXmlBeansException(Exception, boolean) + */ +public class XmlBeansUnmarshallingFailureException extends UnmarshallingFailureException { + + public XmlBeansUnmarshallingFailureException(XmlException ex) { + super("XMLBeans unmarshalling exception: " + ex.getMessage(), ex); + } + + public XmlBeansUnmarshallingFailureException(SAXException ex) { + super("XMLBeans unmarshalling exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansUtils.java b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansUtils.java new file mode 100644 index 00000000..1644f010 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import org.apache.xmlbeans.XMLStreamValidationException; +import org.apache.xmlbeans.XmlException; +import org.xml.sax.SAXException; + +import org.springframework.oxm.XmlMappingException; + +/** + * Generic utility methods for working with XMLBeans. Mainly for internal use within the framework. + * + * @author Arjen Poutsma + */ +public class XmlBeansUtils { + + /** + * Converts the given XMLBeans exception to an appropriate exception from the org.springframework.oxm + * hierarchy. + *

+ * A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since + * XMLBeans itself does not make this distinction in its exception hierarchy. + * + * @param ex XMLBeans Exception that occured + * @param marshalling indicates whether the exception occurs during marshalling (true), or + * unmarshalling (false) + * @return the corresponding XmlMappingException + */ + public static XmlMappingException convertXmlBeansException(Exception ex, boolean marshalling) { + if (ex instanceof XMLStreamValidationException) { + return new XmlBeansValidationFailureException((XMLStreamValidationException) ex); + } + else if (ex instanceof XmlException) { + XmlException xmlException = (XmlException) ex; + if (marshalling) { + return new XmlBeansMarshallingFailureException(xmlException); + } + else { + return new XmlBeansUnmarshallingFailureException(xmlException); + } + } + else if (ex instanceof SAXException) { + SAXException saxException = (SAXException) ex; + if (marshalling) { + return new XmlBeansMarshallingFailureException(saxException); + } + else { + return new XmlBeansUnmarshallingFailureException(saxException); + } + } + // fallback + return new XmlBeansSystemException(ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansValidationFailureException.java b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansValidationFailureException.java new file mode 100644 index 00000000..c7fff246 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansValidationFailureException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import org.apache.xmlbeans.XMLStreamValidationException; +import org.apache.xmlbeans.XmlException; + +import org.springframework.oxm.ValidationFailureException; + +/** + * XMLBeans-specific subclass of ValidationFailureException. + * + * @author Arjen Poutsma + * @see org.springframework.oxm.xmlbeans.XmlBeansUtils#convertXmlBeansException + */ +public class XmlBeansValidationFailureException extends ValidationFailureException { + + public XmlBeansValidationFailureException(XMLStreamValidationException ex) { + super("XmlBeans validation exception: " + ex.getMessage(), ex); + } + + public XmlBeansValidationFailureException(XmlException ex) { + super("XmlBeans validation exception: " + ex.getMessage(), ex); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlOptionsFactoryBean.java b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlOptionsFactoryBean.java new file mode 100644 index 00000000..04897bae --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlOptionsFactoryBean.java @@ -0,0 +1,85 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import java.util.Iterator; +import java.util.Map; + +import org.apache.xmlbeans.XmlOptions; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * Factory bean that configures an XMLBeans XmlOptions object and provides it as a bean reference. + *

+ * Typical usage will be to set XMLBeans options on this bean, and refer to it in the XmlBeansMarshaller. + * + * @author Arjen Poutsma + * @see XmlOptions + * @see #setOptions(java.util.Map) + * @see XmlBeansMarshaller#setXmlOptions(org.apache.xmlbeans.XmlOptions) + */ +public class XmlOptionsFactoryBean implements FactoryBean, InitializingBean { + + private XmlOptions xmlOptions; + + private Map options; + + /** + * Returns the singleton XmlOptions. + */ + public Object getObject() throws Exception { + return xmlOptions; + } + + /** + * Returns the class of XmlOptions. + */ + public Class getObjectType() { + return XmlOptions.class; + } + + /** + * Returns true. + */ + public boolean isSingleton() { + return true; + } + + /** + * Sets options on the underlying XmlOptions object. The keys of the supplied map should be one of the + * string constants defined in XmlOptions, the values vary per option. + * + * @see XmlOptions#put(Object, Object) + * @see XmlOptions#SAVE_PRETTY_PRINT + * @see XmlOptions#LOAD_STRIP_COMMENTS + */ + public void setOptions(Map options) { + this.options = options; + } + + public void afterPropertiesSet() throws Exception { + xmlOptions = new XmlOptions(); + if (options != null) { + for (Iterator iterator = options.keySet().iterator(); iterator.hasNext();) { + Object option = iterator.next(); + xmlOptions.put(option, options.get(option)); + } + } + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/xmlbeans/package.html b/oxm/src/main/java/org/springframework/oxm/xmlbeans/package.html new file mode 100644 index 00000000..799fd625 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xmlbeans/package.html @@ -0,0 +1,5 @@ + + +Package providing integration of XMLBeans with Springs O/X Mapping support. + + diff --git a/oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java new file mode 100644 index 00000000..4b67c92f --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java @@ -0,0 +1,281 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Iterator; +import java.util.Map; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.io.xml.CompactWriter; +import com.thoughtworks.xstream.io.xml.DomReader; +import com.thoughtworks.xstream.io.xml.DomWriter; +import com.thoughtworks.xstream.io.xml.QNameMap; +import com.thoughtworks.xstream.io.xml.SaxWriter; +import com.thoughtworks.xstream.io.xml.StaxReader; +import com.thoughtworks.xstream.io.xml.StaxWriter; +import com.thoughtworks.xstream.io.xml.XppReader; +import org.springframework.beans.propertyeditors.ClassEditor; +import org.springframework.oxm.AbstractMarshaller; +import org.springframework.oxm.XmlMappingException; +import org.springframework.xml.stream.StaxEventContentHandler; +import org.springframework.xml.stream.XmlEventStreamReader; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; + +/** + * Implementation of the Marshaller interface for XStream. By default, XStream does not require any further + * configuration, though class aliases can be used to have more control over the behavior of XStream. + *

+ * Due to XStream's API, it is required to set the encoding used for writing to outputstreams. It defaults to + * UTF-8. + *

+ * Note that XStream is an XML serialization library, not a data binding library. Therefore, it has limited + * namespace support. As such, it is rather unsuitable for usage within Web services. + * + * @author Peter Meijer + * @author Arjen Poutsma + * @see #setEncoding(String) + * @see #DEFAULT_ENCODING + * @see #setAliases(java.util.Map) + * @see #setConverters(com.thoughtworks.xstream.converters.Converter[]) + */ +public class XStreamMarshaller extends AbstractMarshaller { + + /** + * The default encoding used for stream access. + */ + public static final String DEFAULT_ENCODING = "UTF-8"; + + private XStream xstream = new XStream(); + + private String encoding; + + /** + * Returns the encoding to be used for stream access. If this property is not set, the default encoding is used. + * + * @see #DEFAULT_ENCODING + */ + public String getEncoding() { + return encoding != null ? encoding : DEFAULT_ENCODING; + } + + /** + * Sets the encoding to be used for stream access. If this property is not set, the default encoding is used. + * + * @see #DEFAULT_ENCODING + */ + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + /** + * Sets the XStream mode. + * + * @see XStream#XPATH_REFERENCES + * @see XStream#ID_REFERENCES + * @see XStream#NO_REFERENCES + */ + public void setMode(int mode) { + xstream.setMode(mode); + } + + /** + * Sets the Converters to be registered with the XStream instance. + */ + public void setConverters(Converter[] converters) { + for (int i = 0; i < converters.length; i++) { + xstream.registerConverter(converters[i]); + } + } + + /** + * Set a alias/type map, consisting of string aliases mapped to Class instances (or Strings to be + * converted to Class instances). + * + * @see ClassEditor + */ + public void setAliases(Map aliases) { + for (Iterator iterator = aliases.keySet().iterator(); iterator.hasNext();) { + String name = (String) iterator.next(); + Object value = aliases.get(name); + // Check whether we need to convert from String to Class. + Class type = null; + if (value instanceof Class) { + type = (Class) value; + } + else { + ClassEditor editor = new ClassEditor(); + editor.setAsText(String.valueOf(value)); + type = (Class) editor.getValue(); + } + addAlias(name, type); + } + } + + /** + * Adds an alias for the given type. + * + * @param name alias to be used for the type + * @param type the type to be aliased + */ + public void addAlias(String name, Class type) { + xstream.alias(name, type); + } + + /** + * Convert the given XStream exception to an appropriate exception from the org.springframework.oxm + * hierarchy. + *

+ * The default implementation delegates to XStreamUtils. Can be overridden in subclasses. + * + * @param ex exception that occured + * @param marshalling indicates whether the exception occurs during marshalling (true), or + * unmarshalling (false) + * @return the corresponding XmlMappingException instance + * @see XStreamUtils#convertXStreamException(Exception, boolean) + */ + public XmlMappingException convertXStreamException(Exception ex, boolean marshalling) { + return XStreamUtils.convertXStreamException(ex, marshalling); + } + + /** + * Marshals the given graph to the given XStream HierarchicalStreamWriter. Converts exceptions using + * convertXStreamException. + */ + private void marshal(Object graph, HierarchicalStreamWriter streamWriter) { + try { + xstream.marshal(graph, streamWriter); + } + catch (Exception ex) { + throw convertXStreamException(ex, true); + } + } + + protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { + HierarchicalStreamWriter streamWriter = null; + if (node instanceof Document) { + streamWriter = new DomWriter((Document) node); + } + else if (node instanceof Element) { + streamWriter = new DomWriter((Element) node); + } + else { + throw new IllegalArgumentException("DOMResult contains neither Document nor Element"); + } + marshal(graph, streamWriter); + } + + protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException { + ContentHandler contentHandler = new StaxEventContentHandler(eventWriter); + marshalSaxHandlers(graph, contentHandler, null); + } + + protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { + try { + marshal(graph, new StaxWriter(new QNameMap(), streamWriter)); + } + catch (XMLStreamException ex) { + throw convertXStreamException(ex, true); + } + } + + protected void marshalOutputStream(Object graph, OutputStream outputStream) + throws XmlMappingException, IOException { + marshalWriter(graph, new OutputStreamWriter(outputStream, getEncoding())); + } + + protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler) + throws XmlMappingException { + SaxWriter saxWriter = new SaxWriter(); + saxWriter.setContentHandler(contentHandler); + marshal(graph, saxWriter); + } + + protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { + marshal(graph, new CompactWriter(writer)); + } + + private Object unmarshal(HierarchicalStreamReader streamReader) { + try { + return xstream.unmarshal(streamReader); + } + catch (Exception ex) { + throw convertXStreamException(ex, false); + } + } + + protected Object unmarshalDomNode(Node node) throws XmlMappingException { + HierarchicalStreamReader streamReader = null; + if (node instanceof Document) { + streamReader = new DomReader((Document) node); + } + else if (node instanceof Element) { + streamReader = new DomReader((Element) node); + } + else { + throw new IllegalArgumentException("DOMSource contains neither Document nor Element"); + } + return unmarshal(streamReader); + } + + protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException { + try { + XMLStreamReader streamReader = new XmlEventStreamReader(eventReader); + return unmarshalXmlStreamReader(streamReader); + } + catch (XMLStreamException ex) { + throw convertXStreamException(ex, false); + } + } + + protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException { + return unmarshal(new StaxReader(new QNameMap(), streamReader)); + } + + protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { + return unmarshalReader(new InputStreamReader(inputStream, getEncoding())); + } + + protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { + return unmarshal(new XppReader(reader)); + } + + protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) + throws XmlMappingException, IOException { + throw new UnsupportedOperationException("XStreamMarshaller does not support unmarshalling using SAX XMLReaders"); + } +} diff --git a/oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshallingFailureException.java new file mode 100644 index 00000000..4410e369 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshallingFailureException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +import com.thoughtworks.xstream.alias.CannotResolveClassException; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.io.StreamException; + +import org.springframework.oxm.MarshallingFailureException; + +/** + * XStream-specific subclass of MarshallingFailureException. + * + * @author Arjen Poutsma + */ +public class XStreamMarshallingFailureException extends MarshallingFailureException { + + public XStreamMarshallingFailureException(String msg) { + super(msg); + } + + public XStreamMarshallingFailureException(StreamException ex) { + super("XStream marshalling exception: " + ex.getMessage(), ex); + + } + + public XStreamMarshallingFailureException(CannotResolveClassException ex) { + super("XStream resolving exception: " + ex.getMessage(), ex); + } + + public XStreamMarshallingFailureException(ConversionException ex) { + super("XStream conversion exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/xstream/XStreamSystemException.java b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamSystemException.java new file mode 100644 index 00000000..f1247f86 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamSystemException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +import org.springframework.oxm.UncategorizedXmlMappingException; + +/** + * XStream-specific subclass of UncategorizedXmlMappingException, for XStream exceptions that cannot be + * distinguished further. + * + * @author Arjen Poutsma + */ +public class XStreamSystemException extends UncategorizedXmlMappingException { + + public XStreamSystemException(String msg, Throwable ex) { + super(msg, ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/xstream/XStreamUnmarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamUnmarshallingFailureException.java new file mode 100644 index 00000000..36a28030 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamUnmarshallingFailureException.java @@ -0,0 +1,54 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +import javax.xml.stream.XMLStreamException; + +import com.thoughtworks.xstream.alias.CannotResolveClassException; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.io.StreamException; + +import org.springframework.oxm.UnmarshallingFailureException; + +/** + * XStream-specific subclass of UnmarshallingFailureException. + * + * @author Arjen Poutsma + */ +public class XStreamUnmarshallingFailureException extends UnmarshallingFailureException { + + public XStreamUnmarshallingFailureException(StreamException ex) { + super("XStream unmarshalling exception: " + ex.getMessage(), ex); + } + + public XStreamUnmarshallingFailureException(CannotResolveClassException ex) { + super("XStream resolving exception: " + ex.getMessage(), ex); + } + + public XStreamUnmarshallingFailureException(ConversionException ex) { + super("XStream conversion exception: " + ex.getMessage(), ex); + } + + public XStreamUnmarshallingFailureException(String msg) { + super(msg); + } + + public XStreamUnmarshallingFailureException(String msg, XMLStreamException ex) { + super(msg, ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/xstream/XStreamUtils.java b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamUtils.java new file mode 100644 index 00000000..5c1bda75 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xstream/XStreamUtils.java @@ -0,0 +1,73 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +import com.thoughtworks.xstream.alias.CannotResolveClassException; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.io.StreamException; + +import org.springframework.oxm.XmlMappingException; + +/** + * Generic utility methods for working with XStream. Mainly for internal use within the framework. + * + * @author Arjen Poutsma + */ +public abstract class XStreamUtils { + + /** + * Converts the given XStream exception to an appropriate exception from the org.springframework.oxm + * hierarchy. + *

+ * A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since + * XStream itself does not make this distinction in its exception hierarchy. + * + * @param ex XStream exception that occured + * @param marshalling indicates whether the exception occurs during marshalling (true), or + * unmarshalling (false) + * @return the corresponding XmlMappingException + */ + public static XmlMappingException convertXStreamException(Exception ex, boolean marshalling) { + if (ex instanceof StreamException) { + if (marshalling) { + return new XStreamMarshallingFailureException((StreamException) ex); + } + else { + return new XStreamUnmarshallingFailureException((StreamException) ex); + } + } + else if (ex instanceof CannotResolveClassException) { + if (marshalling) { + return new XStreamMarshallingFailureException((CannotResolveClassException) ex); + } + else { + return new XStreamUnmarshallingFailureException((CannotResolveClassException) ex); + } + } + else if (ex instanceof ConversionException) { + if (marshalling) { + return new XStreamMarshallingFailureException((ConversionException) ex); + } + else { + return new XStreamUnmarshallingFailureException((ConversionException) ex); + } + } + // fallback + return new XStreamSystemException("Unknown XStream exception: " + ex.getMessage(), ex); + } + +} diff --git a/oxm/src/main/java/org/springframework/oxm/xstream/package.html b/oxm/src/main/java/org/springframework/oxm/xstream/package.html new file mode 100644 index 00000000..ab174437 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/xstream/package.html @@ -0,0 +1,5 @@ + + +Package providing integration of XStream with Springs O/X Mapping support. + + diff --git a/oxm/src/test/java/org/springframework/oxm/AbstractMarshallerTestCase.java b/oxm/src/test/java/org/springframework/oxm/AbstractMarshallerTestCase.java new file mode 100644 index 00000000..e29550bf --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/AbstractMarshallerTestCase.java @@ -0,0 +1,116 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.custommonkey.xmlunit.XMLUnit; +import org.springframework.xml.transform.StaxResult; +import org.springframework.xml.transform.StringResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +public abstract class AbstractMarshallerTestCase extends XMLTestCase { + + protected Marshaller marshaller; + + protected Object flights; + + protected static final String EXPECTED_STRING = + "" + + "42"; + + protected final void setUp() throws Exception { + marshaller = createMarshaller(); + flights = createFlights(); + XMLUnit.setIgnoreWhitespace(true); + } + + protected abstract Marshaller createMarshaller() throws Exception; + + protected abstract Object createFlights(); + + public void testMarshalDOMResult() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); + Document result = builder.newDocument(); + DOMResult domResult = new DOMResult(result); + marshaller.marshal(flights, domResult); + Document expected = builder.newDocument(); + Element flightsElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:flights"); + expected.appendChild(flightsElement); + Element flightElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:flight"); + flightsElement.appendChild(flightElement); + Element numberElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:number"); + flightElement.appendChild(numberElement); + Text text = expected.createTextNode("42"); + numberElement.appendChild(text); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StringResult stringResult = new StringResult(); + transformer.transform(new DOMSource(result), stringResult); + StringResult stringExpected = new StringResult(); + transformer.transform(new DOMSource(expected), stringExpected); + assertXMLEqual("Marshaller writes invalid DOMResult", stringExpected.toString(), stringResult.toString()); + } + + public void testMarshalStreamResultWriter() throws Exception { + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + marshaller.marshal(flights, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } + + public void testMarshalStreamResultOutputStream() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + StreamResult result = new StreamResult(os); + marshaller.marshal(flights, result); + assertXMLEqual("Marshaller writes invalid StreamResult", + EXPECTED_STRING, + new String(os.toByteArray(), "UTF-8")); + } + + public void testMarshalStaxResultXMLStreamWriter() throws Exception { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + StringWriter writer = new StringWriter(); + XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(writer); + StaxResult result = new StaxResult(streamWriter); + marshaller.marshal(flights, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } + + public void testMarshalStaxResultXMLEventWriter() throws Exception { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + StringWriter writer = new StringWriter(); + XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer); + StaxResult result = new StaxResult(eventWriter); + marshaller.marshal(flights, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } +} diff --git a/oxm/src/test/java/org/springframework/oxm/AbstractUnmarshallerTestCase.java b/oxm/src/test/java/org/springframework/oxm/AbstractUnmarshallerTestCase.java new file mode 100644 index 00000000..2e63fdfd --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/AbstractUnmarshallerTestCase.java @@ -0,0 +1,106 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm; + +import java.io.ByteArrayInputStream; +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; + +import junit.framework.TestCase; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +import org.springframework.xml.transform.StaxSource; + +public abstract class AbstractUnmarshallerTestCase extends TestCase { + + protected Unmarshaller unmarshaller; + + protected static final String INPUT_STRING = + "" + + "42"; + + protected final void setUp() throws Exception { + unmarshaller = createUnmarshaller(); + } + + protected abstract Unmarshaller createUnmarshaller() throws Exception; + + protected abstract void testFlights(Object o); + + public void testUnmarshalDomSource() throws Exception { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = builder.newDocument(); + Element flightsElement = document.createElementNS("http://samples.springframework.org/flight", "tns:flights"); + document.appendChild(flightsElement); + Element flightElement = document.createElementNS("http://samples.springframework.org/flight", "tns:flight"); + flightsElement.appendChild(flightElement); + Element numberElement = document.createElementNS("http://samples.springframework.org/flight", "tns:number"); + flightElement.appendChild(numberElement); + Text text = document.createTextNode("42"); + numberElement.appendChild(text); + DOMSource source = new DOMSource(document); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalStreamSourceReader() throws Exception { + StreamSource source = new StreamSource(new StringReader(INPUT_STRING)); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalStreamSourceInputStream() throws Exception { + StreamSource source = new StreamSource(new ByteArrayInputStream(INPUT_STRING.getBytes("UTF-8"))); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalSAXSource() throws Exception { + XMLReader reader = XMLReaderFactory.createXMLReader(); + SAXSource source = new SAXSource(reader, new InputSource(new StringReader(INPUT_STRING))); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalStaxSourceXmlStreamReader() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(new StringReader(INPUT_STRING)); + StaxSource source = new StaxSource(streamReader); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testUnmarshalStaxSourceXmlEventReader() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(INPUT_STRING)); + StaxSource source = new StaxSource(eventReader); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } +} diff --git a/oxm/src/test/java/org/springframework/oxm/castor/CastorMarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/castor/CastorMarshallerTest.java new file mode 100644 index 00000000..ccf368a5 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/castor/CastorMarshallerTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import javax.xml.transform.sax.SAXResult; + +import org.easymock.MockControl; +import org.springframework.core.io.ClassPathResource; +import org.springframework.oxm.AbstractMarshallerTestCase; +import org.springframework.oxm.Marshaller; +import org.xml.sax.ContentHandler; + +public class CastorMarshallerTest extends AbstractMarshallerTestCase { + + protected Marshaller createMarshaller() throws Exception { + CastorMarshaller marshaller = new CastorMarshaller(); + ClassPathResource mappingLocation = new ClassPathResource("mapping.xml", CastorMarshaller.class); + marshaller.setMappingLocation(mappingLocation); + marshaller.afterPropertiesSet(); + return marshaller; + } + + protected Object createFlights() { + Flight flight = new Flight(); + flight.setNumber(42L); + Flights flights = new Flights(); + flights.addFlight(flight); + return flights; + } + + public void testMarshalSaxResult() throws Exception { + MockControl handlerControl = MockControl.createControl(ContentHandler.class); + ContentHandler handlerMock = (ContentHandler) handlerControl.getMock(); + handlerMock.startDocument(); + handlerMock.startPrefixMapping("tns", "http://samples.springframework.org/flight"); + handlerMock.startElement("http://samples.springframework.org/flight", "flights", "tns:flights", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startElement("http://samples.springframework.org/flight", "flight", "tns:flight", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startElement("http://samples.springframework.org/flight", "number", "tns:number", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.characters(new char[]{'4', '2'}, 0, 2); + handlerControl.setMatcher(MockControl.ARRAY_MATCHER); + handlerMock.endElement("http://samples.springframework.org/flight", "number", "tns:number"); + handlerMock.endElement("http://samples.springframework.org/flight", "flight", "tns:flight"); + handlerMock.endElement("http://samples.springframework.org/flight", "flights", "tns:flights"); + handlerMock.endPrefixMapping("tns"); + handlerMock.endDocument(); + + handlerControl.replay(); + SAXResult result = new SAXResult(handlerMock); + marshaller.marshal(flights, result); + handlerControl.verify(); + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTest.java new file mode 100644 index 00000000..51f75390 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import javax.xml.transform.stream.StreamSource; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.oxm.AbstractUnmarshallerTestCase; +import org.springframework.oxm.Unmarshaller; + +public class CastorUnmarshallerTest extends AbstractUnmarshallerTestCase { + + protected void testFlights(Object o) { + Flights flights = (Flights) o; + assertNotNull("Flights is null", flights); + assertEquals("Invalid amount of flight elements", 1, flights.getFlightCount()); + Flight flight = flights.getFlight()[0]; + assertNotNull("Flight is null", flight); + assertEquals("Number is invalid", 42L, flight.getNumber()); + } + + protected Unmarshaller createUnmarshaller() throws Exception { + CastorMarshaller marshaller = new CastorMarshaller(); + ClassPathResource mappingLocation = new ClassPathResource("mapping.xml", CastorMarshaller.class); + marshaller.setMappingLocation(mappingLocation); + marshaller.afterPropertiesSet(); + return marshaller; + } + + public void testUnmarshalTargetClass() throws Exception { + CastorMarshaller unmarshaller = new CastorMarshaller(); + unmarshaller.setTargetClass(Flights.class); + unmarshaller.afterPropertiesSet(); + StreamSource source = new StreamSource(new ByteArrayInputStream(INPUT_STRING.getBytes("UTF-8"))); + Object flights = unmarshaller.unmarshal(source); + testFlights(flights); + } + + public void testSetBothTargetClassAndMapping() throws IOException { + try { + CastorMarshaller marshaller = new CastorMarshaller(); + marshaller.setMappingLocation(new ClassPathResource("mapping.xml", CastorMarshaller.class)); + marshaller.setTargetClass(getClass()); + marshaller.afterPropertiesSet(); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException ex) { + // expected + } + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/castor/CastorUtilsTest.java b/oxm/src/test/java/org/springframework/oxm/castor/CastorUtilsTest.java new file mode 100644 index 00000000..8f663d0d --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/castor/CastorUtilsTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.castor; + +import junit.framework.TestCase; +import org.exolab.castor.xml.MarshalException; +import org.exolab.castor.xml.ValidationException; +import org.exolab.castor.xml.XMLException; + +public class CastorUtilsTest extends TestCase { + + public void testConvertMarshalException() { + assertTrue("Invalid exception conversion", CastorUtils + .convertXmlException(new MarshalException(""), true) instanceof CastorMarshallingFailureException); + assertTrue("Invalid exception conversion", CastorUtils + .convertXmlException(new MarshalException(""), false) instanceof CastorUnmarshallingFailureException); + } + + public void testConvertValidationException() { + assertTrue("Invalid exception conversion", CastorUtils + .convertXmlException(new ValidationException(""), false) instanceof CastorValidationFailureException); + } + + public void testConvertXMLException() { + assertTrue("Invalid exception conversion", + CastorUtils.convertXmlException(new XMLException(""), false) instanceof CastorSystemException); + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/jaxb/AbstractJaxbMarshallerTestCase.java b/oxm/src/test/java/org/springframework/oxm/jaxb/AbstractJaxbMarshallerTestCase.java new file mode 100644 index 00000000..3e789546 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jaxb/AbstractJaxbMarshallerTestCase.java @@ -0,0 +1,56 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.transform.sax.SAXResult; + +import org.easymock.MockControl; +import org.xml.sax.ContentHandler; + +import org.springframework.oxm.AbstractMarshallerTestCase; + +public abstract class AbstractJaxbMarshallerTestCase extends AbstractMarshallerTestCase { + + public void testMarshalSaxResult() throws Exception { + MockControl handlerControl = MockControl.createStrictControl(ContentHandler.class); + ContentHandler handlerMock = (ContentHandler) handlerControl.getMock(); + handlerMock.setDocumentLocator(null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startDocument(); + handlerMock.startPrefixMapping("", "http://samples.springframework.org/flight"); + handlerMock.startElement("http://samples.springframework.org/flight", "flights", "flights", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startElement("http://samples.springframework.org/flight", "flight", "flight", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startElement("http://samples.springframework.org/flight", "number", "number", null); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.characters(new char[]{'4', '2'}, 0, 2); + handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.endElement("http://samples.springframework.org/flight", "number", "number"); + handlerMock.endElement("http://samples.springframework.org/flight", "flight", "flight"); + handlerMock.endElement("http://samples.springframework.org/flight", "flights", "flights"); + handlerMock.endPrefixMapping(""); + handlerMock.endDocument(); + + handlerControl.replay(); + SAXResult result = new SAXResult(handlerMock); + marshaller.marshal(flights, result); + handlerControl.verify(); + } + + +} diff --git a/oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb1MarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb1MarshallerTest.java new file mode 100644 index 00000000..fd5a0401 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb1MarshallerTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import java.util.Collections; + +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.XmlMappingException; +import org.springframework.oxm.jaxb1.FlightType; +import org.springframework.oxm.jaxb1.Flights; +import org.springframework.oxm.jaxb1.impl.FlightTypeImpl; +import org.springframework.oxm.jaxb1.impl.FlightsImpl; + +public class Jaxb1MarshallerTest extends AbstractJaxbMarshallerTestCase { + + private static final String CONTEXT_PATH = "org.springframework.oxm.jaxb1"; + + protected final Marshaller createMarshaller() throws Exception { + Jaxb1Marshaller marshaller = new Jaxb1Marshaller(); + marshaller.setContextPath(CONTEXT_PATH); + marshaller.afterPropertiesSet(); + return marshaller; + } + + protected Object createFlights() { + FlightType flight = new FlightTypeImpl(); + flight.setNumber(42L); + Flights flights = new FlightsImpl(); + flights.getFlight().add(flight); + return flights; + } + + public void testProperties() throws Exception { + Jaxb1Marshaller marshaller = new Jaxb1Marshaller(); + marshaller.setContextPath(CONTEXT_PATH); + marshaller.setMarshallerProperties( + Collections.singletonMap(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE)); + marshaller.afterPropertiesSet(); + } + + public void testNoContextPath() throws Exception { + try { + Jaxb1Marshaller marshaller = new Jaxb1Marshaller(); + marshaller.afterPropertiesSet(); + fail("Should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + } + } + + public void testInvalidContextPath() throws Exception { + try { + Jaxb1Marshaller marshaller = new Jaxb1Marshaller(); + marshaller.setContextPath("ab"); + marshaller.afterPropertiesSet(); + fail("Should have thrown an XmlMappingException"); + } + catch (XmlMappingException ex) { + } + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb1UnmarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb1UnmarshallerTest.java new file mode 100644 index 00000000..a2ae2308 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb1UnmarshallerTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import org.springframework.oxm.AbstractUnmarshallerTestCase; +import org.springframework.oxm.Unmarshaller; +import org.springframework.oxm.jaxb1.FlightType; +import org.springframework.oxm.jaxb1.Flights; + +public class Jaxb1UnmarshallerTest extends AbstractUnmarshallerTestCase { + + protected Unmarshaller createUnmarshaller() throws Exception { + Jaxb1Marshaller marshaller = new Jaxb1Marshaller(); + marshaller.setContextPath("org.springframework.oxm.jaxb1"); + marshaller.setValidating(true); + marshaller.afterPropertiesSet(); + return marshaller; + } + + protected void testFlights(Object o) { + Flights flights = (Flights) o; + assertNotNull("Flights is null", flights); + assertEquals("Invalid amount of flight elements", 1, flights.getFlight().size()); + FlightType flight = (FlightType) flights.getFlight().get(0); + assertNotNull("Flight is null", flight); + assertEquals("Number is invalid", 42L, flight.getNumber()); + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/jaxb/JaxbUtilsTest.java b/oxm/src/test/java/org/springframework/oxm/jaxb/JaxbUtilsTest.java new file mode 100644 index 00000000..d0e24155 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jaxb/JaxbUtilsTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jaxb; + +import javax.xml.bind.JAXBException; +import javax.xml.bind.MarshalException; +import javax.xml.bind.UnmarshalException; +import javax.xml.bind.ValidationException; + +import junit.framework.TestCase; + +public class JaxbUtilsTest extends TestCase { + + public void testConvertMarshallingException() { + assertTrue("Invalid exception conversion", + JaxbUtils.convertJaxbException(new MarshalException("")) instanceof JaxbMarshallingFailureException); + } + + public void testConvertUnmarshallingException() { + assertTrue("Invalid exception conversion", JaxbUtils + .convertJaxbException(new UnmarshalException("")) instanceof JaxbUnmarshallingFailureException); + } + + public void testConvertValidationException() { + assertTrue("Invalid exception conversion", + JaxbUtils.convertJaxbException(new ValidationException("")) instanceof JaxbValidationFailureException); + } + + public void testConvertJAXBException() { + assertTrue("Invalid exception conversion", + JaxbUtils.convertJaxbException(new JAXBException("")) instanceof JaxbSystemException); + } + + public void testGetJaxbVersion() throws Exception { + assertEquals("Invalid JAXB version", JaxbUtils.JAXB_1, JaxbUtils.getJaxbVersion()); + } +} diff --git a/oxm/src/test/java/org/springframework/oxm/jibx/FlightType.java b/oxm/src/test/java/org/springframework/oxm/jibx/FlightType.java new file mode 100644 index 00000000..c34003aa --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jibx/FlightType.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +public class FlightType { + + protected long number; + + public long getNumber() { + return this.number; + } + + public void setNumber(long number) { + this.number = number; + } +} diff --git a/oxm/src/test/java/org/springframework/oxm/jibx/Flights.java b/oxm/src/test/java/org/springframework/oxm/jibx/Flights.java new file mode 100644 index 00000000..739a3ab9 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jibx/Flights.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import java.util.ArrayList; + +public class Flights { + + protected ArrayList flightList = new ArrayList(); + + public void addFlight(FlightType flight) { + flightList.add(flight); + } + + public FlightType getFlight(int index) { + return (FlightType) flightList.get(index); + } + + public int sizeFlightList() { + return flightList.size(); + } +} diff --git a/oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTest.java new file mode 100644 index 00000000..8b8b774c --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jibx/JibxMarshallerTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import org.springframework.oxm.AbstractMarshallerTestCase; +import org.springframework.oxm.Marshaller; + +public class JibxMarshallerTest extends AbstractMarshallerTestCase { + + protected Marshaller createMarshaller() throws Exception { + JibxMarshaller marshaller = new JibxMarshaller(); + marshaller.setTargetClass(Flights.class); + marshaller.afterPropertiesSet(); + return marshaller; + } + + protected Object createFlights() { + Flights flights = new Flights(); + FlightType flight = new FlightType(); + flight.setNumber(42L); + flights.addFlight(flight); + return flights; + } + + public void testAfterPropertiesSetNoContextPath() throws Exception { + try { + JibxMarshaller marshaller = new JibxMarshaller(); + marshaller.afterPropertiesSet(); + fail("Should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + } + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTest.java new file mode 100644 index 00000000..0e73b83d --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.jibx; + +import org.springframework.oxm.AbstractUnmarshallerTestCase; +import org.springframework.oxm.Unmarshaller; + +public class JibxUnmarshallerTest extends AbstractUnmarshallerTestCase { + + protected Unmarshaller createUnmarshaller() throws Exception { + JibxMarshaller unmarshaller = new JibxMarshaller(); + unmarshaller.setTargetClass(Flights.class); + unmarshaller.afterPropertiesSet(); + return unmarshaller; + } + + protected void testFlights(Object o) { + Flights flights = (Flights) o; + assertNotNull("Flights is null", flights); + assertEquals("Invalid amount of flight elements", 1, flights.sizeFlightList()); + FlightType flight = (FlightType) flights.getFlight(0); + assertNotNull("Flight is null", flight); + assertEquals("Number is invalid", 42L, flight.getNumber()); + } + + +} diff --git a/oxm/src/test/java/org/springframework/oxm/jibx/JibxUtilsTest.java b/oxm/src/test/java/org/springframework/oxm/jibx/JibxUtilsTest.java new file mode 100644 index 00000000..545139a2 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/jibx/JibxUtilsTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.jibx; + +import junit.framework.TestCase; +import org.jibx.runtime.JiBXException; +import org.jibx.runtime.ValidationException; + +public class JibxUtilsTest extends TestCase { + + public void testConvertMarshallingException() throws Exception { + assertTrue("Invalid exception conversion", + JibxUtils.convertJibxException(new JiBXException(""), true) instanceof JibxMarshallingFailureException); + } + + public void testConvertUnmarshallingException() throws Exception { + assertTrue("Invalid exception conversion", JibxUtils + .convertJibxException(new JiBXException(""), false) instanceof JibxUnmarshallingFailureException); + } + + public void testConvertValidationException() throws Exception { + assertTrue("Invalid exception conversion", JibxUtils + .convertJibxException(new ValidationException(""), true) instanceof JibxValidationFailureException); + } +} \ No newline at end of file diff --git a/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansMarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansMarshallerTest.java new file mode 100644 index 00000000..eb2d4545 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansMarshallerTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import java.io.ByteArrayOutputStream; + +import javax.xml.transform.stream.StreamResult; + +import org.springframework.oxm.AbstractMarshallerTestCase; +import org.springframework.oxm.Marshaller; +import org.springframework.samples.flight.FlightType; +import org.springframework.samples.flight.FlightsDocument; +import org.springframework.samples.flight.FlightsDocument.Flights; + +public class XmlBeansMarshallerTest extends AbstractMarshallerTestCase { + + protected Marshaller createMarshaller() throws Exception { + return new XmlBeansMarshaller(); + } + + public void testMarshalNonXmlObject() throws Exception { + try { + this.marshaller.marshal(new Object(), new StreamResult(new ByteArrayOutputStream())); + fail("XmlBeansMarshaller did not throw ClassCastException for non-XmlObject"); + } + catch (ClassCastException e) { + // Expected behavior + } + } + + protected Object createFlights() { + FlightsDocument flightsDocument = FlightsDocument.Factory.newInstance(); + Flights flights = flightsDocument.addNewFlights(); + FlightType flightType = flights.addNewFlight(); + flightType.setNumber(42L); + return flightsDocument; + } + + public void testMarshalStaxResultXMLStreamWriter() throws Exception { + // Unfortu + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansUnmarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansUnmarshallerTest.java new file mode 100644 index 00000000..ed4051f5 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansUnmarshallerTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import org.springframework.oxm.AbstractUnmarshallerTestCase; +import org.springframework.oxm.Unmarshaller; +import org.springframework.samples.flight.FlightType; +import org.springframework.samples.flight.FlightsDocument; +import org.springframework.samples.flight.FlightsDocument.Flights; +import org.springframework.xml.transform.StringSource; + +public class XmlBeansUnmarshallerTest extends AbstractUnmarshallerTestCase { + + protected Unmarshaller createUnmarshaller() throws Exception { + return new XmlBeansMarshaller(); + } + + protected void testFlights(Object o) { + FlightsDocument flightsDocument = (FlightsDocument) o; + assertNotNull("FlightsDocument is null", flightsDocument); + Flights flights = flightsDocument.getFlights(); + assertEquals("Invalid amount of flight elements", 1, flights.sizeOfFlightArray()); + FlightType flight = flights.getFlightArray(0); + assertNotNull("Flight is null", flight); + assertEquals("Number is invalid", 42L, flight.getNumber()); + } + + public void testValidate() throws Exception { + ((XmlBeansMarshaller) unmarshaller).setValidating(true); + String invalidInput = "" + + "abc"; + + try { + unmarshaller.unmarshal(new StringSource(invalidInput)); + fail("Expected a XmlBeansValidationFailureException"); + } + catch (XmlBeansValidationFailureException ex) { + // expected + } + + + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansUtilsTest.java b/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansUtilsTest.java new file mode 100644 index 00000000..9b765b1f --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlBeansUtilsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import junit.framework.TestCase; +import org.apache.xmlbeans.XMLStreamValidationException; +import org.apache.xmlbeans.XmlError; +import org.apache.xmlbeans.XmlException; +import org.xml.sax.SAXException; + +public class XmlBeansUtilsTest extends TestCase { + + public void testConvertXMLStreamValidationException() { + assertTrue("Invalid exception conversion", XmlBeansUtils.convertXmlBeansException( + new XMLStreamValidationException(XmlError.forMessage("")), + true) instanceof XmlBeansValidationFailureException); + + } + + public void testConvertXmlException() { + assertTrue("Invalid exception conversion", XmlBeansUtils + .convertXmlBeansException(new XmlException(""), true) instanceof XmlBeansMarshallingFailureException); + assertTrue("Invalid exception conversion", XmlBeansUtils.convertXmlBeansException(new XmlException(""), + false) instanceof XmlBeansUnmarshallingFailureException); + } + + public void testConvertSAXException() { + assertTrue("Invalid exception conversion", XmlBeansUtils + .convertXmlBeansException(new SAXException(""), true) instanceof XmlBeansMarshallingFailureException); + assertTrue("Invalid exception conversion", XmlBeansUtils.convertXmlBeansException(new SAXException(""), + false) instanceof XmlBeansUnmarshallingFailureException); + } + + public void testFallbackException() { + assertTrue("Invalid exception conversion", + XmlBeansUtils.convertXmlBeansException(new Exception(""), false) instanceof XmlBeansSystemException); + + } + +} diff --git a/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlOptionsFactoryBeanTest.java b/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlOptionsFactoryBeanTest.java new file mode 100644 index 00000000..26fc31de --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/xmlbeans/XmlOptionsFactoryBeanTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xmlbeans; + +import java.util.Collections; + +import junit.framework.TestCase; +import org.apache.xmlbeans.XmlOptions; + +public class XmlOptionsFactoryBeanTest extends TestCase { + + private XmlOptionsFactoryBean factoryBean; + + protected void setUp() throws Exception { + factoryBean = new XmlOptionsFactoryBean(); + } + + public void testXmlOptionsFactoryBean() throws Exception { + factoryBean.setOptions(Collections.singletonMap(XmlOptions.SAVE_PRETTY_PRINT, Boolean.TRUE)); + factoryBean.afterPropertiesSet(); + XmlOptions xmlOptions = (XmlOptions) factoryBean.getObject(); + assertNotNull("No XmlOptions returned", xmlOptions); + assertTrue("Option not set", xmlOptions.hasOption(XmlOptions.SAVE_PRETTY_PRINT)); + assertFalse("Invalid option set", xmlOptions.hasOption(XmlOptions.LOAD_LINE_NUMBERS)); + } +} \ No newline at end of file diff --git a/oxm/src/test/java/org/springframework/oxm/xstream/Flight.java b/oxm/src/test/java/org/springframework/oxm/xstream/Flight.java new file mode 100644 index 00000000..2fcf1411 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/xstream/Flight.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +public class Flight { + + private long flightNumber; + + public long getFlightNumber() { + return flightNumber; + } + + public void setFlightNumber(long number) { + this.flightNumber = number; + } +} diff --git a/oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTest.java new file mode 100644 index 00000000..5b678af8 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Properties; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamResult; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.extended.EncodedByteArrayConverter; +import org.custommonkey.xmlunit.XMLTestCase; +import org.easymock.MockControl; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; +import org.xml.sax.ContentHandler; + +import org.springframework.xml.transform.StaxResult; +import org.springframework.xml.transform.StringResult; +import org.springframework.xml.transform.StringSource; + +public class XStreamMarshallerTest extends XMLTestCase { + + private static final String EXPECTED_STRING = "42"; + + private XStreamMarshaller marshaller; + + private Flight flight; + + protected void setUp() throws Exception { + marshaller = new XStreamMarshaller(); + Properties aliases = new Properties(); + aliases.setProperty("flight", Flight.class.getName()); + marshaller.setAliases(aliases); + flight = new Flight(); + flight.setFlightNumber(42L); + } + + public void testMarshalDOMResult() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); + Document document = builder.newDocument(); + DOMResult domResult = new DOMResult(document); + marshaller.marshal(flight, domResult); + Document expected = builder.newDocument(); + Element flightElement = expected.createElement("flight"); + expected.appendChild(flightElement); + Element numberElement = expected.createElement("flightNumber"); + flightElement.appendChild(numberElement); + Text text = expected.createTextNode("42"); + numberElement.appendChild(text); + assertXMLEqual("Marshaller writes invalid DOMResult", expected, document); + } + + public void testMarshalStreamResultWriter() throws Exception { + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + marshaller.marshal(flight, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } + + public void testMarshalStreamResultOutputStream() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + StreamResult result = new StreamResult(os); + marshaller.marshal(flight, result); + String s = new String(os.toByteArray(), "UTF-8"); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, s); + } + + public void testMarshalSaxResult() throws Exception { + MockControl handlerControl = MockControl.createStrictControl(ContentHandler.class); + handlerControl.setDefaultMatcher(MockControl.ALWAYS_MATCHER); + ContentHandler handlerMock = (ContentHandler) handlerControl.getMock(); + handlerMock.startDocument(); + handlerMock.startElement("", "flight", "flight", null); + handlerMock.startElement("", "number", "number", null); + handlerMock.characters(new char[]{'4', '2'}, 0, 2); + handlerMock.endElement("", "number", "number"); + handlerMock.endElement("", "flight", "flight"); + handlerMock.endDocument(); + + handlerControl.replay(); + SAXResult result = new SAXResult(handlerMock); + marshaller.marshal(flight, result); + handlerControl.verify(); + } + + public void testMarshalStaxResultXMLStreamWriter() throws Exception { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + StringWriter writer = new StringWriter(); + XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(writer); + StaxResult result = new StaxResult(streamWriter); + marshaller.marshal(flight, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } + + public void testMarshalStaxResultXMLEventWriter() throws Exception { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + StringWriter writer = new StringWriter(); + XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer); + StaxResult result = new StaxResult(eventWriter); + marshaller.marshal(flight, result); + assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + } + + public void testConverters() throws Exception { + marshaller.setConverters(new Converter[]{new EncodedByteArrayConverter()}); + byte[] buf = new byte[]{0x1, 0x2}; + StringResult result = new StringResult(); + marshaller.marshal(buf, result); + assertXMLEqual("AQI=", result.toString()); + StringSource source = new StringSource(result.toString()); + byte[] bufResult = (byte[]) marshaller.unmarshal(source); + assertTrue("Invalid result", Arrays.equals(buf, bufResult)); + } + + +} diff --git a/oxm/src/test/java/org/springframework/oxm/xstream/XStreamUnmarshallerTest.java b/oxm/src/test/java/org/springframework/oxm/xstream/XStreamUnmarshallerTest.java new file mode 100644 index 00000000..3b838f74 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/xstream/XStreamUnmarshallerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +import java.io.ByteArrayInputStream; +import java.io.StringReader; +import java.util.Properties; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; + +import junit.framework.TestCase; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; + +import org.springframework.xml.transform.StaxSource; + +public class XStreamUnmarshallerTest extends TestCase { + + protected static final String INPUT_STRING = "42"; + + private XStreamMarshaller unmarshaller; + + protected void setUp() throws Exception { + unmarshaller = new XStreamMarshaller(); + Properties aliases = new Properties(); + aliases.setProperty("flight", Flight.class.getName()); + unmarshaller.setAliases(aliases); + } + + private void testFlight(Object o) { + assertTrue("Unmarshalled object is not Flights", o instanceof Flight); + Flight flight = (Flight) o; + assertNotNull("Flight is null", flight); + assertEquals("Number is invalid", 42L, flight.getFlightNumber()); + } + + public void testUnmarshalDomSource() throws Exception { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = builder.parse(new InputSource(new StringReader(INPUT_STRING))); + DOMSource source = new DOMSource(document); + Object flight = unmarshaller.unmarshal(source); + testFlight(flight); + } + + public void testUnmarshalStaxSourceXmlStreamReader() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(new StringReader(INPUT_STRING)); + StaxSource source = new StaxSource(streamReader); + Object flights = unmarshaller.unmarshal(source); + testFlight(flights); + } + + public void testUnmarshalStreamSourceInputStream() throws Exception { + StreamSource source = new StreamSource(new ByteArrayInputStream(INPUT_STRING.getBytes("UTF-8"))); + Object flights = unmarshaller.unmarshal(source); + testFlight(flights); + } + + public void testUnmarshalStreamSourceReader() throws Exception { + StreamSource source = new StreamSource(new StringReader(INPUT_STRING)); + Object flights = unmarshaller.unmarshal(source); + testFlight(flights); + } +} + diff --git a/oxm/src/test/java/org/springframework/oxm/xstream/XStreamUtilsTest.java b/oxm/src/test/java/org/springframework/oxm/xstream/XStreamUtilsTest.java new file mode 100644 index 00000000..36c19188 --- /dev/null +++ b/oxm/src/test/java/org/springframework/oxm/xstream/XStreamUtilsTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.oxm.xstream; + +import com.thoughtworks.xstream.alias.CannotResolveClassException; +import com.thoughtworks.xstream.io.StreamException; +import junit.framework.TestCase; + +public class XStreamUtilsTest extends TestCase { + + public void testConvertStreamException() { + assertTrue("Invalid exception conversion", XStreamUtils.convertXStreamException( + new StreamException(new Exception()), true) instanceof XStreamMarshallingFailureException); + assertTrue("Invalid exception conversion", XStreamUtils.convertXStreamException( + new StreamException(new Exception()), false) instanceof XStreamUnmarshallingFailureException); + } + + public void testConvertCannotResolveClassException() { + assertTrue("Invalid exception conversion", XStreamUtils.convertXStreamException( + new CannotResolveClassException(""), true) instanceof XStreamMarshallingFailureException); + assertTrue("Invalid exception conversion", XStreamUtils.convertXStreamException( + new CannotResolveClassException(""), false) instanceof XStreamUnmarshallingFailureException); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..bc8caf41 --- /dev/null +++ b/pom.xml @@ -0,0 +1,695 @@ + + + 4.0.0 + org.springframework.ws + spring-ws + pom + 1.0-m3-SNAPSHOT + Spring Web Services + Spring Web Services Framework + http://www.springframework.org/spring-ws + + JIRA + http://opensource2.atlassian.com/projects/spring/browse/SWS + + + continuum + + + mail + +

arjenp@interface21.com
+ + + + + + + static.springframework.org + + scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/maven2 + + + static.springframework.org + + scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/maven2-snapshots + + + 2005 + + + poutsma + Arjen Poutsma + arjenp at interface21.com + Interface21 + http://www.interface21.com + + Project Admin + Developer + + +1 + + + + + Ingo Siebert + + + Ben Ethridge + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + scm:svn:https://svn.sourceforge.net/svnroot/springframework/spring-projects/trunk/spring-ws/ + + scm:svn:https://svn.sourceforge.net/svnroot/springframework/spring-projects/trunk/spring-ws/ + http://svn.sourceforge.net/viewcvs.cgi/springframework/spring-projects/trunk/spring-ws/ + + + spring-xml + spring-oxm + spring-ws-core + spring-ws-security + release + + + + jdk1.5 + + 1.5 + + + spring-oxm-tiger + + + + full + + spring-ws-samples + doc + + + + + + java.net + java.net Maven Repository + https://maven-repository.dev.java.net/nonav/repository + legacy + + + spring-ext + Spring External Dependencies Repository + https://svn.sourceforge.net/svnroot/springframework/repos/repo-ext/ + + + + + + src/etc/test-resources + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.2.1 + + + org.apache.maven.plugins + maven-clean-plugin + 2.1 + + + org.apache.maven.plugins + maven-site-plugin + 2.0-beta-4 + + + org.apache.maven.plugins + maven-compiler-plugin + 2.0.1 + + + org.apache.maven.plugins + maven-surefire-plugin + 2.1.3 + + + org.apache.maven.plugins + maven-jar-plugin + 2.0 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.0 + + + org.apache.maven.plugins + maven-assembly-plugin + 2.1 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.3 + 1.3 + + + + org.apache.maven.plugins + maven-surefire-plugin + + once + false + + + + org.apache.maven.plugins + maven-clover-plugin + + + pre-site + + instrument + + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.2 + + true + + + + org.apache.maven.plugins + maven-idea-plugin + 2.0 + + true + true + + + + maven-release-plugin + + https://svn.sourceforge.net/svnroot/springframework/spring-projects/tags/ + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + true + + + Spring-WS + org.springframework.ws* + + + Spring-OXM + org.springframework.oxm* + + + Spring-XML + org.springframework.xml* + + + org.springframework.ws.samples + + http://java.sun.com/j2se/1.4.2/docs/api + http://java.sun.com/j2ee/1.4/docs/api + http://java.sun.com/webservices/docs/2.0/api + http://java.sun.com/webservices/docs/2.0/xws-security/api/ + http://static.springframework.org/spring/docs/1.2.x/api + http://jakarta.apache.org/commons/logging/apidocs/ + http://xmlbeans.apache.org/docs/2.0.0/reference + http://jibx.sourceforge.net/api + http://ws.apache.org/commons/axiom/apidocs/ + http://dom4j.org/apidocs/ + http://jdom.org/docs/apidocs/ + http://www.xom.nu/apidocs/ + http://acegisecurity.org/multiproject/acegi-security/apidocs/ + + + + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + + + org.apache.maven.plugins + maven-clover-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + true + + + Spring-WS + org.springframework.ws* + + + Spring-OXM + org.springframework.oxm* + + + Spring-XML + org.springframework.xml* + + + org.springframework.ws.samples + + http://java.sun.com/j2se/1.4.2/docs/api + http://java.sun.com/j2ee/1.4/docs/api + http://java.sun.com/webservices/docs/2.0/api + http://java.sun.com/webservices/docs/2.0/xws-security/api/ + http://static.springframework.org/spring/docs/1.2.x/api + http://jakarta.apache.org/commons/logging/apidocs/ + http://xmlbeans.apache.org/docs/2.0.0/reference + http://jibx.sourceforge.net/api + http://ws.apache.org/commons/axiom/apidocs/ + http://dom4j.org/apidocs/ + http://jdom.org/docs/apidocs/ + http://www.xom.nu/apidocs/ + http://acegisecurity.org/multiproject/acegi-security/apidocs/ + + + + + + + + + + org.springframework.ws + spring-xml + ${project.version} + + + org.springframework.ws + spring-oxm + ${project.version} + + + org.springframework.ws + spring-oxm-tiger + ${project.version} + + + org.springframework.ws + spring-ws-core + ${project.version} + + + org.springframework.ws + spring-ws-security + ${project.version} + + + org.springframework.ws + spring-ws-samples + ${project.version} + + + org.springframework.ws + spring-ws-doc + ${project.version} + pom + + + org.springframework.ws + airline + war + ${pom.version} + + + org.springframework.ws + echo + war + ${pom.version} + + + + org.springframework + spring-core + 1.2.8 + + + org.springframework + spring-beans + 1.2.8 + + + org.springframework + spring-context + 1.2.8 + + + org.springframework + spring-web + 1.2.8 + + + org.springframework + spring-webmvc + 1.2.8 + + + org.springframework + spring-hibernate + 1.2.8 + + + org.springframework + spring-jdbc + 1.2.8 + + + org.springframework + spring-mock + 1.2.8 + test + + + + xerces + xercesImpl + 2.8.0 + + + stax + stax-api + 1.0 + + + xalan + xalan + 2.7.0 + + + jaxen + jaxen + 1.1-beta-9 + + + xml-apis + xml-apis + 1.3.03 + + + woodstox + wstx-asl + 2.9.3 + + + jdom + jdom + 1.0 + + + dom4j + dom4j + 1.6.1 + + + xom + xom + 1.1 + + + xpp3 + xpp3 + 1.1.3.4.O + + + + org.codehaus.castor + castor + 1.0.3 + + + + javax.xml.bind + jaxb-api + 1.0 + + + com.sun.xml.bind + jaxb-impl + 1.0.6 + + + com.sun.xml.bind + jaxb-libs + 1.0.6 + + + com.sun.xml.bind + jaxb-xjc + 1.0.6 + + + com.sun.msv.datatype.xsd + xsdlib + 20060615 + + + + xmlbeans + xbean + 2.1.0 + + + + org.jibx + jibx-run + 1.1 + + + org.jibx + jibx-bind + 1.1 + + + bcel + bcel + 5.1 + + + + xstream + xstream + 1.1.3 + + + + com.sun.xml.messaging.saaj + saaj-impl + 1.3 + provided + + + ws-commons + axiom-api + 1.0 + + + ws-commons + axiom-impl + 1.0 + + + + wsdl4j + wsdl4j + 1.5.2 + + + + com.sun.xml.wss + xws-security + 2.0-ea2 + + + com.sun.org.apache.xml.security + xmlsec + 2.0 + + + org.acegisecurity + acegi-security + 1.0.1 + + + org.springframework + spring-remoting + + + org.springframework + spring-jdbc + + + org.springframework + spring-support + + + commons-lang + commons-lang + + + commons-lang + commons-lang + + + commons-codec + commons-codec + + + commons-collections + commons-collections + + + + + + javax.xml.soap + saaj-api + 1.3 + provided + + + javax.servlet + servlet-api + 2.4 + provided + + + javax.mail + mail + 1.4 + provided + + + javax.activation + activation + 1.1 + provided + + + javax.jms + jms + 1.1 + provided + + + + commons-logging + commons-logging + 1.1 + + + logkit + logkit + + + avalon-framework + avalon-framework + + + javax.servlet + servlet-api + + + + + log4j + log4j + 1.2.13 + true + + + + junit + junit + 3.8.2 + test + + + easymock + easymock + 1.2_Java1.3 + test + + + xmlunit + xmlunit + 1.0 + test + + + + + + commons-logging + commons-logging + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + junit + junit + test + + + easymock + easymock + test + + + xmlunit + xmlunit + test + + + log4j + log4j + test + + + + + diff --git a/release/build-maven2.xml b/release/build-maven2.xml new file mode 100644 index 00000000..a4ca7d26 --- /dev/null +++ b/release/build-maven2.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + Spring-Web Services 1-0 M2]]> + Copyright (c) 2005-2006 The Spring Framework Project.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/release/changelog.txt b/release/changelog.txt new file mode 100644 index 00000000..97028334 --- /dev/null +++ b/release/changelog.txt @@ -0,0 +1,166 @@ +SPRING WEB SERVICES CHANGELOG +============================= +http://www.springframework.org/spring-ws + +Changes in version 1.0-M3 +------------------------- +Package org.springframework.oxm.castor +* CastorMarshaller uses a XMLClassDescriptorResolver instead of a Mapping (#SWS-54) + +Package org.springframework.ws.soap +* Added setId() method to Attachment, to set the Content-ID of MIME attachments (#SWS-53) + +Changes in version 1.0-M2 +------------------------- +Package org.springframework.oxm +* all marshallers support StaxSources now (#SWS-36) + +Package org.springframework.oxm.xstream +* added support for registering custom Converters (#SWS-34) + +Package org.springframework.ws.context +* changed contract of MessageContext.getResponse(): now creates a response if none has been created. Required because + creating SOAP fault when a response was already set threw an exception. +* enabling support for direct writing to the TransportResponse +* MessageContext contains TransportRequest, for transport-based routing + +Package org.springframework.ws.endpoint +* improved endpoint test suite +* AbstractStaxPayloadEndpoint now use StaxSource corrently (#SWS-38) +* changed AbstractDom4JPayloadEndpoint to AbstractDom4jPayloadEndpoint (lower case j), to follow Spring naming + conventions (e.g. Log4jConfigurer) +* added getters on AbstractMarshallingPayloadEndpoint + +Package org.springframework.ws.endpoint.interceptor +* moved interceptors to seperate package +* made PayloadValidatingInterceptor more customizable (#SWS-43) +* add schemas property to PayloadValidatingInterceptor (#SWS-44) +* refactoring most of PayloadValidatingInterceptor into AbstractValidatingInterceptor + +Package org.springframework.ws.endpoint.mapping +* moved EndpointMappings to seperate package +* removed SOAP-specific roles functionality + +Package org.springframework.ws.pox +* Added Plain Old XML support (#SWS-50) + +Package org.springframework.ws.soap +* improved content-type handling (#SWS-32) +* moved SOAP 1.1 specfics to separate package and interfaces +* SoapAction has been removed from SoapMessage, SoapActionEndpointMapping uses TransportRequest header + +Package org.springframework.ws.soap.axiom +* add support for SAAJ 1.3 +* added "payloadCaching" property to read the contents of SOAP bodies produced by the AxiomSoapMessageContextFactory in + a non-buffering, read-once fashion. +* increased performance by keeping object references instead of creating new instances for every call + +Package org.springframework.ws.soap.endpoint.interceptor +* added SoapEnvelopeLoggingInterceptor, which logs the entire SoapEnvelope + +Package org.springframework.ws.soap.endpoint.mapping +* moved SOAP-specific EndpointMappings to seperate package +* added Role/Actor functionality to DelegatingSoapEndpointMapping + +Package org.springframework.ws.soap.saaj +* add support for SAAJ 1.3 +* increased performance by keeping object references instead of creating new instances for every call + +Package org.springframework.ws.soap.saaj.support +* added SaajUtils.getVersion() + +Package org.springframework.ws.soap.soap11 +* moved SOAP 1.1 specifics to separate package and interfaces + +Package org.springframework.ws.soap.soap12 +* moved SOAP 1.2 specifics to separate package and interfaces + +Package org.springframework.ws.transport +* added TransportContext, TransportRequest & TransportResponse + +Package org.springframework.ws.transport.http +* removed J2EE 1.4 dependency (#SWS-31) +* renamed MessageHandlerAdapter to MessageEndpointHandlerAdapter + +Package org.springframework.xml +* moved package to seperate jar file, since oxm now also depends on it + +Package org.springframework.xml.namespace +* moved QNameEditor from spring-ws-core +* removed prefix dependency from QName + +Package org.springframework.xml.validation +* improved validation schema loading (#SWS-46) + +Samples +* Added Ben Ethridge's Echo sample client + +Changes in version 1.0-M1 (12-6-2006) +------------------------- + +Package org.springframework.oxm.jaxb +* added support for JAXB 2: refactored JaxbMarshaller into AbstractJaxbMarshaller, Jaxb1Marshaller and Jaxb2Marshaller +* made JaxbMarshallers thread safe +* added property for a ValidationEventHandler + +Package org.springframework.ws.axiom +* added AXIOM (AXis Object Model) implementation of SoapMessage hierarchy + +Package org.springframework.ws.endpoint +* added AbstractDom4JPayloadEndpoint +* added AbstractXomPayloadEndpoint +* renamed AbstractTransformingEndpoint to TransformerObjectSupport + +Package org.springframework.ws.soap +* added SoapFaultDetail and SoapFaultDetailElement +* improved SoapMessage test suite +* fixed serious bug which resulted in not-understood mustUnderstand headers being silently ignored + +Package org.springframework.ws.soap.endpoint +* added SimpleSoapExceptionResolver + +Package org.springframework.ws.soap.security.xwss +* added WS-Security implementation based on XWS-Security with integration for Acegi and JAAS + +Package org.springframework.xml.transform +* added StaxSource and StaxResult, which allow for StAX readers/writers to be used as javax.xml.transform.Source and + javax.xml.transform.Result + +Package org.springframework.xml.xpath +* added XPath abstraction with support for JAXP 1.3, Jaxen, and Xalan + +Samples +* Added Ingo Siebert's Echo sample +* Added WS-Security support to Airline sample +* Added SAAJ clients to Airline sample + + +Changes in version 0.9.1 (6-4-2006) +------------------------------------- + +Overall +* made overall thread safety fixes +* added initial reference documentation in HTML and PDF form +* (lots of minor bugfixes) + +Package org.springframework.oxm +* AbstractMarshallers does proper SAXSource handling +* exposing JAXB properties on JaxbMarshaller +* added XmlOptionsFactoryBean for use with XmlBeansMarshaller +* moved JaxbContextFactoryBean functionality into JaxbMarshaller +* moved JibxBindingFactoryBean functionality into JibxMarshaller + +Package org.springframework.ws.soap +* made AbstractSoapEndpointMapping ignorant of SOAP version +* added initial attachment support + +Package org.springframework.xml +* added a JaxpVersion class for easy detection of JAXP version +* removed JAXP1.3 dependency + + + + +Changes in version 0.9 (11-2-2006) +----------------------------------- +* First public preview release. diff --git a/release/license.txt b/release/license.txt new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/release/license.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/release/pom.xml b/release/pom.xml new file mode 100644 index 00000000..5243fde0 --- /dev/null +++ b/release/pom.xml @@ -0,0 +1,195 @@ + + + + spring-ws + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + spring-ws-release + pom + Spring WS Release + Generates an aggregated zip that contains the full distribution of Spring-WS. + + + + maven-antrun-plugin + + + process-resources + + run + + + + + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + minimal + package + + assembly + + + + src/assembly/minimal.xml + + spring-ws-${version} + + + + full + + + src/assembly/full.xml + + spring-ws-${version}-with-dependencies + + + + + + + + + + + org.springframework.ws + spring-xml + + + org.springframework.ws + spring-oxm + + + org.springframework.ws + spring-oxm-tiger + + + org.springframework.ws + spring-ws-core + + + org.springframework.ws + spring-ws-security + + + org.springframework.ws + airline + war + + + org.springframework.ws + echo + war + + + org.springframework.ws + spring-ws-doc + pom + + + + stax + stax-api + + + woodstox + wstx-asl + + + xpp3 + xpp3 + + + xerces + xercesImpl + + + + + org.codehaus.castor + castor + + + + javax.xml.bind + jaxb-api + 2.0 + + + com.sun.xml.bind + jaxb-impl + 2.0.1 + + + com.sun.xml.bind + jaxb-xjc + 2.0.1 + + + + xmlbeans + xbean + + + + org.jibx + jibx-run + + + org.jibx + jibx-bind + + + bcel + bcel + + + + xstream + xstream + + + + ws-commons + axiom-api + + + ws-commons + axiom-impl + + + com.sun.xml.messaging.saaj + saaj-impl + + + + javax.xml.soap + saaj-api + + + javax.servlet + servlet-api + + + javax.mail + mail + + + javax.activation + activation + + + + diff --git a/release/readme.txt b/release/readme.txt new file mode 100644 index 00000000..0813decd --- /dev/null +++ b/release/readme.txt @@ -0,0 +1,86 @@ +SPRING WEB SERVICES 1.0-M2 (September 2006) +------------------------------- +http://www.springframework.org/spring-ws +http://forum.springframework.org/forumdisplay.php?f=39 + +1. INTRODUCTION + +Spring Web Services (Spring-WS) is a product of the Spring community focused on creating document-driven Web services. +Spring-WS aims to facilitate contract-first SOAP service development, allowing for the creation of flexible web services +using one of the many ways to manipulate XML payloads. + +Spring-WS consists of two major modules: a flexible Object/XML Mapping abstraction with support for JAXB 1 and 2, +XMLBeans, Castor, JiBX and XStream; and a Web service framework that resembles Spring MVC. + +2. RELEASE INFO + +Spring-WS requires J2SE 1.4 and J2EE 1.4. J2SE 1.5 is required for building. + +Release contents: + +"." contains Spring-WS distribution units (jars and source zip archives), readme, and copyright +"docs" contains the Spring-WS reference manual and API Javadocs +"lib" contains dependencies +"modules" contains buildable modules +"modules/spring-oxm" contains buildable Spring O/X Mapping sources +"modules/spring-ws-core" contains buildable Spring-WS core sources +"modules/spring-ws-security" contains buildable Spring-WS security sources +"modules/spring-xml" contains buildable Spring XML utility sources, an internal library used by Spring-WS +"samples" contains buildable Spring-WS sample application sources + +See the readme.txt within the above directories for additional information. + +Spring-WS is released under the terms of the Apache Software License (see license.txt). + +3. DISTRIBUTION JAR FILES + +The following distinct jar files are included in the distribution. This list specifies the respective contents and +third-party dependencies. Libraries in [brackets] are optional, i.e. just necessary for certain functionality. + +* spring-oxm-1.0-m2.jar +- Contents: The Spring Object/XML Mapping framework +- Dependencies: Commons Logging, spring-beans, spring-core + [Log4J, JAXB, Castor, XMLBeans, StAX, JiBX, XStream] + +* spring-ws-core-1.0-m2.jar +- Contents: The Spring-WS Core +- Dependencies: Commons Logging, spring-beans, spring-core, spring-context + [Log4J, spring-oxm, spring-web, spring-webmvc, SAAJ, JDOM, StAX, Servlet API, JAF, Axiom, DOM4J, XOM] + +* spring-ws-security-1.0-m2.jar +- Contents: Spring-WS Security integration +- Dependencies: Commons Logging, spring-beans, spring-core, spring-context, spring-ws-core + [Log4J, xmlsdig, xmlsec, XWS-security, Acegi] + +* spring-xml-1.0-m2.jar +- Contents: Spring XML utility framework +- Dependencies: Commons Logging, spring-beans, spring-core + [StAX, Xalan, Jaxen] + +For an exact list of Spring-WS project dependencies see the respective pom.xml files. + +4. WHERE TO START + +This distribution contains documentation and two sample applications illustrating the features of Spring-WS. + +A great way to get started is to review and run the sample applications, supplementing with reference manual +material as needed. You will require Maven 2, which can be downloaded from http://maven.apache.org/, for building +Spring-WS. To build deployable .war files for all samples, simply access the "samples" directory and +execute the "mvn package" command. + +More information on deploying Spring-WS sample applications can be found at: + samples/readme.txt + +5. ADDITIONAL RESOURCES + +The Spring-WS homepage is located at: + + http://www.springframework.org/spring-ws + +Spring-WS support forums are located at: + + http://forum.springframework.org/forumdisplay.php?f=39 + +The Spring Framework portal is located at: + + http://www.springframework.org diff --git a/release/src/assembly/full.xml b/release/src/assembly/full.xml new file mode 100644 index 00000000..89d5b516 --- /dev/null +++ b/release/src/assembly/full.xml @@ -0,0 +1,102 @@ + + + full + + zip + + + + / + + license.txt + changelog.txt + readme.txt + + dos + + + target/docs/api + /docs/api + + + ../doc/target/docbkx + /docs/reference + + + ../spring-ws-samples + /samples + + readme.txt + + dos + + + ../spring-ws-samples + /samples + + echo/** + airline/** + + + **/target/** + **/target + **/*.iml + **/META-INF/* + **/*.log + + + + .. + /modules + + spring-xml/**/* + spring-oxm/**/* + spring-oxm-tiger/**/* + spring-ws-core/**/* + spring-ws-security/**/* + + + **/docs/** + **/target/** + **/target + **/*.iml + + + + + + lib + test + + org.springframework.ws:spring-xml + org.springframework.ws:spring-oxm + org.springframework.ws:spring-oxm-tiger + org.springframework.ws:spring-ws-core + org.springframework.ws:spring-ws-security + org.springframework.ws:echo + org.springframework.ws:airline + + + + / + runtime + + org.springframework.ws:spring-xml + org.springframework.ws:spring-oxm + org.springframework.ws:spring-oxm-tiger + org.springframework.ws:spring-ws-core + org.springframework.ws:spring-ws-security + + + + + diff --git a/release/src/assembly/minimal.xml b/release/src/assembly/minimal.xml new file mode 100644 index 00000000..89771930 --- /dev/null +++ b/release/src/assembly/minimal.xml @@ -0,0 +1,31 @@ + + + minimal + + zip + + + + / + + license.txt + changelog.txt + readme.txt + + dos + + + + + / + runtime + + org.springframework.ws:spring-xml + org.springframework.ws:spring-oxm + org.springframework.ws:spring-oxm-tiger + org.springframework.ws:spring-ws-core + org.springframework.ws:spring-ws-security + + + + diff --git a/samples/airline/build-maven2.xml b/samples/airline/build-maven2.xml new file mode 100644 index 00000000..5595b60b --- /dev/null +++ b/samples/airline/build-maven2.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/airline/client/cs/Makefile b/samples/airline/client/cs/Makefile new file mode 100644 index 00000000..73ba3c86 --- /dev/null +++ b/samples/airline/client/cs/Makefile @@ -0,0 +1,3 @@ +target: + wsdl -namespace:Spring.Ws.Samples.Airline.Client.CSharp -out:src/AirlineService.cs ../../src/main/webapp/airline.wsdl + mcs -out:bin/airline.exe -r:System.Web.Services src/main.cs src/AirlineService.cs diff --git a/samples/airline/client/cs/bin/airline.exe b/samples/airline/client/cs/bin/airline.exe new file mode 100755 index 00000000..8938e7bb Binary files /dev/null and b/samples/airline/client/cs/bin/airline.exe differ diff --git a/samples/airline/client/cs/bin/getmileage.exe b/samples/airline/client/cs/bin/getmileage.exe new file mode 100755 index 00000000..4f38d57d Binary files /dev/null and b/samples/airline/client/cs/bin/getmileage.exe differ diff --git a/samples/airline/client/cs/build-getmileage.bat b/samples/airline/client/cs/build-getmileage.bat new file mode 100644 index 00000000..fdbc6f7d --- /dev/null +++ b/samples/airline/client/cs/build-getmileage.bat @@ -0,0 +1,4 @@ +@echo off +set WSE_PATH="C:\Program Files\Microsoft WSE\v2.0" +%WSE_PATH%\Tools\Wsdl\WseWsdl2 ..\..\src\main\webapp\airline.wsdl src\AirlineService-wse.cs +csc -nologo -out:bin\getmileage.exe -reference:%WSE_PATH%\Microsoft.Web.Services2.dll src\wse.cs src\AirlineService-wse.cs \ No newline at end of file diff --git a/samples/airline/client/cs/build-wse.bat b/samples/airline/client/cs/build-wse.bat new file mode 100644 index 00000000..9c2bdc23 --- /dev/null +++ b/samples/airline/client/cs/build-wse.bat @@ -0,0 +1,3 @@ +set WSE_PATH="C:\Program Files\Microsoft WSE\v2.0" +%WSE_PATH%\Tools\Wsdl\WseWsdl2 ..\..\src\main\webapp\airline.wsdl src\AirlineService-wse.cs +csc -out:bin\airline-wse.exe -reference:%WSE_PATH%\Microsoft.Web.Services2.dll src\wse.cs src\AirlineService-wse.cs \ No newline at end of file diff --git a/samples/airline/client/cs/build.bat b/samples/airline/client/cs/build.bat new file mode 100644 index 00000000..4105ccbf --- /dev/null +++ b/samples/airline/client/cs/build.bat @@ -0,0 +1,2 @@ +wsdl -out:src\AirlineService.cs ..\..\src\webapp\airline.wsdl ..\..\src\webapp\airline.xsd +csc -out:bin\airline.exe src\main.cs src\AirlineService.cse.csosoft WSE\v2.0\Microsoft.Web.Services2.dll" -define:WSE src\main.cs src\AirlineService.cs \ No newline at end of file diff --git a/samples/airline/client/cs/readme.txt b/samples/airline/client/cs/readme.txt new file mode 100644 index 00000000..d8ba2d63 --- /dev/null +++ b/samples/airline/client/cs/readme.txt @@ -0,0 +1,20 @@ +SPRING WEB SERVICES + +This directory contains two C# clients for the Airline Web Service. One sample (airline.exe) uses non-secure +functionality, and works under both .NET and Mono, while getmileage.exe only works under .NET because it relies on +Microsoft's Web Services enhancements (WSE 2.0) to perform WS-Security. + +The C# client is executable: just run the executable file and (if the war file is deployed at the default +http://localhost:8080/airline) the web service will be called, causing a flight reservations to be created. + +If the web service is NOT running at the default URL, you can append the URL argument to the exectuable file +(e.g. 'airline.exe http://localhost:8080/airline-webservice/Airline'). + + +C# Client Sample table of contents +--------------------------------------------------- +* bin - The executable files for the clients: airline.exe and getmileage.exe +* src - The source files for the clients. +* Makefile - Mono makefile. +* build.bat - .NET build batch file for airline.exe +* build-getmileage.bat - .NET build batch file for getmileage.exe \ No newline at end of file diff --git a/samples/airline/client/cs/src/AirlineService-wse.cs b/samples/airline/client/cs/src/AirlineService-wse.cs new file mode 100644 index 00000000..ec6afb95 --- /dev/null +++ b/samples/airline/client/cs/src/AirlineService-wse.cs @@ -0,0 +1,78 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 1.1.4322.2032 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +// +// InfoVersionComment, WseWsdl2, Version=2.0.3.0, Culture=neutral, PublicKeyToken=26ba029e599ec4f8, RunTime Version: 1.1.4322.2032 +// +using System; +using System.Xml; +using Microsoft.Web.Services2; +using Microsoft.Web.Services2.Addressing; +using Microsoft.Web.Services2.Messaging; +using System.Xml.Serialization; + + +[SoapService("http://www.springframework.org/spring-ws/samples/airline/definitions")] +public class AirlineService : SoapClient +{ + public AirlineService() : base( new Uri("http://localhost:8080/airline/Airline") ) + { + } + + [SoapMethod("http://www.springframework.org/spring-ws/samples/airline/GetFlights")] + public SoapEnvelope GetFlights( SoapEnvelope request ) + { + return base.SendRequestResponse("GetFlights", request); + } + + public IAsyncResult BeginGetFlights( SoapEnvelope request, System.AsyncCallback callback, object asyncState) + { + return base.BeginSendRequestResponse("GetFlights", request, callback, asyncState); + } + + public SoapEnvelope EndGetFlights( System.IAsyncResult asyncResult ) + { + return base.EndSendRequestResponse(asyncResult); + } + + [SoapMethod("http://www.springframework.org/spring-ws/samples/airline/BookFlight")] + public SoapEnvelope BookFlight( SoapEnvelope request ) + { + return base.SendRequestResponse("BookFlight", request); + } + + public IAsyncResult BeginBookFlight( SoapEnvelope request, System.AsyncCallback callback, object asyncState) + { + return base.BeginSendRequestResponse("BookFlight", request, callback, asyncState); + } + + public SoapEnvelope EndBookFlight( System.IAsyncResult asyncResult ) + { + return base.EndSendRequestResponse(asyncResult); + } + + [SoapMethod("http://www.springframework.org/spring-ws/samples/airline/GetFrequentFlyerMileage")] + public SoapEnvelope GetFrequentFlyerMileage( SoapEnvelope request ) + { + return base.SendRequestResponse("GetFrequentFlyerMileage", request); + } + + public IAsyncResult BeginGetFrequentFlyerMileage( SoapEnvelope request, System.AsyncCallback callback, object asyncState) + { + return base.BeginSendRequestResponse("GetFrequentFlyerMileage", request, callback, asyncState); + } + + public SoapEnvelope EndGetFrequentFlyerMileage( System.IAsyncResult asyncResult ) + { + return base.EndSendRequestResponse(asyncResult); + } + +} + diff --git a/samples/airline/client/cs/src/AirlineService.cs b/samples/airline/client/cs/src/AirlineService.cs new file mode 100644 index 00000000..123c63f9 --- /dev/null +++ b/samples/airline/client/cs/src/AirlineService.cs @@ -0,0 +1,204 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Mono Runtime Version: 1.1.4322.2032 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ + +// +// This source code was auto-generated by Mono Web Services Description Language Utility +// +namespace Spring.Ws.Samples.Airline.Client.CSharp { + + /// + [System.Web.Services.WebServiceBinding(Name="AirlinePort", Namespace="http://www.springframework.org/spring-ws/samples/airline/definitions")] + [System.Diagnostics.DebuggerStepThroughAttribute()] + [System.ComponentModel.DesignerCategoryAttribute("code")] + public class AirlineService : System.Web.Services.Protocols.SoapHttpClientProtocol { + + public AirlineService() { + this.Url = "http://localhost:8080/airline/Airline"; + } + + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://www.springframework.org/spring-ws/samples/airline/GetFlights", ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Bare, Use=System.Web.Services.Description.SoapBindingUse.Literal)] + [return: System.Xml.Serialization.XmlArray(ElementName="GetFlightsResponse", Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + [return: System.Xml.Serialization.XmlArrayItem(ElementName="flight", IsNullable=false)] + public Flight[] GetFlights([System.Xml.Serialization.XmlElement(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] MessageGetFlightsRequest GetFlightsRequest) { + object[] results = this.Invoke("GetFlights", new object[] { + GetFlightsRequest}); + return ((Flight[])(results[0])); + } + + public System.IAsyncResult BeginGetFlights(MessageGetFlightsRequest GetFlightsRequest, System.AsyncCallback callback, object asyncState) { + return this.BeginInvoke("GetFlights", new object[] { + GetFlightsRequest}, callback, asyncState); + } + + public Flight[] EndGetFlights(System.IAsyncResult asyncResult) { + object[] results = this.EndInvoke(asyncResult); + return ((Flight[])(results[0])); + } + + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://www.springframework.org/spring-ws/samples/airline/BookFlight", ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Bare, Use=System.Web.Services.Description.SoapBindingUse.Literal)] + [return: System.Xml.Serialization.XmlElement("BookFlightResponse", Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public Ticket BookFlight([System.Xml.Serialization.XmlElement(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] MessageBookFlightRequest BookFlightRequest) { + object[] results = this.Invoke("BookFlight", new object[] { + BookFlightRequest}); + return ((Ticket)(results[0])); + } + + public System.IAsyncResult BeginBookFlight(MessageBookFlightRequest BookFlightRequest, System.AsyncCallback callback, object asyncState) { + return this.BeginInvoke("BookFlight", new object[] { + BookFlightRequest}, callback, asyncState); + } + + public Ticket EndBookFlight(System.IAsyncResult asyncResult) { + object[] results = this.EndInvoke(asyncResult); + return ((Ticket)(results[0])); + } + + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://www.springframework.org/spring-ws/samples/airline/GetFrequentFlyerMileage", ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Bare, Use=System.Web.Services.Description.SoapBindingUse.Literal)] + [return: System.Xml.Serialization.XmlElement("GetFrequentFlyerMileageResponse", Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public int GetFrequentFlyerMileage([System.Xml.Serialization.XmlElement(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] object GetFrequentFlyerMileageRequest) { + object[] results = this.Invoke("GetFrequentFlyerMileage", new object[] { + GetFrequentFlyerMileageRequest}); + return ((int)(results[0])); + } + + public System.IAsyncResult BeginGetFrequentFlyerMileage(object GetFrequentFlyerMileageRequest, System.AsyncCallback callback, object asyncState) { + return this.BeginInvoke("GetFrequentFlyerMileage", new object[] { + GetFrequentFlyerMileageRequest}, callback, asyncState); + } + + public int EndGetFrequentFlyerMileage(System.IAsyncResult asyncResult) { + object[] results = this.EndInvoke(asyncResult); + return ((int)(results[0])); + } + } + + /// + [System.Xml.Serialization.XmlType(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + [System.Xml.Serialization.XmlRoot("GetFlightsRequest", Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public class MessageGetFlightsRequest { + + /// + public string from; + + /// + public string to; + + /// + [System.Xml.Serialization.XmlElement(DataType="date")] + public System.DateTime departureDate; + + /// + public ServiceClass serviceClass; + + /// + [System.Xml.Serialization.XmlIgnore()] + public bool serviceClassSpecified; + } + + /// + [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public enum ServiceClass { + + /// + economy, + + /// + business, + + /// + first, + } + + /// + [System.Xml.Serialization.XmlType(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public class Flight { + + /// + public string number; + + /// + public System.DateTime departureTime; + + /// + public Airport from; + + /// + public System.DateTime arrivalTime; + + /// + public Airport to; + + /// + public ServiceClass serviceClass; + } + + /// + [System.Xml.Serialization.XmlType(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public class Airport { + + /// + public string code; + + /// + public string name; + + /// + public string city; + } + + /// + [System.Xml.Serialization.XmlType(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + [System.Xml.Serialization.XmlRoot("BookFlightRequest", Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public class MessageBookFlightRequest { + + /// + public string flightNumber; + + /// + public System.DateTime departureTime; + + /// + [System.Xml.Serialization.XmlArrayItem(ElementName="passenger", Type=typeof(Name), IsNullable=false)] + [System.Xml.Serialization.XmlArrayItem(ElementName="username", Type=typeof(string), IsNullable=false)] + public object[] passengers; + } + + /// + [System.Xml.Serialization.XmlType(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public class Name { + + /// + public string first; + + /// + public string last; + } + + /// + [System.Xml.Serialization.XmlType(Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + [System.Xml.Serialization.XmlRoot("BookFlightResponse", Namespace="http://www.springframework.org/spring-ws/samples/airline/schemas")] + public class Ticket { + + /// + public long id; + + /// + [System.Xml.Serialization.XmlElement(DataType="date")] + public System.DateTime issueDate; + + /// + [System.Xml.Serialization.XmlArrayItem(ElementName="passenger", IsNullable=false)] + public Name[] passengers; + + /// + public Flight flight; + } + +} diff --git a/samples/airline/client/cs/src/main.cs b/samples/airline/client/cs/src/main.cs new file mode 100644 index 00000000..6b5953b3 --- /dev/null +++ b/samples/airline/client/cs/src/main.cs @@ -0,0 +1,66 @@ +using System; +using System.Web; +using System.Web.Services.Protocols; + +namespace Spring.Ws.Samples.Airline.Client.CSharp { + + public class Client { + public static void Main(string[] args) { + try { + AirlineService service = new AirlineService(); + if (args.Length > 0) { + service.Url = args[0]; + } else { + service.Url = "http://localhost:8080/airline/Airline"; + } + // Get all flights on 31st Januari, 2006 from Amsterdam to Venice + MessageGetFlightsRequest getFlightsRequest = new MessageGetFlightsRequest(); + getFlightsRequest.from = "AMS"; + getFlightsRequest.to = "VCE"; + getFlightsRequest.departureDate = new DateTime(2006,1,31); + Console.WriteLine("Requesting flights on {0:d}", getFlightsRequest.departureDate); + Flight[] flights = service.GetFlights(getFlightsRequest); + Console.WriteLine("Got {0} results", flights.Length); + if (flights.Length > 0) { + // Book the first flight using John Doe as a frequent flyer + MessageBookFlightRequest bookFlightRequest = new MessageBookFlightRequest(); + bookFlightRequest.flightNumber = flights[0].number; + bookFlightRequest.departureTime = flights[0].departureTime; + bookFlightRequest.passengers = new object[] { "john" }; + Ticket ticket = service.BookFlight(bookFlightRequest); + WriteTicket(ticket); + } + } catch (SoapException ex) { + Console.Error.WriteLine("SOAP Fault Code {0}", ex.Code); + Console.Error.WriteLine("SOAP Fault String: {0}", ex.Message); + } + } + + private static void WriteTicket(Ticket ticket) { + Console.WriteLine("Ticket {0}", ticket.id); + Console.WriteLine("Ticket issue date:\t{0:d}", ticket.issueDate); + foreach (Name passenger in ticket.passengers) { + WriteName(passenger); + } + WriteFlight(ticket.flight); + } + + private static void WriteName(Name name) { + Console.WriteLine("Passenger Name:"); + Console.WriteLine("{0} {1}", name.first, name.last); + Console.WriteLine("------------"); + } + + private static void WriteFlight(Flight flight) { + Console.WriteLine("{0:d}", flight.departureTime); + Console.WriteLine("{0}\t{1}", flight.number, flight.serviceClass); + Console.WriteLine("------------"); + Console.WriteLine("Depart:\t{0}-{1}\t{2:t}", flight.from.code, flight.from.name, + flight.departureTime); + Console.WriteLine("\t{0}", flight.from.city); + Console.WriteLine("Arrive:\t{0}-{1}\t{2:t}", flight.to.code, flight.to.name, + flight.arrivalTime); + Console.WriteLine("\t{0}", flight.to.city); + } + } +} diff --git a/samples/airline/client/cs/src/wse.cs b/samples/airline/client/cs/src/wse.cs new file mode 100644 index 00000000..b5e83452 --- /dev/null +++ b/samples/airline/client/cs/src/wse.cs @@ -0,0 +1,32 @@ +using System; +using System.Web; +using System.Web.Services.Protocols; +using System.Xml; +using Microsoft.Web.Services2; +using Microsoft.Web.Services2.Security; +using Microsoft.Web.Services2.Security.Tokens; + +namespace Spring.Ws.Samples.Airline.Client.CSharp { + + public class Client { + public static void Main(string[] args) { + try { + AirlineService service = new AirlineService(); + if (args.Length > 0) { + service.Destination = new Uri(args[0]); + } + SoapEnvelope request = new SoapEnvelope(); + request.SetBodyObject(@""); + UsernameToken userToken = new UsernameToken("john","changeme", PasswordOption.SendHashed); + request.Context.Security.Tokens.Add(userToken); + SoapEnvelope response = service.GetFrequentFlyerMileage(request); + int miles = XmlConvert.ToInt32(response.Body.FirstChild.InnerText); + Console.Out.WriteLine("'john' has {0} frequent flyer miles", miles); + } catch (SoapException ex) { + Console.Error.WriteLine("SOAP Fault Code {0}", ex.Code); + Console.Error.WriteLine("SOAP Fault String: {0}", ex.Message); + } + } + + } +} diff --git a/samples/airline/client/readme.txt b/samples/airline/client/readme.txt new file mode 100644 index 00000000..ca645a19 --- /dev/null +++ b/samples/airline/client/readme.txt @@ -0,0 +1,6 @@ +SPRING WEB SERVICES + +Client table of contents +--------------------------------------------------- +* cs - A client for the airline service written in C#. +* saaj - Two clients that are written in Java and use SAAJ. \ No newline at end of file diff --git a/samples/airline/client/saaj/build.xml b/samples/airline/client/saaj/build.xml new file mode 100644 index 00000000..852558e5 --- /dev/null +++ b/samples/airline/client/saaj/build.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/airline/client/saaj/lib/activation.jar b/samples/airline/client/saaj/lib/activation.jar new file mode 100644 index 00000000..1c88eb4e Binary files /dev/null and b/samples/airline/client/saaj/lib/activation.jar differ diff --git a/samples/airline/client/saaj/lib/mail.jar b/samples/airline/client/saaj/lib/mail.jar new file mode 100644 index 00000000..9966ca30 Binary files /dev/null and b/samples/airline/client/saaj/lib/mail.jar differ diff --git a/samples/airline/client/saaj/lib/saaj-api.jar b/samples/airline/client/saaj/lib/saaj-api.jar new file mode 100644 index 00000000..ad2d46d6 Binary files /dev/null and b/samples/airline/client/saaj/lib/saaj-api.jar differ diff --git a/samples/airline/client/saaj/lib/saaj-impl.jar b/samples/airline/client/saaj/lib/saaj-impl.jar new file mode 100644 index 00000000..612b638c Binary files /dev/null and b/samples/airline/client/saaj/lib/saaj-impl.jar differ diff --git a/samples/airline/client/saaj/lib/xercesImpl.jar b/samples/airline/client/saaj/lib/xercesImpl.jar new file mode 100644 index 00000000..d5f3e98c Binary files /dev/null and b/samples/airline/client/saaj/lib/xercesImpl.jar differ diff --git a/samples/airline/client/saaj/lib/xmldsig.jar b/samples/airline/client/saaj/lib/xmldsig.jar new file mode 100644 index 00000000..e4b87321 Binary files /dev/null and b/samples/airline/client/saaj/lib/xmldsig.jar differ diff --git a/samples/airline/client/saaj/lib/xmlsec.jar b/samples/airline/client/saaj/lib/xmlsec.jar new file mode 100644 index 00000000..3c79d4e5 Binary files /dev/null and b/samples/airline/client/saaj/lib/xmlsec.jar differ diff --git a/samples/airline/client/saaj/lib/xws-security.jar b/samples/airline/client/saaj/lib/xws-security.jar new file mode 100644 index 00000000..7b9caef9 Binary files /dev/null and b/samples/airline/client/saaj/lib/xws-security.jar differ diff --git a/samples/airline/client/saaj/readme.txt b/samples/airline/client/saaj/readme.txt new file mode 100644 index 00000000..b65bd622 --- /dev/null +++ b/samples/airline/client/saaj/readme.txt @@ -0,0 +1,11 @@ +SPRING WEB SERVICES + +This directory contains two Java clients for the Airline Web Service that use SAAJ: SOAP with Attachments API for Java. +There is a client for the GetFlights operation and a client for the secure GetFrequentFlyerMileage operation. Both +clients can be run from the provided ant file, by calling "ant run". + +SAJA Client Sample table of contents +--------------------------------------------------- +* src - The source files for the client +* build.xml - Ant build file with a 'build' and a 'run' target + diff --git a/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/GetFlights.java b/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/GetFlights.java new file mode 100644 index 00000000..790894a4 --- /dev/null +++ b/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/GetFlights.java @@ -0,0 +1,127 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.client.saaj; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Iterator; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPBodyElement; +import javax.xml.soap.SOAPConnection; +import javax.xml.soap.SOAPConnectionFactory; +import javax.xml.soap.SOAPElement; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; +import javax.xml.soap.SOAPMessage; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +/** + * Simple client that calls the GetFlights operation using SAAJ. + * + * @author Arjen Poutsma + */ +public class GetFlights { + + public static final String NAMESPACE_URI = "http://www.springframework.org/spring-ws/samples/airline/schemas"; + + public static final String PREFIX = "airline"; + + private SOAPConnectionFactory connectionFactory; + + private MessageFactory messageFactory; + + private URL url; + + private TransformerFactory transfomerFactory; + + public GetFlights(String url) throws SOAPException, MalformedURLException { + connectionFactory = SOAPConnectionFactory.newInstance(); + messageFactory = MessageFactory.newInstance(); + transfomerFactory = TransformerFactory.newInstance(); + this.url = new URL(url); + } + + private SOAPMessage createGetFlightsRequest() throws SOAPException { + SOAPMessage message = messageFactory.createMessage(); + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name getFlightsRequestName = envelope.createName("GetFlightsRequest", PREFIX, NAMESPACE_URI); + SOAPBodyElement getFlightsRequestElement = message.getSOAPBody().addBodyElement(getFlightsRequestName); + Name fromName = envelope.createName("from", PREFIX, NAMESPACE_URI); + SOAPElement fromElement = getFlightsRequestElement.addChildElement(fromName); + fromElement.setValue("AMS"); + Name toName = envelope.createName("to", PREFIX, NAMESPACE_URI); + SOAPElement toElement = getFlightsRequestElement.addChildElement(toName); + toElement.setValue("VCE"); + Name departureDateName = envelope.createName("departureDate", PREFIX, NAMESPACE_URI); + SOAPElement departureDateElement = getFlightsRequestElement.addChildElement(departureDateName); + departureDateElement.setValue("2006-01-31"); + return message; + } + + public void getFlights() throws SOAPException, IOException, TransformerException { + SOAPMessage request = createGetFlightsRequest(); + SOAPConnection connection = connectionFactory.createConnection(); + SOAPMessage response = connection.call(request, url); + if (!response.getSOAPBody().hasFault()) { + writeGetFlightsResponse(response); + } + else { + SOAPFault fault = response.getSOAPBody().getFault(); + System.err.println("Received SOAP Fault"); + System.err.println("SOAP Fault Code :" + fault.getFaultCode()); + System.err.println("SOAP Fault String :" + fault.getFaultString()); + } + } + + private void writeGetFlightsResponse(SOAPMessage message) throws SOAPException, TransformerException { + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name getFlightsResponseName = envelope.createName("GetFlightsResponse", PREFIX, NAMESPACE_URI); + SOAPBodyElement getFlightsResponseElement = + (SOAPBodyElement) message.getSOAPBody().getChildElements(getFlightsResponseName).next(); + Name flightName = envelope.createName("flight", PREFIX, NAMESPACE_URI); + Iterator iterator = getFlightsResponseElement.getChildElements(flightName); + Transformer transformer = transfomerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + int count = 1; + while (iterator.hasNext()) { + System.out.println("Flight " + count); + System.out.println("--------"); + SOAPElement flightElement = (SOAPElement) iterator.next(); + DOMSource source = new DOMSource(flightElement); + transformer.transform(source, new StreamResult(System.out)); + } + } + + public static void main(String[] args) throws Exception { + String url = "http://localhost:8080/airline/Airline"; + if (args.length > 0) { + url = args[0]; + } + GetFlights getFlights = new GetFlights(url); + getFlights.getFlights(); + } +} diff --git a/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/GetFrequentFlyerMileage.java b/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/GetFrequentFlyerMileage.java new file mode 100644 index 00000000..0a75b2f4 --- /dev/null +++ b/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/GetFrequentFlyerMileage.java @@ -0,0 +1,147 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.client.saaj; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPBodyElement; +import javax.xml.soap.SOAPConnection; +import javax.xml.soap.SOAPConnectionFactory; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; +import javax.xml.soap.SOAPMessage; + +import com.sun.xml.wss.ProcessingContext; +import com.sun.xml.wss.XWSSProcessor; +import com.sun.xml.wss.XWSSProcessorFactory; +import com.sun.xml.wss.XWSSecurityException; +import com.sun.xml.wss.impl.callback.PasswordCallback; +import com.sun.xml.wss.impl.callback.UsernameCallback; + +/** + * Simple client that calls the WS-Security GetFrequentFlyerMileage operation using SAAJ and XWSS. + * + * @author Arjen Poutsma + */ +public class GetFrequentFlyerMileage { + + public static final String NAMESPACE_URI = "http://www.springframework.org/spring-ws/samples/airline/schemas"; + + public static final String PREFIX = "airline"; + + private SOAPConnectionFactory connectionFactory; + + private MessageFactory messageFactory; + + private URL url; + + private XWSSProcessorFactory processorFactory; + + public GetFrequentFlyerMileage(String url) throws SOAPException, MalformedURLException, XWSSecurityException { + connectionFactory = SOAPConnectionFactory.newInstance(); + messageFactory = MessageFactory.newInstance(); + processorFactory = XWSSProcessorFactory.newInstance(); + this.url = new URL(url); + } + + private SOAPMessage createGetMileageRequest() throws SOAPException { + SOAPMessage message = messageFactory.createMessage(); + message.getMimeHeaders().addHeader("SOAPAction", + "\"http://www.springframework.org/spring-ws/samples/airline/GetFrequentFlyerMileage\""); + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name getFlightsRequestName = envelope.createName("GetFrequentFlyerMileage", GetFrequentFlyerMileage.PREFIX, + GetFrequentFlyerMileage.NAMESPACE_URI); + message.getSOAPBody().addBodyElement(getFlightsRequestName); + return message; + } + + private SOAPMessage secureMessage(SOAPMessage message, final String username, final String password) + throws IOException, XWSSecurityException { + CallbackHandler callbackHandler = new CallbackHandler() { + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof UsernameCallback) { + UsernameCallback callback = (UsernameCallback) callbacks[i]; + callback.setUsername(username); + } + else if (callbacks[i] instanceof PasswordCallback) { + PasswordCallback callback = (PasswordCallback) callbacks[i]; + callback.setPassword(password); + } + else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + }; + InputStream policyStream = null; + XWSSProcessor processor = null; + try { + policyStream = getClass().getResourceAsStream("securityPolicy.xml"); + processor = processorFactory.createProcessorForSecurityConfiguration(policyStream, callbackHandler); + } + finally { + if (policyStream != null) { + policyStream.close(); + } + } + ProcessingContext context = processor.createProcessingContext(message); + return processor.secureOutboundMessage(context); + } + + public void getMileage(String username, String password) throws SOAPException, IOException, XWSSecurityException { + SOAPMessage request = createGetMileageRequest(); + request = secureMessage(request, username, password); + SOAPConnection connection = connectionFactory.createConnection(); + SOAPMessage response = connection.call(request, url); + if (!response.getSOAPBody().hasFault()) { + SOAPBodyElement mileage = (SOAPBodyElement) response.getSOAPBody().getChildElements().next(); + System.out.println("'" + username + "' has " + mileage.getValue() + " frequent flyer miles"); + } + else { + SOAPFault fault = response.getSOAPBody().getFault(); + System.err.println("Received SOAP Fault"); + System.err.println("SOAP Fault Code :" + fault.getFaultCode()); + System.err.println("SOAP Fault String :" + fault.getFaultString()); + } + } + + public static void main(String[] args) throws Exception { + String url = "http://localhost:8080/airline/Airline"; + if (args.length < 2) { + System.err.println( + "Usage: java org.springframework.ws.samples.airline.client.saaj.GetFrequentFlyerMileage " + + "username password [url]"); + } + String username = args[0]; + String password = args[1]; + if (args.length > 2) { + url = args[2]; + } + GetFrequentFlyerMileage getMileage = new GetFrequentFlyerMileage(url); + getMileage.getMileage(username, password); + } +} diff --git a/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/securityPolicy.xml b/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/securityPolicy.xml new file mode 100644 index 00000000..85090738 --- /dev/null +++ b/samples/airline/client/saaj/src/org/springframework/ws/samples/airline/client/saaj/securityPolicy.xml @@ -0,0 +1,3 @@ + + + diff --git a/samples/airline/pom.xml b/samples/airline/pom.xml new file mode 100644 index 00000000..3e275aaa --- /dev/null +++ b/samples/airline/pom.xml @@ -0,0 +1,172 @@ + + + spring-ws-samples + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + airline + war + Spring WS Airline Sample + + + + org.apache.maven.plugins + maven-antrun-plugin + + + generate-sources + + + + + + + ${project.build.directory}/generated-sources/main/java + + + run + + + + + + + + + + org.springframework.ws + spring-ws-core + + + org.springframework.ws + spring-oxm + + + org.springframework.ws + spring-ws-security + + + + org.springframework + spring-hibernate + + + org.springframework + spring-jdbc + + + org.springframework + spring-mock + + + + jdom + jdom + + + jaxen + jaxen + + + dom4j + dom4j + + + jdom + jdom + + + xerces + xmlParserAPIs + + + xerces + xercesImpl + + + xom + xom + + + + + xalan + xalan + test + + + + javax.xml.bind + jaxb-api + + + com.sun.xml.bind + jaxb-impl + 1.0.6 + compile + + + com.sun.xml.bind + jaxb-libs + 1.0.6 + compile + + + com.sun.xml.bind + jaxb-xjc + 1.0.6 + runtime + + + com.sun.msv.datatype.xsd + xsdlib + 20060615 + compile + + + + org.hibernate + hibernate + 3.1.3 + + + commons-dbcp + commons-dbcp + 1.2.1 + + + xml-apis + xml-apis + + + xerces + xerces + + + + + mysql + mysql-connector-java + 3.1.11 + + + hsqldb + hsqldb + 1.8.0.4 + test + + + + joda-time + joda-time + 1.2.1 + + + joda-time + joda-time-hibernate + 0.8 + + + + \ No newline at end of file diff --git a/samples/airline/readme.txt b/samples/airline/readme.txt new file mode 100644 index 00000000..daffa7cf --- /dev/null +++ b/samples/airline/readme.txt @@ -0,0 +1,32 @@ +===================================================== +== Spring Web Service Airline sample application == +===================================================== + +1. INTRODUCTION + +Features a web service on top of an airline reservation system, backed by a database. The web service works by using XML +Marshalling techniques (JAXB 1), and JDOM in combination with XPath queries to pull information from a message. All +messages follow following the airline.xsd schema in src/webapp. + +A C# client is available in the client directory. + +2. INSTALLATION + +The Airline sample is a normal web application that connects to a database of your choice. + +1. Create a database using one of the scripts in src/etc/db. First, initialize the database using either the MySQL or + PostgreSQL initDB.sql script, and after that run populateDb.sql. +2. Adjust the jdbc.properties in src/main/resources/org/springframework/ws/samples/airline/dao + to reflect your database connection settings +3. Adjust the hibernate.properties in src/main/resources/org/springframework/ws/samples/airline/dao/hibernate +4. run "mvn package" and deploy the war file generated in target; or run "mvn jetty:run" to run the sample + using the Jetty Web container built into Maven 2. + +Note that both MySQL drivers are linked in using Maven so you don't have include these in your server if you're using +this database. + +3. THE CLIENTS + +The client directory contains two sample clients: one in C# and one using SAAJ. More instructions are provided in the +readme files in the directories. + diff --git a/samples/airline/src/etc/db/mysql/dropDB.sql b/samples/airline/src/etc/db/mysql/dropDB.sql new file mode 100644 index 00000000..c91a0015 --- /dev/null +++ b/samples/airline/src/etc/db/mysql/dropDB.sql @@ -0,0 +1 @@ +DROP DATABASE IF EXISTS airline; diff --git a/samples/airline/src/etc/db/mysql/initDB.sql b/samples/airline/src/etc/db/mysql/initDB.sql new file mode 100644 index 00000000..7d16f9ce --- /dev/null +++ b/samples/airline/src/etc/db/mysql/initDB.sql @@ -0,0 +1,53 @@ +CREATE DATABASE IF NOT EXISTS airline; + +GRANT ALL ON airline.* TO airline@localhost IDENTIFIED BY 'airline'; + +USE airline; + +CREATE TABLE AIRPORT ( + CODE CHAR(3) NOT NULL PRIMARY KEY, + NAME VARCHAR(20) NOT NULL, + CITY VARCHAR(20) NOT NULL +); + +CREATE TABLE FLIGHT ( + ID INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + NUMBER VARCHAR(20) NOT NULL, + DEPARTURE_TIME DATETIME NOT NULL, + FROM_AIRPORT_CODE CHAR(3) NOT NULL REFERENCES AIRPORT(CODE), + ARRIVAL_TIME DATETIME NOT NULL, + TO_AIRPORT_CODE CHAR(3) NOT NULL REFERENCES AIRPORT(CODE), + SERVICE_CLASS VARCHAR(10) NOT NULL, + SEATS_AVAILABLE INT(4) UNSIGNED NOT NULL, + MILES INT(4) UNSIGNED NOT NULL, + UNIQUE KEY IDX_NUMBER_DEPARTURE_TIME (NUMBER, DEPARTURE_TIME) +); + +CREATE TABLE TICKET ( + ID INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + ISSUE_DATE DATE NOT NULL, + FLIGHT_ID INT(4) UNSIGNED NOT NULL REFERENCES FLIGHT(ID) +); + +CREATE TABLE PASSENGER ( + ID INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + FIRST_NAME VARCHAR(30), + LAST_NAME VARCHAR(30) +); + +CREATE TABLE PASSENGER_TICKET ( + PASSENGER_ID INT(4) UNSIGNED NOT NULL REFERENCES PASSENGER(ID), + TICKET_ID INT(4) UNSIGNED NOT NULL REFERENCES TICKET(ID) +); + +CREATE TABLE FREQUENT_FLYER ( + PASSENGER_ID INT(4) UNSIGNED NOT NULL PRIMARY KEY REFERENCES PASSENGER(ID), + USERNAME VARCHAR(10) NOT NULL, + PASSWORD VARCHAR(10) NOT NULL, + MILES INT(4) UNSIGNED NOT NULL, + UNIQUE KEY IDX_USERNAME (USERNAME) +); + + + + diff --git a/samples/airline/src/etc/db/populateDB.sql b/samples/airline/src/etc/db/populateDB.sql new file mode 100644 index 00000000..22778cae --- /dev/null +++ b/samples/airline/src/etc/db/populateDB.sql @@ -0,0 +1,8 @@ +INSERT INTO AIRPORT(CODE, NAME, CITY) VALUES('AMS', 'Schiphol Airport', 'Amsterdam'); +INSERT INTO AIRPORT(CODE, NAME, CITY) VALUES('VCE', 'Marco Polo Airport', 'Venice'); + +INSERT INTO FLIGHT(ID, NUMBER, DEPARTURE_TIME, FROM_AIRPORT_CODE, ARRIVAL_TIME, TO_AIRPORT_CODE, SERVICE_CLASS, SEATS_AVAILABLE, MILES) VALUES (1,'KL1653','2006-01-31 10:05:00', 'AMS', '2006-01-31 12:25:00', 'VCE', 'economy', 5, 200); +INSERT INTO FLIGHT(ID, NUMBER, DEPARTURE_TIME, FROM_AIRPORT_CODE, ARRIVAL_TIME, TO_AIRPORT_CODE, SERVICE_CLASS, SEATS_AVAILABLE, MILES) VALUES (2,'KL1654','2006-02-05 12:40:00', 'VCE', '2006-02-05 14:15:00', 'AMS', 'economy', 5, 200); + +INSERT INTO PASSENGER(ID, FIRST_NAME, LAST_NAME) VALUES (1, 'John', 'Doe'); +INSERT INTO FREQUENT_FLYER(PASSENGER_ID, USERNAME, PASSWORD, MILES) VALUES (1, 'john', 'changeme', 10); \ No newline at end of file diff --git a/samples/airline/src/etc/db/postgresql/dropDB.sql b/samples/airline/src/etc/db/postgresql/dropDB.sql new file mode 100644 index 00000000..1ea5d56d --- /dev/null +++ b/samples/airline/src/etc/db/postgresql/dropDB.sql @@ -0,0 +1,6 @@ +DROP TABLE FREQUENT_FLYER; +DROP TABLE PASSENGER_TICKET; +DROP TABLE PASSENGER; +DROP TABLE TICKET; +DROP TABLE FLIGHT; +DROP TABLE AIRPORT; diff --git a/samples/airline/src/etc/db/postgresql/initDB.sql b/samples/airline/src/etc/db/postgresql/initDB.sql new file mode 100644 index 00000000..63bd12ee --- /dev/null +++ b/samples/airline/src/etc/db/postgresql/initDB.sql @@ -0,0 +1,46 @@ +CREATE DATABASE airline; +GRANT ALL ON DATABASE airline to airline; + +CREATE TABLE AIRPORT ( + CODE CHAR(3) NOT NULL PRIMARY KEY, + NAME VARCHAR(20) NOT NULL, + CITY VARCHAR(20) NOT NULL +); + +CREATE TABLE FLIGHT ( + ID BIGSERIAL NOT NULL PRIMARY KEY, + NUMBER VARCHAR(20) NOT NULL, + DEPARTURE_TIME TIMESTAMP NOT NULL, + FROM_AIRPORT_CODE CHAR(3) NOT NULL REFERENCES AIRPORT(CODE), + ARRIVAL_TIME TIMESTAMP NOT NULL, + TO_AIRPORT_CODE CHAR(3) NOT NULL REFERENCES AIRPORT(CODE), + SERVICE_CLASS VARCHAR(10) NOT NULL, + SEATS_AVAILABLE INT NOT NULL, + MILES INT NOT NULL, + UNIQUE(NUMBER, DEPARTURE_TIME) +); + +CREATE TABLE TICKET ( + ID BIGSERIAL NOT NULL PRIMARY KEY, + ISSUE_DATE DATE NOT NULL, + FLIGHT_ID INT NOT NULL REFERENCES FLIGHT(ID) +); + +CREATE TABLE PASSENGER ( + ID BIGSERIAL NOT NULL PRIMARY KEY, + FIRST_NAME VARCHAR(30), + LAST_NAME VARCHAR(30) +); + +CREATE TABLE PASSENGER_TICKET ( + PASSENGER_ID INT NOT NULL REFERENCES PASSENGER(ID), + TICKET_ID INT NOT NULL REFERENCES TICKET(ID) +); + +CREATE TABLE FREQUENT_FLYER ( + PASSENGER_ID INT NOT NULL PRIMARY KEY REFERENCES PASSENGER(ID), + USERNAME VARCHAR(10) NOT NULL, + PASSWORD VARCHAR(10) NOT NULL, + MILES INT NOT NULL, + UNIQUE (USERNAME) +); diff --git a/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightRequest.xml b/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightRequest.xml new file mode 100644 index 00000000..cea57ce0 --- /dev/null +++ b/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightRequest.xml @@ -0,0 +1,11 @@ + + + EF1234 + 2006-01-01T00:00:00.000Z + + + John + Doe + + + diff --git a/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightRequestFrequentFlyer.xml b/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightRequestFrequentFlyer.xml new file mode 100644 index 00000000..64bc5237 --- /dev/null +++ b/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightRequestFrequentFlyer.xml @@ -0,0 +1,8 @@ + + + EF1234 + 2006-01-01T00:00:00.000Z + + john + + diff --git a/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightResponse.xml b/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightResponse.xml new file mode 100644 index 00000000..f0184e5d --- /dev/null +++ b/samples/airline/src/etc/test-resources/org/springframework/ws/samples/airline/ws/bookFlightResponse.xml @@ -0,0 +1,27 @@ + + + 42 + 2006-01-01 + + + John + Doe + + + + EF1234 + 2006-01-01T00:00:00Z + + ABC + Airport + City + + 2006-02-02T00:00:00Z + + DEF + Airport + City + + economy + + \ No newline at end of file diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/FlightDao.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/FlightDao.java new file mode 100644 index 00000000..cafd3479 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/FlightDao.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.samples.airline.dao; + +import java.util.List; + +import org.joda.time.DateTime; + +import org.springframework.dao.DataAccessException; +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.domain.ServiceClass; + +public interface FlightDao { + + List findFlights(String fromAirportCode, + String toAirportCode, + DateTime startOfPeriod, + DateTime endOfPeriod, + ServiceClass serviceClass) throws DataAccessException; + + Flight getFlight(String flightNumber, DateTime departureTime); + + void update(Flight flight); +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/FrequentFlyerDao.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/FrequentFlyerDao.java new file mode 100644 index 00000000..2a5c8541 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/FrequentFlyerDao.java @@ -0,0 +1,28 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.dao; + +import org.springframework.dao.DataAccessException; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +public interface FrequentFlyerDao { + + FrequentFlyer get(String username) throws DataAccessException; + + void update(FrequentFlyer frequentFlyer) throws DataAccessException; + +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/TicketDao.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/TicketDao.java new file mode 100644 index 00000000..e3be0acd --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/TicketDao.java @@ -0,0 +1,26 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.dao; + +import org.springframework.dao.DataAccessException; +import org.springframework.ws.samples.airline.domain.Ticket; + +public interface TicketDao { + + void save(Ticket ticket) throws DataAccessException; + +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFlightDao.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFlightDao.java new file mode 100644 index 00000000..befc7563 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFlightDao.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.samples.airline.dao.hibernate; + +import java.util.List; + +import org.joda.time.DateTime; + +import org.springframework.dao.DataAccessException; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.ws.samples.airline.dao.FlightDao; +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.domain.ServiceClass; + +public class HibernateFlightDao extends HibernateDaoSupport implements FlightDao { + + public Flight getFlight(String flightNumber, DateTime departureTime) { + List flights = getHibernateTemplate().findByNamedParam( + "from Flight f where f.number = :number and " + "f.departureTime = :departureTime", + new String[]{"number", "departureTime"}, new Object[]{flightNumber, departureTime}); + return !flights.isEmpty() ? (Flight) flights.get(0) : null; + } + + public void update(Flight flight) { + getHibernateTemplate().update(flight); + } + + public List findFlights(String fromAirportCode, + String toAirportCode, + DateTime startOfPeriod, + DateTime endOfPeriod, + ServiceClass serviceClass) throws DataAccessException { + return getHibernateTemplate().findByNamedParam("from Flight f where f.from.code = :from " + + "and f.to.code = :to and " + "f.departureTime >= :start and f.departureTime <= :end and " + + "f.serviceClass = :class", new String[]{"from", "to", "start", "end", "class"}, + new Object[]{fromAirportCode, toAirportCode, startOfPeriod, endOfPeriod, serviceClass}); + } + +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFrequentFlyerDao.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFrequentFlyerDao.java new file mode 100644 index 00000000..f37dba58 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFrequentFlyerDao.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.dao.hibernate; + +import java.util.List; + +import org.springframework.dao.DataAccessException; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.util.CollectionUtils; +import org.springframework.ws.samples.airline.dao.FrequentFlyerDao; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +public class HibernateFrequentFlyerDao extends HibernateDaoSupport implements FrequentFlyerDao { + + public FrequentFlyer get(String username) { + List flyers = getHibernateTemplate().find("from FrequentFlyer f where f.username = ?", username); + return !CollectionUtils.isEmpty(flyers) ? (FrequentFlyer) flyers.get(0) : null; + } + + public void update(FrequentFlyer frequentFlyer) throws DataAccessException { + getHibernateTemplate().update(frequentFlyer); + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateTicketDao.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateTicketDao.java new file mode 100644 index 00000000..25593688 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateTicketDao.java @@ -0,0 +1,29 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.dao.hibernate; + +import org.springframework.dao.DataAccessException; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.ws.samples.airline.dao.TicketDao; +import org.springframework.ws.samples.airline.domain.Ticket; + +public class HibernateTicketDao extends HibernateDaoSupport implements TicketDao { + + public void save(Ticket ticket) throws DataAccessException { + getHibernateTemplate().save(ticket); + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Airport.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Airport.java new file mode 100644 index 00000000..36c43240 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Airport.java @@ -0,0 +1,76 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.domain; + +import java.io.Serializable; + +public class Airport implements Serializable { + + private String code; + + private String name; + + private String city; + + public Airport() { + } + + public Airport(String code, String name, String city) { + this.code = code; + this.name = name; + this.city = city; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Airport)) { + return false; + } + final Airport that = (Airport) other; + return this.code.equals(that.code); + } + + public int hashCode() { + return code.hashCode(); + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Entity.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Entity.java new file mode 100644 index 00000000..54b4812b --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Entity.java @@ -0,0 +1,32 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.ws.samples.airline.domain; + +import java.io.Serializable; + +public class Entity implements Serializable { + + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Flight.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Flight.java new file mode 100644 index 00000000..316bf2c9 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Flight.java @@ -0,0 +1,140 @@ +/* + * Copyright 2005, 2006 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 + * + * 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 org.springframework.ws.samples.airline.domain; + +import org.joda.time.DateTime; + +public class Flight extends Entity { + + private String number; + + private DateTime departureTime; + + private DateTime arrivalTime; + + private Airport to; + + private Airport from; + + private ServiceClass serviceClass; + + private int seatsAvailable; + + private int miles; + + public DateTime getArrivalTime() { + return arrivalTime; + } + + public void setArrivalTime(DateTime arrivalTime) { + this.arrivalTime = arrivalTime; + } + + public DateTime getDepartureTime() { + return departureTime; + } + + public void setDepartureTime(DateTime departureTime) { + this.departureTime = departureTime; + } + + public Airport getFrom() { + return from; + } + + public void setFrom(Airport from) { + this.from = from; + } + + public int getMiles() { + return miles; + } + + public void setMiles(int miles) { + this.miles = miles; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public int getSeatsAvailable() { + return seatsAvailable; + } + + public void setSeatsAvailable(int seatsAvailable) { + this.seatsAvailable = seatsAvailable; + } + + public ServiceClass getServiceClass() { + return serviceClass; + } + + public void setServiceClass(ServiceClass serviceClass) { + this.serviceClass = serviceClass; + } + + public Airport getTo() { + return to; + } + + public void setTo(Airport to) { + this.to = to; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Flight flight = (Flight) o; + + if (!departureTime.equals(flight.departureTime)) { + return false; + } + if (!number.equals(flight.number)) { + return false; + } + + return true; + } + + public int hashCode() { + int result; + result = number.hashCode(); + result = 29 * result + departureTime.hashCode(); + return result; + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append(getNumber()); + buffer.append(' '); + buffer.append(getDepartureTime()); + return buffer.toString(); + } + + public void substractSeats(int count) { + this.seatsAvailable -= count; + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/FrequentFlyer.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/FrequentFlyer.java new file mode 100644 index 00000000..4ac222b8 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/FrequentFlyer.java @@ -0,0 +1,82 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.domain; + +public class FrequentFlyer extends Passenger { + + private String username; + + private String password; + + private int miles; + + public FrequentFlyer() { + } + + public FrequentFlyer(String username) { + this.username = username; + } + + public FrequentFlyer(String username, String password, String firstName, String lastName) { + super(firstName, lastName); + this.username = username; + this.password = password; + } + + public int getMiles() { + return miles; + } + + public void setMiles(int miles) { + this.miles = miles; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof FrequentFlyer)) { + return false; + } + final FrequentFlyer that = (FrequentFlyer) other; + return this.username.equals(that.username); + } + + public int hashCode() { + return username.hashCode(); + } + + public void addMiles(int miles) { + this.miles += miles; + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Passenger.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Passenger.java new file mode 100644 index 00000000..632d9fa8 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Passenger.java @@ -0,0 +1,78 @@ +/* + * Copyright 2005, 2006 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 + * + * 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 org.springframework.ws.samples.airline.domain; + +import org.springframework.util.Assert; + +public class Passenger extends Entity { + + private String firstName; + + private String lastName; + + public Passenger() { + } + + public Passenger(String firstName, String lastName) { + Assert.hasLength(firstName); + Assert.hasLength(lastName); + this.firstName = firstName; + this.lastName = lastName; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Passenger passenger = (Passenger) o; + + if (!getFirstName().equals(passenger.getFirstName())) { + return false; + } + if (!getLastName().equals(passenger.getLastName())) { + return false; + } + + return true; + } + + public int hashCode() { + int result; + result = getFirstName().hashCode(); + result = 29 * result + getLastName().hashCode(); + return result; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/ServiceClass.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/ServiceClass.java new file mode 100644 index 00000000..2e1992ab --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/ServiceClass.java @@ -0,0 +1,56 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.domain; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +public class ServiceClass implements Serializable { + + private String name; + + public static final ServiceClass ECONOMY = new ServiceClass("economy"); + + public static final ServiceClass BUSINESS = new ServiceClass("business"); + + public static final ServiceClass FIRST = new ServiceClass("first"); + + private static final Map INSTANCES = new HashMap(); + + static { + INSTANCES.put(ECONOMY.toString(), ECONOMY); + INSTANCES.put(BUSINESS.toString(), BUSINESS); + INSTANCES.put(FIRST.toString(), FIRST); + } + + private ServiceClass(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + Object readResolve() { + return getInstance(name); + } + + public static ServiceClass getInstance(String name) { + return (ServiceClass) INSTANCES.get(name); + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Ticket.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Ticket.java new file mode 100644 index 00000000..d86058d7 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/Ticket.java @@ -0,0 +1,59 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.domain; + +import java.util.HashSet; +import java.util.Set; + +import org.joda.time.YearMonthDay; + +public class Ticket extends Entity { + + private YearMonthDay issueDate; + + private Set passengers = new HashSet(); + + private Flight flight; + + public Flight getFlight() { + return flight; + } + + public void setFlight(Flight flight) { + this.flight = flight; + } + + public YearMonthDay getIssueDate() { + return issueDate; + } + + public void setIssueDate(YearMonthDay issueDate) { + this.issueDate = issueDate; + } + + public Set getPassengers() { + return passengers; + } + + public void setPassengers(Set passengers) { + this.passengers = passengers; + } + + public void addPassenger(Passenger passenger) { + passengers.add(passenger); + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/hibernate/ServiceClassUserType.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/hibernate/ServiceClassUserType.java new file mode 100644 index 00000000..5c193666 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/domain/hibernate/ServiceClassUserType.java @@ -0,0 +1,84 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.domain.hibernate; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.HibernateException; +import org.hibernate.usertype.UserType; +import org.springframework.ws.samples.airline.domain.ServiceClass; + +public class ServiceClassUserType implements UserType { + + private static final int[] SQL_TYPES = {Types.VARCHAR}; + + public boolean isMutable() { + return false; + } + + public int[] sqlTypes() { + return SQL_TYPES; + } + + public Class returnedClass() { + return ServiceClass.class; + } + + public boolean equals(Object x, Object y) { + return x == y; + } + + public int hashCode(Object object) throws HibernateException { + return object.hashCode(); + } + + public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) + throws HibernateException, SQLException { + String name = resultSet.getString(names[0]); + return resultSet.wasNull() ? null : ServiceClass.getInstance(name); + } + + public void nullSafeSet(PreparedStatement statement, Object value, int index) + throws HibernateException, SQLException { + if (value == null) { + statement.setNull(index, Types.VARCHAR); + } + else { + statement.setString(index, value.toString()); + } + } + + public Object deepCopy(Object value) { + return value; + } + + public Serializable disassemble(Object object) throws HibernateException { + return (Serializable) object; + } + + public Object assemble(Serializable serializable, Object object) throws HibernateException { + return serializable; + } + + public Object replace(Object original, Object target, Object owner) throws HibernateException { + return original; + } +} \ No newline at end of file diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/AcegiFrequentFlyerSecurityService.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/AcegiFrequentFlyerSecurityService.java new file mode 100644 index 00000000..d2abe19c --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/AcegiFrequentFlyerSecurityService.java @@ -0,0 +1,59 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.security; + +import org.acegisecurity.Authentication; +import org.acegisecurity.context.SecurityContext; +import org.acegisecurity.context.SecurityContextHolder; + +import org.springframework.ws.samples.airline.dao.FrequentFlyerDao; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +/** + * Implementation of the FrequentFlyerSecurityService that uses Acegi. + * + * @author Arjen Poutsma + */ +public class AcegiFrequentFlyerSecurityService implements FrequentFlyerSecurityService { + + private FrequentFlyerDao frequentFlyerDao; + + public void setFrequentFlyerDao(FrequentFlyerDao frequentFlyerDao) { + this.frequentFlyerDao = frequentFlyerDao; + } + + public FrequentFlyer getCurrentlyAuthenticatedFrequentFlyer() { + SecurityContext context = SecurityContextHolder.getContext(); + Authentication authentication = context.getAuthentication(); + if (authentication != null) { + if (authentication.getPrincipal() instanceof FrequentFlyerDetails) { + FrequentFlyerDetails details = (FrequentFlyerDetails) authentication.getPrincipal(); + return details.getFrequentFlyer(); + } + else { + return (FrequentFlyer) authentication.getPrincipal(); + } + } + else { + return null; + } + } + + public FrequentFlyer getFrequentFlyer(String username) { + return frequentFlyerDao.get(username); + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetails.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetails.java new file mode 100644 index 00000000..21bac109 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetails.java @@ -0,0 +1,73 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.security; + +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.GrantedAuthorityImpl; +import org.acegisecurity.userdetails.UserDetails; + +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +/** + * A wrapper around an FrequentFlyer which provides extra functionality needed to implement the + * UserDetails interface. + * + * @author Arjen Poutsma + */ +public class FrequentFlyerDetails implements UserDetails { + + private FrequentFlyer frequentFlyer; + + public static final GrantedAuthority[] GRANTED_AUTHORITIES = + new GrantedAuthority[]{new GrantedAuthorityImpl("ROLE_FREQUENT_FLYER")}; + + public FrequentFlyerDetails(FrequentFlyer frequentFlyer) { + this.frequentFlyer = frequentFlyer; + } + + public GrantedAuthority[] getAuthorities() { + return GRANTED_AUTHORITIES; + } + + public String getPassword() { + return frequentFlyer.getPassword(); + } + + public String getUsername() { + return frequentFlyer.getUsername(); + } + + public boolean isAccountNonExpired() { + return true; + } + + public boolean isAccountNonLocked() { + return true; + } + + public boolean isCredentialsNonExpired() { + return true; + } + + public boolean isEnabled() { + return true; + } + + public FrequentFlyer getFrequentFlyer() { + return frequentFlyer; + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetailsService.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetailsService.java new file mode 100644 index 00000000..2b2384c1 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerDetailsService.java @@ -0,0 +1,49 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.security; + +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UserDetailsService; +import org.acegisecurity.userdetails.UsernameNotFoundException; + +import org.springframework.dao.DataAccessException; +import org.springframework.ws.samples.airline.dao.FrequentFlyerDao; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +/** + * Implementation of the Acegi UserDetailsService for FrequentFlyerDetails. + * + * @author Arjen Poutsma + */ +public class FrequentFlyerDetailsService implements UserDetailsService { + + private FrequentFlyerDao frequentFlyerDao; + + public void setFrequentFlyerDao(FrequentFlyerDao frequentFlyerDao) { + this.frequentFlyerDao = frequentFlyerDao; + } + + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + FrequentFlyer frequentFlyer = frequentFlyerDao.get(username); + if (frequentFlyer != null) { + return new FrequentFlyerDetails(frequentFlyer); + } + else { + throw new UsernameNotFoundException("Frequent flyer '" + username + "' not found"); + } + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerSecurityService.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerSecurityService.java new file mode 100644 index 00000000..57431e0a --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/FrequentFlyerSecurityService.java @@ -0,0 +1,43 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.security; + +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +/** + * Defines the business logic for handling frequent flyers. + * + * @author Arjen Poutsma + */ +public interface FrequentFlyerSecurityService { + + /** + * Returns the FrequentFlyer with the given username. + * + * @param username the username + * @return the frequent flyer with the given username, or null if not found + */ + FrequentFlyer getFrequentFlyer(String username); + + /** + * Returns the FrequentFlyer that is currently logged in. + * + * @return the frequent flyer that is currently logged in, or null if not found + */ + FrequentFlyer getCurrentlyAuthenticatedFrequentFlyer(); + +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/GetFrequentFlyerMileageEndpoint.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/GetFrequentFlyerMileageEndpoint.java new file mode 100644 index 00000000..f54a3e83 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/security/GetFrequentFlyerMileageEndpoint.java @@ -0,0 +1,53 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.security; + +import org.jdom.Element; +import org.jdom.Namespace; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.ws.endpoint.AbstractJDomPayloadEndpoint; +import org.springframework.ws.samples.airline.service.AirlineService; + +/** + * Endpoint that returns the amount of frequent flyer miles for the currently logged in user. Secured via a WS-Security + * UsernameToken + * + * @author Arjen Poutsma + */ +public class GetFrequentFlyerMileageEndpoint extends AbstractJDomPayloadEndpoint implements InitializingBean { + + private AirlineService airlineService; + + private Namespace namespace; + + public void setAirlineService(AirlineService airlineService) { + this.airlineService = airlineService; + } + + protected Element invokeInternal(Element ignored) throws Exception { + int result = airlineService.getFrequentFlyerMileage(); + Element response = new Element("GetFrequentFlyerMileageResponse", namespace); + response.setText(Integer.toString(result)); + return response; + } + + public void afterPropertiesSet() throws Exception { + namespace = Namespace.getNamespace("tns", "http://www.springframework.org/spring-ws/samples/airline/schemas"); + + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/AirlineService.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/AirlineService.java new file mode 100644 index 00000000..70839529 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/AirlineService.java @@ -0,0 +1,71 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.service; + +import java.util.List; + +import org.joda.time.DateTime; +import org.joda.time.YearMonthDay; + +import org.springframework.ws.samples.airline.domain.ServiceClass; +import org.springframework.ws.samples.airline.domain.Ticket; + +/** + * Defines the business logic of the Airline application. + * + * @author Arjen Poutsma + */ +public interface AirlineService { + + /** + * Returns a list of Flight objects that fall within the specified criteria. + * + * @param fromAirportCode the three-letter airport code to get flights from + * @param toAirportCode the three-letter airport code to get flights to + * @param departureDate the date of the flights + * @param serviceClass the desired service class level. May be null + * @return a list of flights + * @see org.springframework.ws.samples.airline.domain.Flight + */ + List getFlights(String fromAirportCode, + String toAirportCode, + YearMonthDay departureDate, + ServiceClass serviceClass); + + /** + * Books a single flight for a number of passengers. Passengers can be either specified by name or by frequent flyer + * username. If a FrequentFlyer is specified, the first and last name are looked up in the database. + * + * @param flightNumber the number of the flight to book + * @param departureTime the departure time of the flight to book + * @param passengers the list of passengers for the flight to book. Can be either Passengers with a + * first and last name, or FrequentFlyers with a username. + * @return the created ticket + * @throws NoSuchFlightException if a flight with the specified flight number and departure time does not exist + * @throws NoSeatAvailableException if not enough seats are available for the flight + * @see org.springframework.ws.samples.airline.domain.Passenger + * @see org.springframework.ws.samples.airline.domain.FrequentFlyer + */ + Ticket bookFlight(String flightNumber, DateTime departureTime, List passengers) + throws NoSuchFlightException, NoSeatAvailableException; + + /** + * Returns the amount of frequent flyer award miles for the currently logged in frequent flyer. + * + * @return the amount of frequent flyer miles + */ + int getFrequentFlyerMileage(); +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/NoSeatAvailableException.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/NoSeatAvailableException.java new file mode 100644 index 00000000..8ff3f844 --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/NoSeatAvailableException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.service; + +import org.springframework.ws.samples.airline.domain.Flight; + +/** + * Exception thrown when not enough seats are available for a flight. + * + * @author Arjen Poutsma + */ +public class NoSeatAvailableException extends Exception { + + private Flight flight; + + public NoSeatAvailableException(Flight flight) { + super("Flight [" + flight + "] has not more seats available"); + this.flight = flight; + } + + public Flight getFlight() { + return flight; + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/NoSuchFlightException.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/NoSuchFlightException.java new file mode 100644 index 00000000..e38c552e --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/NoSuchFlightException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.service; + +import org.joda.time.DateTime; + +/** + * Exception thrown when a specified flight cannot be found. + * + * @author Arjen Poutsma + */ +public class NoSuchFlightException extends Exception { + + private String flightNumber; + + private DateTime departureTime; + + public NoSuchFlightException(String flightNumber, DateTime departureTime) { + super("No flight with number [" + flightNumber + "] and departure time [" + departureTime + "]"); + this.flightNumber = flightNumber; + this.departureTime = departureTime; + } + + public String getFlightNumber() { + return flightNumber; + } + + public DateTime getDepartureTime() { + return departureTime; + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java new file mode 100644 index 00000000..0a0bf6ce --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImpl.java @@ -0,0 +1,123 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.service.impl; + +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joda.time.DateTime; +import org.joda.time.YearMonthDay; + +import org.springframework.util.Assert; +import org.springframework.ws.samples.airline.dao.FlightDao; +import org.springframework.ws.samples.airline.dao.TicketDao; +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; +import org.springframework.ws.samples.airline.domain.Passenger; +import org.springframework.ws.samples.airline.domain.ServiceClass; +import org.springframework.ws.samples.airline.domain.Ticket; +import org.springframework.ws.samples.airline.security.FrequentFlyerSecurityService; +import org.springframework.ws.samples.airline.service.AirlineService; +import org.springframework.ws.samples.airline.service.NoSeatAvailableException; +import org.springframework.ws.samples.airline.service.NoSuchFlightException; + +/** + * Default implementation of the AirlineService interface. + * + * @author Arjen Poutsma + */ +public class AirlineServiceImpl implements AirlineService { + + private final static Log logger = LogFactory.getLog(AirlineServiceImpl.class); + + private FlightDao flightDao; + + private TicketDao ticketDao; + + private FrequentFlyerSecurityService frequentFlyerSecurityService; + + public void setFlightDao(FlightDao flightDao) { + this.flightDao = flightDao; + } + + public void setFrequentFlyerSecurityService(FrequentFlyerSecurityService frequentFlyerSecurityService) { + this.frequentFlyerSecurityService = frequentFlyerSecurityService; + } + + public void setTicketDao(TicketDao ticketDao) { + this.ticketDao = ticketDao; + } + + public Ticket bookFlight(String flightNumber, DateTime departureTime, List passengers) + throws NoSuchFlightException, NoSeatAvailableException { + Assert.notEmpty(passengers, "No passengers given"); + if (logger.isDebugEnabled()) { + logger.debug("Booking flight '" + flightNumber + "' on '" + departureTime + "' for " + passengers.size() + + " passengers"); + } + Flight flight = flightDao.getFlight(flightNumber, departureTime); + if (flight == null) { + throw new NoSuchFlightException(flightNumber, departureTime); + } + else if (flight.getSeatsAvailable() < passengers.size()) { + throw new NoSeatAvailableException(flight); + } + Ticket ticket = new Ticket(); + ticket.setIssueDate(new YearMonthDay()); + ticket.setFlight(flight); + for (Iterator iterator = passengers.iterator(); iterator.hasNext();) { + Passenger passenger = (Passenger) iterator.next(); + // frequent flyer service is not required + if (passenger instanceof FrequentFlyer && frequentFlyerSecurityService != null) { + String username = ((FrequentFlyer) passenger).getUsername(); + Assert.hasLength(username, "No username specified"); + FrequentFlyer frequentFlyer = frequentFlyerSecurityService.getFrequentFlyer(username); + frequentFlyer.addMiles(flight.getMiles()); + ticket.addPassenger(frequentFlyer); + } + else { + ticket.addPassenger(passenger); + } + } + flight.substractSeats(passengers.size()); + flightDao.update(flight); + ticketDao.save(ticket); + return ticket; + } + + public int getFrequentFlyerMileage() { + FrequentFlyer frequentFlyer = frequentFlyerSecurityService.getCurrentlyAuthenticatedFrequentFlyer(); + return frequentFlyer.getMiles(); + } + + public List getFlights(String fromAirportCode, + String toAirportCode, + YearMonthDay departureDate, + ServiceClass serviceClass) { + if (serviceClass == null) { + serviceClass = ServiceClass.ECONOMY; + } + if (logger.isDebugEnabled()) { + logger.debug( + "Getting flights from '" + fromAirportCode + "' to '" + toAirportCode + "' on " + departureDate); + } + DateTime startOfPeriod = departureDate.toDateTimeAtMidnight(); + DateTime endOfPeriod = startOfPeriod.plusDays(1); + return flightDao.findFlights(fromAirportCode, toAirportCode, startOfPeriod, endOfPeriod, serviceClass); + } +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/ws/BookFlightEndpoint.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/ws/BookFlightEndpoint.java new file mode 100644 index 00000000..e58b54dd --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/ws/BookFlightEndpoint.java @@ -0,0 +1,166 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.ws; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.xpath.XPath; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.YearMonthDay; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.ws.endpoint.AbstractJDomPayloadEndpoint; +import org.springframework.ws.samples.airline.domain.Airport; +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; +import org.springframework.ws.samples.airline.domain.Passenger; +import org.springframework.ws.samples.airline.domain.ServiceClass; +import org.springframework.ws.samples.airline.domain.Ticket; +import org.springframework.ws.samples.airline.service.AirlineService; + +public class BookFlightEndpoint extends AbstractJDomPayloadEndpoint implements InitializingBean { + + private AirlineService airlineService; + + private XPath flightNumberXPath; + + private XPath departureTimeXPath; + + private Namespace namespace; + + private DateTimeFormatter dateFormatter; + + private DateTimeFormatter dateTimeFormatter; + + private DateTimeFormatter parser; + + private XPath passengersXPath; + + public void setAirlineService(AirlineService airlineService) { + this.airlineService = airlineService; + } + + protected Element invokeInternal(Element requestElement) throws Exception { + String flightNumber = flightNumberXPath.valueOf(requestElement); + String departureTimeString = departureTimeXPath.valueOf(requestElement); + DateTime departureTime = parser.parseDateTime(departureTimeString); + if (logger.isDebugEnabled()) { + logger.debug("BookFlight request for flight number [" + flightNumber + "] at [" + departureTime + "]"); + } + List passengerElements = passengersXPath.selectNodes(requestElement); + List passengers = new ArrayList(); + for (Iterator iterator = passengerElements.iterator(); iterator.hasNext();) { + Element passengerElement = (Element) iterator.next(); + if ("passenger".equals(passengerElement.getName()) && namespace.equals(passengerElement.getNamespace())) { + Passenger passenger = new Passenger(passengerElement.getChildTextNormalize("first", namespace), + passengerElement.getChildTextNormalize("last", namespace)); + passengers.add(passenger); + } + else + if ("username".equals(passengerElement.getName()) && namespace.equals(passengerElement.getNamespace())) { + FrequentFlyer frequentFlyer = new FrequentFlyer(passengerElement.getTextNormalize()); + passengers.add(frequentFlyer); + } + } + Ticket ticket = airlineService.bookFlight(flightNumber, departureTime, passengers); + return createResponse(ticket); + } + + private Element createResponse(Ticket ticket) { + Element responseElement = new Element("BookFlightResponse", namespace); + responseElement.addContent(new Element("id", namespace).setText(ticket.getId().toString())); + responseElement.addContent(createIssueDateElement(ticket.getIssueDate())); + responseElement.addContent(createPassengersElement(ticket.getPassengers())); + responseElement.addContent(createFlightElement(ticket.getFlight())); + return responseElement; + } + + protected Element createIssueDateElement(YearMonthDay issueDate) { + Element issueDateElement = new Element("issueDate", namespace); + issueDateElement.setText(dateFormatter.print(issueDate)); + return issueDateElement; + } + + protected Element createPassengersElement(Set passengers) { + Element passengersElement = new Element("passengers", namespace); + for (Iterator iterator = passengers.iterator(); iterator.hasNext();) { + Passenger passenger = (Passenger) iterator.next(); + Element passengerElement = new Element("passenger", namespace); + passengersElement.addContent(passengerElement); + passengerElement.addContent(new Element("first", namespace).setText(passenger.getFirstName())); + passengerElement.addContent(new Element("last", namespace).setText(passenger.getLastName())); + } + return passengersElement; + } + + protected Element createFlightElement(Flight flight) { + Element flightElement = new Element("flight", namespace); + flightElement.addContent(new Element("number", namespace).setText(flight.getNumber())); + flightElement.addContent(new Element("departureTime", + namespace).setText(dateTimeFormatter.print(flight.getDepartureTime()))); + flightElement.addContent(createAirportElement("from", flight.getFrom())); + flightElement + .addContent(new Element("arrivalTime", + namespace).setText(dateTimeFormatter.print(flight.getArrivalTime()))); + flightElement.addContent(createAirportElement("to", flight.getTo())); + flightElement.addContent(createServiceClassElement(flight.getServiceClass())); + return flightElement; + } + + protected Element createAirportElement(String localName, Airport airport) { + Element airportElement = new Element(localName, namespace); + airportElement.addContent(new Element("code", namespace).setText(airport.getCode())); + airportElement.addContent(new Element("name", namespace).setText(airport.getName())); + airportElement.addContent(new Element("city", namespace).setText(airport.getCity())); + return airportElement; + } + + protected Element createServiceClassElement(ServiceClass serviceClass) { + Element serviceClassElement = new Element("serviceClass", namespace); + if (ServiceClass.BUSINESS.equals(serviceClass)) { + serviceClassElement.setText("business"); + } + else if (ServiceClass.ECONOMY.equals(serviceClass)) { + serviceClassElement.setText("economy"); + } + else if (ServiceClass.FIRST.equals(serviceClass)) { + serviceClassElement.setText("first"); + } + return serviceClassElement; + } + + public void afterPropertiesSet() throws Exception { + namespace = Namespace.getNamespace("tns", "http://www.springframework.org/spring-ws/samples/airline/schemas"); + flightNumberXPath = XPath.newInstance("/tns:BookFlightRequest/tns:flightNumber/text()"); + flightNumberXPath.addNamespace(namespace); + departureTimeXPath = XPath.newInstance("/tns:BookFlightRequest/tns:departureTime/text()"); + departureTimeXPath.addNamespace(namespace); + passengersXPath = XPath.newInstance("/tns:BookFlightRequest/tns:passengers/*"); + passengersXPath.addNamespace(namespace); + parser = ISODateTimeFormat.dateTimeParser().withZone(DateTimeZone.UTC); + dateTimeFormatter = ISODateTimeFormat.dateTimeNoMillis(); + dateFormatter = ISODateTimeFormat.date(); + } + + +} diff --git a/samples/airline/src/main/java/org/springframework/ws/samples/airline/ws/GetFlightsEndpoint.java b/samples/airline/src/main/java/org/springframework/ws/samples/airline/ws/GetFlightsEndpoint.java new file mode 100644 index 00000000..cb1f94ce --- /dev/null +++ b/samples/airline/src/main/java/org/springframework/ws/samples/airline/ws/GetFlightsEndpoint.java @@ -0,0 +1,131 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.ws; + +import java.util.Iterator; +import java.util.List; + +import org.joda.time.YearMonthDay; +import org.joda.time.chrono.ISOChronology; + +import org.springframework.ws.endpoint.AbstractMarshallingPayloadEndpoint; +import org.springframework.ws.samples.airline.schema.Airport; +import org.springframework.ws.samples.airline.schema.Flight; +import org.springframework.ws.samples.airline.schema.GetFlightsRequest; +import org.springframework.ws.samples.airline.schema.GetFlightsResponse; +import org.springframework.ws.samples.airline.schema.ServiceClass; +import org.springframework.ws.samples.airline.schema.impl.AirportImpl; +import org.springframework.ws.samples.airline.schema.impl.FlightImpl; +import org.springframework.ws.samples.airline.schema.impl.GetFlightsResponseImpl; +import org.springframework.ws.samples.airline.service.AirlineService; + +/** + * Endpoint that returns a list of flights with a given number, and that lie between a given start and end date. It uses + * JAXB-based marshalling for both request and response objects. Because we use separate POJOs for our schema and our + * domain objects, we need to convert the response domain objects to schema-based objects. + * + * @author Arjen Poutsma + */ +public class GetFlightsEndpoint extends AbstractMarshallingPayloadEndpoint { + + private AirlineService airlineService; + + public void setAirlineService(AirlineService airlineService) { + this.airlineService = airlineService; + } + + protected Object invokeInternal(Object requestObject) throws Exception { + GetFlightsRequest request = (GetFlightsRequest) requestObject; + YearMonthDay departureDate = new YearMonthDay(request.getDepartureDate(), ISOChronology.getInstance()); + if (logger.isDebugEnabled()) { + logger.debug("Request for flights from '" + request.getFrom() + "' to '" + request.getTo() + "' on " + + departureDate); + } + List flights = airlineService.getFlights(request.getFrom(), request.getTo(), departureDate, + convertToDomainType(request.getServiceClass())); + if (logger.isDebugEnabled()) { + logger.debug("Marshalling " + flights.size() + " flight results"); + } + GetFlightsResponse response = new GetFlightsResponseImpl(); + for (Iterator iter = flights.iterator(); iter.hasNext();) { + org.springframework.ws.samples.airline.domain.Flight domainFlight = + (org.springframework.ws.samples.airline.domain.Flight) iter.next(); + Flight schemaFlight = convertToSchemaType(domainFlight); + response.getFlight().add(schemaFlight); + } + return response; + } + + private Flight convertToSchemaType(org.springframework.ws.samples.airline.domain.Flight domainFlight) { + Flight schemaFlight = new FlightImpl(); + schemaFlight.setNumber(domainFlight.getNumber()); + schemaFlight.setDepartureTime(domainFlight.getDepartureTime().toGregorianCalendar()); + schemaFlight.setFrom(convertToSchemaType(domainFlight.getFrom())); + schemaFlight.setArrivalTime(domainFlight.getArrivalTime().toGregorianCalendar()); + schemaFlight.setTo(convertToSchemaType(domainFlight.getTo())); + schemaFlight.setServiceClass(convertToSchemaType(domainFlight.getServiceClass())); + return schemaFlight; + } + + private Airport convertToSchemaType(org.springframework.ws.samples.airline.domain.Airport domainAirport) { + if (domainAirport == null) { + return null; + } + Airport schemaAirport = new AirportImpl(); + schemaAirport.setCode(domainAirport.getCode()); + schemaAirport.setName(domainAirport.getName()); + schemaAirport.setCity(domainAirport.getCity()); + return schemaAirport; + } + + private ServiceClass convertToSchemaType(org.springframework.ws.samples.airline.domain.ServiceClass domainServiceClass) { + if (domainServiceClass == null) { + return null; + } + else if (domainServiceClass.equals(org.springframework.ws.samples.airline.domain.ServiceClass.BUSINESS)) { + return ServiceClass.BUSINESS; + } + else if (domainServiceClass.equals(org.springframework.ws.samples.airline.domain.ServiceClass.ECONOMY)) { + return ServiceClass.ECONOMY; + } + else if (domainServiceClass.equals(org.springframework.ws.samples.airline.domain.ServiceClass.FIRST)) { + return ServiceClass.FIRST; + } + else { + throw new IllegalArgumentException("Invalid domain service class: [" + domainServiceClass + "]"); + } + } + + private org.springframework.ws.samples.airline.domain.ServiceClass convertToDomainType(ServiceClass schemaServiceClass) { + if (schemaServiceClass == null) { + return null; + } + else if (schemaServiceClass.equals(ServiceClass.BUSINESS)) { + return org.springframework.ws.samples.airline.domain.ServiceClass.BUSINESS; + } + else if (schemaServiceClass.equals(ServiceClass.ECONOMY)) { + return org.springframework.ws.samples.airline.domain.ServiceClass.ECONOMY; + } + else if (schemaServiceClass.equals(ServiceClass.FIRST)) { + return org.springframework.ws.samples.airline.domain.ServiceClass.FIRST; + } + else { + throw new IllegalArgumentException("Invalid schema service class: [" + schemaServiceClass + "]"); + } + + } + +} diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml new file mode 100644 index 00000000..fd5e3993 --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + classpath:org/springframework/ws/samples/airline/domain/hibernate/airline.hbm.xml + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/hibernate.properties b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/hibernate.properties new file mode 100644 index 00000000..ee1d84fe --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/hibernate/hibernate.properties @@ -0,0 +1,8 @@ +# Properties file with hibernate-related settings. + +#hibernate.dialect=org.hibernate.dialect.HSQLDialect +hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect +#hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider +#hibernate.hbm2ddl.auto=create-drop +#hibernate.show_sql=true diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/jdbc.properties b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/jdbc.properties new file mode 100644 index 00000000..e71f5bb9 --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/dao/jdbc.properties @@ -0,0 +1,14 @@ +#jdbc.driverClassName=org.hsqldb.jdbcDriver +#jdbc.username=sa +#jdbc.password= +#jdbc.url=jdbc:hsqldb:mem:airline + +jdbc.driverClassName=com.mysql.jdbc.Driver +jdbc.username=airline +jdbc.password=airline +jdbc.url=jdbc:mysql://localhost/airline + +#jdbc.driverClassName=org.postgresql.Driver +#jdbc.username=airline +#jdbc.password=airline +#jdbc.url=jdbc:postgresql://localhost/airline \ No newline at end of file diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/domain/hibernate/airline.hbm.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/domain/hibernate/airline.hbm.xml new file mode 100644 index 00000000..e2fc3d35 --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/domain/hibernate/airline.hbm.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/security/applicationContext-security.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/security/applicationContext-security.xml new file mode 100644 index 00000000..8fb3a6aa --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/security/applicationContext-security.xml @@ -0,0 +1,111 @@ + + + + + + This application context contains the WS-Security and Acegi beans. + + + + + A transactional security service used to obtain Frequent Flyer information. + + + + + + + + + + PROPAGATION_REQUIRED + + + + + + + + + This SOAP Action endpoint mapping is used for endpoints that are secured via WS-Security. It uses a + securityInterceptor to validate incoming messages. + + + + + getFrequentFlyerMileageEndpoint + + + + + + + + + + + + + This interceptor validates incoming messages according to the policy defined in 'securityPolicy.xml'. + The policy defines that all incoming requests must have a UsernameToken with a password digest in it. + The actual authentication is performed by the Acegi callback handler. + + + + + + + + + + + + + This endpoint handles get frequent flier mileage requests. + + + + + + + + + A standard Acegi authentication manager. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.ws.samples.airline.service.AirlineService.getFrequentFlyerMileage=ROLE_FREQUENT_FLYER + + + + + \ No newline at end of file diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/security/securityPolicy.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/security/securityPolicy.xml new file mode 100644 index 00000000..b6d644b4 --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/security/securityPolicy.xml @@ -0,0 +1,3 @@ + + + diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/service/applicationContext.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/service/applicationContext.xml new file mode 100644 index 00000000..a287297b --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/service/applicationContext.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + PROPAGATION_REQUIRED,readOnly + PROPAGATION_REQUIRED + + + + + + \ No newline at end of file diff --git a/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws.xml b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws.xml new file mode 100644 index 00000000..88511404 --- /dev/null +++ b/samples/airline/src/main/resources/org/springframework/ws/samples/airline/ws/applicationContext-ws.xml @@ -0,0 +1,121 @@ + + + + + + This application context contains the Spring-WS beans. + + + + The MessageDispatcher is responsible for routing messages to endpoints. + + + + + + + + + + + + + + + + + + This endpoint mapping uses the qualified name of the payload (body contents) to determine the endpoint for + an incoming message. The name GetFlightsRequest with namespace + http://www.springframework.org/spring-ws/samples/airline/schemas is mapped to the getFlightsEndpoint. + Additionally, messages are logged using the logging interceptor. + + + + getFlightsEndpoint + + + + + + + + + + This endpoint mapping uses SOAP Actions to determine the endpoint for an incoming message. The key + http://www.springframework.org/spring-ws/samples/airline/BookFlight is mapped to the bookFlightEndpoint. + Additionally, message are logged and validated. + + + + bookFlightEndpoint + + + + + + + + + + + + + This interceptor validates both incoming and outgoing message contents according to the 'airline.xsd' XML + Schema file. + + + + + + + + This interceptor logs the message payload. + + + + + This endpoint handles book flight request. + + + + + + This endpoint handles get flights request. + + + + + + + + The validating JAXB Marshaller is used by the getFlightsEndpoint to unmarshal XML to objects and + vice-versa. + + + + + + + This exception resolver maps exceptions to SOAP Faults. The business logic exceptions + NoSeatAvailableException and NoSuchFlightException are mapped to custom SOAP Fault. Both + UnmarshallingException andValidationFailureException are mapped to a SOAP Fault with a "Sender" fault code. + All other exceptions are mapped to a "Receiver" error code, the default. + + RECEIVER,Server error + + + + + {http://www.springframework.org/spring-ws/samples/airline}airline:NoMoreSeats, No more seats + available + + {http://www.springframework.org/spring-ws/samples/airline}airline:NoSuchFlight, No such flight + exists + SENDER,Invalid request + SENDER,Invalid request + + + + + \ No newline at end of file diff --git a/samples/airline/src/main/webapp/WEB-INF/airline-servlet.xml b/samples/airline/src/main/webapp/WEB-INF/airline-servlet.xml new file mode 100644 index 00000000..c6a6419e --- /dev/null +++ b/samples/airline/src/main/webapp/WEB-INF/airline-servlet.xml @@ -0,0 +1,25 @@ + + + + + + The handlerAdapter makes sure that Spring's DispatcherServlet + supports MessageEndpoints instances as handlers. + It uses a SAAJ to construct SoapMessageContexts (and SoapMessages). + + + + + + + + + All incoming request are mapped to the messageDispatcher defined in applicationContext-ws.xml + + + + + \ No newline at end of file diff --git a/samples/airline/src/main/webapp/WEB-INF/log4j.properties b/samples/airline/src/main/webapp/WEB-INF/log4j.properties new file mode 100644 index 00000000..f8c051ec --- /dev/null +++ b/samples/airline/src/main/webapp/WEB-INF/log4j.properties @@ -0,0 +1,15 @@ +log4j.rootLogger=WARN, stdout, logfile +log4j.logger.org.springframework.oxm=DEBUG +log4j.logger.org.springframework.ws=DEBUG +log4j.logger.org.springframework.xml=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n + +log4j.appender.logfile=org.apache.log4j.RollingFileAppender +log4j.appender.logfile.File=${airline.root}/WEB-INF/airline.log +log4j.appender.logfile.MaxFileSize=512KB +log4j.appender.logfile.MaxBackupIndex=3 +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n \ No newline at end of file diff --git a/samples/airline/src/main/webapp/WEB-INF/web.xml b/samples/airline/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..b3bb8244 --- /dev/null +++ b/samples/airline/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,48 @@ + + + + Spring-WS Airline Sample + + webAppRootKey + airline.root + + + log4jConfigLocation + /WEB-INF/log4j.properties + + + contextConfigLocation + + classpath:org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml + classpath:org/springframework/ws/samples/airline/service/applicationContext.xml + + classpath:org/springframework/ws/samples/airline/security/applicationContext-security.xml + classpath:org/springframework/ws/samples/airline/ws/applicationContext-ws.xml + + + + org.springframework.web.util.Log4jConfigListener + + + org.springframework.web.context.ContextLoaderListener + + + airline + org.springframework.web.servlet.DispatcherServlet + + + airline + /Airline + + + wsdl + text/xml + + + xsd + text/xml + + \ No newline at end of file diff --git a/samples/airline/src/main/webapp/airline.wsdl b/samples/airline/src/main/webapp/airline.wsdl new file mode 100644 index 00000000..38a5897f --- /dev/null +++ b/samples/airline/src/main/webapp/airline.wsdl @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/airline/src/main/webapp/airline.xsd b/samples/airline/src/main/webapp/airline.xsd new file mode 100644 index 00000000..2a9dddc1 --- /dev/null +++ b/samples/airline/src/main/webapp/airline.xsd @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFlightDaoTest.java b/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFlightDaoTest.java new file mode 100644 index 00000000..2311db15 --- /dev/null +++ b/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFlightDaoTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.dao.hibernate; + +import java.util.List; + +import org.joda.time.DateTime; +import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; +import org.springframework.ws.samples.airline.domain.Airport; +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.domain.ServiceClass; + +public class HibernateFlightDaoTest extends AbstractTransactionalDataSourceSpringContextTests { + + private HibernateFlightDao flightDao; + + private Airport fromAirport; + + private Airport toAirport; + + private DateTime departureTime; + + private DateTime arrivalTime; + + protected String[] getConfigLocations() { + return new String[]{ + "classpath:org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml"}; + } + + public void setFlightDao(HibernateFlightDao flightDao) { + this.flightDao = flightDao; + } + + protected void onSetUpBeforeTransaction() throws Exception { + departureTime = new DateTime(2006, 1, 31, 10, 5, 0, 0); + arrivalTime = new DateTime(2006, 1, 31, 12, 25, 0, 0); + } + + protected void onSetUpInTransaction() throws Exception { + jdbcTemplate.update("INSERT INTO AIRPORT(CODE, NAME, CITY) VALUES('RTM', 'Rotterdam Airport', 'Rotterdam')"); + fromAirport = new Airport("RTM", "Rotterdam Airport", "Rotterdam"); + jdbcTemplate.update("INSERT INTO AIRPORT(CODE, NAME, CITY) VALUES('OSL', 'Gardermoen', 'Oslo')"); + toAirport = new Airport("OSL", "Gardermoen", "Oslo"); + } + + public void testGetFlightsInPeriod() throws Exception { + jdbcTemplate + .update("INSERT INTO FLIGHT(NUMBER, DEPARTURE_TIME, FROM_AIRPORT_CODE, ARRIVAL_TIME, TO_AIRPORT_CODE, SERVICE_CLASS, SEATS_AVAILABLE, MILES) " + + "VALUES ('KL020','2006-01-31 10:05:00', 'RTM', '2006-01-31 12:25:00', 'OSL', 'business', 90, 10)"); + List flights = flightDao.findFlights("RTM", "OSL", departureTime, arrivalTime, ServiceClass.BUSINESS); + assertNotNull("Invalid result", flights); + assertEquals("Invalid amount of flights", 1, flights.size()); + } + + public void testGetFlightsOutOfPeriod() throws Exception { + jdbcTemplate + .update("INSERT INTO FLIGHT(NUMBER, DEPARTURE_TIME, FROM_AIRPORT_CODE, ARRIVAL_TIME, TO_AIRPORT_CODE, SERVICE_CLASS, SEATS_AVAILABLE, MILES) " + + "VALUES ('KL020','2006-01-31 10:05:00', 'RTM', '2006-01-31 12:25:00', 'OSL', 'business', 90, 10)"); + DateTime dateTime = new DateTime(2006, 6, 1, 0, 0, 0, 0); + List flights = flightDao.findFlights("RTM", "OSL", dateTime, dateTime, ServiceClass.BUSINESS); + assertNotNull("Invalid result", flights); + assertEquals("Invalid amount of flights", 0, flights.size()); + } + + public void testGetFlightByNumberDepartureTime() throws Exception { + jdbcTemplate + .update("INSERT INTO FLIGHT(NUMBER, DEPARTURE_TIME, FROM_AIRPORT_CODE, ARRIVAL_TIME, TO_AIRPORT_CODE, SERVICE_CLASS, SEATS_AVAILABLE, MILES) " + + "VALUES ('KL020','2006-01-31 10:05:00', 'RTM', '2006-01-31 12:25:00', 'OSL', 'business', 90, 10)"); + Flight flight = flightDao.getFlight("KL020", departureTime); + assertNotNull("No flight returned", flight); + assertNotNull("Invalid flight id", flight.getId()); + assertEquals("Invalid flight number", "KL020", flight.getNumber()); + assertEquals("Invalid flight departure time", departureTime, flight.getDepartureTime()); + assertEquals("Invalid flight arrival time", arrivalTime, flight.getArrivalTime()); + assertEquals("Invalid flight from airport", fromAirport, flight.getFrom()); + assertEquals("Invalid flight to airport", toAirport, flight.getTo()); + assertEquals("Invalid flight service class", ServiceClass.BUSINESS, flight.getServiceClass()); + } + + public void testUpdate() throws Exception { + jdbcTemplate + .update("INSERT INTO FLIGHT(NUMBER, DEPARTURE_TIME, FROM_AIRPORT_CODE, ARRIVAL_TIME, TO_AIRPORT_CODE, SERVICE_CLASS, SEATS_AVAILABLE, MILES) " + + "VALUES ('KL020','2006-01-31 10:05:00', 'RTM', '2006-01-31 12:25:00', 'OSL', 'business', 90, 10)"); + Flight flight = flightDao.getFlight("KL020", departureTime); + flight.setSeatsAvailable(0); + flightDao.update(flight); + flightDao.getHibernateTemplate().flush(); + int count = jdbcTemplate + .queryForInt("SELECT SEATS_AVAILABLE FROM FLIGHT WHERE ID = ?", new Object[]{flight.getId()}); + assertEquals("Flight not updated", 0, count); + } +} \ No newline at end of file diff --git a/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFrequentFlyerDaoTest.java b/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFrequentFlyerDaoTest.java new file mode 100644 index 00000000..2fb4d3a3 --- /dev/null +++ b/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateFrequentFlyerDaoTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.dao.hibernate; + +import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +public class HibernateFrequentFlyerDaoTest extends AbstractTransactionalDataSourceSpringContextTests { + + private HibernateFrequentFlyerDao dao; + + protected String[] getConfigLocations() { + return new String[]{ + "classpath:org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml"}; + } + + public void setDao(HibernateFrequentFlyerDao dao) { + this.dao = dao; + } + + public void testGetByUsername() throws Exception { + jdbcTemplate + .update("INSERT INTO PASSENGER(ID, FIRST_NAME, LAST_NAME) " + "VALUES (42, 'Arjen', 'Poutsma')"); + jdbcTemplate + .update("INSERT INTO FREQUENT_FLYER(PASSENGER_ID, USERNAME, PASSWORD, MILES) " + + "VALUES (42, 'arjen', 'changeme', 0)"); + FrequentFlyer flyer = dao.get("arjen"); + assertNotNull("No frequent flyer returned", flyer); + assertEquals("Invalid username", "arjen", flyer.getUsername()); + assertEquals("Invalid password", "changeme", flyer.getPassword()); + assertEquals("Invalid first name", "Arjen", flyer.getFirstName()); + assertEquals("Invalid last name", "Poutsma", flyer.getLastName()); + } +} diff --git a/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateTicketDaoTest.java b/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateTicketDaoTest.java new file mode 100644 index 00000000..43ad81d4 --- /dev/null +++ b/samples/airline/src/test/java/org/springframework/ws/samples/airline/dao/hibernate/HibernateTicketDaoTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.dao.hibernate; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.YearMonthDay; +import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; +import org.springframework.ws.samples.airline.domain.Airport; +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.domain.Passenger; +import org.springframework.ws.samples.airline.domain.ServiceClass; +import org.springframework.ws.samples.airline.domain.Ticket; + +public class HibernateTicketDaoTest extends AbstractTransactionalDataSourceSpringContextTests { + + private HibernateTicketDao dao; + + private Flight flight; + + private DateTime departureTime; + + private DateTime arrivalTime; + + private Passenger passenger; + + protected String[] getConfigLocations() { + return new String[]{ + "classpath:org/springframework/ws/samples/airline/dao/hibernate/applicationContext-hibernate.xml"}; + } + + public void setDao(HibernateTicketDao dao) { + this.dao = dao; + } + + protected void onSetUpBeforeTransaction() throws Exception { + departureTime = new DateTime(2006, 1, 31, 10, 5, 0, 0, DateTimeZone.UTC); + arrivalTime = new DateTime(2006, 1, 31, 12, 25, 0, 0, DateTimeZone.UTC); + } + + protected void onSetUpInTransaction() throws Exception { + jdbcTemplate.update("INSERT INTO AIRPORT(CODE, NAME, CITY) VALUES('RTM', 'Rotterdam Airport', 'Rotterdam')"); + Airport fromAirport = new Airport("RTM", "Rotterdam Airport", "Rotterdam"); + jdbcTemplate.update("INSERT INTO AIRPORT(CODE, NAME, CITY) VALUES('OSL', 'Gardermoen', 'Oslo')"); + Airport toAirport = new Airport("OSL", "Gardermoen", "Oslo"); + jdbcTemplate + .update("INSERT INTO FLIGHT(ID, NUMBER, DEPARTURE_TIME, FROM_AIRPORT_CODE, ARRIVAL_TIME, TO_AIRPORT_CODE, SERVICE_CLASS, SEATS_AVAILABLE, MILES) " + + "VALUES (42, 'KL020','2006-01-31 10:05:00', 'RTM', '2006-01-31 12:25:00', 'OSL', 'business', 90, 10)"); + flight = new Flight(); + flight.setId(new Long(42)); + flight.setNumber("KL1653"); + flight.setDepartureTime(departureTime); + flight.setFrom(fromAirport); + flight.setArrivalTime(arrivalTime); + flight.setTo(toAirport); + flight.setServiceClass(ServiceClass.BUSINESS); + flight.setSeatsAvailable(90); + passenger = new Passenger(); + passenger.setFirstName("John"); + passenger.setLastName("Doe"); + } + + public void testInsert() throws Exception { + Ticket ticket = new Ticket(); + ticket.addPassenger(passenger); + ticket.setFlight(flight); + ticket.setIssueDate(new YearMonthDay()); + int startTicketCount = jdbcTemplate.queryForInt("SELECT COUNT(0) FROM TICKET"); + int startPassengerCount = jdbcTemplate.queryForInt("SELECT COUNT(0) FROM PASSENGER"); + dao.save(ticket); + int endTicketCount = jdbcTemplate.queryForInt("SELECT COUNT(0) FROM TICKET"); + int endPassengerCount = jdbcTemplate.queryForInt("SELECT COUNT(0) FROM PASSENGER"); + assertEquals("Flight not inserted", 1, endTicketCount - startTicketCount); + assertEquals("Passenger not inserted", 1, endPassengerCount - startPassengerCount); + } + +} diff --git a/samples/airline/src/test/java/org/springframework/ws/samples/airline/security/AcegiFrequentFlyerSecurityServiceTest.java b/samples/airline/src/test/java/org/springframework/ws/samples/airline/security/AcegiFrequentFlyerSecurityServiceTest.java new file mode 100644 index 00000000..080bf71c --- /dev/null +++ b/samples/airline/src/test/java/org/springframework/ws/samples/airline/security/AcegiFrequentFlyerSecurityServiceTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.security; + +import junit.framework.TestCase; +import org.acegisecurity.context.SecurityContext; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.context.SecurityContextImpl; +import org.acegisecurity.providers.TestingAuthenticationToken; +import org.easymock.MockControl; + +import org.springframework.ws.samples.airline.dao.FrequentFlyerDao; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; + +public class AcegiFrequentFlyerSecurityServiceTest extends TestCase { + + private AcegiFrequentFlyerSecurityService securityService; + + private MockControl control; + + private FrequentFlyerDao mock; + + protected void setUp() throws Exception { + securityService = new AcegiFrequentFlyerSecurityService(); + control = MockControl.createControl(FrequentFlyerDao.class); + mock = (FrequentFlyerDao) control.getMock(); + securityService.setFrequentFlyerDao(mock); + } + + public void testGetCurrentlyAuthenticatedFrequentFlyer() throws Exception { + FrequentFlyer frequentFlyer = new FrequentFlyer("john"); + FrequentFlyerDetails detail = new FrequentFlyerDetails(frequentFlyer); + TestingAuthenticationToken token = new TestingAuthenticationToken(detail, null, null); + SecurityContext context = new SecurityContextImpl(); + context.setAuthentication(token); + SecurityContextHolder.setContext(context); + control.replay(); + FrequentFlyer result = securityService.getCurrentlyAuthenticatedFrequentFlyer(); + assertEquals("Invalid result", frequentFlyer, result); + control.verify(); + } + + public void testGetFrequentFlyer() throws Exception { + FrequentFlyer frequentFlyer = new FrequentFlyer("john"); + control.expectAndReturn(mock.get("john"), frequentFlyer); + control.replay(); + FrequentFlyer result = securityService.getFrequentFlyer("john"); + assertEquals("Invalid result", frequentFlyer, result); + control.verify(); + } +} \ No newline at end of file diff --git a/samples/airline/src/test/java/org/springframework/ws/samples/airline/security/GetFrequentFlyerMileageEndpointTest.java b/samples/airline/src/test/java/org/springframework/ws/samples/airline/security/GetFrequentFlyerMileageEndpointTest.java new file mode 100644 index 00000000..ab7bea72 --- /dev/null +++ b/samples/airline/src/test/java/org/springframework/ws/samples/airline/security/GetFrequentFlyerMileageEndpointTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.security; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.jdom.Element; + +import org.springframework.ws.samples.airline.service.AirlineService; + +public class GetFrequentFlyerMileageEndpointTest extends TestCase { + + private GetFrequentFlyerMileageEndpoint endpoint; + + private MockControl control; + + private AirlineService mock; + + protected void setUp() throws Exception { + endpoint = new GetFrequentFlyerMileageEndpoint(); + control = MockControl.createControl(AirlineService.class); + mock = (AirlineService) control.getMock(); + endpoint.setAirlineService(mock); + endpoint.afterPropertiesSet(); + + } + + public void testInvokeInternal() throws Exception { + control.expectAndReturn(mock.getFrequentFlyerMileage(), 42); + control.replay(); + Element element = endpoint.invokeInternal(null); + assertNotNull("No element returned", element); + assertEquals("Invalid local name", "GetFrequentFlyerMileageResponse", element.getName()); + assertEquals("Invalid namespace", "http://www.springframework.org/spring-ws/samples/airline/schemas", + element.getNamespaceURI()); + assertEquals("Invalid result", "42", element.getTextNormalize()); + control.verify(); + } +} \ No newline at end of file diff --git a/samples/airline/src/test/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImplTest.java b/samples/airline/src/test/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImplTest.java new file mode 100644 index 00000000..3edc3d8b --- /dev/null +++ b/samples/airline/src/test/java/org/springframework/ws/samples/airline/service/impl/AirlineServiceImplTest.java @@ -0,0 +1,190 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.service.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.joda.time.DateTime; +import org.joda.time.YearMonthDay; + +import org.springframework.ws.samples.airline.dao.FlightDao; +import org.springframework.ws.samples.airline.dao.TicketDao; +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; +import org.springframework.ws.samples.airline.domain.Passenger; +import org.springframework.ws.samples.airline.domain.ServiceClass; +import org.springframework.ws.samples.airline.domain.Ticket; +import org.springframework.ws.samples.airline.security.FrequentFlyerSecurityService; +import org.springframework.ws.samples.airline.service.NoSeatAvailableException; +import org.springframework.ws.samples.airline.service.NoSuchFlightException; + +public class AirlineServiceImplTest extends TestCase { + + private AirlineServiceImpl airlineService; + + private MockControl flightDaoControl; + + private FlightDao flightDaoMock; + + private MockControl ticketDaoControl; + + private TicketDao ticketDaoMock; + + private MockControl securityServiceControl; + + private FrequentFlyerSecurityService securityServiceMock; + + protected void setUp() throws Exception { + airlineService = new AirlineServiceImpl(); + flightDaoControl = MockControl.createControl(FlightDao.class); + flightDaoMock = (FlightDao) flightDaoControl.getMock(); + airlineService.setFlightDao(flightDaoMock); + ticketDaoControl = MockControl.createControl(TicketDao.class); + ticketDaoMock = (TicketDao) ticketDaoControl.getMock(); + airlineService.setTicketDao(ticketDaoMock); + securityServiceControl = MockControl.createControl(FrequentFlyerSecurityService.class); + securityServiceMock = (FrequentFlyerSecurityService) securityServiceControl.getMock(); + airlineService.setFrequentFlyerSecurityService(securityServiceMock); + } + + protected void tearDown() throws Exception { + flightDaoControl.verify(); + ticketDaoControl.verify(); + securityServiceControl.verify(); + } + + public void testBookFlight() throws Exception { + String flightNumber = "AB1234"; + DateTime departureTime = new DateTime(); + Passenger passenger = new Passenger("John", "Doe"); + List passengers = new ArrayList(); + passengers.add(passenger); + Flight flight = new Flight(); + flight.setNumber(flightNumber); + flight.setSeatsAvailable(10); + flightDaoControl.expectAndReturn(flightDaoMock.getFlight(flightNumber, departureTime), flight); + flightDaoMock.update(flight); + ticketDaoMock.save(null); + ticketDaoControl.setMatcher(MockControl.ALWAYS_MATCHER); + flightDaoControl.replay(); + ticketDaoControl.replay(); + securityServiceControl.replay(); + Ticket ticket = airlineService.bookFlight(flightNumber, departureTime, passengers); + assertNotNull("Invalid ticket", ticket); + assertEquals("Invalid flight", flight, ticket.getFlight()); + assertEquals("Invalid seats available", 9, flight.getSeatsAvailable()); + assertEquals("Invalid passengers count", 1, ticket.getPassengers().size()); + } + + public void testBookFlightFrequentFlyer() throws Exception { + String flightNumber = "AB1234"; + DateTime departureTime = new DateTime(); + FrequentFlyer frequentFlyer = new FrequentFlyer("john", "changeme", "John", "Doe"); + List passengers = new ArrayList(); + passengers.add(frequentFlyer); + Flight flight = new Flight(); + flight.setNumber(flightNumber); + flight.setSeatsAvailable(1); + flight.setMiles(10); + securityServiceControl.expectAndReturn(securityServiceMock.getFrequentFlyer("john"), frequentFlyer); + flightDaoControl.expectAndReturn(flightDaoMock.getFlight(flightNumber, departureTime), flight); + flightDaoMock.update(flight); + ticketDaoMock.save(null); + ticketDaoControl.setMatcher(MockControl.ALWAYS_MATCHER); + flightDaoControl.replay(); + ticketDaoControl.replay(); + securityServiceControl.replay(); + Ticket ticket = airlineService.bookFlight(flightNumber, departureTime, passengers); + assertNotNull("Invalid ticket", ticket); + assertEquals("Invalid flight", flight, ticket.getFlight()); + assertEquals("Invalid amount of miles", 10, frequentFlyer.getMiles()); + } + + public void testBookFlightNoSeatAvailable() throws Exception { + String flightNumber = "AB1234"; + DateTime departureTime = new DateTime(); + List passengers = Collections.singletonList(new Passenger()); + Flight flight = new Flight(); + flightDaoControl.expectAndReturn(flightDaoMock.getFlight(flightNumber, departureTime), flight); + flightDaoControl.replay(); + ticketDaoControl.replay(); + securityServiceControl.replay(); + try { + airlineService.bookFlight(flightNumber, departureTime, passengers); + fail("Should have thrown an NoSeatAvailableException"); + } + catch (NoSeatAvailableException ex) { + } + } + + public void testBookFlightNoSuchFlight() throws Exception { + String flightNumber = "AB1234"; + DateTime departureTime = new DateTime(); + List passengers = Collections.singletonList(new Passenger()); + flightDaoControl.expectAndReturn(flightDaoMock.getFlight(flightNumber, departureTime), null); + flightDaoControl.replay(); + ticketDaoControl.replay(); + securityServiceControl.replay(); + try { + airlineService.bookFlight(flightNumber, departureTime, passengers); + fail("Should have thrown an NoSuchFlightException"); + } + catch (NoSuchFlightException ex) { + } + } + + public void testGetFlights() throws Exception { + String toCode = "to"; + String fromCode = "from"; + YearMonthDay departureDate = new YearMonthDay(2006, 1, 31); + Flight flight = new Flight(); + List flights = new ArrayList(); + flights.add(flight); + DateTime startOfPeriod = new DateTime(2006, 1, 31, 0, 0, 0, 0); + DateTime endOfPeriod = new DateTime(2006, 2, 1, 0, 0, 0, 0); + flightDaoControl.expectAndReturn( + flightDaoMock.findFlights(fromCode, toCode, startOfPeriod, endOfPeriod, ServiceClass.ECONOMY), flights); + flightDaoControl.replay(); + ticketDaoControl.replay(); + securityServiceControl.replay(); + + List result = airlineService.getFlights(fromCode, toCode, departureDate, ServiceClass.ECONOMY); + assertEquals("Invalid result", flights, result); + } + + public void testGetFlightsDefaultServiceClass() throws Exception { + String toCode = "to"; + String fromCode = "from"; + YearMonthDay departureDate = new YearMonthDay(2006, 1, 31); + Flight flight = new Flight(); + List flights = new ArrayList(); + flights.add(flight); + DateTime startOfPeriod = new DateTime(2006, 1, 31, 0, 0, 0, 0); + DateTime endOfPeriod = new DateTime(2006, 2, 1, 0, 0, 0, 0); + flightDaoControl.expectAndReturn( + flightDaoMock.findFlights(fromCode, toCode, startOfPeriod, endOfPeriod, ServiceClass.ECONOMY), flights); + flightDaoControl.replay(); + ticketDaoControl.replay(); + securityServiceControl.replay(); + + List result = airlineService.getFlights(fromCode, toCode, departureDate, null); + assertEquals("Invalid result", flights, result); + } +} diff --git a/samples/airline/src/test/java/org/springframework/ws/samples/airline/ws/BookFlightEndpointTest.java b/samples/airline/src/test/java/org/springframework/ws/samples/airline/ws/BookFlightEndpointTest.java new file mode 100644 index 00000000..a2a433c0 --- /dev/null +++ b/samples/airline/src/test/java/org/springframework/ws/samples/airline/ws/BookFlightEndpointTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.ws; + +import java.util.Collections; +import java.util.List; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.custommonkey.xmlunit.XMLUnit; +import org.easymock.MockControl; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.YearMonthDay; +import org.springframework.ws.samples.airline.domain.Airport; +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.domain.FrequentFlyer; +import org.springframework.ws.samples.airline.domain.Passenger; +import org.springframework.ws.samples.airline.domain.ServiceClass; +import org.springframework.ws.samples.airline.domain.Ticket; +import org.springframework.ws.samples.airline.service.AirlineService; + +public class BookFlightEndpointTest extends XMLTestCase { + + private BookFlightEndpoint endpoint; + + private MockControl serviceControl; + + private AirlineService serviceMock; + + private DateTime departure; + + private Ticket ticket; + + private Document responseDocument; + + private SAXBuilder saxBuilder; + + protected void setUp() throws Exception { + XMLUnit.setIgnoreWhitespace(true); + endpoint = new BookFlightEndpoint(); + serviceControl = MockControl.createControl(AirlineService.class); + serviceMock = (AirlineService) serviceControl.getMock(); + endpoint.setAirlineService(serviceMock); + endpoint.afterPropertiesSet(); + saxBuilder = new SAXBuilder(); + responseDocument = saxBuilder.build(getClass().getResourceAsStream("bookFlightResponse.xml")); + departure = new DateTime(2006, 1, 1, 0, 0, 0, 0, DateTimeZone.UTC); + DateTime arrival = new DateTime(2006, 2, 2, 0, 0, 0, 0, DateTimeZone.UTC); + Flight flight = new Flight(); + flight.setNumber("EF1234"); + flight.setDepartureTime(departure); + flight.setArrivalTime(arrival); + Airport from = new Airport("ABC", "Airport", "City"); + Airport to = new Airport("DEF", "Airport", "City"); + flight.setFrom(from); + flight.setTo(to); + flight.setServiceClass(ServiceClass.ECONOMY); + ticket = new Ticket(); + ticket.setId(new Long(42)); + ticket.setFlight(flight); + ticket.setIssueDate(new YearMonthDay(2006, 1, 1)); + } + + public void testInvoke() throws Exception { + Passenger passenger = new Passenger("John", "Doe"); + List passengers = Collections.singletonList(passenger); + ticket.addPassenger(passenger); + serviceControl.expectAndReturn(serviceMock.bookFlight("EF1234", departure, passengers), ticket); + serviceControl.replay(); + Document requestDocument = saxBuilder.build(getClass().getResourceAsStream("bookFlightRequest.xml")); + Element result = endpoint.invokeInternal(requestDocument.getRootElement()); + assertNotNull("Invalid result", result); + Document resultDocument = new Document(); + resultDocument.setRootElement(result); + XMLOutputter outputter = new XMLOutputter(); + assertXMLEqual(outputter.outputString(responseDocument), outputter.outputString(resultDocument)); + serviceControl.verify(); + } + + public void testInvokeFrequentFlyer() throws Exception { + FrequentFlyer frequentFlyer = new FrequentFlyer("john", "changeme", "John", "Doe"); + List passengers = Collections.singletonList(frequentFlyer); + ticket.addPassenger(frequentFlyer); + serviceControl.expectAndReturn(serviceMock.bookFlight("EF1234", departure, passengers), ticket); + serviceControl.replay(); + Document requestDocument = + saxBuilder.build(getClass().getResourceAsStream("bookFlightRequestFrequentFlyer.xml")); + Element result = endpoint.invokeInternal(requestDocument.getRootElement()); + assertNotNull("Invalid result", result); + Document resultDocument = new Document(); + resultDocument.setRootElement(result); + XMLOutputter outputter = new XMLOutputter(); + assertXMLEqual(outputter.outputString(responseDocument), outputter.outputString(resultDocument)); + serviceControl.verify(); + } + + +} \ No newline at end of file diff --git a/samples/airline/src/test/java/org/springframework/ws/samples/airline/ws/GetFlightsEndpointTest.java b/samples/airline/src/test/java/org/springframework/ws/samples/airline/ws/GetFlightsEndpointTest.java new file mode 100644 index 00000000..1f7e58fd --- /dev/null +++ b/samples/airline/src/test/java/org/springframework/ws/samples/airline/ws/GetFlightsEndpointTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.airline.ws; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; +import org.easymock.MockControl; +import org.joda.time.DateTime; +import org.joda.time.YearMonthDay; + +import org.springframework.ws.samples.airline.domain.Flight; +import org.springframework.ws.samples.airline.schema.GetFlightsRequest; +import org.springframework.ws.samples.airline.schema.GetFlightsResponse; +import org.springframework.ws.samples.airline.schema.ServiceClass; +import org.springframework.ws.samples.airline.schema.impl.GetFlightsRequestImpl; +import org.springframework.ws.samples.airline.service.AirlineService; + +public class GetFlightsEndpointTest extends TestCase { + + private GetFlightsEndpoint endpoint; + + private MockControl serviceControl; + + private AirlineService serviceMock; + + protected void setUp() throws Exception { + endpoint = new GetFlightsEndpoint(); + serviceControl = MockControl.createControl(AirlineService.class); + serviceMock = (AirlineService) serviceControl.getMock(); + endpoint.setAirlineService(serviceMock); + } + + public void testInvoke() throws Exception { + String fromAirportCode = "ABC"; + String toAirportCode = "DEF"; + YearMonthDay departureDate = new YearMonthDay(); + GetFlightsRequest request = new GetFlightsRequestImpl(); + request.setFrom(fromAirportCode); + request.setTo(toAirportCode); + request.setDepartureDate(departureDate.toDateTimeAtMidnight().toGregorianCalendar()); + request.setServiceClass(ServiceClass.FIRST); + List flights = new ArrayList(); + Flight flight = new Flight(); + flight.setNumber("1"); + flight.setDepartureTime(new DateTime()); + flight.setArrivalTime(new DateTime()); + flights.add(flight); + serviceControl.expectAndReturn(serviceMock.getFlights(fromAirportCode, toAirportCode, departureDate, + org.springframework.ws.samples.airline.domain.ServiceClass.FIRST), flights); + serviceControl.replay(); + GetFlightsResponse response = (GetFlightsResponse) endpoint.invokeInternal(request); + serviceControl.verify(); + assertNotNull("Response is null", response); + assertEquals("Invalid amount of flights in response", 1, response.getFlight().size()); + org.springframework.ws.samples.airline.schema.Flight responseFlight = + (org.springframework.ws.samples.airline.schema.Flight) response.getFlight().get(0); + assertEquals("Invalid flight number on flight", "1", responseFlight.getNumber()); + } + +} diff --git a/samples/echo/client/saaj/build.xml b/samples/echo/client/saaj/build.xml new file mode 100644 index 00000000..2cabefd0 --- /dev/null +++ b/samples/echo/client/saaj/build.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/echo/client/saaj/lib/activation.jar b/samples/echo/client/saaj/lib/activation.jar new file mode 100644 index 00000000..1c88eb4e Binary files /dev/null and b/samples/echo/client/saaj/lib/activation.jar differ diff --git a/samples/echo/client/saaj/lib/mail.jar b/samples/echo/client/saaj/lib/mail.jar new file mode 100644 index 00000000..9966ca30 Binary files /dev/null and b/samples/echo/client/saaj/lib/mail.jar differ diff --git a/samples/echo/client/saaj/lib/saaj-api.jar b/samples/echo/client/saaj/lib/saaj-api.jar new file mode 100644 index 00000000..ad2d46d6 Binary files /dev/null and b/samples/echo/client/saaj/lib/saaj-api.jar differ diff --git a/samples/echo/client/saaj/lib/saaj-impl.jar b/samples/echo/client/saaj/lib/saaj-impl.jar new file mode 100644 index 00000000..612b638c Binary files /dev/null and b/samples/echo/client/saaj/lib/saaj-impl.jar differ diff --git a/samples/echo/client/saaj/lib/xercesImpl.jar b/samples/echo/client/saaj/lib/xercesImpl.jar new file mode 100644 index 00000000..d5f3e98c Binary files /dev/null and b/samples/echo/client/saaj/lib/xercesImpl.jar differ diff --git a/samples/echo/client/saaj/readme.txt b/samples/echo/client/saaj/readme.txt new file mode 100644 index 00000000..49ece4d5 --- /dev/null +++ b/samples/echo/client/saaj/readme.txt @@ -0,0 +1,10 @@ +SPRING WEB SERVICES + +This directory contains a Java clients for the Echo Web Service that uses SAAJ: SOAP with Attachments API for Java. It +creates a SOAP message for the 'echo' operation, and sends it using SAAJ. This client does not use Spring-WS in any way. + +SAJA Client Sample table of contents +--------------------------------------------------- +* src - The source files for the client +* build.xml - Ant build file with a 'build' and a 'run' target + diff --git a/samples/echo/client/saaj/src/org/springframework/ws/samples/echo/client/saaj/EchoClient.java b/samples/echo/client/saaj/src/org/springframework/ws/samples/echo/client/saaj/EchoClient.java new file mode 100644 index 00000000..5761a569 --- /dev/null +++ b/samples/echo/client/saaj/src/org/springframework/ws/samples/echo/client/saaj/EchoClient.java @@ -0,0 +1,101 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.echo.client.saaj; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.Name; +import javax.xml.soap.SOAPBodyElement; +import javax.xml.soap.SOAPConnection; +import javax.xml.soap.SOAPConnectionFactory; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPFault; +import javax.xml.soap.SOAPMessage; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; + +/** + * A client for the Echo Web Service that uses SAAJ. + * + * @author Ben Ethridge + * @author Arjen Poutsma + */ +public class EchoClient { + + public static final String NAMESPACE_URI = "http://www.springframework.org/spring-ws/samples/echo"; + + public static final String PREFIX = "tns"; + + private SOAPConnectionFactory connectionFactory; + + private MessageFactory messageFactory; + + private URL url; + + public EchoClient(String url) throws SOAPException, MalformedURLException { + connectionFactory = SOAPConnectionFactory.newInstance(); + messageFactory = MessageFactory.newInstance(); + this.url = new URL(url); + } + + private SOAPMessage createEchoRequest() throws SOAPException { + SOAPMessage message = messageFactory.createMessage(); + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name echoRequestName = envelope.createName("echoRequest", PREFIX, NAMESPACE_URI); + SOAPBodyElement echoRequestElement = message.getSOAPBody() + .addBodyElement(echoRequestName); + echoRequestElement.setValue("Hello"); + return message; + } + + public void callWebService() throws SOAPException, IOException { + SOAPMessage request = createEchoRequest(); + SOAPConnection connection = connectionFactory.createConnection(); + SOAPMessage response = connection.call(request, url); + if (!response.getSOAPBody().hasFault()) { + writeEchoResponse(response); + } + else { + SOAPFault fault = response.getSOAPBody().getFault(); + System.err.println("Received SOAP Fault"); + System.err.println("SOAP Fault Code :" + fault.getFaultCode()); + System.err.println("SOAP Fault String :" + fault.getFaultString()); + } + } + + private void writeEchoResponse(SOAPMessage message) throws SOAPException { + SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); + Name echoResponseName = envelope.createName("echoResponse", PREFIX, NAMESPACE_URI); + SOAPBodyElement echoResponseElement = (SOAPBodyElement) message + .getSOAPBody().getChildElements(echoResponseName).next(); + String echoValue = echoResponseElement.getTextContent(); + System.out.println("Echo Response [" + echoValue + "]"); + } + + public static void main(String[] args) throws Exception { + String url = "http://localhost:8080/echo/services"; + if (args.length > 0) { + url = args[0]; + } + EchoClient echoClient = new EchoClient(url); + echoClient.callWebService(); + } +} \ No newline at end of file diff --git a/samples/echo/pom.xml b/samples/echo/pom.xml new file mode 100644 index 00000000..31943c94 --- /dev/null +++ b/samples/echo/pom.xml @@ -0,0 +1,18 @@ + + + spring-ws-samples + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + echo + war + Spring WS Echo Sample + + + + org.springframework.ws + spring-ws-core + + + \ No newline at end of file diff --git a/samples/echo/readme.txt b/samples/echo/readme.txt new file mode 100644 index 00000000..34ed668d --- /dev/null +++ b/samples/echo/readme.txt @@ -0,0 +1,13 @@ +===================================================== +== Spring Web Service Echo sample application == +===================================================== + + +1. INTRODUCTION + +This sample shows a bare-bones echoing service. Incoming messages are handled via DOM, and a simple 'business logic' +service is used to obtain the result. + +2. INSTALLATION + +Simply run "mvn package" and deploy the war file generated in target \ No newline at end of file diff --git a/samples/echo/src/etc/test-resources/log4j.properties b/samples/echo/src/etc/test-resources/log4j.properties new file mode 100644 index 00000000..c6d11b4d --- /dev/null +++ b/samples/echo/src/etc/test-resources/log4j.properties @@ -0,0 +1,16 @@ +log4j.rootCategory=WARN, stdout, logfile + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.appender.logfile=org.apache.log4j.RollingFileAppender +log4j.appender.logfile.File=${@PROJECT_NAME@.root}/@PROJECT_NAME@.log +log4j.appender.logfile.MaxFileSize=512KB + +# Keep three backup files +log4j.appender.logfile.MaxBackupIndex=3 +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout + +#Pattern to output : date priority [category] - line_separator +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - <%m>%n diff --git a/samples/echo/src/main/java/org/springframework/ws/samples/echo/service/EchoService.java b/samples/echo/src/main/java/org/springframework/ws/samples/echo/service/EchoService.java new file mode 100644 index 00000000..602adee3 --- /dev/null +++ b/samples/echo/src/main/java/org/springframework/ws/samples/echo/service/EchoService.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.echo.service; + +/** + * Defines the "business logic" of the web service. + * + * @author Ingo Siebert + * @author Arjen Poutsma + */ +public interface EchoService { + + /** + * Returns the given string. + * + * @return s + */ + String echo(String s); +} diff --git a/samples/echo/src/main/java/org/springframework/ws/samples/echo/service/impl/EchoServiceImpl.java b/samples/echo/src/main/java/org/springframework/ws/samples/echo/service/impl/EchoServiceImpl.java new file mode 100755 index 00000000..3f106550 --- /dev/null +++ b/samples/echo/src/main/java/org/springframework/ws/samples/echo/service/impl/EchoServiceImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.echo.service.impl; + +import org.springframework.ws.samples.echo.service.EchoService; + +/** + * Default implementation of EchoService. + * + * @author Ingo Siebert + * @author Arjen Poutsma + */ +public class EchoServiceImpl implements EchoService { + + public String echo(String s) { + return s; + } +} diff --git a/samples/echo/src/main/java/org/springframework/ws/samples/echo/ws/EchoEndpoint.java b/samples/echo/src/main/java/org/springframework/ws/samples/echo/ws/EchoEndpoint.java new file mode 100755 index 00000000..2f045f46 --- /dev/null +++ b/samples/echo/src/main/java/org/springframework/ws/samples/echo/ws/EchoEndpoint.java @@ -0,0 +1,89 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.echo.ws; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +import org.springframework.util.Assert; +import org.springframework.ws.endpoint.AbstractDomPayloadEndpoint; +import org.springframework.ws.samples.echo.service.EchoService; + +/** + * Simple echoing Web service endpoint. Uses a EchoService to create a response string. + * + * @author Ingo Siebert + * @author Arjen Poutsma + */ +public class EchoEndpoint extends AbstractDomPayloadEndpoint { + + /** + * Namespace of both request and response. + */ + public static final String NAMESPACE_URI = "http://www.springframework.org/spring-ws/samples/echo"; + + /** + * The local name of the expected request. + */ + public static final String ECHO_REQUEST_LOCAL_NAME = "echoRequest"; + + /** + * The local name of the created response. + */ + public static final String ECHO_RESPONSE_LOCAL_NAME = "echoResponse"; + + private EchoService echoService; + + /** + * Sets the "business service" to delegate to. + */ + public void setEchoService(EchoService echoService) { + this.echoService = echoService; + } + + /** + * Reads the given requestElement, and sends a the response back. + * + * @param requestElement the contents of the SOAP message as DOM elements + * @param document a DOM document to be used for constructing Nodes + * @return the response element + */ + protected Element invokeInternal(Element requestElement, Document document) throws Exception { + Assert.isTrue(NAMESPACE_URI.equals(requestElement.getNamespaceURI()), "Invalid namespace"); + Assert.isTrue(ECHO_REQUEST_LOCAL_NAME.equals(requestElement.getLocalName()), "Invalid local name"); + NodeList children = requestElement.getChildNodes(); + Text requestText = null; + for (int i = 0; i < children.getLength(); i++) { + if (children.item(i).getNodeType() == Node.TEXT_NODE) { + requestText = (Text) children.item(i); + break; + } + } + if (requestText == null) { + throw new IllegalArgumentException("Could not find request text node"); + } + + String echo = echoService.echo(requestText.getNodeValue()); + Element responseElement = document.createElementNS(NAMESPACE_URI, ECHO_RESPONSE_LOCAL_NAME); + Text responseText = document.createTextNode(echo); + responseElement.appendChild(responseText); + return responseElement; + } +} diff --git a/samples/echo/src/main/webapp/WEB-INF/applicationContext.xml b/samples/echo/src/main/webapp/WEB-INF/applicationContext.xml new file mode 100755 index 00000000..53eca912 --- /dev/null +++ b/samples/echo/src/main/webapp/WEB-INF/applicationContext.xml @@ -0,0 +1,56 @@ + + + + + + + The MessageDispatcher is responsible for routing messages to endpoints. + + + + + + + This endpoint mapping uses the qualified name of the payload (body contents) to determine the endpoint for + an incoming message. Every message is passed to the default endpoint. Additionally, messages are logged + using the logging interceptor. + + + + + + + + + + + + This interceptor validates both incoming and outgoing message contents according to the 'echo.xsd' XML + Schema file. + + + + + + + + This interceptor logs the message payload. + + + + + This endpoint handles echo requests. + + + + + + + + + + + + + + diff --git a/samples/echo/src/main/webapp/WEB-INF/log4j.properties b/samples/echo/src/main/webapp/WEB-INF/log4j.properties new file mode 100644 index 00000000..0f73a758 --- /dev/null +++ b/samples/echo/src/main/webapp/WEB-INF/log4j.properties @@ -0,0 +1,7 @@ +log4j.rootLogger=WARN, stdout +log4j.logger.org.springframework.ws=DEBUG +log4j.logger.org.springframework.xml=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n \ No newline at end of file diff --git a/samples/echo/src/main/webapp/WEB-INF/springws-servlet.xml b/samples/echo/src/main/webapp/WEB-INF/springws-servlet.xml new file mode 100755 index 00000000..021e9115 --- /dev/null +++ b/samples/echo/src/main/webapp/WEB-INF/springws-servlet.xml @@ -0,0 +1,32 @@ + + + + + This handler adapter makes sure that Spring's DispatcherServlet supports MessageEndpoint instances as + handlers. It uses a SAAJ to construct SoapMessageContexts (and SoapMessages). + + + + + + + + This handler adapter adds support for WsdlDefinitions to Spring's DispatcherServlet. It transforms location + attributes in the original WSDL to reflect the URL of the incoming HTTP request. + + + + + + All incoming request are mapped to the messageDispatcher defined in applicationContext-ws.xml + + + + messageDispatcher + echoWsdl + + + + \ No newline at end of file diff --git a/samples/echo/src/main/webapp/WEB-INF/web.xml b/samples/echo/src/main/webapp/WEB-INF/web.xml new file mode 100755 index 00000000..151aab11 --- /dev/null +++ b/samples/echo/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,57 @@ + + + + + + + "Echo" WebService + Returns a given string(only A-Z and a-z chars allowed). See echo.xsd file. + + + + log4jConfigLocation + /WEB-INF/log4j.properties + + + + + org.springframework.web.util.Log4jConfigListener + + + + + org.springframework.web.context.ContextLoaderListener + + + + + springws + org.springframework.web.servlet.DispatcherServlet + + + + + springws + /services + + + + springws + *.wsdl + + + + xsd + text/xml + + + \ No newline at end of file diff --git a/samples/echo/src/main/webapp/echo.wsdl b/samples/echo/src/main/webapp/echo.wsdl new file mode 100644 index 00000000..c893406b --- /dev/null +++ b/samples/echo/src/main/webapp/echo.wsdl @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/echo/src/main/webapp/echo.xsd b/samples/echo/src/main/webapp/echo.xsd new file mode 100755 index 00000000..1fac4c16 --- /dev/null +++ b/samples/echo/src/main/webapp/echo.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/echo/src/main/webapp/index.html b/samples/echo/src/main/webapp/index.html new file mode 100755 index 00000000..e1fb4f6f --- /dev/null +++ b/samples/echo/src/main/webapp/index.html @@ -0,0 +1,14 @@ + + + + + + + Spring WS test page + + +

Spring-WS Echo

+If you see this page, the WAR deployment was successful. + + + diff --git a/samples/echo/src/test/java/org/springframework/ws/samples/echo/ws/EchoEndpointTest.java b/samples/echo/src/test/java/org/springframework/ws/samples/echo/ws/EchoEndpointTest.java new file mode 100644 index 00000000..a8e0b99e --- /dev/null +++ b/samples/echo/src/test/java/org/springframework/ws/samples/echo/ws/EchoEndpointTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.samples.echo.ws; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.easymock.MockControl; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +import org.springframework.ws.samples.echo.service.EchoService; + +public class EchoEndpointTest extends XMLTestCase { + + private EchoEndpoint endpoint; + + private Document requestDocument; + + private Document responseDocument; + + private MockControl control; + + private EchoService mock; + + protected void setUp() throws Exception { + endpoint = new EchoEndpoint(); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + requestDocument = documentBuilder.newDocument(); + responseDocument = documentBuilder.newDocument(); + control = MockControl.createControl(EchoService.class); + mock = (EchoService) control.getMock(); + endpoint.setEchoService(mock); + } + + public void testInvokeInternal() throws Exception { + Element echoRequest = + requestDocument.createElementNS(EchoEndpoint.NAMESPACE_URI, EchoEndpoint.ECHO_REQUEST_LOCAL_NAME); + String content = "ABC"; + Text requestText = requestDocument.createTextNode(content); + echoRequest.appendChild(requestText); + String result = "DEF"; + control.expectAndReturn(mock.echo(content), result); + control.replay(); + Element echoResponse = endpoint.invokeInternal(echoRequest, responseDocument); + assertEquals("Invalid namespace", EchoEndpoint.NAMESPACE_URI, echoResponse.getNamespaceURI()); + assertEquals("Invalid namespace", EchoEndpoint.ECHO_RESPONSE_LOCAL_NAME, echoResponse.getLocalName()); + Text responseText = (Text) echoResponse.getChildNodes().item(0); + assertEquals("Invalid content", result, responseText.getNodeValue()); + control.verify(); + } + +} \ No newline at end of file diff --git a/samples/pom.xml b/samples/pom.xml new file mode 100644 index 00000000..73bcfa92 --- /dev/null +++ b/samples/pom.xml @@ -0,0 +1,72 @@ + + + + spring-ws + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + spring-ws-samples + pom + Spring WS Samples + + echo + airline + + + + mortbay-repo + mortbay-repo + http://www.mortbay.org/maven2/release + + + + + + org.mortbay.jetty + maven-jetty-plugin + 6.0.0rc0 + + 3 + + + + org.apache.maven.plugins + maven-war-plugin + + ${pom.artifactId} + + + + + + + + log4j + log4j + runtime + + + + javax.xml.soap + saaj-api + runtime + + + javax.mail + mail + runtime + + + javax.activation + activation + runtime + + + com.sun.xml.messaging.saaj + saaj-impl + runtime + + + diff --git a/samples/readme.txt b/samples/readme.txt new file mode 100644 index 00000000..a050ca7c --- /dev/null +++ b/samples/readme.txt @@ -0,0 +1,10 @@ +SPRING WEB SERVICES + +Sample table of contents +--------------------------------------------------- +* airline - A complete airline sample that shows both Web Service and O/X Mapping functionality in a complete application + +* echo - A simple sample that shows a bare-bones Echo service + +Both of these samples can be run by using the Jetty Web container. Simply issue the "mvn jetty:run" command in one of +the sample directories. Alternatively, you can build deployable war archives using "mvn package". \ No newline at end of file diff --git a/security/pom.xml b/security/pom.xml new file mode 100644 index 00000000..a4384d40 --- /dev/null +++ b/security/pom.xml @@ -0,0 +1,59 @@ + + + spring-ws + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + spring-ws-security + jar + Spring WS Security + Spring-WS WS-Security implementation + + + + org.springframework.ws + spring-ws-core + + + + org.springframework + spring-context + + + org.springframework + spring-mock + + + org.springframework + spring-jdbc + + + + com.sun.xml.messaging.saaj + saaj-impl + + + + com.sun.xml.wss + xws-security + + + com.sun.org.apache.xml.security + xmlsec + + + org.acegisecurity + acegi-security + + + + javax.xml.soap + saaj-api + + + javax.mail + mail + + + \ No newline at end of file diff --git a/security/src/etc/test-resources/log4j.properties b/security/src/etc/test-resources/log4j.properties new file mode 100644 index 00000000..92e896c8 --- /dev/null +++ b/security/src/etc/test-resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootCategory=INFO, stdout +log4j.logger.org.springframework.ws=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/callback/jaas/jaas.config b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/callback/jaas/jaas.config new file mode 100644 index 00000000..31b98de3 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/callback/jaas/jaas.config @@ -0,0 +1,6 @@ +PlainText { + org.springframework.ws.soap.security.xwss.callback.jaas.PlainTextLoginModule Required; +}; +Certificate { + org.springframework.ws.soap.security.xwss.callback.jaas.CertificateLoginModule Required; +}; diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/decrypt-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/decrypt-config.xml new file mode 100644 index 00000000..0cb4b648 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/decrypt-config.xml @@ -0,0 +1,3 @@ + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/empty-soap.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/empty-soap.xml new file mode 100644 index 00000000..18e3966d --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/empty-soap.xml @@ -0,0 +1,5 @@ + + + QQQ + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypt-alias-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypt-alias-config.xml new file mode 100644 index 00000000..d8164108 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypt-alias-config.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypt-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypt-config.xml new file mode 100644 index 00000000..691d4929 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypt-config.xml @@ -0,0 +1,3 @@ + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypted-soap.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypted-soap.xml new file mode 100644 index 00000000..14c25a4f --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/encrypted-soap.xml @@ -0,0 +1,22 @@ + + +MIICXDCCAcUCBER/bZ8wDQYJKoZIhvcNAQEEBQAwdTELMAkGA1UEBhMCTkwxEDAOBgNVBAgTB1Vu +a25vd24xEjAQBgNVBAcTCUFtc3RlcmRhbTEUMBIGA1UEChMLSW50ZXJmYWNlMjExEjAQBgNVBAsT +CVNwcmluZy1XUzEWMBQGA1UEAxMNQXJqZW4gUG91dHNtYTAeFw0wNjA2MDEyMjQzNDNaFw0wNjA4 +MzAyMjQzNDNaMHUxCzAJBgNVBAYTAk5MMRAwDgYDVQQIEwdVbmtub3duMRIwEAYDVQQHEwlBbXN0 +ZXJkYW0xFDASBgNVBAoTC0ludGVyZmFjZTIxMRIwEAYDVQQLEwlTcHJpbmctV1MxFjAUBgNVBAMT +DUFyamVuIFBvdXRzbWEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOVGA4N3bkrJhTUq/6ol +S85RAueC4QpSKlc7Ppg84f/TQZkMpAEKx/NGZq/SlqUqjYS9OSu+2xVTju/svAYcIarVvxlUfIfL +Ua4l/6ETS/bJCxZFiKiHhSjAQ9+CZOjMn4z0IzXMzM+9yqpDlJd1cRilbIVZ4GEsutWR74DoBRYB +AgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAEi9JV6Rdq9gYO9l1FjegS78SHTbKBp614fU2V9op0ArB +1iUiBjteBfELIpjIbd36lkdwsgHmK8lusNRYiwAFptkQffQPJq4F4xCv5wVLkhr1qkY9vgmCjevJ +VQK9W6ggUMHEtwiWCnLuPcUe8k3hnCmqNv5CtcKv4QduvrHPrR8= + + + +EaldhRqsjjckrksnrNlmwbco1MmG/cIe5U21zs8e1fOtx2yvLEP//ZeEDyiZb6qQJ/nlbs5XQriv +oHnI8BrXnWFADJijWQm3WSNxoSvUn3NOOjTj+qjlM0YG3CW2/19ZZLrj0XRiKJyCZXj0fYM+xODr +PY70j2YZcIAaH3dtLAs=pOPYwI954LnKSTn1iHJthTlli3m8grr+AfElSNY3mJus0CtKb+Hr+/yThIA7hkqXGYGYx3ReoKcK +J+auHXeC2llvcs8kMTZTRLxZl7/qLwWuWX0dBAmBpwXaJy0RXhx5kNr0CWDl0g6Qm4p1VRlnHWNc +YXwt4x70RthlVlX6RczAdcthlLZdXqpEjqKQ11zQ9aXFTPFL+1H8gSvSflTOOUg95fPFPZCR + \ No newline at end of file diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireSignature-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireSignature-config.xml new file mode 100644 index 00000000..96a0390f --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireSignature-config.xml @@ -0,0 +1,3 @@ + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireUsernameToken-digest-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireUsernameToken-digest-config.xml new file mode 100644 index 00000000..b6d644b4 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireUsernameToken-digest-config.xml @@ -0,0 +1,3 @@ + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireUsernameToken-plainText-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireUsernameToken-plainText-config.xml new file mode 100644 index 00000000..b8309e5a --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/requireUsernameToken-plainText-config.xml @@ -0,0 +1,3 @@ + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/sign-alias-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/sign-alias-config.xml new file mode 100644 index 00000000..50181e96 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/sign-alias-config.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/sign-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/sign-config.xml new file mode 100644 index 00000000..fabbc108 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/sign-config.xml @@ -0,0 +1,3 @@ + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/signed-soap.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/signed-soap.xml new file mode 100644 index 00000000..a51d40b6 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/signed-soap.xml @@ -0,0 +1,20 @@ + + +MIICXDCCAcUCBER/bZ8wDQYJKoZIhvcNAQEEBQAwdTELMAkGA1UEBhMCTkwxEDAOBgNVBAgTB1Vu +a25vd24xEjAQBgNVBAcTCUFtc3RlcmRhbTEUMBIGA1UEChMLSW50ZXJmYWNlMjExEjAQBgNVBAsT +CVNwcmluZy1XUzEWMBQGA1UEAxMNQXJqZW4gUG91dHNtYTAeFw0wNjA2MDEyMjQzNDNaFw0wNjA4 +MzAyMjQzNDNaMHUxCzAJBgNVBAYTAk5MMRAwDgYDVQQIEwdVbmtub3duMRIwEAYDVQQHEwlBbXN0 +ZXJkYW0xFDASBgNVBAoTC0ludGVyZmFjZTIxMRIwEAYDVQQLEwlTcHJpbmctV1MxFjAUBgNVBAMT +DUFyamVuIFBvdXRzbWEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOVGA4N3bkrJhTUq/6ol +S85RAueC4QpSKlc7Ppg84f/TQZkMpAEKx/NGZq/SlqUqjYS9OSu+2xVTju/svAYcIarVvxlUfIfL +Ua4l/6ETS/bJCxZFiKiHhSjAQ9+CZOjMn4z0IzXMzM+9yqpDlJd1cRilbIVZ4GEsutWR74DoBRYB +AgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAEi9JV6Rdq9gYO9l1FjegS78SHTbKBp614fU2V9op0ArB +1iUiBjteBfELIpjIbd36lkdwsgHmK8lusNRYiwAFptkQffQPJq4F4xCv5wVLkhr1qkY9vgmCjevJ +VQK9W6ggUMHEtwiWCnLuPcUe8k3hnCmqNv5CtcKv4QduvrHPrR8=/ewlu0ecAQ2DsEJqDcM4AT6oWPE=ihZA0zibv/mSMKWb0lc6sXJmA+Tp12ku26mhh2/2QlScm6/eKBopUcskEOlLNVL5LLhvTBa+fNVw +XVAQKnYyKY2hG5Bsqc+1/Ec6eoN7H9dCbBbwDOdU/H7qV9NFJprLu98tUtPTbBT4IRtnNV5NWKYT +odKKjip/W0n1Hz/ym9I= + + + QQQ + + \ No newline at end of file diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/test-keystore.jks b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/test-keystore.jks new file mode 100644 index 00000000..61f861a0 Binary files /dev/null and b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/test-keystore.jks differ diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/test-truststore.jks b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/test-truststore.jks new file mode 100644 index 00000000..e292465d Binary files /dev/null and b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/test-truststore.jks differ diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameToken-digest-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameToken-digest-config.xml new file mode 100644 index 00000000..85090738 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameToken-digest-config.xml @@ -0,0 +1,3 @@ + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameToken-plainText-config.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameToken-plainText-config.xml new file mode 100644 index 00000000..0ce02dfc --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameToken-plainText-config.xml @@ -0,0 +1,3 @@ + + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameTokenDigest-soap.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameTokenDigest-soap.xml new file mode 100644 index 00000000..98d82b7a --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameTokenDigest-soap.xml @@ -0,0 +1,6 @@ + + +BertkwNstEaiFOrI7B31j7GuETYvdgk=9mdsYDCrjjYRur0rxzYt2oD72006-06-01T23:48:42Z + QQQ + + diff --git a/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameTokenPlainText-soap.xml b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameTokenPlainText-soap.xml new file mode 100644 index 00000000..7c228e00 --- /dev/null +++ b/security/src/etc/test-resources/org/springframework/ws/soap/security/xwss/usernameTokenPlainText-soap.xml @@ -0,0 +1,19 @@ + + + + + Bert + Ernie + + + + + QQQ + + \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java b/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java new file mode 100644 index 00000000..7df2ddb4 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java @@ -0,0 +1,136 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security; + +import java.util.Locale; +import javax.xml.namespace.QName; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapEndpointInterceptor; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapBody; +import org.springframework.ws.soap.context.SoapMessageContext; + +/** + * Interceptor base class for interceptors that handle WS-Security. + *

+ * Subclasses of this base class can be configured to validate incoming and secure outgoing messages. By default, both + * are on. + * + * @author Arjen Poutsma + */ +public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInterceptor { + + /** + * Logger available to subclasses. + */ + private final Log logger = LogFactory.getLog(getClass()); + + private boolean validateRequest = true; + + private boolean secureResponse = true; + + private static final QName WS_SECURITY_NAME = + new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security"); + + /** + * Indicates whether outgoing responsed are to be secured. Defaults to true. + */ + public void setSecureResponse(boolean secureResponse) { + this.secureResponse = secureResponse; + } + + /** + * Indicates whether incoming request are to be validated. Defaults to true. + */ + public void setValidateRequest(boolean validateRequest) { + this.validateRequest = validateRequest; + } + + public final boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { + if (validateRequest) { + Assert.isTrue(messageContext instanceof SoapMessageContext, + "WsSecurityInterceptor requires a SoapMessageContext"); + SoapMessageContext soapMessageContext = (SoapMessageContext) messageContext; + try { + validateRequest(soapMessageContext); + return true; + } + catch (WsSecurityValidationException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Could not validate request: " + ex.getMessage()); + } + SoapBody response = soapMessageContext.getSoapResponse().getSoapBody(); + response.addClientOrSenderFault(ex.getMessage(), Locale.ENGLISH); + return false; + } + } + else { + return true; + } + } + + public final boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { + if (secureResponse) { + Assert.isTrue(messageContext instanceof SoapMessageContext, + "WsSecurityInterceptor requires a SoapMessageContext"); + SoapMessageContext soapMessageContext = (SoapMessageContext) messageContext; + try { + secureResponse(soapMessageContext); + return true; + } + catch (WsSecuritySecurementException ex) { + if (logger.isErrorEnabled()) { + logger.error("Could not secure response: " + ex.getMessage(), ex); + } + return false; + } + } + else { + return true; + } + } + + public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception { + return true; + } + + /** + * Abstract template method. Subclasses are required to validate the request contained in the given + * SoapMessageContext, and replace the original request with the validated version. + * + * @param messageContext the soap message context + * @throws WsSecurityValidationException in case of validation errors + */ + protected abstract void validateRequest(SoapMessageContext messageContext) throws WsSecurityValidationException; + + /** + * Abstract template method. Subclasses are required to secure the response contained in the given + * SoapMessageContext, and replace the original response with the secured version. + * + * @param messageContext the soap message context + * @throws WsSecuritySecurementException in case of securement errors + */ + protected abstract void secureResponse(SoapMessageContext messageContext) throws WsSecuritySecurementException; + + public boolean understands(SoapHeaderElement headerElement) { + return WS_SECURITY_NAME.equals(headerElement.getName()); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/WsSecurityException.java b/security/src/main/java/org/springframework/ws/soap/security/WsSecurityException.java new file mode 100644 index 00000000..02ffb34d --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/WsSecurityException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security; + +import org.springframework.ws.WebServiceException; + +/** + * Exception indicating that something went wrong during WS-Security executions. Has specific subclasses for securement + * and validation. + * + * @author Arjen Poutsma + */ +public abstract class WsSecurityException extends WebServiceException { + + public WsSecurityException(String msg) { + super(msg); + } + + public WsSecurityException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/WsSecuritySecurementException.java b/security/src/main/java/org/springframework/ws/soap/security/WsSecuritySecurementException.java new file mode 100644 index 00000000..0dfb1ca5 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/WsSecuritySecurementException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security; + +/** + * Exception indicating that something went wrong during the securement of a message. + *

+ * This is a checked exception since we want it to be caught, logged and handled rather than cause the application to + * fail. Failure to secure a message is usually not a fatal problem. + * + * @author Arjen Poutsma + */ +public abstract class WsSecuritySecurementException extends WsSecurityException { + + public WsSecuritySecurementException(String msg) { + super(msg); + } + + public WsSecuritySecurementException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/WsSecurityValidationException.java b/security/src/main/java/org/springframework/ws/soap/security/WsSecurityValidationException.java new file mode 100644 index 00000000..c41e2e85 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/WsSecurityValidationException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security; + +/** + * Exception indicating that something went wrong during the validation of a message. + *

+ * This is a checked exception since we want it to be caught, logged and handled rather than cause the application to + * fail. Failure to validate a message is usually not a fatal problem. + * + * @author Arjen Poutsma + */ +public abstract class WsSecurityValidationException extends WsSecurityException { + + public WsSecurityValidationException(String msg) { + super(msg); + } + + public WsSecurityValidationException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/package.html b/security/src/main/java/org/springframework/ws/soap/security/package.html new file mode 100644 index 00000000..fc5c3c41 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/package.html @@ -0,0 +1,5 @@ + + +Provided WS-Security implementation classes. Contains the AbstractWsSecurityInterceptor and exceptions. + + \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/support/KeyStoreFactoryBean.java b/security/src/main/java/org/springframework/ws/soap/security/support/KeyStoreFactoryBean.java new file mode 100644 index 00000000..b1f4e4fd --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/support/KeyStoreFactoryBean.java @@ -0,0 +1,131 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.support; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.util.StringUtils; + +/** + * Spring factory bean for a java.security.KeyStore. + *

+ * To load an existing key store, you must set the location property. If this property is not set, a new, + * empty key store is created, which is most likely not what you want. + * + * @author Arjen Poutsma + * @see #setLocation(org.springframework.core.io.Resource) + */ +public class KeyStoreFactoryBean implements FactoryBean, InitializingBean { + + private static final Log logger = LogFactory.getLog(KeyStoreFactoryBean.class); + + private KeyStore keyStore; + + private String type; + + private String provider; + + private Resource location; + + private char[] password; + + /** + * Sets the location of the key store to use. If this is not set, a new, empty key store will be used. + * + * @see KeyStore#load(java.io.InputStream, char[]) + */ + public void setLocation(Resource location) { + this.location = location; + } + + /** + * Sets the password to use for integrity checking. If this property is not set, then integrity checking is not + * performed. + */ + public void setPassword(String password) { + if (password != null) { + this.password = password.toCharArray(); + } + } + + /** + * Sets the provider of the key store to use. If this is not set, the default is used. + */ + public void setProvider(String provider) { + this.provider = provider; + } + + /** + * Sets the type of the KeyStore to use. If this is not set, the default is used. + * + * @see KeyStore#getDefaultType() + */ + public void setType(String type) { + this.type = type; + } + + public Object getObject() throws Exception { + return keyStore; + } + + public Class getObjectType() { + return KeyStore.class; + } + + public boolean isSingleton() { + return true; + } + + public final void afterPropertiesSet() throws GeneralSecurityException, IOException { + if (StringUtils.hasLength(provider) && StringUtils.hasLength(type)) { + keyStore = KeyStore.getInstance(type, provider); + } + else if (StringUtils.hasLength(type)) { + keyStore = KeyStore.getInstance(type); + } + else { + keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + } + InputStream is = null; + try { + if (location != null && location.exists()) { + is = location.getInputStream(); + if (logger.isInfoEnabled()) { + logger.info("Loading key store from " + location); + } + } + else if (logger.isWarnEnabled()) { + logger.warn("Creating empty key store"); + } + keyStore.load(is, password); + } + finally { + if (is != null) { + is.close(); + } + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/support/package.html b/security/src/main/java/org/springframework/ws/soap/security/support/package.html new file mode 100644 index 00000000..c39e401f --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/support/package.html @@ -0,0 +1,5 @@ + + +Contains support classes for handling WS-Security messages. + + \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptor.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptor.java new file mode 100644 index 00000000..719410db --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptor.java @@ -0,0 +1,172 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import java.io.InputStream; + +import javax.security.auth.callback.CallbackHandler; +import javax.xml.soap.SOAPMessage; + +import com.sun.xml.wss.ProcessingContext; +import com.sun.xml.wss.XWSSProcessor; +import com.sun.xml.wss.XWSSProcessorFactory; +import com.sun.xml.wss.XWSSecurityException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.ws.soap.context.SoapMessageContext; +import org.springframework.ws.soap.saaj.SaajSoapMessageContext; +import org.springframework.ws.soap.security.AbstractWsSecurityInterceptor; +import org.springframework.ws.soap.security.xwss.callback.CallbackHandlerChain; + +/** + * WS-Security endpoint interceptor that is based on Sun's XML and Web Services Security package (XWSS). This + * WS-Security implementation is part of the Java Web Services Developer Pack (Java WSDP). + *

+ * This interceptor needs a CallbackHandler to operate. This handler is used to retrieve certificates, + * private keys, validate user credentials, etc. Refer to the XWSS Javadoc to learn more about the specific + * Callbacks fired by XWSS. You can also set multiple handlers, each of which will be used in turn. + *

+ * Additionally, you must define a XWSS policy file by setting policyConfiguration property. The format of + * the policy file is documented in the Java + * Web Services Tutorial. + *

+ * Note that this interceptor depends on SAAJ, and thus requires SaajSoapMessages to operate. This + * means that you must use a SaajSoapMessageContextFactory to create the SOAP messages. + * + * @author Arjen Poutsma + * @see #setCallbackHandler(javax.security.auth.callback.CallbackHandler) + * @see #setPolicyConfiguration(org.springframework.core.io.Resource) + * @see com.sun.xml.wss.impl.callback.XWSSCallback + * @see org.springframework.ws.soap.saaj.SaajSoapMessageContextFactory + * @see XWSS + */ +public class XwsSecurityInterceptor extends AbstractWsSecurityInterceptor implements InitializingBean { + + private static final Log logger = LogFactory.getLog(XwsSecurityInterceptor.class); + + private XWSSProcessor processor; + + private CallbackHandler callbackHandler; + + private Resource policyConfiguration; + + /** + * Sets the handler to resolve XWSS callbacks. Setting either this propery, or callbackHandlers, is + * required. + * + * @see com.sun.xml.wss.impl.callback.XWSSCallback + * @see #setCallbackHandlers(javax.security.auth.callback.CallbackHandler[]) + */ + public void setCallbackHandler(CallbackHandler callbackHandler) { + this.callbackHandler = callbackHandler; + } + + /** + * Sets the handlers to resolve XWSS callbacks. Setting either this propery, or callbackHandlers, is + * required. + * + * @see com.sun.xml.wss.impl.callback.XWSSCallback + * @see #setCallbackHandler(javax.security.auth.callback.CallbackHandler) + */ + public void setCallbackHandlers(CallbackHandler[] callbackHandler) { + this.callbackHandler = new CallbackHandlerChain(callbackHandler); + } + + /** + * Sets the policy configuration to use for XWSS. Required. + */ + public void setPolicyConfiguration(Resource policyConfiguration) { + this.policyConfiguration = policyConfiguration; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(policyConfiguration, "policyConfiguration is required"); + Assert.isTrue(policyConfiguration.exists(), "policyConfiguration [" + policyConfiguration + "] does not exist"); + Assert.notNull(callbackHandler, "callbackHandler is required"); + XWSSProcessorFactory processorFactory = XWSSProcessorFactory.newInstance(); + InputStream is = null; + try { + if (logger.isInfoEnabled()) { + logger.info("Loading policy configuration from from '" + policyConfiguration.getFilename() + "'"); + } + is = policyConfiguration.getInputStream(); + processor = processorFactory.createProcessorForSecurityConfiguration(is, callbackHandler); + } + finally { + if (is != null) { + is.close(); + } + } + } + + protected void secureResponse(SoapMessageContext soapMessageContext) throws XwsSecuritySecurementException { + Assert.isTrue(soapMessageContext instanceof SaajSoapMessageContext, + "XwsSecurityInterceptor requires a SaajSoapMessageContext. " + + "Use a SaajSoapMessageContextFactory to create the SOAP messages."); + SaajSoapMessageContext saajMessageContext = (SaajSoapMessageContext) soapMessageContext; + SOAPMessage securedMessage = secureMessage(saajMessageContext.getSaajResponse()); + saajMessageContext.setSaajResponse(securedMessage); + } + + protected void validateRequest(SoapMessageContext soapMessageContext) throws XwsSecurityValidationException { + Assert.isTrue(soapMessageContext instanceof SaajSoapMessageContext, + "XwsSecurityInterceptor requires a SaajSoapMessageContext" + + "Use a SaajSoapMessageContextFactory to create the SOAP messages."); + SaajSoapMessageContext saajMessageContext = (SaajSoapMessageContext) soapMessageContext; + SOAPMessage validatedMessage = validateMessage(saajMessageContext.getSaajRequest()); + saajMessageContext.setSaajRequest(validatedMessage); + } + + /** + * Secures the given SAAJ message in accordance with the defined security policy and returns the secured result. + * + * @param message the message to be secured + * @return the secured message + * @throws XwsSecuritySecurementException in case of errors + */ + protected SOAPMessage secureMessage(SOAPMessage message) throws XwsSecuritySecurementException { + try { + ProcessingContext context = processor.createProcessingContext(message); + return processor.secureOutboundMessage(context); + } + catch (XWSSecurityException ex) { + throw new XwsSecuritySecurementException(ex.getMessage(), ex); + } + } + + /** + * Validates the given SAAJ message in accordance with the defined security policy and returns the validated + * result. + * + * @param message the message to be validated + * @return the validated message + * @throws XwsSecurityValidationException in case of errors + */ + protected SOAPMessage validateMessage(SOAPMessage message) throws XwsSecurityValidationException { + try { + ProcessingContext context = processor.createProcessingContext(message); + return processor.verifyInboundMessage(context); + } + catch (XWSSecurityException ex) { + throw new XwsSecurityValidationException(ex.getMessage(), ex); + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecuritySecurementException.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecuritySecurementException.java new file mode 100644 index 00000000..3813c8e8 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecuritySecurementException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import org.springframework.ws.soap.security.WsSecuritySecurementException; + +public class XwsSecuritySecurementException extends WsSecuritySecurementException { + + public XwsSecuritySecurementException(String msg) { + super(msg); + } + + public XwsSecuritySecurementException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityValidationException.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityValidationException.java new file mode 100644 index 00000000..409b5946 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/XwsSecurityValidationException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import org.springframework.ws.soap.security.WsSecurityValidationException; + +public class XwsSecurityValidationException extends WsSecurityValidationException { + + public XwsSecurityValidationException(String msg) { + super(msg); + } + + public XwsSecurityValidationException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/AbstractCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/AbstractCallbackHandler.java new file mode 100644 index 00000000..c787fea1 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/AbstractCallbackHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract implementation of a CallbackHandler. + * + * @author Arjen Poutsma + */ +public abstract class AbstractCallbackHandler implements CallbackHandler { + + /** + * Logger available to subclasses. + */ + protected final Log logger = LogFactory.getLog(getClass()); + + protected AbstractCallbackHandler() { + } + + /** + * Iterates over the given callbacks, and calls handleInternal for each of them. + * + * @param callbacks the callbacks + * @see #handleInternal(javax.security.auth.callback.Callback) + */ + public final void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + handleInternal(callbacks[i]); + } + } + + /** + * Template method that should be implemented by subclasses. + */ + protected abstract void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException; +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChain.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChain.java new file mode 100644 index 00000000..931b63a5 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChain.java @@ -0,0 +1,175 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.io.IOException; +import java.security.cert.X509Certificate; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import com.sun.xml.wss.impl.callback.TimestampValidationCallback; + +/** + * Represents a chain of CallbackHandlers. For each callback, each of the handlers is called in term. If a + * handler throws a UnsupportedCallbackException, the next handler is tried. + * + * @author Arjen Poutsma + */ +public class CallbackHandlerChain extends AbstractCallbackHandler { + + private CallbackHandler[] callbackHandlers; + + public CallbackHandlerChain(CallbackHandler[] callbackHandlers) { + this.callbackHandlers = callbackHandlers; + } + + public void setCallbackHandlers(CallbackHandler[] callbackHandlers) { + this.callbackHandlers = callbackHandlers; + } + + protected void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof CertificateValidationCallback) { + handleCertificateValidationCallback((CertificateValidationCallback) callback); + } + else if (callback instanceof PasswordValidationCallback) { + handlePasswordValidationCallback((PasswordValidationCallback) callback); + } + else if (callback instanceof TimestampValidationCallback) { + handleTimestampValidationCallback((TimestampValidationCallback) callback); + } + else { + boolean allUnsupported = true; + for (int i = 0; i < callbackHandlers.length; i++) { + CallbackHandler callbackHandler = callbackHandlers[i]; + try { + callbackHandler.handle(new Callback[]{callback}); + allUnsupported = false; + } + catch (UnsupportedCallbackException ex) { + // if an UnsupportedCallbackException occurs, go to the next handler + } + } + if (allUnsupported) { + throw new UnsupportedCallbackException(callback); + } + } + } + + private void handleCertificateValidationCallback(CertificateValidationCallback callback) { + callback.setValidator(new CertificateValidatorChain(callback)); + } + + private void handlePasswordValidationCallback(PasswordValidationCallback callback) { + callback.setValidator(new PasswordValidatorChain(callback)); + } + + private void handleTimestampValidationCallback(TimestampValidationCallback callback) { + callback.setValidator(new TimestampValidatorChain(callback)); + } + + private class TimestampValidatorChain implements TimestampValidationCallback.TimestampValidator { + + private TimestampValidationCallback callback; + + private TimestampValidatorChain(TimestampValidationCallback callback) { + this.callback = callback; + } + + public void validate(TimestampValidationCallback.Request request) + throws TimestampValidationCallback.TimestampValidationException { + for (int i = 0; i < callbackHandlers.length; i++) { + CallbackHandler callbackHandler = callbackHandlers[i]; + try { + callbackHandler.handle(new Callback[]{callback}); + callback.getResult(); + } + catch (IOException e) { + throw new TimestampValidationCallback.TimestampValidationException(e); + } + catch (UnsupportedCallbackException e) { + // ignore + } + } + } + } + + private class PasswordValidatorChain implements PasswordValidationCallback.PasswordValidator { + + private PasswordValidationCallback callback; + + private PasswordValidatorChain(PasswordValidationCallback callback) { + this.callback = callback; + } + + public boolean validate(PasswordValidationCallback.Request request) + throws PasswordValidationCallback.PasswordValidationException { + boolean allUnsupported = true; + for (int i = 0; i < callbackHandlers.length; i++) { + CallbackHandler callbackHandler = callbackHandlers[i]; + try { + callbackHandler.handle(new Callback[]{callback}); + allUnsupported = false; + if (!callback.getResult()) { + return false; + } + } + catch (IOException e) { + throw new PasswordValidationCallback.PasswordValidationException(e); + } + catch (UnsupportedCallbackException e) { + // ignore + } + } + return !allUnsupported; + } + } + + private class CertificateValidatorChain implements CertificateValidationCallback.CertificateValidator { + + private CertificateValidationCallback callback; + + private CertificateValidatorChain(CertificateValidationCallback callback) { + this.callback = callback; + } + + public boolean validate(X509Certificate certificate) + throws CertificateValidationCallback.CertificateValidationException { + boolean allUnsupported = true; + for (int i = 0; i < callbackHandlers.length; i++) { + CallbackHandler callbackHandler = callbackHandlers[i]; + try { + callbackHandler.handle(new Callback[]{callback}); + allUnsupported = false; + if (!callback.getResult()) { + return false; + } + } + catch (IOException e) { + throw new CertificateValidationCallback.CertificateValidationException(e); + } + catch (UnsupportedCallbackException e) { + // ignore + } + } + return !allUnsupported; + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CryptographyCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CryptographyCallbackHandler.java new file mode 100644 index 00000000..b9abbead --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/CryptographyCallbackHandler.java @@ -0,0 +1,492 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import com.sun.xml.wss.impl.callback.DecryptionKeyCallback; +import com.sun.xml.wss.impl.callback.EncryptionKeyCallback; +import com.sun.xml.wss.impl.callback.SignatureKeyCallback; +import com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback; + +/** + * Default callback handler that handles cryptographic callback. This handler determines the exact callback passed, and + * calls a template method for it. By default, all template methods throw an UnsupportedCallbackException, + * so you only need to override those you need. + * + * @author Arjen Poutsma + */ +public class CryptographyCallbackHandler extends AbstractCallbackHandler { + + protected final void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof CertificateValidationCallback) { + handleCertificateValidationCallback((CertificateValidationCallback) callback); + } + else if (callback instanceof DecryptionKeyCallback) { + handleDecryptionKeyCallback((DecryptionKeyCallback) callback); + } + else if (callback instanceof EncryptionKeyCallback) { + handleEncryptionKeyCallback((EncryptionKeyCallback) callback); + } + else if (callback instanceof SignatureKeyCallback) { + handleSignatureKeyCallback((SignatureKeyCallback) callback); + } + else if (callback instanceof SignatureVerificationKeyCallback) { + handleSignatureVerificationKeyCallback((SignatureVerificationKeyCallback) callback); + } + else { + throw new UnsupportedCallbackException(callback); + } + + } + + // + // Certificate validation + // + + /** + * Template method that handles CertificateValidationCallbacks. Called from + * handleInternal(). Default implementation throws an UnsupportedCallbackException. + */ + protected void handleCertificateValidationCallback(CertificateValidationCallback callback) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + // + // Decryption + // + + /** + * Method that handles DecryptionKeyCallbacks. Called from handleInternal(). Default + * implementation delegates to specific handling methods. + * + * @see #handlePrivateKeyRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.PrivateKeyRequest) + * @see #handleSymmetricKeyRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.SymmetricKeyRequest) + */ + protected final void handleDecryptionKeyCallback(DecryptionKeyCallback callback) + throws IOException, UnsupportedCallbackException { + if (callback.getRequest() instanceof DecryptionKeyCallback.PrivateKeyRequest) { + handlePrivateKeyRequest(callback, (DecryptionKeyCallback.PrivateKeyRequest) callback.getRequest()); + } + else if (callback.getRequest() instanceof DecryptionKeyCallback.SymmetricKeyRequest) { + handleSymmetricKeyRequest(callback, (DecryptionKeyCallback.SymmetricKeyRequest) callback.getRequest()); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Method that handles DecryptionKeyCallbacks with PrivateKeyRequest . Called from + * handleDecryptionKeyCallback(). Default implementation delegates to specific handling methods. + * + * @see #handlePublicKeyBasedPrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest) + * @see #handleX509CertificateBasedRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509CertificateBasedRequest) + * @see #handleX509IssuerSerialBasedRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509IssuerSerialBasedRequest) + * @see #handleX509SubjectKeyIdentifierBasedRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.X509SubjectKeyIdentifierBasedRequest) + */ + protected final void handlePrivateKeyRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.PrivateKeyRequest request) + throws IOException, UnsupportedCallbackException { + if (request instanceof DecryptionKeyCallback.PublicKeyBasedPrivKeyRequest) { + handlePublicKeyBasedPrivKeyRequest(callback, (DecryptionKeyCallback.PublicKeyBasedPrivKeyRequest) request); + } + else if (request instanceof DecryptionKeyCallback.X509CertificateBasedRequest) { + handleX509CertificateBasedRequest(callback, (DecryptionKeyCallback.X509CertificateBasedRequest) request); + } + else if (request instanceof DecryptionKeyCallback.X509IssuerSerialBasedRequest) { + handleX509IssuerSerialBasedRequest(callback, (DecryptionKeyCallback.X509IssuerSerialBasedRequest) request); + } + else if (request instanceof DecryptionKeyCallback.X509SubjectKeyIdentifierBasedRequest) { + handleX509SubjectKeyIdentifierBasedRequest(callback, + (DecryptionKeyCallback.X509SubjectKeyIdentifierBasedRequest) request); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Template method that handles DecryptionKeyCallbacks with PublicKeyBasedPrivKeyRequests. + * Called from handlePrivateKeyRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handlePublicKeyBasedPrivKeyRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.PublicKeyBasedPrivKeyRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles DecryptionKeyCallbacks with X509CertificateBasedRequests. + * Called from handlePrivateKeyRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleX509CertificateBasedRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.X509CertificateBasedRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles DecryptionKeyCallbacks with X509IssuerSerialBasedRequests. + * Called from handlePrivateKeyRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleX509IssuerSerialBasedRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.X509IssuerSerialBasedRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles DecryptionKeyCallbacks with X509SubjectKeyIdentifierBasedRequests. + * Called from handlePrivateKeyRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleX509SubjectKeyIdentifierBasedRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.X509SubjectKeyIdentifierBasedRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Method that handles DecryptionKeyCallbacks with SymmetricKeyRequest . Called from + * handleDecryptionKeyCallback(). Default implementation delegates to specific handling methods. + * + * @see #handleAliasSymmetricKeyRequest(com.sun.xml.wss.impl.callback.DecryptionKeyCallback, + * com.sun.xml.wss.impl.callback.DecryptionKeyCallback.AliasSymmetricKeyRequest) + */ + protected final void handleSymmetricKeyRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.SymmetricKeyRequest request) + throws IOException, UnsupportedCallbackException { + if (request instanceof DecryptionKeyCallback.AliasSymmetricKeyRequest) { + DecryptionKeyCallback.AliasSymmetricKeyRequest aliasSymmetricKeyRequest = + (DecryptionKeyCallback.AliasSymmetricKeyRequest) request; + handleAliasSymmetricKeyRequest(callback, aliasSymmetricKeyRequest); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Template method that handles DecryptionKeyCallbacks with AliasSymmetricKeyRequests. + * Called from handleSymmetricKeyRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleAliasSymmetricKeyRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.AliasSymmetricKeyRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + // + // Encryption + // + + /** + * Method that handles EncryptionKeyCallbacks. Called from handleInternal(). Default + * implementation delegates to specific handling methods. + * + * @see #handleSymmetricKeyRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.SymmetricKeyRequest) + * @see #handleX509CertificateRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.X509CertificateRequest) + */ + protected final void handleEncryptionKeyCallback(EncryptionKeyCallback callback) + throws IOException, UnsupportedCallbackException { + if (callback.getRequest() instanceof EncryptionKeyCallback.SymmetricKeyRequest) { + handleSymmetricKeyRequest(callback, (EncryptionKeyCallback.SymmetricKeyRequest) callback.getRequest()); + } + else if (callback.getRequest() instanceof EncryptionKeyCallback.X509CertificateRequest) { + handleX509CertificateRequest(callback, + (EncryptionKeyCallback.X509CertificateRequest) callback.getRequest()); + } + else { + throw new UnsupportedCallbackException(callback); + + } + } + + /** + * Method that handles EncryptionKeyCallbacks with SymmetricKeyRequest . Called from + * handleEncryptionKeyCallback(). Default implementation delegates to specific handling methods. + * + * @see #handleAliasSymmetricKeyRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.AliasSymmetricKeyRequest) + */ + protected final void handleSymmetricKeyRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.SymmetricKeyRequest request) + throws IOException, UnsupportedCallbackException { + if (request instanceof EncryptionKeyCallback.AliasSymmetricKeyRequest) { + handleAliasSymmetricKeyRequest(callback, (EncryptionKeyCallback.AliasSymmetricKeyRequest) request); + } + } + + /** + * Template method that handles EncryptionKeyCallbacks with AliasSymmetricKeyRequests. + * Called from handleSymmetricKeyRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleAliasSymmetricKeyRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.AliasSymmetricKeyRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Method that handles EncryptionKeyCallbacks with X509CertificateRequest . Called from + * handleEncryptionKeyCallback(). Default implementation delegates to specific handling methods. + * + * @see #handleAliasX509CertificateRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.AliasX509CertificateRequest) + * @see #handleDefaultX509CertificateRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.DefaultX509CertificateRequest) + * @see #handlePublicKeyBasedRequest(com.sun.xml.wss.impl.callback.EncryptionKeyCallback, + * com.sun.xml.wss.impl.callback.EncryptionKeyCallback.PublicKeyBasedRequest) + */ + protected final void handleX509CertificateRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.X509CertificateRequest request) + throws IOException, UnsupportedCallbackException { + if (request instanceof EncryptionKeyCallback.AliasX509CertificateRequest) { + handleAliasX509CertificateRequest(callback, (EncryptionKeyCallback.AliasX509CertificateRequest) request); + } + else if (request instanceof EncryptionKeyCallback.DefaultX509CertificateRequest) { + handleDefaultX509CertificateRequest(callback, + (EncryptionKeyCallback.DefaultX509CertificateRequest) request); + } + else if (request instanceof EncryptionKeyCallback.PublicKeyBasedRequest) { + handlePublicKeyBasedRequest(callback, (EncryptionKeyCallback.PublicKeyBasedRequest) request); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Template method that handles EncryptionKeyCallbacks with AliasX509CertificateRequests. + * Called from handleX509CertificateRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleAliasX509CertificateRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.AliasX509CertificateRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles EncryptionKeyCallbacks with DefaultX509CertificateRequests. + * Called from handleX509CertificateRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleDefaultX509CertificateRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.DefaultX509CertificateRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles EncryptionKeyCallbacks with PublicKeyBasedRequests. Called + * from handleX509CertificateRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handlePublicKeyBasedRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.PublicKeyBasedRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + // + // Signing + // + + /** + * Method that handles SignatureKeyCallbacks. Called from handleInternal(). Default + * implementation delegates to specific handling methods. + * + * @see #handlePrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.PrivKeyCertRequest) + */ + protected final void handleSignatureKeyCallback(SignatureKeyCallback callback) + throws IOException, UnsupportedCallbackException { + if (callback.getRequest() instanceof SignatureKeyCallback.PrivKeyCertRequest) { + handlePrivKeyCertRequest(callback, (SignatureKeyCallback.PrivKeyCertRequest) callback.getRequest()); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Method that handles SignatureKeyCallbacks with PrivKeyCertRequests. Called from + * handleSignatureKeyCallback(). Default implementation delegates to specific handling methods. + * + * @see #handleDefaultPrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.DefaultPrivKeyCertRequest) + * @see #handleAliasPrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.AliasPrivKeyCertRequest) + * @see #handlePublicKeyBasedPrivKeyCertRequest(com.sun.xml.wss.impl.callback.SignatureKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest) + */ + protected final void handlePrivKeyCertRequest(SignatureKeyCallback cb, + SignatureKeyCallback.PrivKeyCertRequest request) + throws IOException, UnsupportedCallbackException { + if (request instanceof SignatureKeyCallback.DefaultPrivKeyCertRequest) { + handleDefaultPrivKeyCertRequest(cb, (SignatureKeyCallback.DefaultPrivKeyCertRequest) request); + } + else if (cb.getRequest() instanceof SignatureKeyCallback.AliasPrivKeyCertRequest) { + handleAliasPrivKeyCertRequest(cb, (SignatureKeyCallback.AliasPrivKeyCertRequest) request); + } + else if (cb.getRequest() instanceof SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest) { + handlePublicKeyBasedPrivKeyCertRequest(cb, (SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest) request); + } + else { + throw new UnsupportedCallbackException(cb); + } + } + + /** + * Template method that handles SignatureKeyCallbacks with DefaultPrivKeyCertRequests. + * Called from handlePrivKeyCertRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleDefaultPrivKeyCertRequest(SignatureKeyCallback callback, + SignatureKeyCallback.DefaultPrivKeyCertRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles SignatureKeyCallbacks with AliasPrivKeyCertRequests. + * Called from handlePrivKeyCertRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleAliasPrivKeyCertRequest(SignatureKeyCallback callback, + SignatureKeyCallback.AliasPrivKeyCertRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles SignatureKeyCallbacks with PublicKeyBasedPrivKeyCertRequests. + * Called from handlePrivKeyCertRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handlePublicKeyBasedPrivKeyCertRequest(SignatureKeyCallback callback, + SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + // + // Signature verification + // + + /** + * Method that handles SignatureVerificationKeyCallbacks. Called from handleInternal(). + * Default implementation delegates to specific handling methods. + * + * @see #handleX509CertificateRequest(com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509CertificateRequest) + */ + protected final void handleSignatureVerificationKeyCallback(SignatureVerificationKeyCallback callback) + throws UnsupportedCallbackException, IOException { + if (callback.getRequest() instanceof SignatureVerificationKeyCallback.X509CertificateRequest) { + handleX509CertificateRequest(callback, + (SignatureVerificationKeyCallback.X509CertificateRequest) callback.getRequest()); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Method that handles SignatureVerificationKeyCallbacks with X509CertificateRequests. + * Called from handleSignatureVerificationKeyCallback(). Default implementation delegates to specific + * handling methods. + * + * @see #handlePublicKeyBasedRequest(com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.PublicKeyBasedRequest) + * @see #handleX509IssuerSerialBasedRequest(com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509IssuerSerialBasedRequest) + * @see #handleX509SubjectKeyIdentifierBasedRequest(com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback, + * com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback.X509SubjectKeyIdentifierBasedRequest) + */ + protected final void handleX509CertificateRequest(SignatureVerificationKeyCallback callback, + SignatureVerificationKeyCallback.X509CertificateRequest request) + throws UnsupportedCallbackException, IOException { + if (request instanceof SignatureVerificationKeyCallback.PublicKeyBasedRequest) { + handlePublicKeyBasedRequest(callback, (SignatureVerificationKeyCallback.PublicKeyBasedRequest) request); + } + else if (request instanceof SignatureVerificationKeyCallback.X509IssuerSerialBasedRequest) { + handleX509IssuerSerialBasedRequest(callback, + (SignatureVerificationKeyCallback.X509IssuerSerialBasedRequest) request); + } + else if (request instanceof SignatureVerificationKeyCallback.X509SubjectKeyIdentifierBasedRequest) { + handleX509SubjectKeyIdentifierBasedRequest(callback, + (SignatureVerificationKeyCallback.X509SubjectKeyIdentifierBasedRequest) request); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Template method that handles SignatureKeyCallbacks with PublicKeyBasedPrivKeyCertRequests. + * Called from handlePrivKeyCertRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleX509SubjectKeyIdentifierBasedRequest(SignatureVerificationKeyCallback callback, + SignatureVerificationKeyCallback.X509SubjectKeyIdentifierBasedRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles SignatureKeyCallbacks with X509IssuerSerialBasedRequests. + * Called from handlePrivKeyCertRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handleX509IssuerSerialBasedRequest(SignatureVerificationKeyCallback callback, + SignatureVerificationKeyCallback.X509IssuerSerialBasedRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } + + /** + * Template method that handles SignatureKeyCallbacks with PublicKeyBasedRequests. Called + * from handlePrivKeyCertRequest(). Default implementation throws an + * UnsupportedCallbackException. + */ + protected void handlePublicKeyBasedRequest(SignatureVerificationKeyCallback callback, + SignatureVerificationKeyCallback.PublicKeyBasedRequest request) + throws IOException, UnsupportedCallbackException { + throw new UnsupportedCallbackException(callback); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/DefaultTimestampValidator.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/DefaultTimestampValidator.java new file mode 100644 index 00000000..d1e196b6 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/DefaultTimestampValidator.java @@ -0,0 +1,131 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import com.sun.xml.wss.impl.callback.TimestampValidationCallback; + +/** + * A default implementation of a TimestampValidationCallback.TimestampValidator. Based on a version found + * in the JWSDP samples. + * + * @author Arjen Poutsma + */ +public class DefaultTimestampValidator implements TimestampValidationCallback.TimestampValidator { + + public void validate(TimestampValidationCallback.Request request) + throws TimestampValidationCallback.TimestampValidationException { + if (request instanceof TimestampValidationCallback.UTCTimestampRequest) { + TimestampValidationCallback.UTCTimestampRequest utcRequest = + ((TimestampValidationCallback.UTCTimestampRequest) request); + Date created = parseDate(utcRequest.getCreated()); + Date expired = parseDate(utcRequest.getExpired()); + + validateCreationTime(created, utcRequest.getMaxClockSkew(), utcRequest.getTimestampFreshnessLimit()); + + if (expired != null) { + validateExpirationTime(expired, utcRequest.getMaxClockSkew()); + } + } + else { + throw new TimestampValidationCallback.TimestampValidationException("Unsupport request: [" + request + "]"); + } + } + + private Date getFreshnessAndSkewAdjustedDate(long maxClockSkew, long timestampFreshnessLimit) { + Calendar c = new GregorianCalendar(); + long offset = c.get(Calendar.ZONE_OFFSET); + if (c.getTimeZone().inDaylightTime(c.getTime())) { + offset += c.getTimeZone().getDSTSavings(); + } + long beforeTime = c.getTimeInMillis(); + long currentTime = beforeTime - offset; + + long adjustedTime = currentTime - maxClockSkew - timestampFreshnessLimit; + c.setTimeInMillis(adjustedTime); + + return c.getTime(); + } + + private Date getGMTDateWithSkewAdjusted(Calendar calendar, long maxClockSkew, boolean addSkew) { + long offset = calendar.get(Calendar.ZONE_OFFSET); + if (calendar.getTimeZone().inDaylightTime(calendar.getTime())) { + offset += calendar.getTimeZone().getDSTSavings(); + } + long beforeTime = calendar.getTimeInMillis(); + long currentTime = beforeTime - offset; + + if (addSkew) { + currentTime = currentTime + maxClockSkew; + } + else { + currentTime = currentTime - maxClockSkew; + } + + calendar.setTimeInMillis(currentTime); + return calendar.getTime(); + } + + private Date parseDate(String date) throws TimestampValidationCallback.TimestampValidationException { + SimpleDateFormat calendarFormatter1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + SimpleDateFormat calendarFormatter2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'sss'Z'"); + + try { + try { + return calendarFormatter1.parse(date); + } + catch (ParseException ignored) { + return calendarFormatter2.parse(date); + } + } + catch (ParseException ex) { + throw new TimestampValidationCallback.TimestampValidationException("Could not parse request date: " + date, + ex); + } + } + + private void validateCreationTime(Date created, long maxClockSkew, long timestampFreshnessLimit) + throws TimestampValidationCallback.TimestampValidationException { + Date current = getFreshnessAndSkewAdjustedDate(maxClockSkew, timestampFreshnessLimit); + + if (created.before(current)) { + throw new TimestampValidationCallback.TimestampValidationException( + "The creation time is older than currenttime - timestamp-freshness-limit - max-clock-skew"); + } + + Date currentTime = getGMTDateWithSkewAdjusted(new GregorianCalendar(), maxClockSkew, true); + if (currentTime.before(created)) { + throw new TimestampValidationCallback.TimestampValidationException( + "The creation time is ahead of the current time."); + } + } + + private void validateExpirationTime(Date expires, long maxClockSkew) + throws TimestampValidationCallback.TimestampValidationException { + Date currentTime = getGMTDateWithSkewAdjusted(new GregorianCalendar(), maxClockSkew, false); + if (expires.before(currentTime)) { + throw new TimestampValidationCallback.TimestampValidationException( + "The current time is ahead of the expiration time in Timestamp"); + } + } + +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/KeyStoreCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/KeyStoreCallbackHandler.java new file mode 100644 index 00000000..7105382c --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/KeyStoreCallbackHandler.java @@ -0,0 +1,772 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.CertPathBuilder; +import java.security.cert.CertPathBuilderException; +import java.security.cert.Certificate; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.X509CertSelector; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Enumeration; + +import javax.crypto.SecretKey; + +import com.sun.org.apache.xml.internal.security.utils.RFC2253Parser; +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import com.sun.xml.wss.impl.callback.DecryptionKeyCallback; +import com.sun.xml.wss.impl.callback.EncryptionKeyCallback; +import com.sun.xml.wss.impl.callback.SignatureKeyCallback; +import com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.util.StringUtils; +import org.springframework.ws.soap.security.support.KeyStoreFactoryBean; + +/** + * Callback handler that uses Java Security KeyStores to handle cryptographic callbacks. Allows for + * specific key stores to be set for various cryptographic operations. + *

+ * This handler requires one or more key stores to be set. You can configure them in your application context by using a + * KeyStoreFactoryBean. The exact stores to be set depends on the cryptographic operations that are to be + * performed by this handler. The table underneath show the key store to be used for each operation: + * + * + * + * + * + *
Cryptographic operation Key store used
Certificate validation first keyStore, then trustStore
Decryption based on private key keyStore
Decryption based on symmetric + * key symmetricStore
Encryption based on certificatetrustStore
Encryption based on symmetric keysymmetricStore
Signing keyStore
Signature verification trustStore
+ *

+ *

Default key stores

If the symmetricStore is not set, it will default to the + * keyStore. If the key or trust store is not set, this handler will use the standard Java mechanism to + * load or create it. See {@link #loadDefaultKeyStore()} and {@link #loadDefaultTrustStore()}. + *

+ *

Examples

For instance, if you want to use the KeyStoreCallbackHandler to validate incoming + * certificates or signatures, you would use a trust store, like so: + *
+ * <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
+ *     <property name="trustStore" ref="trustStore"/>
+ * </bean>
+ * 

+ * <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> + * <property name="location" value="classpath:truststore.jks"/> + * <property name="password" value="changeit"/> + * </bean> + *

+ * If you want to use it to decrypt incoming certificates or sign outgoing messages, you would use a key store, like + * so: + *
+ * <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
+ *     <property name="keyStore" ref="keyStore"/>
+ *     <property name="privateKeyPassword" value="changeit"/>
+ * </bean>
+ * 

+ * <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> + * <property name="location" value="classpath:keystore.jks"/> + * <property name="password" value="changeit"/> + * </bean> + *

+ *

+ *

Handled callbacks

This class handles CertificateValidationCallbacks, + * DecryptionKeyCallbacks, EncryptionKeyCallbacks, SignatureKeyCallbacks, and + * SignatureVerificationKeyCallbacks. It throws an UnsupportedCallbackException for others. + * + * @author Arjen Poutsma + * @see KeyStore + * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean + * @see CertificateValidationCallback + * @see DecryptionKeyCallback + * @see EncryptionKeyCallback + * @see SignatureKeyCallback + * @see SignatureVerificationKeyCallback + * @see The + * standard Java trust store mechanism + */ +public class KeyStoreCallbackHandler extends CryptographyCallbackHandler implements InitializingBean { + + private static final String X_509_CERTIFICATE_TYPE = "X.509"; + + private static final String SUBJECT_KEY_IDENTIFIER_OID = "2.5.29.14"; + + private KeyStore keyStore; + + private KeyStore symmetricStore; + + private KeyStore trustStore; + + private String defaultAlias; + + private char[] privateKeyPassword; + + private char[] symmetricKeyPassword; + + private static X509Certificate getCertificate(String alias, KeyStore store) throws IOException { + try { + return (X509Certificate) store.getCertificate(alias); + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + } + + private static X509Certificate getCertificate(PublicKey pk, KeyStore store) throws IOException { + try { + Enumeration aliases = store.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + Certificate cert = store.getCertificate(alias); + if (cert == null || !X_509_CERTIFICATE_TYPE.equals(cert.getType())) { + continue; + } + X509Certificate x509Cert = (X509Certificate) cert; + if (x509Cert.getPublicKey().equals(pk)) { + return x509Cert; + } + } + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + return null; + } + + /** + * Sets the key store alias for the default certificate and private key. + */ + public void setDefaultAlias(String defaultAlias) { + this.defaultAlias = defaultAlias; + } + + /** + * Sets the default key store. This property is required for decription based on private keys, and signing. If this + * property is not set, a default key store is loaded. + * + * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean + * @see #loadDefaultTrustStore() + */ + public void setKeyStore(KeyStore keyStore) { + this.keyStore = keyStore; + } + + /** + * Sets the password used to retrieve private keys from the keystore. This property is required for decription based + * on private keys, and signing. + */ + public void setPrivateKeyPassword(String privateKeyPassword) { + if (privateKeyPassword != null) { + this.privateKeyPassword = privateKeyPassword.toCharArray(); + } + } + + /** + * Sets the password used to retrieve keys from the symmetric keystore. If this property is not set, it default to + * the private key password. + * + * @see #setPrivateKeyPassword(String) + */ + public void setSymmetricKeyPassword(String symmetricKeyPassword) { + if (symmetricKeyPassword != null) { + this.symmetricKeyPassword = symmetricKeyPassword.toCharArray(); + } + } + + /** + * Sets the key store used for encryption and decryption using symmetric keys. If this property is not set, it + * defaults to the keyStore property. + * + * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean + * @see #setKeyStore(java.security.KeyStore) + */ + public void setSymmetricStore(KeyStore symmetricStore) { + this.symmetricStore = symmetricStore; + } + + /** + * Sets the key store used for signature verifications and encryptions. If this property is not set, a default key + * store will be loaded. + * + * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean + * @see #loadDefaultTrustStore() + */ + public void setTrustStore(KeyStore trustStore) { + this.trustStore = trustStore; + } + + public void afterPropertiesSet() throws Exception { + if (keyStore == null) { + loadDefaultKeyStore(); + } + if (trustStore == null) { + loadDefaultTrustStore(); + } + if (symmetricStore == null) { + symmetricStore = keyStore; + } + if (symmetricKeyPassword == null) { + symmetricKeyPassword = privateKeyPassword; + } + } + + protected final void handleAliasPrivKeyCertRequest(SignatureKeyCallback callback, + SignatureKeyCallback.AliasPrivKeyCertRequest request) + throws IOException { + PrivateKey privateKey = getPrivateKey(request.getAlias()); + X509Certificate certificate = getCertificate(request.getAlias()); + request.setPrivateKey(privateKey); + request.setX509Certificate(certificate); + } + + protected final void handleAliasSymmetricKeyRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.AliasSymmetricKeyRequest request) + throws IOException { + SecretKey secretKey = getSymmetricKey(request.getAlias()); + request.setSymmetricKey(secretKey); + } + + // + // Encryption + // + + protected final void handleAliasSymmetricKeyRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.AliasSymmetricKeyRequest request) + throws IOException { + SecretKey secretKey = getSymmetricKey(request.getAlias()); + request.setSymmetricKey(secretKey); + } + + protected final void handleAliasX509CertificateRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.AliasX509CertificateRequest request) + throws IOException { + X509Certificate certificate = getCertificateFromTrustStore(request.getAlias()); + request.setX509Certificate(certificate); + } + + // + // Certificate validation + // + + protected final void handleCertificateValidationCallback(CertificateValidationCallback callback) { + callback.setValidator(new KeyStoreCertificateValidator()); + } + + // + // Signing + // + + protected final void handleDefaultPrivKeyCertRequest(SignatureKeyCallback callback, + SignatureKeyCallback.DefaultPrivKeyCertRequest request) + throws IOException { + PrivateKey privateKey = getPrivateKey(defaultAlias); + X509Certificate certificate = getCertificate(defaultAlias); + request.setPrivateKey(privateKey); + request.setX509Certificate(certificate); + } + + protected final void handleDefaultX509CertificateRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.DefaultX509CertificateRequest request) + throws IOException { + X509Certificate certificate = getCertificateFromTrustStore(defaultAlias); + request.setX509Certificate(certificate); + } + + protected final void handlePublicKeyBasedPrivKeyCertRequest(SignatureKeyCallback callback, + SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest request) + throws IOException { + PrivateKey privateKey = getPrivateKey(request.getPublicKey()); + X509Certificate certificate = getCertificate(request.getPublicKey()); + request.setPrivateKey(privateKey); + request.setX509Certificate(certificate); + } + + // + // Decryption + // + protected final void handlePublicKeyBasedPrivKeyRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.PublicKeyBasedPrivKeyRequest request) + throws IOException { + PrivateKey key = getPrivateKey(request.getPublicKey()); + request.setPrivateKey(key); + } + + protected final void handlePublicKeyBasedRequest(EncryptionKeyCallback callback, + EncryptionKeyCallback.PublicKeyBasedRequest request) + throws IOException { + X509Certificate certificate = getCertificateFromTrustStore(request.getPublicKey()); + request.setX509Certificate(certificate); + } + + protected final void handlePublicKeyBasedRequest(SignatureVerificationKeyCallback callback, + SignatureVerificationKeyCallback.PublicKeyBasedRequest request) + throws IOException { + X509Certificate certificate = getCertificateFromTrustStore(request.getPublicKey()); + request.setX509Certificate(certificate); + } + + protected final void handleX509CertificateBasedRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.X509CertificateBasedRequest request) + throws IOException { + PrivateKey privKey = getPrivateKey(request.getX509Certificate()); + request.setPrivateKey(privKey); + } + + protected final void handleX509IssuerSerialBasedRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.X509IssuerSerialBasedRequest request) + throws IOException { + PrivateKey key = getPrivateKey(request.getIssuerName(), request.getSerialNumber()); + request.setPrivateKey(key); + } + + protected final void handleX509IssuerSerialBasedRequest(SignatureVerificationKeyCallback callback, + SignatureVerificationKeyCallback.X509IssuerSerialBasedRequest request) + throws IOException { + X509Certificate certificate = getCertificateFromTrustStore(request.getIssuerName(), request.getSerialNumber()); + request.setX509Certificate(certificate); + } + + protected final void handleX509SubjectKeyIdentifierBasedRequest(DecryptionKeyCallback callback, + DecryptionKeyCallback.X509SubjectKeyIdentifierBasedRequest request) + throws IOException { + PrivateKey key = getPrivateKey(request.getSubjectKeyIdentifier()); + request.setPrivateKey(key); + } + + // + // Signature verification + // + + protected final void handleX509SubjectKeyIdentifierBasedRequest(SignatureVerificationKeyCallback callback, + SignatureVerificationKeyCallback.X509SubjectKeyIdentifierBasedRequest request) + throws IOException { + X509Certificate certificate = getCertificateFromTrustStore(request.getSubjectKeyIdentifier()); + request.setX509Certificate(certificate); + } + + // Certificate methods + + protected X509Certificate getCertificate(String alias) throws IOException { + return getCertificate(alias, keyStore); + } + + protected X509Certificate getCertificate(PublicKey pk) throws IOException { + return getCertificate(pk, keyStore); + } + + protected X509Certificate getCertificateFromTrustStore(String alias) throws IOException { + return getCertificate(alias, trustStore); + } + + protected X509Certificate getCertificateFromTrustStore(byte[] subjectKeyIdentifier) throws IOException { + try { + Enumeration aliases = trustStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + Certificate cert = trustStore.getCertificate(alias); + if (cert == null || !X_509_CERTIFICATE_TYPE.equals(cert.getType())) { + continue; + } + X509Certificate x509Cert = (X509Certificate) cert; + byte[] keyId = getSubjectKeyIdentifier(x509Cert); + if (keyId == null) { + // Cert does not contain a key identifier + continue; + } + if (Arrays.equals(subjectKeyIdentifier, keyId)) { + return x509Cert; + } + } + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + return null; + } + + protected X509Certificate getCertificateFromTrustStore(PublicKey pk) throws IOException { + return getCertificate(pk, trustStore); + } + + protected X509Certificate getCertificateFromTrustStore(String issuerName, BigInteger serialNumber) + throws IOException { + try { + Enumeration aliases = trustStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + Certificate cert = trustStore.getCertificate(alias); + if (cert == null || !X_509_CERTIFICATE_TYPE.equals(cert.getType())) { + continue; + } + X509Certificate x509Cert = (X509Certificate) cert; + String thisIssuerName = RFC2253Parser.normalize(x509Cert.getIssuerDN().getName()); + BigInteger thisSerialNumber = x509Cert.getSerialNumber(); + if (thisIssuerName.equals(issuerName) && thisSerialNumber.equals(serialNumber)) { + return x509Cert; + } + } + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + return null; + } + + // Private Key methods + + protected PrivateKey getPrivateKey(String alias) throws IOException { + try { + return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + } + + protected PrivateKey getPrivateKey(PublicKey publicKey) throws IOException { + try { + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + if (keyStore.isKeyEntry(alias)) { + // Just returning the first one here + return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); + } + } + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + return null; + } + + protected PrivateKey getPrivateKey(X509Certificate certificate) throws IOException { + try { + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + if (!keyStore.isKeyEntry(alias)) { + continue; + } + Certificate cert = keyStore.getCertificate(alias); + if (cert != null && cert.equals(certificate)) { + return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); + } + } + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + return null; + } + + protected PrivateKey getPrivateKey(byte[] keyIdentifier) throws IOException { + try { + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + if (!keyStore.isKeyEntry(alias)) { + continue; + } + Certificate cert = keyStore.getCertificate(alias); + if (cert == null || !"X.509".equals(cert.getType())) { + continue; + } + X509Certificate x509Cert = (X509Certificate) cert; + byte[] keyId = getSubjectKeyIdentifier(x509Cert); + if (keyId == null) { + // Cert does not contain a key identifier + continue; + } + if (Arrays.equals(keyIdentifier, keyId)) { + return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); + } + } + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + return null; + } + + protected PrivateKey getPrivateKey(String issuerName, BigInteger serialNumber) throws IOException { + try { + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + if (!keyStore.isKeyEntry(alias)) { + continue; + } + Certificate cert = keyStore.getCertificate(alias); + if (cert == null || !"X.509".equals(cert.getType())) { + continue; + } + X509Certificate x509Cert = (X509Certificate) cert; + String thisIssuerName = RFC2253Parser.normalize(x509Cert.getIssuerDN().getName()); + BigInteger thisSerialNumber = x509Cert.getSerialNumber(); + if (thisIssuerName.equals(issuerName) && thisSerialNumber.equals(serialNumber)) { + return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); + } + } + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + return null; + } + + // Utility methods + + protected final byte[] getSubjectKeyIdentifier(X509Certificate cert) { + byte[] subjectKeyIdentifier = cert.getExtensionValue(SUBJECT_KEY_IDENTIFIER_OID); + if (subjectKeyIdentifier == null) { + return null; + } + byte[] dest = new byte[subjectKeyIdentifier.length - 4]; + System.arraycopy(subjectKeyIdentifier, 4, dest, 0, subjectKeyIdentifier.length - 4); + return dest; + } + + // + // Symmetric key methods + // + + protected SecretKey getSymmetricKey(String alias) throws IOException { + try { + return (SecretKey) symmetricStore.getKey(alias, symmetricKeyPassword); + } + catch (GeneralSecurityException e) { + throw new IOException(e.getMessage()); + } + } + + /** + * Loads the key store indicated by system properties. This method tries to load a key store by consulting the + * following system properties:javax.net.ssl.keyStore, javax.net.ssl.keyStorePassword, and + * javax.net.ssl.keyStoreType. + *

+ * If these properties specify a file with an appropriate password, the factory uses this file for the key store. If + * that file does not exist, then a default, empty keystore is created. + *

+ * This behavior corresponds to the standard J2SDK behavior for SSL key stores. + * + * @see The + * standard J2SDK SSL key store mechanism + */ + protected void loadDefaultKeyStore() { + Resource location = null; + String type = null; + String password = null; + String locationProperty = System.getProperty("javax.net.ssl.keyStore"); + if (StringUtils.hasLength(locationProperty)) { + File f = new File(locationProperty); + if (f.exists() && f.isFile() && f.canRead()) { + location = new FileSystemResource(f); + } + String passwordProperty = System.getProperty("javax.net.ssl.keyStorePassword"); + if (StringUtils.hasLength(passwordProperty)) { + password = passwordProperty; + } + type = System.getProperty("javax.net.ssl.trustStore"); + } + // use the factory bean here, easier to setup + KeyStoreFactoryBean factoryBean = new KeyStoreFactoryBean(); + factoryBean.setLocation(location); + factoryBean.setPassword(password); + factoryBean.setType(type); + try { + factoryBean.afterPropertiesSet(); + this.trustStore = (KeyStore) factoryBean.getObject(); + if (logger.isDebugEnabled()) { + logger.debug("Loaded default key store"); + } + } + catch (Exception ex) { + logger.warn("Could not open default key store", ex); + } + } + + /** + * Loads a default trust store. This method uses the following algorithm:

  1. If the system property + * javax.net.ssl.trustStore is defined, its value is loaded. If the + * javax.net.ssl.trustStorePassword system property is also defined, its value is used as a password. + * If the javax.net.ssl.trustStoreType system property is defined, its value is used as a key store + * type. + *

    + * If javax.net.ssl.trustStore is defined but the specified file does not exist, then a default, empty + * trust store is created.

  2. If the javax.net.ssl.trustStore system property was not + * specified, but if the file $JAVA_HOME/lib/security/jssecacerts exists, that file is used.
  3. + * Otherwise,
  4. If the file $JAVA_HOME/lib/security/cacerts exists, that file is used.
+ *

+ * This behavior corresponds to the standard J2SDK behavior for SSL trust stores. + * + * @see The + * standard J2SDK SSL trust store mechanism + */ + protected void loadDefaultTrustStore() { + Resource location = null; + String type = null; + String password = null; + String locationProperty = System.getProperty("javax.net.ssl.trustStore"); + if (StringUtils.hasLength(locationProperty)) { + File f = new File(locationProperty); + if (f.exists() && f.isFile() && f.canRead()) { + location = new FileSystemResource(f); + } + String passwordProperty = System.getProperty("javax.net.ssl.trustStorePassword"); + if (StringUtils.hasLength(passwordProperty)) { + password = passwordProperty; + } + type = System.getProperty("javax.net.ssl.trustStoreType"); + } + else { + String javaHome = System.getProperty("java.home"); + location = new FileSystemResource(javaHome + "/lib/security/jssecacerts"); + if (!location.exists()) { + location = new FileSystemResource(javaHome + "/lib/security/cacerts"); + } + } + // use the factory bean here, easier to setup + KeyStoreFactoryBean factoryBean = new KeyStoreFactoryBean(); + factoryBean.setLocation(location); + factoryBean.setPassword(password); + factoryBean.setType(type); + try { + factoryBean.afterPropertiesSet(); + this.trustStore = (KeyStore) factoryBean.getObject(); + if (logger.isDebugEnabled()) { + logger.debug("Loaded default trust store"); + } + } + catch (Exception ex) { + logger.warn("Could not open default trust store", ex); + } + } + + // + // Inner classes + // + + private class KeyStoreCertificateValidator implements CertificateValidationCallback.CertificateValidator { + + public boolean validate(X509Certificate certificate) + throws CertificateValidationCallback.CertificateValidationException { + if (isOwnedCert(certificate)) { + if (logger.isDebugEnabled()) { + logger.debug("Certificate with DN [" + certificate.getSubjectX500Principal().getName() + + "] is in private keystore"); + } + return true; + } + else if (trustStore == null) { + return false; + } + + try { + certificate.checkValidity(); + } + catch (CertificateExpiredException e) { + if (logger.isDebugEnabled()) { + logger.debug("Certificate with DN [" + certificate.getSubjectX500Principal().getName() + + "] has expired"); + } + return false; + } + catch (CertificateNotYetValidException e) { + if (logger.isDebugEnabled()) { + logger.debug("Certificate with DN [" + certificate.getSubjectX500Principal().getName() + + "] is not yet valid"); + } + return false; + } + + X509CertSelector certSelector = new X509CertSelector(); + certSelector.setCertificate(certificate); + + PKIXBuilderParameters parameters; + CertPathBuilder builder; + try { + parameters = new PKIXBuilderParameters(trustStore, certSelector); + parameters.setRevocationEnabled(false); + builder = CertPathBuilder.getInstance("PKIX"); + } + catch (GeneralSecurityException ex) { + throw new CertificateValidationCallback.CertificateValidationException( + "Could not create PKIX CertPathBuilder", ex); + } + + try { + builder.build(parameters); + } + catch (CertPathBuilderException e) { + if (logger.isDebugEnabled()) { + logger.debug("Certification path of certificate with DN [" + + certificate.getSubjectX500Principal().getName() + "] could not be validated"); + } + return false; + } + catch (InvalidAlgorithmParameterException e) { + if (logger.isDebugEnabled()) { + logger.debug("Algorithm of certificate with DN [" + + certificate.getSubjectX500Principal().getName() + "] could not be validated"); + } + return false; + } + if (logger.isDebugEnabled()) { + logger.debug("Certificate with DN [" + certificate.getSubjectX500Principal().getName() + "] validated"); + } + return true; + } + + private boolean isOwnedCert(X509Certificate cert) + throws CertificateValidationCallback.CertificateValidationException { + if (keyStore == null) { + return false; + } + try { + Enumeration aliases = keyStore.aliases(); + while (aliases.hasMoreElements()) { + String alias = (String) aliases.nextElement(); + if (keyStore.isKeyEntry(alias)) { + X509Certificate x509Cert = (X509Certificate) keyStore.getCertificate(alias); + if (x509Cert != null) { + if (x509Cert.equals(cert)) { + return true; + } + } + } + } + return false; + } + catch (GeneralSecurityException e) { + throw new CertificateValidationCallback.CertificateValidationException( + "Could not determine whether certificate is contained in main key store", e); + } + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/MockValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/MockValidationCallbackHandler.java new file mode 100644 index 00000000..b2241a03 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/MockValidationCallbackHandler.java @@ -0,0 +1,83 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.io.IOException; +import java.security.cert.X509Certificate; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; + +/** + * Mock implementation of of callback handler that accepts all password and certificate validation callbacks. + *

+ * If the valid property is set to true (the default), this handler simply accepts and + * validates every password or certificate validation callback that is passed to it. + *

+ * This class handles CertificateValidationCallbacks and PasswordValidationCallbacks, and + * throws an UnsupportedCallbackException for others + * + * @author Arjen Poutsma + */ +public class MockValidationCallbackHandler extends AbstractCallbackHandler { + + private boolean isValid = true; + + public MockValidationCallbackHandler() { + } + + public MockValidationCallbackHandler(boolean valid) { + isValid = valid; + } + + protected void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof CertificateValidationCallback) { + CertificateValidationCallback validationCallback = (CertificateValidationCallback) callback; + validationCallback.setValidator(new MockCertificateValidator()); + } + else if (callback instanceof PasswordValidationCallback) { + PasswordValidationCallback validationCallback = (PasswordValidationCallback) callback; + validationCallback.setValidator(new MockPasswordValidator()); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + public void setValid(boolean valid) { + isValid = valid; + } + + private class MockCertificateValidator implements CertificateValidationCallback.CertificateValidator { + + public boolean validate(X509Certificate certificate) + throws CertificateValidationCallback.CertificateValidationException { + return isValid; + } + } + + private class MockPasswordValidator implements PasswordValidationCallback.PasswordValidator { + + public boolean validate(PasswordValidationCallback.Request request) + throws PasswordValidationCallback.PasswordValidationException { + return isValid; + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandler.java new file mode 100644 index 00000000..74d9fb9c --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandler.java @@ -0,0 +1,100 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import com.sun.xml.wss.impl.callback.TimestampValidationCallback; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * Simple callback handler that validates passwords agains a in-memory Properties object. Password + * validation is done on a case-sensitive basis. + *

+ * This class only handles PasswordValidationCallbacks, and throws an + * UnsupportedCallbackException for others + * + * @author Arjen Poutsma + * @see #setUsers(java.util.Properties) + */ +public class SimplePasswordValidationCallbackHandler extends AbstractCallbackHandler implements InitializingBean { + + private Properties users = new Properties(); + + /** + * Sets the users to validate against. Property names are usernames, property values are passwords. + */ + public void setUsers(Properties users) { + this.users = users; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(users, "users is required"); + } + + protected void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof PasswordValidationCallback) { + PasswordValidationCallback passwordCallback = (PasswordValidationCallback) callback; + if (passwordCallback.getRequest() instanceof PasswordValidationCallback.PlainTextPasswordRequest) { + passwordCallback.setValidator(new SimplePlainTextPasswordValidator()); + } + else if (passwordCallback.getRequest() instanceof PasswordValidationCallback.DigestPasswordRequest) { + PasswordValidationCallback.DigestPasswordRequest digestPasswordRequest = + (PasswordValidationCallback.DigestPasswordRequest) passwordCallback.getRequest(); + String password = users.getProperty(digestPasswordRequest.getUsername()); + digestPasswordRequest.setPassword(password); + passwordCallback.setValidator(new PasswordValidationCallback.DigestPasswordValidator()); + } + passwordCallback.setValidator(new SimplePlainTextPasswordValidator()); + } + else if (callback instanceof TimestampValidationCallback) { + TimestampValidationCallback timestampCallback = (TimestampValidationCallback) callback; + timestampCallback.setValidator(new DefaultTimestampValidator()); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + public void setUsersMap(Map users) { + for (Iterator iterator = users.keySet().iterator(); iterator.hasNext();) { + String username = (String) iterator.next(); + String password = (String) users.get(username); + this.users.setProperty(username, password); + } + } + + private class SimplePlainTextPasswordValidator implements PasswordValidationCallback.PasswordValidator { + + public boolean validate(PasswordValidationCallback.Request request) + throws PasswordValidationCallback.PasswordValidationException { + PasswordValidationCallback.PlainTextPasswordRequest plainTextPasswordRequest = + (PasswordValidationCallback.PlainTextPasswordRequest) request; + String password = users.getProperty(plainTextPasswordRequest.getUsername()); + return password != null && password.equals(plainTextPasswordRequest.getPassword()); + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandler.java new file mode 100644 index 00000000..64263a3e --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.PasswordCallback; +import com.sun.xml.wss.impl.callback.UsernameCallback; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * Simple callback handler that supplies a username and password to a username token at runtime. + *

+ * This class handles UsernameCallbacks and PasswordCallbacks, and throws an + * UnsupportedCallbackException for others + * + * @author Arjen Poutsma + * @see #setUsername(String) + * @see #setPassword(String) + */ +public class SimpleUsernamePasswordCallbackHandler extends AbstractCallbackHandler implements InitializingBean { + + private String username; + + private String password; + + public void setPassword(String password) { + this.password = password; + } + + public void setUsername(String username) { + this.username = username; + } + + public void afterPropertiesSet() throws Exception { + Assert.hasLength(username, "username must be set"); + Assert.hasLength(password, "password must be set"); + } + + protected void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof UsernameCallback) { + UsernameCallback usernameCallback = (UsernameCallback) callback; + usernameCallback.setUsername(username); + } + else if (callback instanceof PasswordCallback) { + PasswordCallback passwordCallback = (PasswordCallback) callback; + passwordCallback.setPassword(password); + } + else { + throw new UnsupportedCallbackException(callback); + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandler.java new file mode 100644 index 00000000..be6ef2fa --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandler.java @@ -0,0 +1,115 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.acegi; + +import java.io.IOException; +import java.security.cert.X509Certificate; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationException; +import org.acegisecurity.AuthenticationManager; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.x509.X509AuthenticationToken; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; + +/** + * Callback handler that validates a certificate using an Acegi AuthenticationManager. Logic based on + * Acegi's X509ProcessingFilter. + *

+ * An Acegi X509AuthenticationToken is created with the certificate as the credentials. + *

+ * The configured authentication manager is expected to supply a provider which can handle this token (usually an + * instance of X509AuthenticationProvider).

+ *

+ * This class only handles CertificateValidationCallbacks, and throws an + * UnsupportedCallbackException for others. + * + * @author Arjen Poutsma + * @see X509AuthenticationToken + * @see org.acegisecurity.providers.x509.X509AuthenticationProvider + * @see org.acegisecurity.ui.x509.X509ProcessingFilter + * @see CertificateValidationCallback + */ +public class AcegiCertificateValidationCallbackHandler extends AbstractCallbackHandler { + + private AuthenticationManager authenticationManager; + + private boolean ignoreFailure = false; + + /** + * Sets the Acegi authentication manager. Required. + */ + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public void setIgnoreFailure(boolean ignoreFailure) { + this.ignoreFailure = ignoreFailure; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(authenticationManager, "authenticationManager is required"); + } + + /** + * Handles CertificateValidationCallbacks, and throws an UnsupportedCallbackException for + * others + * + * @throws UnsupportedCallbackException when the callback is not supported + */ + protected void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof CertificateValidationCallback) { + ((CertificateValidationCallback) callback).setValidator(new AcegiCertificateValidator()); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + private class AcegiCertificateValidator implements CertificateValidationCallback.CertificateValidator { + + public boolean validate(X509Certificate certificate) + throws CertificateValidationCallback.CertificateValidationException { + boolean result; + try { + Authentication authResult = + authenticationManager.authenticate(new X509AuthenticationToken(certificate)); + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for certificate with DN [" + + certificate.getSubjectX500Principal().getName() + "] successful"); + } + SecurityContextHolder.getContext().setAuthentication(authResult); + return true; + } + catch (AuthenticationException failed) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for certificate with DN [" + + certificate.getSubjectX500Principal().getName() + "] failed: " + failed.toString()); + } + SecurityContextHolder.getContext().setAuthentication(null); + result = ignoreFailure; + } + return result; + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java new file mode 100644 index 00000000..163623ae --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandler.java @@ -0,0 +1,152 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.acegi; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import com.sun.xml.wss.impl.callback.TimestampValidationCallback; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.acegisecurity.providers.dao.UserCache; +import org.acegisecurity.providers.dao.cache.NullUserCache; +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UserDetailsService; +import org.acegisecurity.userdetails.UsernameNotFoundException; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; +import org.springframework.ws.soap.security.xwss.callback.DefaultTimestampValidator; + +/** + * Callback handler that validates a password digest using an Acegi UserDetailsService. Logic based on + * Acegi's DigestProcessingFilter. + *

+ * An Acegi UserDetailService is used to load UserDetails from. The digest of the password + * contained in this details object is then compared with the digest in the message. + *

+ * This class only handles PasswordValidationCallbacks that contain a DigestPasswordRequest, + * and throws an UnsupportedCallbackException for others. + * + * @author Arjen Poutsma + * @see UserDetailsService + * @see PasswordValidationCallback + * @see com.sun.xml.wss.impl.callback.PasswordValidationCallback.DigestPasswordRequest + * @see org.acegisecurity.ui.digestauth.DigestProcessingFilter + */ +public class AcegiDigestPasswordValidationCallbackHandler extends AbstractCallbackHandler { + + private UserCache userCache = new NullUserCache(); + + private UserDetailsService userDetailsService; + + /** + * Sets the users cache. Not required, but can benefit performance. + */ + public void setUserCache(UserCache userCache) { + this.userCache = userCache; + } + + /** + * Sets the Acegi user details service. Required. + */ + public void setUserDetailsService(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(userDetailsService, "userDetailsService is required"); + } + + /** + * Handles PasswordValidationCallbacks that contain a DigestPasswordRequest, and throws an + * UnsupportedCallbackException for others + * + * @throws UnsupportedCallbackException when the callback is not supported + */ + protected void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof PasswordValidationCallback) { + PasswordValidationCallback passwordCallback = ((PasswordValidationCallback) callback); + if (passwordCallback.getRequest() instanceof PasswordValidationCallback.DigestPasswordRequest) { + PasswordValidationCallback.DigestPasswordRequest request = + (PasswordValidationCallback.DigestPasswordRequest) passwordCallback.getRequest(); + String username = request.getUsername(); + UserDetails user = loadUserDetails(username); + if (user != null) { + request.setPassword(user.getPassword()); + } + AcegiDigestPasswordValidator validator = new AcegiDigestPasswordValidator(user); + passwordCallback.setValidator(validator); + return; + } + } + else if (callback instanceof TimestampValidationCallback) { + TimestampValidationCallback timestampCallback = (TimestampValidationCallback) callback; + timestampCallback.setValidator(new DefaultTimestampValidator()); + + } + throw new UnsupportedCallbackException(callback); + } + + private UserDetails loadUserDetails(String username) { + UserDetails user = userCache.getUserFromCache(username); + + if (user == null) { + try { + user = userDetailsService.loadUserByUsername(username); + } + catch (UsernameNotFoundException notFound) { + if (logger.isDebugEnabled()) { + logger.debug("Username '" + username + "' not found"); + } + return null; + } + userCache.putUserInCache(user); + } + return user; + } + + private class AcegiDigestPasswordValidator extends PasswordValidationCallback.DigestPasswordValidator { + + private UserDetails user; + + private AcegiDigestPasswordValidator(UserDetails user) { + this.user = user; + } + + public boolean validate(PasswordValidationCallback.Request request) + throws PasswordValidationCallback.PasswordValidationException { + if (super.validate(request)) { + UsernamePasswordAuthenticationToken authRequest = + new UsernamePasswordAuthenticationToken(user, user.getPassword()); + if (logger.isDebugEnabled()) { + logger.debug("Authentication success: " + authRequest.toString()); + } + + SecurityContextHolder.getContext().setAuthentication(authRequest); + return true; + } + else { + return false; + } + } + } + +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandler.java new file mode 100644 index 00000000..aa0d41ff --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandler.java @@ -0,0 +1,115 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.acegi; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationException; +import org.acegisecurity.AuthenticationManager; +import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; + +import org.springframework.util.Assert; +import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; + +/** + * Callback handler that validates a certificate uses an Acegi AuthenticationManager. Logic based on + * Acegi's BasicProcessingFilter. + *

+ * This handler requires an Acegi AuthenticationManager to operate. It can be set using the + * authenticationManager property. An Acegi UsernamePasswordAuthenticationToken is created + * with the username as principal and password as credentials. + *

+ * This class only handles PasswordValidationCallbacks that contain a + * PlainTextPasswordRequest, and throws an UnsupportedCallbackException for others. + * + * @author Arjen Poutsma + * @see UsernamePasswordAuthenticationToken + * @see PasswordValidationCallback + * @see com.sun.xml.wss.impl.callback.PasswordValidationCallback.PlainTextPasswordRequest + * @see org.acegisecurity.ui.basicauth.BasicProcessingFilter + */ +public class AcegiPlainTextPasswordValidationCallbackHandler extends AbstractCallbackHandler { + + private AuthenticationManager authenticationManager; + + private boolean ignoreFailure = false; + + /** + * Sets the Acegi authentication manager. Required. + */ + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public void setIgnoreFailure(boolean ignoreFailure) { + this.ignoreFailure = ignoreFailure; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(authenticationManager, "authenticationManager is required"); + } + + /** + * Handles PasswordValidationCallbacks that contain a PlainTextPasswordRequest, and throws + * an UnsupportedCallbackException for others. + * + * @throws UnsupportedCallbackException when the callback is not supported + */ + protected void handleInternal(Callback callback) throws IOException, UnsupportedCallbackException { + if (callback instanceof PasswordValidationCallback) { + PasswordValidationCallback validationCallback = (PasswordValidationCallback) callback; + if (validationCallback.getRequest() instanceof PasswordValidationCallback.PlainTextPasswordRequest) { + validationCallback.setValidator(new AcegiPlainTextPasswordValidator()); + return; + } + } + throw new UnsupportedCallbackException(callback); + } + + private class AcegiPlainTextPasswordValidator implements PasswordValidationCallback.PasswordValidator { + + public boolean validate(PasswordValidationCallback.Request request) + throws PasswordValidationCallback.PasswordValidationException { + PasswordValidationCallback.PlainTextPasswordRequest plainTextRequest = + (PasswordValidationCallback.PlainTextPasswordRequest) request; + try { + Authentication authResult = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( + plainTextRequest.getUsername(), plainTextRequest.getPassword())); + if (logger.isDebugEnabled()) { + logger.debug("Authentication success: " + authResult.toString()); + } + SecurityContextHolder.getContext().setAuthentication(authResult); + return true; + } + catch (AuthenticationException failed) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for user '" + plainTextRequest.getUsername() + "' failed: " + + failed.toString()); + } + SecurityContextHolder.getContext().setAuthentication(null); + return ignoreFailure; + } + } + } + +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/package.html b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/package.html new file mode 100644 index 00000000..10c499c8 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/acegi/package.html @@ -0,0 +1,6 @@ + + +Contains CallbackHandler implementations for XWSS that use the Acegi + Security System for Spring. + + \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/AbstractJaasValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/AbstractJaasValidationCallbackHandler.java new file mode 100644 index 00000000..c6fa32cd --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/AbstractJaasValidationCallbackHandler.java @@ -0,0 +1,53 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.jaas; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; + +/** + * Abstract base class for integrating with JAAS. Provides a login context name property. + * + * @author Arjen Poutsma + */ +public abstract class AbstractJaasValidationCallbackHandler extends AbstractCallbackHandler + implements InitializingBean { + + private String loginContextName; + + protected AbstractJaasValidationCallbackHandler() { + } + + /** + * Returns the login context name. + */ + public String getLoginContextName() { + return loginContextName; + } + + /** + * Sets the login context name. + */ + public void setLoginContextName(String loginContextName) { + this.loginContextName = loginContextName; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(loginContextName, "loginContextName is required"); + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasCertificateValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasCertificateValidationCallbackHandler.java new file mode 100644 index 00000000..118a200d --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasCertificateValidationCallbackHandler.java @@ -0,0 +1,101 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.jaas; + +import java.security.cert.X509Certificate; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; + +/** + * Provides basic support for integrating with JAAS and certificates. Requires the loginContextName to be + * set.Requires a LoginContext which handles X500Principals. + *

+ * This class only handles CertificateValidationCallbacks, and throws an + * UnsupportedCallbackException for others. + * + * @author Arjen Poutsma + * @see javax.security.auth.x500.X500Principal + * @see #setLoginContextName(String) + */ +public class JaasCertificateValidationCallbackHandler extends AbstractJaasValidationCallbackHandler { + + /** + * Handles CertificateValidationCallbacks, and throws an UnsupportedCallbackException for + * others + * + * @throws UnsupportedCallbackException when the callback is not supported + */ + protected final void handleInternal(Callback callback) throws UnsupportedCallbackException { + if (callback instanceof CertificateValidationCallback) { + ((CertificateValidationCallback) callback).setValidator(new JaasCertificateValidator()); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + + private class JaasCertificateValidator implements CertificateValidationCallback.CertificateValidator { + + public boolean validate(X509Certificate certificate) + throws CertificateValidationCallback.CertificateValidationException { + LoginContext loginContext = null; + Subject subject = new Subject(); + subject.getPrincipals().add(certificate.getSubjectX500Principal()); + try { + loginContext = new LoginContext(getLoginContextName(), subject); + } + catch (LoginException ex) { + throw new CertificateValidationCallback.CertificateValidationException(ex); + } + catch (SecurityException ex) { + throw new CertificateValidationCallback.CertificateValidationException(ex); + } + + try { + loginContext.login(); + Subject subj = loginContext.getSubject(); + if (!subj.getPrincipals().isEmpty()) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for certificate with DN [" + + certificate.getSubjectX500Principal().getName() + "] successful"); + } + return true; + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for certificate with DN [" + + certificate.getSubjectX500Principal().getName() + "] failed"); + } + return false; + } + } + catch (LoginException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for certificate with DN [" + + certificate.getSubjectX500Principal().getName() + "] failed"); + } + return false; + } + } + } +} diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandler.java b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandler.java new file mode 100644 index 00000000..63583042 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandler.java @@ -0,0 +1,123 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.jaas; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; + +import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; + +/** + * Provides basic support for integrating with JAAS and plain text passwords. + *

+ * This class only handles PasswordValidationCallbacks that contain a + * PlainTextPasswordRequest, and throws an UnsupportedCallbackException for others. + * + * @author Arjen Poutsma + * @see #getLoginContextName() + */ +public class JaasPlainTextPasswordValidationCallbackHandler extends AbstractJaasValidationCallbackHandler { + + protected JaasPlainTextPasswordValidationCallbackHandler() { + } + + /** + * Handles PasswordValidationCallbacks that contain a PlainTextPasswordRequest, and throws + * an UnsupportedCallbackException for others. + * + * @throws UnsupportedCallbackException when the callback is not supported + */ + protected final void handleInternal(Callback callback) throws UnsupportedCallbackException { + if (callback instanceof PasswordValidationCallback) { + PasswordValidationCallback validationCallback = (PasswordValidationCallback) callback; + if (validationCallback.getRequest() instanceof PasswordValidationCallback.PlainTextPasswordRequest) { + validationCallback.setValidator(new JaasPlainTextPasswordValidator()); + return; + } + } + throw new UnsupportedCallbackException(callback); + } + + private class JaasPlainTextPasswordValidator implements PasswordValidationCallback.PasswordValidator { + + public boolean validate(PasswordValidationCallback.Request request) + throws PasswordValidationCallback.PasswordValidationException { + PasswordValidationCallback.PlainTextPasswordRequest plainTextRequest = + (PasswordValidationCallback.PlainTextPasswordRequest) request; + + final String username = plainTextRequest.getUsername(); + final String password = plainTextRequest.getPassword(); + + LoginContext loginContext = null; + try { + loginContext = new LoginContext(getLoginContextName(), new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) throws UnsupportedCallbackException { + if (callback instanceof NameCallback) { + ((NameCallback) callback).setName(username); + } + else if (callback instanceof PasswordCallback) { + ((PasswordCallback) callback).setPassword(password.toCharArray()); + } + else { + throw new UnsupportedCallbackException(callback); + } + } + }); + } + catch (LoginException ex) { + throw new PasswordValidationCallback.PasswordValidationException(ex); + } + catch (SecurityException ex) { + throw new PasswordValidationCallback.PasswordValidationException(ex); + } + + try { + loginContext.login(); + Subject subject = loginContext.getSubject(); + if (!subject.getPrincipals().isEmpty()) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for user '" + username + "' successful"); + } + return true; + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for user '" + username + "' failed"); + } + return false; + } + } + catch (LoginException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Authentication request for user '" + username + "' failed"); + } + return false; + } + } + + + } +} + diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/package.html b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/package.html new file mode 100644 index 00000000..adb5dfe6 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/jaas/package.html @@ -0,0 +1,6 @@ + + +Contains CallbackHandler implementations for XWSS that use the Java Authentication and Authorization Service (JAAS). + + \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/package.html b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/package.html new file mode 100644 index 00000000..c0983d39 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/callback/package.html @@ -0,0 +1,5 @@ + + +Contains CallbackHandler implementations for XWSS. + + \ No newline at end of file diff --git a/security/src/main/java/org/springframework/ws/soap/security/xwss/package.html b/security/src/main/java/org/springframework/ws/soap/security/xwss/package.html new file mode 100644 index 00000000..b5b057f4 --- /dev/null +++ b/security/src/main/java/org/springframework/ws/soap/security/xwss/package.html @@ -0,0 +1,6 @@ + + +Contains classes for using the XML and WebServices Security WS-Security +implementation within Spring-WS. + + \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java new file mode 100644 index 00000000..40faba67 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Iterator; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.SOAPMessage; + +import junit.framework.TestCase; +import org.springframework.ws.soap.saaj.SaajSoapMessageContext; +import org.springframework.ws.transport.TransportRequest; +import org.springframework.ws.transport.TransportException; + +public class XwsSecurityInterceptorTest extends TestCase { + + private MessageFactory messageFactory; + + protected void setUp() throws Exception { + messageFactory = MessageFactory.newInstance(); + } + + public void testhandleRequest() throws Exception { + final SOAPMessage request = messageFactory.createMessage(); + final SOAPMessage validatedRequest = messageFactory.createMessage(); + XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { + protected SOAPMessage secureMessage(SOAPMessage message) throws XwsSecuritySecurementException { + fail("secure not expected"); + return null; + } + + protected SOAPMessage validateMessage(SOAPMessage message) throws XwsSecurityValidationException { + assertEquals("Invalid message", request, message); + return validatedRequest; + } + + }; + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new DummyTransportRequest(), messageFactory); + interceptor.handleRequest(context, null); + assertEquals("Invalid request", validatedRequest, context.getSaajRequest()); + } + + public void testhandleResponse() throws Exception { + final SOAPMessage response = messageFactory.createMessage(); + final SOAPMessage securedResponse = messageFactory.createMessage(); + XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { + protected SOAPMessage secureMessage(SOAPMessage message) throws XwsSecuritySecurementException { + assertEquals("Invalid message", response, message); + return securedResponse; + } + + protected SOAPMessage validateMessage(SOAPMessage message) throws XwsSecurityValidationException { + fail("validate not expected"); + return null; + } + + }; + SOAPMessage request = messageFactory.createMessage(); + SaajSoapMessageContext context = + new SaajSoapMessageContext(request, new DummyTransportRequest(), messageFactory); + context.setSaajResponse(response); + interceptor.handleResponse(context, null); + assertEquals("Invalid response", securedResponse, context.getSaajResponse()); + } + + private static class DummyTransportRequest implements TransportRequest { + + public Iterator getHeaderNames() { + return Collections.EMPTY_LIST.iterator(); + } + + public Iterator getHeaders(String name) { + return Collections.EMPTY_LIST.iterator(); + } + + public String getUrl() throws TransportException { + return null; + } + + public InputStream getInputStream() throws IOException { + return null; + } + } +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorEncryptTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorEncryptTest.java new file mode 100644 index 00000000..12475244 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorEncryptTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.xml.soap.SOAPMessage; + +import com.sun.xml.wss.impl.callback.DecryptionKeyCallback; +import com.sun.xml.wss.impl.callback.EncryptionKeyCallback; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; + +public class XwssMessageInterceptorEncryptTest extends XwssMessageInterceptorKeyStoreTestCase { + + public void testEncryptDefaultCertificate() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("encrypt-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof EncryptionKeyCallback) { + EncryptionKeyCallback keyCallback = (EncryptionKeyCallback) callback; + if (keyCallback.getRequest() instanceof EncryptionKeyCallback.AliasX509CertificateRequest) { + EncryptionKeyCallback.AliasX509CertificateRequest request = + (EncryptionKeyCallback.AliasX509CertificateRequest) keyCallback.getRequest(); + assertEquals("Invalid alias", "", request.getAlias()); + request.setX509Certificate(certificate); + } + else { + fail("Unexpected request"); + } + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("empty-soap.xml"); + SOAPMessage result = interceptor.secureMessage(message); + assertNotNull("No result returned", result); + assertXpathExists("BinarySecurityToken does not exist", + "SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:BinarySecurityToken", result); + assertXpathExists("Signature does not exist", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/xenc:EncryptedKey", result); + } + + public void testEncryptAlias() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("encrypt-alias-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof EncryptionKeyCallback) { + EncryptionKeyCallback keyCallback = (EncryptionKeyCallback) callback; + if (keyCallback.getRequest() instanceof EncryptionKeyCallback.AliasX509CertificateRequest) { + EncryptionKeyCallback.AliasX509CertificateRequest request = + (EncryptionKeyCallback.AliasX509CertificateRequest) keyCallback.getRequest(); + assertEquals("Invalid alias", "alias", request.getAlias()); + request.setX509Certificate(certificate); + } + else { + fail("Unexpected request"); + } + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("empty-soap.xml"); + SOAPMessage result = interceptor.secureMessage(message); + assertNotNull("No result returned", result); + assertXpathExists("BinarySecurityToken does not exist", + "SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:BinarySecurityToken", result); + assertXpathExists("Signature does not exist", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/xenc:EncryptedKey", result); + } + + public void testDecrypt() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("decrypt-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof DecryptionKeyCallback) { + DecryptionKeyCallback keyCallback = (DecryptionKeyCallback) callback; + if (keyCallback.getRequest() instanceof DecryptionKeyCallback.X509CertificateBasedRequest) { + DecryptionKeyCallback.X509CertificateBasedRequest request = + (DecryptionKeyCallback.X509CertificateBasedRequest) keyCallback.getRequest(); + assertEquals("Invalid certificate", certificate, request.getX509Certificate()); + request.setPrivateKey(privateKey); + } + else { + fail("Unexpected request"); + } + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("encrypted-soap.xml"); + SOAPMessage result = interceptor.validateMessage(message); + assertNotNull("No result returned", result); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", result); + } + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorKeyStoreTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorKeyStoreTestCase.java new file mode 100644 index 00000000..bf1660c6 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorKeyStoreTestCase.java @@ -0,0 +1,47 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import java.io.InputStream; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +public abstract class XwssMessageInterceptorKeyStoreTestCase extends XwssMessageInterceptorTestCase { + + protected X509Certificate certificate; + + protected PrivateKey privateKey; + + protected void onSetup() throws Exception { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream is = null; + try { + is = getClass().getResourceAsStream("test-keystore.jks"); + keyStore.load(is, "password".toCharArray()); + } + finally { + if (is != null) { + is.close(); + } + } + certificate = (X509Certificate) keyStore.getCertificate("alias"); + privateKey = (PrivateKey) keyStore.getKey("alias", "password".toCharArray()); + + } + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorSignTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorSignTest.java new file mode 100644 index 00000000..b3002a84 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorSignTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import java.security.cert.X509Certificate; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.xml.soap.SOAPMessage; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import com.sun.xml.wss.impl.callback.SignatureKeyCallback; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; + +public class XwssMessageInterceptorSignTest extends XwssMessageInterceptorKeyStoreTestCase { + + public void testSignDefaultCertificate() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("sign-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof SignatureKeyCallback) { + SignatureKeyCallback keyCallback = (SignatureKeyCallback) callback; + if (keyCallback.getRequest() instanceof SignatureKeyCallback.DefaultPrivKeyCertRequest) { + SignatureKeyCallback.DefaultPrivKeyCertRequest request = + (SignatureKeyCallback.DefaultPrivKeyCertRequest) keyCallback.getRequest(); + request.setX509Certificate(certificate); + request.setPrivateKey(privateKey); + } + else { + fail("Unexpected request"); + } + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("empty-soap.xml"); + SOAPMessage result = interceptor.secureMessage(message); + assertNotNull("No result returned", result); + assertXpathExists("BinarySecurityToken does not exist", + "SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:BinarySecurityToken", result); + assertXpathExists("Signature does not exist", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/ds:Signature", + result); + } + + public void testSignAlias() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("sign-alias-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof SignatureKeyCallback) { + SignatureKeyCallback keyCallback = (SignatureKeyCallback) callback; + if (keyCallback.getRequest() instanceof SignatureKeyCallback.AliasPrivKeyCertRequest) { + SignatureKeyCallback.AliasPrivKeyCertRequest request = + (SignatureKeyCallback.AliasPrivKeyCertRequest) keyCallback.getRequest(); + assertEquals("Invalid alias", "alias", request.getAlias()); + request.setX509Certificate(certificate); + request.setPrivateKey(privateKey); + } + else { + fail("Unexpected request"); + } + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("empty-soap.xml"); + SOAPMessage result = interceptor.secureMessage(message); + assertNotNull("No result returned", result); + assertXpathExists("BinarySecurityToken does not exist", + "SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:BinarySecurityToken", result); + assertXpathExists("Signature does not exist", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/ds:Signature", + result); + } + + public void testValidateCertificate() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("requireSignature-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof CertificateValidationCallback) { + CertificateValidationCallback validationCallback = (CertificateValidationCallback) callback; + validationCallback.setValidator(new CertificateValidationCallback.CertificateValidator() { + public boolean validate(X509Certificate passedCertificate) { + assertEquals("Invalid certificate", certificate, passedCertificate); + return true; + } + }); + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("signed-soap.xml"); + SOAPMessage result = interceptor.validateMessage(message); + assertNotNull("No result returned", result); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", result); + } + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorTestCase.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorTestCase.java new file mode 100644 index 00000000..87199b43 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorTestCase.java @@ -0,0 +1,97 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; + +import junit.framework.TestCase; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import org.springframework.xml.xpath.XPathExpression; +import org.springframework.xml.xpath.XPathExpressionFactory; + +public abstract class XwssMessageInterceptorTestCase extends TestCase { + + protected XwsSecurityInterceptor interceptor; + + private MessageFactory messageFactory; + + private Map namespaces; + + protected final void setUp() throws Exception { + interceptor = new XwsSecurityInterceptor(); + messageFactory = MessageFactory.newInstance(); + namespaces = new HashMap(); + namespaces.put("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/"); + namespaces.put("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); + namespaces.put("ds", "http://www.w3.org/2000/09/xmldsig#"); + namespaces.put("xenc", "http://www.w3.org/2001/04/xmlenc#"); + onSetup(); + } + + protected void assertXpathEvaluatesTo(String message, + String expectedValue, + String xpathExpression, + SOAPMessage soapMessage) { + XPathExpression expression = XPathExpressionFactory.createXPathExpression(xpathExpression, namespaces); + Document document = soapMessage.getSOAPPart(); + String actualValue = expression.evaluateAsString(document); + assertEquals(message, expectedValue, actualValue); + } + + protected void assertXpathExists(String message, String xpathExpression, SOAPMessage soapMessage) { + XPathExpression expression = XPathExpressionFactory.createXPathExpression(xpathExpression, namespaces); + Document document = soapMessage.getSOAPPart(); + Node node = expression.evaluateAsNode(document); + assertNotNull(message, node); + } + + protected void assertXpathNotExists(String message, String xpathExpression, SOAPMessage soapMessage) { + XPathExpression expression = XPathExpressionFactory.createXPathExpression(xpathExpression, namespaces); + Document document = soapMessage.getSOAPPart(); + Node node = expression.evaluateAsNode(document); + assertNull(message, node); + } + + protected SOAPMessage loadSaajMessage(String fileName) throws SOAPException, IOException { + MimeHeaders mimeHeaders = new MimeHeaders(); + mimeHeaders.addHeader("Content-Type", "text/xml"); + InputStream is = null; + try { + is = getClass().getResourceAsStream(fileName); + assertNotNull("Could not load SAAJ message with name [" + fileName + "]", is); + return messageFactory.createMessage(mimeHeaders, is); + } + finally { + if (is != null) { + is.close(); + } + } + } + + protected void onSetup() throws Exception { + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorUsernameTokenTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorUsernameTokenTest.java new file mode 100644 index 00000000..ac2d1ccd --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwssMessageInterceptorUsernameTokenTest.java @@ -0,0 +1,170 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.xml.soap.SOAPMessage; + +import com.sun.xml.wss.impl.callback.PasswordCallback; +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import com.sun.xml.wss.impl.callback.TimestampValidationCallback; +import com.sun.xml.wss.impl.callback.UsernameCallback; +import org.springframework.core.io.ClassPathResource; +import org.springframework.ws.soap.security.xwss.callback.AbstractCallbackHandler; + +public class XwssMessageInterceptorUsernameTokenTest extends XwssMessageInterceptorTestCase { + + public void testAddUsernameTokenDigest() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("usernameToken-digest-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof UsernameCallback) { + ((UsernameCallback) callback).setUsername("Bert"); + } + else if (callback instanceof PasswordCallback) { + PasswordCallback passwordCallback = (PasswordCallback) callback; + passwordCallback.setPassword("Ernie"); + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("empty-soap.xml"); + SOAPMessage result = interceptor.secureMessage(message); + assertNotNull("No result returned", result); + assertXpathEvaluatesTo("Invalid Username", + "Bert", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Username/text()", + result); + assertXpathExists("Password does not exist", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Password[@Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest']", + result); + } + + public void testAddUsernameTokenPlainText() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("usernameToken-plainText-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof UsernameCallback) { + ((UsernameCallback) callback).setUsername("Bert"); + } + else if (callback instanceof PasswordCallback) { + PasswordCallback passwordCallback = (PasswordCallback) callback; + passwordCallback.setPassword("Ernie"); + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("empty-soap.xml"); + SOAPMessage result = interceptor.secureMessage(message); + assertNotNull("No result returned", result); + assertXpathEvaluatesTo("Invalid Username", + "Bert", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Username/text()", + result); + assertXpathEvaluatesTo("Invalid Password", + "Ernie", + "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/wsse:UsernameToken/wsse:Password[@Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText']/text()", + result); + } + + public void testValidateUsernameTokenPlainText() throws Exception { + interceptor + .setPolicyConfiguration(new ClassPathResource("requireUsernameToken-plainText-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof PasswordValidationCallback) { + PasswordValidationCallback validationCallback = (PasswordValidationCallback) callback; + validationCallback.setValidator(new PasswordValidationCallback.PasswordValidator() { + public boolean validate(PasswordValidationCallback.Request request) { + if (request instanceof PasswordValidationCallback.PlainTextPasswordRequest) { + PasswordValidationCallback.PlainTextPasswordRequest passwordRequest = + (PasswordValidationCallback.PlainTextPasswordRequest) request; + assertEquals("Invalid username", "Bert", passwordRequest.getUsername()); + assertEquals("Invalid password", "Ernie", passwordRequest.getPassword()); + return true; + } + else { + fail("Unexpected request"); + return false; + } + } + }); + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("usernameTokenPlainText-soap.xml"); + SOAPMessage result = interceptor.validateMessage(message); + assertNotNull("No result returned", result); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", result); + } + + public void testValidateUsernameTokenDigest() throws Exception { + interceptor.setPolicyConfiguration(new ClassPathResource("requireUsernameToken-digest-config.xml", getClass())); + CallbackHandler handler = new AbstractCallbackHandler() { + + protected void handleInternal(Callback callback) { + if (callback instanceof PasswordValidationCallback) { + PasswordValidationCallback validationCallback = (PasswordValidationCallback) callback; + if (validationCallback.getRequest() instanceof PasswordValidationCallback.DigestPasswordRequest) { + PasswordValidationCallback.DigestPasswordRequest passwordRequest = + (PasswordValidationCallback.DigestPasswordRequest) validationCallback.getRequest(); + assertEquals("Invalid username", "Bert", passwordRequest.getUsername()); + passwordRequest.setPassword("Ernie"); + validationCallback.setValidator(new PasswordValidationCallback.DigestPasswordValidator()); + } + else { + fail("Unexpected request"); + } + } + else if (callback instanceof TimestampValidationCallback) { + TimestampValidationCallback validationCallback = (TimestampValidationCallback) callback; + validationCallback.setValidator(new TimestampValidationCallback.TimestampValidator() { + public void validate(TimestampValidationCallback.Request request) { + } + }); + } + else { + fail("Unexpected callback"); + } + } + }; + interceptor.setCallbackHandler(handler); + interceptor.afterPropertiesSet(); + SOAPMessage message = loadSaajMessage("usernameTokenDigest-soap.xml"); + SOAPMessage result = interceptor.validateMessage(message); + assertNotNull("No result returned", result); + assertXpathNotExists("Security Header not removed", "/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security", result); + } + +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChainTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChainTest.java new file mode 100644 index 00000000..13d40537 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/CallbackHandlerChainTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import junit.framework.TestCase; + +public class CallbackHandlerChainTest extends TestCase { + + private CallbackHandler supported = new CallbackHandler() { + public void handle(Callback[] callbacks) { + } + }; + + private CallbackHandler unsupported = new CallbackHandler() { + public void handle(Callback[] callbacks) throws UnsupportedCallbackException { + throw new UnsupportedCallbackException(callbacks[0]); + } + }; + + private Callback callback = new Callback() { + }; + + protected void setUp() throws Exception { + } + + public void testSupported() throws Exception { + CallbackHandlerChain chain = new CallbackHandlerChain(new CallbackHandler[]{supported}); + chain.handle(new Callback[]{callback}); + } + + public void testUnsupportedNormal() throws Exception { + CallbackHandlerChain chain = new CallbackHandlerChain(new CallbackHandler[]{unsupported, supported}); + chain.handle(new Callback[]{callback}); + } + + public void testUnsupported() throws Exception { + CallbackHandlerChain chain = new CallbackHandlerChain(new CallbackHandler[]{unsupported}); + try { + chain.handle(new Callback[]{callback}); + fail("Expected UnsupportedCallbackException"); + } + catch (UnsupportedCallbackException ex) { + // expected behavior + } + } +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/KeyStoreCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/KeyStoreCallbackHandlerTest.java new file mode 100644 index 00000000..baef0e78 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/KeyStoreCallbackHandlerTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import junit.framework.TestCase; + +public class KeyStoreCallbackHandlerTest extends TestCase { + + private KeyStoreCallbackHandler handler; + + protected void setUp() throws Exception { + handler = new KeyStoreCallbackHandler(); + } + + public void testLoadDefaultTrustStore() throws Exception { + System.setProperty("javax.net.ssl.trustStore", + "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/"); + handler.loadDefaultTrustStore(); + } +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandlerTest.java new file mode 100644 index 00000000..ab195eb4 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SimplePasswordValidationCallbackHandlerTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import java.util.Properties; + +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import junit.framework.TestCase; + +public class SimplePasswordValidationCallbackHandlerTest extends TestCase { + + private SimplePasswordValidationCallbackHandler handler; + + protected void setUp() throws Exception { + handler = new SimplePasswordValidationCallbackHandler(); + Properties users = new Properties(); + users.setProperty("Bert", "Ernie"); + handler.setUsers(users); + } + + public void testPlainTextPasswordValid() throws Exception { + PasswordValidationCallback.PlainTextPasswordRequest request = + new PasswordValidationCallback.PlainTextPasswordRequest("Bert", "Ernie"); + PasswordValidationCallback callback = new PasswordValidationCallback(request); + handler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertTrue("Not authenticated", authenticated); + } + + public void testPlainTextPasswordInvalid() throws Exception { + PasswordValidationCallback.PlainTextPasswordRequest request = + new PasswordValidationCallback.PlainTextPasswordRequest("Bert", "Big bird"); + PasswordValidationCallback callback = new PasswordValidationCallback(request); + handler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + } + + public void testPlainTextPasswordNoSuchUser() throws Exception { + PasswordValidationCallback.PlainTextPasswordRequest request = + new PasswordValidationCallback.PlainTextPasswordRequest("Big bird", "Bert"); + PasswordValidationCallback callback = new PasswordValidationCallback(request); + handler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + } + + public void testDigestPasswordValid() throws Exception { + String username = "Bert"; + String nonce = "9mdsYDCrjjYRur0rxzYt2oD7"; + String passwordDigest = "kwNstEaiFOrI7B31j7GuETYvdgk="; + String creationTime = "2006-06-01T23:48:42Z"; + PasswordValidationCallback.DigestPasswordRequest request = + new PasswordValidationCallback.DigestPasswordRequest(username, passwordDigest, nonce, creationTime); + PasswordValidationCallback callback = new PasswordValidationCallback(request); + handler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + + } + + public void testDigestPasswordInvalid() throws Exception { + String username = "Bert"; + String nonce = "9mdsYDCrjjYRur0rxzYt2oD7"; + String passwordDigest = "kwNstEaiFOrI7B31j7GuETYvdgk"; + String creationTime = "2006-06-01T23:48:42Z"; + PasswordValidationCallback.DigestPasswordRequest request = + new PasswordValidationCallback.DigestPasswordRequest(username, passwordDigest, nonce, creationTime); + PasswordValidationCallback callback = new PasswordValidationCallback(request); + handler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + + } +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandlerTest.java new file mode 100644 index 00000000..0cc1041c --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/SimpleUsernamePasswordCallbackHandlerTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback; + +import com.sun.xml.wss.impl.callback.PasswordCallback; +import com.sun.xml.wss.impl.callback.UsernameCallback; +import junit.framework.TestCase; + +public class SimpleUsernamePasswordCallbackHandlerTest extends TestCase { + + private SimpleUsernamePasswordCallbackHandler handler; + + protected void setUp() throws Exception { + handler = new SimpleUsernamePasswordCallbackHandler(); + handler.setUsername("Bert"); + handler.setPassword("Ernie"); + } + + public void testUsernameCallback() throws Exception { + UsernameCallback usernameCallback = new UsernameCallback(); + handler.handleInternal(usernameCallback); + assertEquals("Invalid username", "Bert", usernameCallback.getUsername()); + } + + public void testPasswordCallback() throws Exception { + PasswordCallback passwordCallback = new PasswordCallback(); + handler.handleInternal(passwordCallback); + assertEquals("Invalid username", "Ernie", passwordCallback.getPassword()); + } +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandlerTest.java new file mode 100644 index 00000000..e8076e84 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiCertificateValidationCallbackHandlerTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.acegi; + +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import junit.framework.TestCase; +import org.acegisecurity.AuthenticationManager; +import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.providers.TestingAuthenticationToken; +import org.acegisecurity.providers.x509.X509AuthenticationToken; +import org.easymock.MockControl; + +import org.springframework.core.io.ClassPathResource; + +public class AcegiCertificateValidationCallbackHandlerTest extends TestCase { + + private AcegiCertificateValidationCallbackHandler callbackHandler; + + private MockControl control; + + private AuthenticationManager mock; + + private X509Certificate certificate; + + private CertificateValidationCallback callback; + + protected void setUp() throws Exception { + callbackHandler = new AcegiCertificateValidationCallbackHandler(); + control = MockControl.createControl(AuthenticationManager.class); + mock = (AuthenticationManager) control.getMock(); + callbackHandler.setAuthenticationManager(mock); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream is = null; + try { + is = new ClassPathResource("/org/springframework/ws/soap/security/xwss/test-keystore.jks").getInputStream(); + keyStore.load(is, "password".toCharArray()); + } + finally { + if (is != null) { + is.close(); + } + } + certificate = (X509Certificate) keyStore.getCertificate("alias"); + callback = new CertificateValidationCallback(certificate); + } + + public void testValidateCertificateValid() throws Exception { + mock.authenticate(new X509AuthenticationToken(certificate)); + control.setMatcher(MockControl.ALWAYS_MATCHER); + control.setReturnValue(new TestingAuthenticationToken(certificate, null, new GrantedAuthority[0])); + control.replay(); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertTrue("Not authenticated", authenticated); + control.verify(); + } + + public void testValidateCertificateInvalid() throws Exception { + mock.authenticate(new X509AuthenticationToken(certificate)); + control.setMatcher(MockControl.ALWAYS_MATCHER); + control.setThrowable(new BadCredentialsException("")); + control.replay(); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + control.verify(); + } + +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java new file mode 100644 index 00000000..0742a944 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiDigestPasswordValidationCallbackHandlerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.acegi; + +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import junit.framework.TestCase; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.userdetails.User; +import org.acegisecurity.userdetails.UserDetailsService; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.easymock.MockControl; + +public class AcegiDigestPasswordValidationCallbackHandlerTest extends TestCase { + + private AcegiDigestPasswordValidationCallbackHandler callbackHandler; + + private MockControl control; + + private UserDetailsService mock; + + private String username; + + private String password; + + private PasswordValidationCallback callback; + + protected void setUp() throws Exception { + callbackHandler = new AcegiDigestPasswordValidationCallbackHandler(); + control = MockControl.createControl(UserDetailsService.class); + mock = (UserDetailsService) control.getMock(); + callbackHandler.setUserDetailsService(mock); + username = "Bert"; + password = "Ernie"; + String nonce = "9mdsYDCrjjYRur0rxzYt2oD7"; + String passwordDigest = "kwNstEaiFOrI7B31j7GuETYvdgk="; + String creationTime = "2006-06-01T23:48:42Z"; + PasswordValidationCallback.DigestPasswordRequest request = + new PasswordValidationCallback.DigestPasswordRequest(username, passwordDigest, nonce, creationTime); + callback = new PasswordValidationCallback(request); + } + + public void testAuthenticateUserDigestUserNotFound() throws Exception { + control.expectAndThrow(mock.loadUserByUsername(username), new UsernameNotFoundException(username)); + control.replay(); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + control.verify(); + } + + public void testAuthenticateUserDigestValid() throws Exception { + User user = new User(username, password, true, true, true, true, new GrantedAuthority[0]); + control.expectAndReturn(mock.loadUserByUsername(username), user); + control.replay(); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertTrue("Not authenticated", authenticated); + control.verify(); + } + + public void testAuthenticateUserDigestValidInvalid() throws Exception { + User user = new User(username, "Big bird", true, true, true, true, new GrantedAuthority[0]); + control.expectAndReturn(mock.loadUserByUsername(username), user); + control.replay(); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + control.verify(); + } +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandlerTest.java new file mode 100644 index 00000000..353dcd44 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/acegi/AcegiPlainTextPasswordValidationCallbackHandlerTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.acegi; + +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import junit.framework.TestCase; +import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationManager; +import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.providers.TestingAuthenticationToken; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.easymock.MockControl; + +public class AcegiPlainTextPasswordValidationCallbackHandlerTest extends TestCase { + + private AcegiPlainTextPasswordValidationCallbackHandler callbackHandler; + + private MockControl control; + + private AuthenticationManager mock; + + private PasswordValidationCallback callback; + + private String username; + + private String password; + + protected void setUp() throws Exception { + callbackHandler = new AcegiPlainTextPasswordValidationCallbackHandler(); + control = MockControl.createControl(AuthenticationManager.class); + mock = (AuthenticationManager) control.getMock(); + callbackHandler.setAuthenticationManager(mock); + username = "Bert"; + password = "Ernie"; + PasswordValidationCallback.PlainTextPasswordRequest request = + new PasswordValidationCallback.PlainTextPasswordRequest(username, password); + callback = new PasswordValidationCallback(request); + } + + public void testAuthenticateUserPlainTextValid() throws Exception { + Authentication authResult = new TestingAuthenticationToken(username, password, new GrantedAuthority[0]); + control.expectAndReturn(mock.authenticate(new UsernamePasswordAuthenticationToken(username, password)), + authResult); + control.replay(); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertTrue("Not authenticated", authenticated); + control.verify(); + } + + public void testAuthenticateUserPlainTextInvalid() throws Exception { + control.expectAndThrow(mock.authenticate(new UsernamePasswordAuthenticationToken(username, password)), + new BadCredentialsException("")); + control.replay(); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + control.verify(); + } + +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/CertificateLoginModule.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/CertificateLoginModule.java new file mode 100644 index 00000000..9ab46094 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/CertificateLoginModule.java @@ -0,0 +1,80 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.jaas; + +import java.security.Principal; +import java.util.Iterator; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; +import javax.security.auth.x500.X500Principal; + +public class CertificateLoginModule implements LoginModule { + + private Subject subject; + + private boolean loginSuccessful = false; + + public boolean abort() { + return true; + } + + public boolean commit() { + if (!loginSuccessful) { + subject.getPrincipals().clear(); + subject.getPrivateCredentials().clear(); + return false; + } + return true; + } + + public void initialize(Subject subject, + CallbackHandler callbackHandler, + java.util.Map sharedState, + java.util.Map options) { + this.subject = subject; + } + + public boolean login() throws LoginException { + if (subject == null) { + return false; + } + + String name = getName(subject); + + loginSuccessful = "CN=Arjen Poutsma,OU=Spring-WS,O=Interface21,L=Amsterdam,ST=Unknown,C=NL".equals(name); + return loginSuccessful; + } + + public boolean logout() { + subject.getPrincipals().clear(); + subject.getPrivateCredentials().clear(); + return true; + } + + private String getName(Subject subject) { + for (Iterator iterator = subject.getPrincipals().iterator(); iterator.hasNext();) { + Principal principal = (Principal) iterator.next(); + if (principal instanceof X500Principal) { + return ((X500Principal) principal).getName(); + } + } + return null; + } +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasCertificateValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasCertificateValidationCallbackHandlerTest.java new file mode 100644 index 00000000..acc308f5 --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasCertificateValidationCallbackHandlerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.jaas; + +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +import com.sun.xml.wss.impl.callback.CertificateValidationCallback; +import junit.framework.TestCase; + +import org.springframework.core.io.ClassPathResource; + +public class JaasCertificateValidationCallbackHandlerTest extends TestCase { + + private JaasCertificateValidationCallbackHandler callbackHandler; + + private CertificateValidationCallback callback; + + protected void setUp() throws Exception { + System.setProperty("java.security.auth.login.config", getClass().getResource("jaas.config").toString()); + callbackHandler = new JaasCertificateValidationCallbackHandler(); + callbackHandler.setLoginContextName("Certificate"); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream is = null; + try { + is = new ClassPathResource("/org/springframework/ws/soap/security/xwss/test-keystore.jks").getInputStream(); + keyStore.load(is, "password".toCharArray()); + } + finally { + if (is != null) { + is.close(); + } + } + X509Certificate certificate = (X509Certificate) keyStore.getCertificate("alias"); + callback = new CertificateValidationCallback(certificate); + } + + public void testValidateCertificateValid() throws Exception { + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertTrue("Not authenticated", authenticated); + } + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandlerTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandlerTest.java new file mode 100644 index 00000000..c3da562f --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/JaasPlainTextPasswordValidationCallbackHandlerTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.jaas; + +import com.sun.xml.wss.impl.callback.PasswordValidationCallback; +import junit.framework.TestCase; + +public class JaasPlainTextPasswordValidationCallbackHandlerTest extends TestCase { + + private JaasPlainTextPasswordValidationCallbackHandler callbackHandler; + + protected void setUp() throws Exception { + System.setProperty("java.security.auth.login.config", getClass().getResource("jaas.config").toString()); + callbackHandler = new JaasPlainTextPasswordValidationCallbackHandler(); + callbackHandler.setLoginContextName("PlainText"); + } + + public void testAuthenticateUserPlainTextValid() throws Exception { + PasswordValidationCallback.PlainTextPasswordRequest request = + new PasswordValidationCallback.PlainTextPasswordRequest("Bert", "Ernie"); + PasswordValidationCallback callback = new PasswordValidationCallback(request); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertTrue("Not authenticated", authenticated); + } + + public void testAuthenticateUserPlainTextInvalid() throws Exception { + PasswordValidationCallback.PlainTextPasswordRequest request = + new PasswordValidationCallback.PlainTextPasswordRequest("Bert", "Big bird"); + PasswordValidationCallback callback = new PasswordValidationCallback(request); + callbackHandler.handleInternal(callback); + boolean authenticated = callback.getResult(); + assertFalse("Authenticated", authenticated); + } + +} \ No newline at end of file diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/PlainTextLoginModule.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/PlainTextLoginModule.java new file mode 100644 index 00000000..ce112bbc --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/PlainTextLoginModule.java @@ -0,0 +1,134 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.jaas; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +public class PlainTextLoginModule implements LoginModule { + + private Subject subject; + + private CallbackHandler callbackHandler; + + private boolean success; + + private List principals = new ArrayList(); + + public boolean abort() { + success = false; + logout(); + return true; + } + + public boolean commit() throws LoginException { + if (success) { + if (subject.isReadOnly()) { + throw new LoginException("Subject is read-only"); + } + try { + subject.getPrincipals().addAll(principals); + principals.clear(); + return true; + } + catch (Exception e) { + throw new LoginException(e.getMessage()); + } + } + else { + principals.clear(); + } + return true; + } + + public void initialize(Subject subject, + CallbackHandler callbackHandler, + java.util.Map sharedState, + java.util.Map options) { + this.subject = subject; + this.callbackHandler = callbackHandler; + } + + public boolean login() throws LoginException { + if (callbackHandler == null) { + return false; + } + try { + NameCallback nameCallback = new NameCallback("Username: "); + PasswordCallback passwordCallback = new PasswordCallback("Password: ", false); + Callback[] callbacks = new Callback[]{nameCallback, passwordCallback}; + + callbackHandler.handle(callbacks); + + String username = nameCallback.getName(); + String password = new String(passwordCallback.getPassword()); + + ((PasswordCallback) callbacks[1]).clearPassword(); + + success = validate(username, password); + + callbacks[0] = null; + callbacks[1] = null; + + if (!success) { + throw new LoginException("Authentication failed: Password does not match"); + } + + return true; + } + catch (LoginException ex) { + throw ex; + } + catch (Exception ex) { + success = false; + throw new LoginException(ex.getMessage()); + } + } + + private boolean validate(String username, String password) { + if ("Bert".equals(username) && "Ernie".equals(password)) { + this.principals.add(new SimplePrincipal(username)); + return true; + } + else { + return false; + } + } + + public boolean logout() { + principals.clear(); + + Iterator iterator = subject.getPrincipals(SimplePrincipal.class).iterator(); + while (iterator.hasNext()) { + SimplePrincipal principal = (SimplePrincipal) iterator.next(); + subject.getPrincipals().remove(principal); + } + + return true; + } + + +} diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/SimplePrincipal.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/SimplePrincipal.java new file mode 100644 index 00000000..2441280f --- /dev/null +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/callback/jaas/SimplePrincipal.java @@ -0,0 +1,51 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.ws.soap.security.xwss.callback.jaas; + +import java.security.Principal; + +public final class SimplePrincipal implements Principal { + + private String name; + + public SimplePrincipal() { + name = ""; + } + + public SimplePrincipal(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public int hashCode() { + return name.hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof SimplePrincipal)) { + return false; + } + return name.equals(((SimplePrincipal) o).name); + } + + public String toString() { + return name; + } +} \ No newline at end of file diff --git a/xml/pom.xml b/xml/pom.xml new file mode 100644 index 00000000..6e738850 --- /dev/null +++ b/xml/pom.xml @@ -0,0 +1,36 @@ + + + + spring-ws + org.springframework.ws + 1.0-m3-SNAPSHOT + + 4.0.0 + spring-xml + jar + Spring XML + Various XML support classes for Spring Web Services + + + + stax + stax-api + true + + + xalan + xalan + true + + + jaxen + jaxen + true + + + woodstox + wstx-asl + test + + + diff --git a/xml/src/etc/test-resources/log4j.properties b/xml/src/etc/test-resources/log4j.properties new file mode 100644 index 00000000..17f4d6bf --- /dev/null +++ b/xml/src/etc/test-resources/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootCategory=INFO, stdout +log4j.logger.org.springframework.xml=DEBUG + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/xml/src/etc/test-resources/org/springframework/xml/validation/invalidDocument.xml b/xml/src/etc/test-resources/org/springframework/xml/validation/invalidDocument.xml new file mode 100644 index 00000000..a7551338 --- /dev/null +++ b/xml/src/etc/test-resources/org/springframework/xml/validation/invalidDocument.xml @@ -0,0 +1,4 @@ + + + 20 + \ No newline at end of file diff --git a/xml/src/etc/test-resources/org/springframework/xml/validation/schema.xsd b/xml/src/etc/test-resources/org/springframework/xml/validation/schema.xsd new file mode 100644 index 00000000..60d338c5 --- /dev/null +++ b/xml/src/etc/test-resources/org/springframework/xml/validation/schema.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/xml/src/etc/test-resources/org/springframework/xml/validation/validDocument.xml b/xml/src/etc/test-resources/org/springframework/xml/validation/validDocument.xml new file mode 100644 index 00000000..ccd9449b --- /dev/null +++ b/xml/src/etc/test-resources/org/springframework/xml/validation/validDocument.xml @@ -0,0 +1,5 @@ + + + 42 + 10 + \ No newline at end of file diff --git a/xml/src/etc/test-resources/org/springframework/xml/xpath/namespaces.xml b/xml/src/etc/test-resources/org/springframework/xml/xpath/namespaces.xml new file mode 100644 index 00000000..bf3da70a --- /dev/null +++ b/xml/src/etc/test-resources/org/springframework/xml/xpath/namespaces.xml @@ -0,0 +1,8 @@ + + + + text + 42.0 + 1 + + \ No newline at end of file diff --git a/xml/src/etc/test-resources/org/springframework/xml/xpath/nonamespaces.xml b/xml/src/etc/test-resources/org/springframework/xml/xpath/nonamespaces.xml new file mode 100644 index 00000000..5072aac3 --- /dev/null +++ b/xml/src/etc/test-resources/org/springframework/xml/xpath/nonamespaces.xml @@ -0,0 +1,8 @@ + + + + text + 42.0 + 1 + + \ No newline at end of file diff --git a/xml/src/main/java/org/springframework/xml/JaxpVersion.java b/xml/src/main/java/org/springframework/xml/JaxpVersion.java new file mode 100644 index 00000000..f59127ca --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/JaxpVersion.java @@ -0,0 +1,80 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml; + +/** + * Helper class used to find the current version of JAXP. We cannot depend on the Java version, since JAXP can be + * upgraded idenpendantly of the Java version. + *

+ * Only distinguishes between JAXP 1.0, 1.1, 1.3, and 1.4, since JAXP 1.2 was a maintenance release with no new + * classes. + * + * @author Arjen Poutsma + */ +public abstract class JaxpVersion { + + public static final int JAXP_10 = 0; + + public static final int JAXP_11 = 1; + + public static final int JAXP_13 = 3; + + public static final int JAXP_14 = 4; + + private static final String JAXP_11_CLASS_NAME = "javax.xml.transform.Transformer"; + + private static final String JAXP_13_CLASS_NAME = "javax.xml.xpath.XPath"; + + private static final String JAXP_14_CLASS_NAME = "javax.xml.transform.stax.StAXSource"; + + private static int jaxpVersion = JAXP_10; + + static { + try { + Class.forName(JAXP_14_CLASS_NAME); + jaxpVersion = JAXP_14; + } + catch (ClassNotFoundException ex1) { + try { + Class.forName(JAXP_13_CLASS_NAME); + jaxpVersion = JAXP_13; + } + catch (ClassNotFoundException ex2) { + try { + Class.forName(JAXP_11_CLASS_NAME); + jaxpVersion = JAXP_11; + } + catch (ClassNotFoundException ex3) { + // default to JAXP 1.0 + } + } + } + } + + /** + * Gets the major JAXP version. This means we can do things like if (getJaxpVersion() < JAXP_13). + * + * @return a code comparable to the JAXP_XX codes in this class + * @see #JAXP_10 + * @see #JAXP_11 + * @see #JAXP_13 + * @see #JAXP_14 + */ + public static int getJaxpVersion() { + return jaxpVersion; + } +} \ No newline at end of file diff --git a/xml/src/main/java/org/springframework/xml/XmlException.java b/xml/src/main/java/org/springframework/xml/XmlException.java new file mode 100644 index 00000000..d70fbdfd --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/XmlException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml; + +import org.springframework.core.NestedRuntimeException; + +/** + * Root of the hierarchy of XML exception. + * + * @author Arjen Poutsma + */ +public abstract class XmlException extends NestedRuntimeException { + + /** + * Constructs a new instance of the XmlException with the specific detail message. + * + * @param message the detail message + */ + protected XmlException(String message) { + super(message); + } + + /** + * Constructs a new instance of the XmlException with the specific detail message and exception. + * + * @param message the detail message + * @param throwable the wrapped exception + */ + protected XmlException(String message, Throwable throwable) { + super(message, throwable); + } +} diff --git a/xml/src/main/java/org/springframework/xml/namespace/QNameEditor.java b/xml/src/main/java/org/springframework/xml/namespace/QNameEditor.java new file mode 100644 index 00000000..f6df3bde --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/namespace/QNameEditor.java @@ -0,0 +1,72 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.xml.namespace; + +import java.beans.PropertyEditorSupport; +import javax.xml.namespace.QName; + +import org.springframework.util.StringUtils; + +/** + * PropertyEditor for javax.xml.namespace.QName, to populate a property of type QName from a String value. + *

+ * Expects the syntax + *

+ * localPart
+ * 
+ * or + *
+ * {namespace}localPart
+ * 
+ * or + *
+ * {namespace}prefix:localPart
+ * 
+ * This resembles the toString() representation of QName itself, but allows for prefixes to be + * specified as well. + * + * @author Arjen Poutsma + * @see javax.xml.namespace.QName + * @see javax.xml.namespace.QName#toString() + * @see javax.xml.namespace.QName#valueOf(String) + */ +public class QNameEditor extends PropertyEditorSupport { + + public void setAsText(String text) throws IllegalArgumentException { + setValue(QNameUtils.parseQNameString(text)); + } + + public String getAsText() { + Object value = getValue(); + if (value == null || !(value instanceof QName)) { + return ""; + } + else { + QName qName = (QName) value; + String prefix = QNameUtils.getPrefix(qName); + if (StringUtils.hasLength(qName.getNamespaceURI()) && StringUtils.hasLength(prefix)) { + return "{" + qName.getNamespaceURI() + "}" + prefix + ":" + qName.getLocalPart(); + } + else if (StringUtils.hasLength(qName.getNamespaceURI())) { + return "{" + qName.getNamespaceURI() + "}" + qName.getLocalPart(); + } + else { + return qName.getLocalPart(); + } + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/namespace/QNameUtils.java b/xml/src/main/java/org/springframework/xml/namespace/QNameUtils.java new file mode 100644 index 00000000..f6b39dbc --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/namespace/QNameUtils.java @@ -0,0 +1,189 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.xml.namespace; + +import javax.xml.namespace.QName; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.w3c.dom.Node; + +/** + * Helper class for using javax.xml.namespace.QName. + * + * @author Arjen Poutsma + * @see javax.xml.namespace.QName + */ +public abstract class QNameUtils { + + private static boolean qNameHasPrefix; + + static { + try { + QName.class.getDeclaredConstructor(new Class[]{String.class, String.class, String.class}); + qNameHasPrefix = true; + } + catch (NoSuchMethodException e) { + qNameHasPrefix = false; + } + } + + /** + * Creates a new QName with the given parameters. Sets the prefix if possible, i.e. if the + * QName(String, String, String) constructor can be found. If this constructor is not available (as is + * the case on older implementations of JAX-RPC), the prefix is ignored. + * + * @param namespaceUri namespace URI of the QName + * @param localPart local part of the QName + * @param prefix prefix of the QName. May be ignored. + * @return the created QName + * @see QName#QName(String, String, String) + */ + public static QName createQName(String namespaceUri, String localPart, String prefix) { + if (qNameHasPrefix) { + return new QName(namespaceUri, localPart, prefix); + } + else { + return new QName(namespaceUri, localPart); + } + } + + /** + * Returns the prefix of the given QName. Returns the prefix if available, i.e. if the + * QName.getPrefix() method can be found. If this method is not available (as is the case on older + * implementations of JAX-RPC), an empty string is returned. + * + * @param qName the QName to return the prefix from + * @return the prefix, if available, or an empty string + * @see javax.xml.namespace.QName#getPrefix() + */ + public static String getPrefix(QName qName) { + return qNameHasPrefix ? qName.getPrefix() : ""; + } + + /** + * Validates the given String as a QName + * + * @param text the qualified name + * @return true if valid, false otherwise + */ + public static boolean validateQName(String text) { + if (!StringUtils.hasLength(text)) { + return false; + } + if (text.charAt(0) == '{') { + int i = text.indexOf('}'); + + if (i == -1 || i == text.length() - 1) { + return false; + } + } + return true; + } + + /** + * Returns the qualified name of the given DOM Node. + * + * @param node the node + * @return the qualified name of the node + */ + public static QName getQNameForNode(Node node) { + if (node.getNamespaceURI() != null && node.getPrefix() != null && node.getLocalName() != null) { + return createQName(node.getNamespaceURI(), node.getLocalName(), node.getPrefix()); + } + else if (node.getNamespaceURI() != null && node.getLocalName() != null) { + return new QName(node.getNamespaceURI(), node.getLocalName()); + } + else if (node.getLocalName() != null) { + return new QName(node.getLocalName()); + } + else { + // as a last resort, use the node name + return new QName(node.getNodeName()); + } + } + + /** + * Convert a QName to a qualified name, as used by DOM and SAX. The returned string has a format of + * prefix:localName if the prefix is set, or just localName if not. + * + * @param qName the QName + * @return the qualified name + */ + public static String toQualifiedName(QName qName) { + String prefix = getPrefix(qName); + if (!StringUtils.hasLength(prefix)) { + return qName.getLocalPart(); + } + else { + return prefix + ":" + qName.getLocalPart(); + } + } + + /** + * Convert a namespace URI and DOM or SAX qualified name to a QName. The qualified name can have the + * form prefix:localname or localName. + * + * @param namespaceUri the namespace URI + * @param qualifiedName the qualified name + * @return a QName + */ + public static QName toQName(String namespaceUri, String qualifiedName) { + int idx = qualifiedName.indexOf(':'); + if (idx == -1) { + return new QName(namespaceUri, qualifiedName); + } + else { + return createQName(namespaceUri, qualifiedName.substring(idx + 1), qualifiedName.substring(0, idx)); + } + } + + /** + * Parse the given qualified name string into a QName. Expects the syntax localPart, + * {namespace}localPart, or {namespace}prefix:localPart. This format resembles the + * toString() representation of QName itself, but allows for prefixes to be specified as + * well. + * + * @return a corresponding QName instance + * @throws IllegalArgumentException when the given string is null or empty. + */ + public static QName parseQNameString(String qNameString) { + Assert.hasLength(qNameString, "QName text may not be null or empty"); + if (qNameString.charAt(0) != '{') { + return new QName(qNameString); + } + else { + int endOfNamespaceURI = qNameString.indexOf('}'); + if (endOfNamespaceURI == -1) { + throw new IllegalArgumentException( + "Cannot create QName from \"" + qNameString + "\", missing closing \"}\""); + } + int prefixSeperator = qNameString.indexOf(':', endOfNamespaceURI + 1); + String namespaceURI = qNameString.substring(1, endOfNamespaceURI); + if (prefixSeperator == -1) { + return new QName(namespaceURI, qNameString.substring(endOfNamespaceURI + 1)); + } + else { + return createQName(namespaceURI, qNameString.substring(prefixSeperator + 1), + qNameString.substring(endOfNamespaceURI + 1, prefixSeperator)); + } + } + + } + + +} diff --git a/xml/src/main/java/org/springframework/xml/namespace/SimpleNamespaceContext.java b/xml/src/main/java/org/springframework/xml/namespace/SimpleNamespaceContext.java new file mode 100644 index 00000000..65bc9dc8 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/namespace/SimpleNamespaceContext.java @@ -0,0 +1,165 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.namespace; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; + +import org.springframework.util.Assert; + +/** + * Simple javax.xml.namespace.NamespaceContext implementation. Follows the standard + * NamespaceContext contract, and is loadable via a java.util.Map or + * java.util.Properties object + * + * @author Arjen Poutsma + */ +public class SimpleNamespaceContext implements NamespaceContext { + + private Map prefixToNamespaceUri = new HashMap(); + + /** + * Maps a String namespaceUri to a List of prefixes + */ + private Map namespaceUriToPrefixes = new HashMap(); + + private String defaultNamespaceUri = ""; + + public String getNamespaceURI(String prefix) { + Assert.notNull(prefix, "prefix is null"); + if (XMLConstants.XML_NS_PREFIX.equals(prefix)) { + return XMLConstants.XML_NS_URI; + } + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) { + return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + } + else if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { + return defaultNamespaceUri; + } + else if (prefixToNamespaceUri.containsKey(prefix)) { + return (String) prefixToNamespaceUri.get(prefix); + } + return ""; + } + + public String getPrefix(String namespaceUri) { + List prefixes = getPrefixesInternal(namespaceUri); + return prefixes.isEmpty() ? null : (String) prefixes.get(0); + } + + public Iterator getPrefixes(String namespaceUri) { + return getPrefixesInternal(namespaceUri).iterator(); + } + + /** + * Sets the bindings for this namespace context. The supplied map must consist of string key value pairs. + * + * @param bindings the bindings + */ + public void setBindings(Map bindings) { + for (Iterator iterator = bindings.keySet().iterator(); iterator.hasNext();) { + String prefix = (String) iterator.next(); + bindNamespaceUri(prefix, (String) bindings.get(prefix)); + } + } + + /** + * Binds the given namespace as default namespace. + * + * @param namespaceUri the namespace uri + */ + public void bindDefaultNamespaceUri(String namespaceUri) { + bindNamespaceUri(XMLConstants.DEFAULT_NS_PREFIX, namespaceUri); + } + + /** + * Binds the given prefix to the given namespace. + * + * @param prefix the namespace prefix + * @param namespaceUri the namespace uri + */ + public void bindNamespaceUri(String prefix, String namespaceUri) { + Assert.notNull(prefix, "No prefix given"); + Assert.notNull(namespaceUri, "No namespaceUri given"); + if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { + defaultNamespaceUri = namespaceUri; + } + else { + prefixToNamespaceUri.put(prefix, namespaceUri); + getPrefixesInternal(namespaceUri).add(prefix); + } + } + + /** + * Removes all declared prefixes. + */ + public void clear() { + prefixToNamespaceUri.clear(); + } + + /** + * Returns all declared prefixes. + * + * @return the declared prefixes + */ + public Iterator getBoundPrefixes() { + return prefixToNamespaceUri.keySet().iterator(); + } + + private List getPrefixesInternal(String namespaceUri) { + if (defaultNamespaceUri.equals(namespaceUri)) { + return Collections.singletonList(XMLConstants.DEFAULT_NS_PREFIX); + } + else if (XMLConstants.XML_NS_URI.equals(namespaceUri)) { + return Collections.singletonList(XMLConstants.XML_NS_PREFIX); + } + else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceUri)) { + return Collections.singletonList(XMLConstants.XMLNS_ATTRIBUTE); + } + else { + List list = (List) namespaceUriToPrefixes.get(namespaceUri); + if (list == null) { + list = new ArrayList(); + namespaceUriToPrefixes.put(namespaceUri, list); + } + return list; + } + } + + /** + * Removes the given prefix from this context. + * + * @param prefix the prefix to be removed + */ + public void removeBinding(String prefix) { + if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) { + defaultNamespaceUri = ""; + } + else { + String namespaceUri = (String) namespaceUriToPrefixes.get(prefix); + List prefixes = getPrefixesInternal(namespaceUri); + prefixes.remove(prefix); + namespaceUriToPrefixes.remove(prefixes); + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/namespace/package.html b/xml/src/main/java/org/springframework/xml/namespace/package.html new file mode 100644 index 00000000..5f82c9f2 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/namespace/package.html @@ -0,0 +1,5 @@ + + +Provides classes that help with XML Namespace processing. Mostly for internal use by the framework. + + diff --git a/xml/src/main/java/org/springframework/xml/package.html b/xml/src/main/java/org/springframework/xml/package.html new file mode 100644 index 00000000..3f016dd9 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/package.html @@ -0,0 +1,6 @@ + + +Provides classes for XML handling: version detection and a base XML exception class. Mostly for internal use by the +framework. + + diff --git a/xml/src/main/java/org/springframework/xml/sax/SaxUtils.java b/xml/src/main/java/org/springframework/xml/sax/SaxUtils.java new file mode 100644 index 00000000..8b6c03cd --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/sax/SaxUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.sax; + +import java.io.IOException; + +import org.springframework.core.io.Resource; +import org.xml.sax.InputSource; + +/** + * @author Arjen Poutsma + */ +public abstract class SaxUtils { + + /** + * Creates a SAX InputSource from the given resource. Sets the system identifier to the resource's + * URL, if available. + * + * @param resource the resource + * @return the input source created from the resource + * @throws IOException if an I/O exception occurs + * @see InputSource#setSystemId(String) + * @see #getSystemId(org.springframework.core.io.Resource) + */ + public static InputSource createInputSource(Resource resource) throws IOException { + InputSource inputSource = new InputSource(resource.getInputStream()); + inputSource.setSystemId(getSystemId(resource)); + return inputSource; + } + + /** + * Retrieves the URL from the given resource as System ID. Returns null if it cannot be openened. + */ + public static String getSystemId(Resource resource) { + try { + return resource.getURL().toString(); + } + catch (IOException e) { + return null; + } + } + +} diff --git a/xml/src/main/java/org/springframework/xml/sax/package.html b/xml/src/main/java/org/springframework/xml/sax/package.html new file mode 100644 index 00000000..eb99a697 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/sax/package.html @@ -0,0 +1,5 @@ + + +Provides classes that help with SAX: the Simple API for XML. Mostly for internal use by the framework. + + diff --git a/xml/src/main/java/org/springframework/xml/stream/AbstractXmlStreamReader.java b/xml/src/main/java/org/springframework/xml/stream/AbstractXmlStreamReader.java new file mode 100644 index 00000000..b244f627 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/AbstractXmlStreamReader.java @@ -0,0 +1,181 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.springframework.util.Assert; + +/** + * Abstract base class for XMLStreamReaders. + * + * @author Arjen Poutsma + */ +public abstract class AbstractXmlStreamReader implements XMLStreamReader { + + public String getElementText() throws XMLStreamException { + if (getEventType() != XMLStreamConstants.START_ELEMENT) { + throw new XMLStreamException("parser must be on START_ELEMENT to read next text", getLocation()); + } + int eventType = next(); + StringBuffer buffer = new StringBuffer(); + while (eventType != XMLStreamConstants.END_ELEMENT) { + if (eventType == XMLStreamConstants.CHARACTERS || eventType == XMLStreamConstants.CDATA || + eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.ENTITY_REFERENCE) { + buffer.append(getText()); + } + else + if (eventType == XMLStreamConstants.PROCESSING_INSTRUCTION || eventType == XMLStreamConstants.COMMENT) { + // skipping + } + else if (eventType == XMLStreamConstants.END_DOCUMENT) { + throw new XMLStreamException("unexpected end of document when reading element text content", + getLocation()); + } + else if (eventType == XMLStreamConstants.START_ELEMENT) { + throw new XMLStreamException("element text content may not contain START_ELEMENT", getLocation()); + } + else { + throw new XMLStreamException("Unexpected event type " + eventType, getLocation()); + } + eventType = next(); + } + return buffer.toString(); + } + + public String getAttributeLocalName(int index) { + return getAttributeName(index).getLocalPart(); + } + + public String getAttributeNamespace(int index) { + return getAttributeName(index).getNamespaceURI(); + } + + public String getAttributePrefix(int index) { + return getAttributeName(index).getPrefix(); + } + + public String getNamespaceURI() { + int eventType = getEventType(); + if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { + return getName().getNamespaceURI(); + } + else { + throw new IllegalStateException("parser must be on START_ELEMENT or END_ELEMENT state"); + } + } + + public String getNamespaceURI(String prefix) { + Assert.notNull(prefix, "No prefix given"); + return getNamespaceContext().getNamespaceURI(prefix); + } + + public boolean hasText() { + int eventType = getEventType(); + return eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.CHARACTERS || + eventType == XMLStreamConstants.COMMENT || eventType == XMLStreamConstants.CDATA || + eventType == XMLStreamConstants.ENTITY_REFERENCE; + } + + public String getPrefix() { + int eventType = getEventType(); + if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { + return getName().getPrefix(); + } + else { + throw new IllegalStateException("parser must be on START_ELEMENT or END_ELEMENT state"); + } + } + + public boolean hasName() { + int eventType = getEventType(); + return eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT; + } + + public boolean isWhiteSpace() { + return getEventType() == XMLStreamConstants.SPACE; + } + + public boolean isStartElement() { + return getEventType() == XMLStreamConstants.START_ELEMENT; + } + + public boolean isEndElement() { + return getEventType() == XMLStreamConstants.END_ELEMENT; + } + + public boolean isCharacters() { + return getEventType() == XMLStreamConstants.CHARACTERS; + } + + public int nextTag() throws XMLStreamException { + int eventType = next(); + while (eventType == XMLStreamConstants.CHARACTERS && isWhiteSpace() || + eventType == XMLStreamConstants.CDATA && isWhiteSpace() || eventType == XMLStreamConstants.SPACE || + eventType == XMLStreamConstants.PROCESSING_INSTRUCTION || eventType == XMLStreamConstants.COMMENT) { + eventType = next(); + } + if (eventType != XMLStreamConstants.START_ELEMENT && eventType != XMLStreamConstants.END_ELEMENT) { + throw new XMLStreamException("expected start or end tag", getLocation()); + } + return eventType; + } + + public void require(int expectedType, String namespaceURI, String localName) throws XMLStreamException { + int eventType = getEventType(); + if (eventType != expectedType) { + throw new XMLStreamException("Expected [" + expectedType + "] but read [" + eventType + "]"); + } + } + + public String getAttributeValue(String namespaceURI, String localName) { + for (int i = 0; i < getAttributeCount(); i++) { + QName name = getAttributeName(i); + if (name.getNamespaceURI().equals(namespaceURI) && name.getLocalPart().equals(localName)) { + return getAttributeValue(i); + } + } + return null; + } + + public boolean hasNext() throws XMLStreamException { + return getEventType() != END_DOCUMENT; + } + + public String getLocalName() { + return getName().getLocalPart(); + } + + public char[] getTextCharacters() { + return getText().toCharArray(); + } + + public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) + throws XMLStreamException { + char[] source = getTextCharacters(); + length = Math.min(length, source.length); + System.arraycopy(source, sourceStart, target, targetStart, length); + return length; + } + + public int getTextLength() { + return getText().length(); + } +} diff --git a/xml/src/main/java/org/springframework/xml/stream/StaxContentHandler.java b/xml/src/main/java/org/springframework/xml/stream/StaxContentHandler.java new file mode 100644 index 00000000..e1bd73d1 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/StaxContentHandler.java @@ -0,0 +1,152 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +import org.springframework.xml.namespace.QNameUtils; +import org.springframework.xml.namespace.SimpleNamespaceContext; + +/** + * Abstract base class for SAX ContentHandler implementations that use StAX as a basis. All methods + * delegate to internal template methods, capable of throwing a XMLStreamException. Additionally, an + * namespace context is used to keep track of declared namespaces. + * + * @author Arjen Poutsma + */ +public abstract class StaxContentHandler implements ContentHandler { + + private SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext(); + + /** + * @throws SAXException + */ + public final void startDocument() throws SAXException { + namespaceContext.clear(); + try { + startDocumentInternal(); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex); + } + } + + protected abstract void startDocumentInternal() throws XMLStreamException; + + public final void endDocument() throws SAXException { + namespaceContext.clear(); + try { + endDocumentInternal(); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex); + } + } + + protected abstract void endDocumentInternal() throws XMLStreamException; + + /** + * Binds the given prefix to the given namespaces. + * + * @see SimpleNamespaceContext#bindNamespaceUri(String, String) + */ + public final void startPrefixMapping(String prefix, String uri) { + namespaceContext.bindNamespaceUri(prefix, uri); + } + + /** + * Removes the binding for the given prefix. + * + * @see SimpleNamespaceContext#removeBinding(String) + */ + public final void endPrefixMapping(String prefix) { + namespaceContext.removeBinding(prefix); + } + + public final void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + try { + startElementInternal(QNameUtils.toQName(uri, qName), atts, namespaceContext); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startElement: " + ex.getMessage(), ex); + } + } + + protected abstract void startElementInternal(QName name, Attributes atts, SimpleNamespaceContext namespaceContext) + throws XMLStreamException; + + public final void endElement(String uri, String localName, String qName) throws SAXException { + try { + endElementInternal(QNameUtils.toQName(uri, qName), namespaceContext); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle endElement: " + ex.getMessage(), ex); + } + } + + protected abstract void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) + throws XMLStreamException; + + public final void characters(char ch[], int start, int length) throws SAXException { + try { + charactersInternal(ch, start, length); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle characters: " + ex.getMessage(), ex); + } + } + + protected abstract void charactersInternal(char[] ch, int start, int length) throws XMLStreamException; + + public final void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { + try { + ignorableWhitespaceInternal(ch, start, length); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle ignorableWhitespace:" + ex.getMessage(), ex); + } + } + + protected abstract void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException; + + public final void processingInstruction(String target, String data) throws SAXException { + try { + processingInstructionInternal(target, data); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle processingInstruction: " + ex.getMessage(), ex); + } + } + + protected abstract void processingInstructionInternal(String target, String data) throws XMLStreamException; + + public final void skippedEntity(String name) throws SAXException { + try { + skippedEntityInternal(name); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle skippedEntity: " + ex.getMessage(), ex); + } + } + + protected abstract void skippedEntityInternal(String name) throws XMLStreamException; +} diff --git a/xml/src/main/java/org/springframework/xml/stream/StaxEventContentHandler.java b/xml/src/main/java/org/springframework/xml/stream/StaxEventContentHandler.java new file mode 100644 index 00000000..e8f0a301 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/StaxEventContentHandler.java @@ -0,0 +1,181 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.Location; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; + +import org.springframework.util.StringUtils; +import org.springframework.xml.namespace.QNameUtils; +import org.springframework.xml.namespace.SimpleNamespaceContext; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +/** + * SAX ContentHandler that transforms callback calls to XMLEvents and writes them to a + * XMLEventConsumer. + * + * @author Arjen Poutsma + * @see XMLEvent + * @see XMLEventConsumer + */ +public class StaxEventContentHandler extends StaxContentHandler { + + private final XMLEventFactory eventFactory; + + private final XMLEventConsumer eventConsumer; + + private Locator locator; + + /** + * Constructs a new instance of the StaxEventContentHandler that writes to the given + * XMLEventConsumer. A default XMLEventFactory will be created. + * + * @param consumer the consumer to write events to + */ + public StaxEventContentHandler(XMLEventConsumer consumer) { + eventFactory = XMLEventFactory.newInstance(); + eventConsumer = consumer; + } + + /** + * Constructs a new instance of the StaxEventContentHandler that uses the given event factory to create + * events and writes to the given XMLEventConsumer. + * + * @param consumer the consumer to write events to + * @param factory the factory used to create events + */ + public StaxEventContentHandler(XMLEventConsumer consumer, XMLEventFactory factory) { + eventFactory = factory; + eventConsumer = consumer; + } + + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + protected void startDocumentInternal() throws XMLStreamException { + consumeEvent(eventFactory.createStartDocument()); + } + + protected void endDocumentInternal() throws XMLStreamException { + consumeEvent(eventFactory.createEndDocument()); + } + + protected void startElementInternal(QName name, Attributes atts, SimpleNamespaceContext namespaceContext) + throws XMLStreamException { + List attributes = getAttributes(atts); + List namespaces = createNamespaces(namespaceContext); + consumeEvent(eventFactory.createStartElement(name, attributes.iterator(), namespaces.iterator())); + } + + protected void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) throws XMLStreamException { + List namespaces = createNamespaces(namespaceContext); + consumeEvent(eventFactory.createEndElement(name, namespaces.iterator())); + } + + protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException { + consumeEvent(eventFactory.createCharacters(new String(ch, start, length))); + } + + protected void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException { + consumeEvent(eventFactory.createIgnorableSpace(new String(ch, start, length))); + } + + protected void processingInstructionInternal(String target, String data) throws XMLStreamException { + consumeEvent(eventFactory.createProcessingInstruction(target, data)); + } + + private void consumeEvent(XMLEvent event) throws XMLStreamException { + if (locator != null) { + eventFactory.setLocation(new SaxLocation(locator)); + } + eventConsumer.add(event); + } + + /** + * Creates and returns a list of NameSpace objects from the NamespaceContext. + */ + private List createNamespaces(SimpleNamespaceContext namespaceContext) { + List namespaces = new ArrayList(); + String defaultNamespaceUri = namespaceContext.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX); + if (StringUtils.hasLength(defaultNamespaceUri)) { + namespaces.add(eventFactory.createNamespace(defaultNamespaceUri)); + } + for (Iterator iterator = namespaceContext.getBoundPrefixes(); iterator.hasNext();) { + String prefix = (String) iterator.next(); + String namespaceUri = namespaceContext.getNamespaceURI(prefix); + namespaces.add(eventFactory.createNamespace(prefix, namespaceUri)); + } + return namespaces; + } + + private List getAttributes(Attributes attributes) { + List list = new ArrayList(); + for (int i = 0; i < attributes.getLength(); i++) { + QName name = QNameUtils.toQName(attributes.getURI(i), attributes.getQName(i)); + if (!("xmlns".equals(name.getLocalPart()) || "xmlns".equals(QNameUtils.getPrefix(name)))) { + list.add(eventFactory.createAttribute(name, attributes.getValue(i))); + } + } + return list; + } + + // + // No operation + // + + protected void skippedEntityInternal(String name) throws XMLStreamException { + } + + private static class SaxLocation implements Location { + + private Locator locator; + + public SaxLocation(Locator locator) { + this.locator = locator; + } + + public int getLineNumber() { + return locator.getLineNumber(); + } + + public int getColumnNumber() { + return locator.getColumnNumber(); + } + + public int getCharacterOffset() { + return -1; + } + + public String getPublicId() { + return locator.getPublicId(); + } + + public String getSystemId() { + return locator.getSystemId(); + } + } +} \ No newline at end of file diff --git a/xml/src/main/java/org/springframework/xml/stream/StaxEventXmlReader.java b/xml/src/main/java/org/springframework/xml/stream/StaxEventXmlReader.java new file mode 100644 index 00000000..c8d42e00 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/StaxEventXmlReader.java @@ -0,0 +1,187 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.util.Iterator; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.EndElement; +import javax.xml.stream.events.Namespace; +import javax.xml.stream.events.NotationDeclaration; +import javax.xml.stream.events.ProcessingInstruction; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.springframework.xml.namespace.QNameUtils; + +/** + * SAX XMLReader that reads from a StAX XMLEventReader. Consumes XMLEvents from + * an XMLEventReader, and calls the corresponding methods on the SAX callback interfaces. + * + * @author Arjen Poutsma + * @see XMLEventReader + * @see #setContentHandler(org.xml.sax.ContentHandler) + * @see #setDTDHandler(org.xml.sax.DTDHandler) + * @see #setEntityResolver(org.xml.sax.EntityResolver) + * @see #setErrorHandler(org.xml.sax.ErrorHandler) + */ +public class StaxEventXmlReader extends StaxXmlReader { + + private final XMLEventReader reader; + + /** + * Constructs a new instance of the StaxEventXmlReader that reads from the given + * XMLEventReader. The supplied event reader must be in XMLStreamConstants.START_DOCUMENT + * or XMLStreamConstants.START_ELEMENT state. + * + * @param reader the XMLEventReader to read from + * @throws IllegalStateException if the reader is not at the start of a document or element + */ + public StaxEventXmlReader(XMLEventReader reader) { + try { + XMLEvent event = reader.peek(); + if (event == null || !(event.isStartDocument() || event.isStartElement())) { + throw new IllegalStateException("XMLEventReader not at start of document or element"); + } + } + catch (XMLStreamException ex) { + throw new IllegalStateException("Could not read first element: " + ex.getMessage(), ex); + } + + this.reader = reader; + } + + protected void parseInternal() throws SAXException, XMLStreamException { + boolean documentEnded = false; + while (reader.hasNext()) { + XMLEvent event = reader.nextEvent(); + switch (event.getEventType()) { + case XMLStreamConstants.START_ELEMENT: + handleStartElement(event.asStartElement()); + break; + case XMLStreamConstants.END_ELEMENT: + handleEndElement(event.asEndElement()); + break; + case XMLStreamConstants.PROCESSING_INSTRUCTION: + handleProcessingInstruction((ProcessingInstruction) event); + break; + case XMLStreamConstants.CHARACTERS: + case XMLStreamConstants.SPACE: + case XMLStreamConstants.CDATA: + handleCharacters(event.asCharacters()); + break; + case XMLStreamConstants.START_DOCUMENT: + setLocator(event.getLocation()); + handleStartDocument(); + break; + case XMLStreamConstants.END_DOCUMENT: + handleEndDocument(); + documentEnded = true; + break; + case XMLStreamConstants.NOTATION_DECLARATION: + handleNotationDeclaration((NotationDeclaration) event); + break; + } + } + if (!documentEnded) { + handleEndDocument(); + } + + } + + private void handleCharacters(Characters characters) throws SAXException { + if (getContentHandler() != null) { + if (characters.isIgnorableWhiteSpace()) { + getContentHandler() + .ignorableWhitespace(characters.getData().toCharArray(), 0, characters.getData().length()); + } + else { + getContentHandler().characters(characters.getData().toCharArray(), 0, characters.getData().length()); + } + } + } + + private void handleEndDocument() throws SAXException { + if (getContentHandler() != null) { + getContentHandler().endDocument(); + } + } + + private void handleEndElement(EndElement endElement) throws SAXException { + if (getContentHandler() != null) { + getContentHandler().endElement(endElement.getName().getNamespaceURI(), endElement.getName().getLocalPart(), + QNameUtils.toQualifiedName(endElement.getName())); + + for (Iterator i = endElement.getNamespaces(); i.hasNext();) { + Namespace namespace = (Namespace) i.next(); + getContentHandler().endPrefixMapping(namespace.getPrefix()); + } + } + } + + private void handleNotationDeclaration(NotationDeclaration declaration) throws SAXException { + if (getDtdHandler() != null) { + getDtdHandler().notationDecl(declaration.getName(), declaration.getPublicId(), declaration.getSystemId()); + } + } + + private void handleProcessingInstruction(ProcessingInstruction pi) throws SAXException { + if (getContentHandler() != null) { + getContentHandler().processingInstruction(pi.getTarget(), pi.getData()); + } + } + + private void handleStartDocument() throws SAXException { + if (getContentHandler() != null) { + getContentHandler().startDocument(); + } + } + + private void handleStartElement(StartElement startElement) throws SAXException { + if (getContentHandler() != null) { + for (Iterator i = startElement.getNamespaces(); i.hasNext();) { + Namespace namespace = (Namespace) i.next(); + getContentHandler().startPrefixMapping(namespace.getPrefix(), namespace.getNamespaceURI()); + } + + getContentHandler().startElement(startElement.getName().getNamespaceURI(), + startElement.getName().getLocalPart(), QNameUtils.toQualifiedName(startElement.getName()), + getAttributes(startElement)); + } + } + + private Attributes getAttributes(StartElement event) { + AttributesImpl attributes = new AttributesImpl(); + + for (Iterator i = event.getAttributes(); i.hasNext();) { + Attribute attribute = (Attribute) i.next(); + attributes.addAttribute(attribute.getName().getNamespaceURI(), attribute.getName().getLocalPart(), + QNameUtils.toQualifiedName(attribute.getName()), attribute.getDTDType(), attribute.getValue()); + } + + return attributes; + } + +} diff --git a/xml/src/main/java/org/springframework/xml/stream/StaxStreamContentHandler.java b/xml/src/main/java/org/springframework/xml/stream/StaxStreamContentHandler.java new file mode 100644 index 00000000..0af53728 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/StaxStreamContentHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.util.Iterator; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.springframework.util.StringUtils; +import org.springframework.xml.namespace.QNameUtils; +import org.springframework.xml.namespace.SimpleNamespaceContext; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +/** + * SAX ContentHandler that writes to a XMLStreamWriter. + * + * @author Arjen Poutsma + * @see XMLStreamWriter + */ +public class StaxStreamContentHandler extends StaxContentHandler { + + private final XMLStreamWriter streamWriter; + + /** + * Constructs a new instance of the StaxStreamContentHandler that writes to the given + * XMLStreamWriter. + * + * @param streamWriter the stream writer to write to + */ + public StaxStreamContentHandler(XMLStreamWriter streamWriter) { + this.streamWriter = streamWriter; + } + + public void setDocumentLocator(Locator locator) { + } + + protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException { + streamWriter.writeCharacters(ch, start, length); + } + + protected void endDocumentInternal() throws XMLStreamException { + streamWriter.writeEndDocument(); + } + + protected void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) throws XMLStreamException { + streamWriter.writeEndElement(); + } + + protected void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException { + streamWriter.writeCharacters(ch, start, length); + } + + protected void processingInstructionInternal(String target, String data) throws XMLStreamException { + streamWriter.writeProcessingInstruction(target, data); + } + + protected void skippedEntityInternal(String name) { + } + + protected void startDocumentInternal() throws XMLStreamException { + streamWriter.writeStartDocument(); + } + + protected void startElementInternal(QName name, Attributes attributes, SimpleNamespaceContext namespaceContext) + throws XMLStreamException { + streamWriter.writeStartElement(QNameUtils.getPrefix(name), name.getLocalPart(), name.getNamespaceURI()); + String defaultNamespaceUri = namespaceContext.getNamespaceURI(""); + if (StringUtils.hasLength(defaultNamespaceUri)) { + streamWriter.writeNamespace("", defaultNamespaceUri); + streamWriter.setDefaultNamespace(defaultNamespaceUri); + } + for (Iterator iterator = namespaceContext.getBoundPrefixes(); iterator.hasNext();) { + String prefix = (String) iterator.next(); + streamWriter.writeNamespace(prefix, namespaceContext.getNamespaceURI(prefix)); + streamWriter.setPrefix(prefix, namespaceContext.getNamespaceURI(prefix)); + } + for (int i = 0; i < attributes.getLength(); i++) { + QName attrName = QNameUtils.toQName(attributes.getURI(i), attributes.getQName(i)); + String attrPrefix = QNameUtils.getPrefix(attrName); + if (!("xmlns".equals(attrName.getLocalPart()) || "xmlns".equals(attrPrefix))) { + streamWriter.writeAttribute(attrPrefix, attrName.getNamespaceURI(), attrName.getLocalPart(), + attributes.getValue(i)); + } + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/stream/StaxStreamXmlReader.java b/xml/src/main/java/org/springframework/xml/stream/StaxStreamXmlReader.java new file mode 100644 index 00000000..da8390f2 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/StaxStreamXmlReader.java @@ -0,0 +1,187 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.springframework.xml.namespace.QNameUtils; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * SAX XMLReader that reads from a StAX XMLStreamReader. Reads from an + * XMLStreamReader, and calls the corresponding methods on the SAX callback interfaces. + * + * @author Arjen Poutsma + * @see XMLStreamReader + * @see #setContentHandler(org.xml.sax.ContentHandler) + * @see #setDTDHandler(org.xml.sax.DTDHandler) + * @see #setEntityResolver(org.xml.sax.EntityResolver) + * @see #setErrorHandler(org.xml.sax.ErrorHandler) + */ +public class StaxStreamXmlReader extends StaxXmlReader { + + private final XMLStreamReader reader; + + /** + * Constructs a new instance of the StaxStreamXmlReader that reads from the given + * XMLStreamReader. The supplied stream reader must be in XMLStreamConstants.START_DOCUMENT + * or XMLStreamConstants.START_ELEMENT state. + * + * @param reader the XMLEventReader to read from + * @throws IllegalStateException if the reader is not at the start of a document or element + */ + public StaxStreamXmlReader(XMLStreamReader reader) { + int event = reader.getEventType(); + if (!(event == XMLStreamConstants.START_DOCUMENT || event == XMLStreamConstants.START_ELEMENT)) { + throw new IllegalStateException("XMLEventReader not at start of document or element"); + } + this.reader = reader; + } + + protected void parseInternal() throws SAXException, XMLStreamException { + boolean documentEnded = false; + while (true) { + switch (reader.getEventType()) { + case XMLStreamConstants.START_ELEMENT: + handleStartElement(); + break; + case XMLStreamConstants.END_ELEMENT: + handleEndElement(); + break; + case XMLStreamConstants.PROCESSING_INSTRUCTION: + handleProcessingInstruction(); + break; + case XMLStreamConstants.CHARACTERS: + case XMLStreamConstants.SPACE: + case XMLStreamConstants.CDATA: + handleCharacters(); + break; + case XMLStreamConstants.START_DOCUMENT: + handleStartDocument(); + break; + case XMLStreamConstants.END_DOCUMENT: + handleEndDocument(); + documentEnded = true; + break; + } + if (reader.hasNext()) { + reader.next(); + } + else { + break; + } + } + if (!documentEnded) { + handleEndDocument(); + } + + } + + private void handleCharacters() throws SAXException { + if (getContentHandler() != null) { + if (reader.isWhiteSpace()) { + getContentHandler() + .ignorableWhitespace(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength()); + } + else { + getContentHandler() + .characters(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength()); + } + } + } + + private void handleEndDocument() throws SAXException { + if (getContentHandler() != null) { + getContentHandler().endDocument(); + } + } + + private void handleEndElement() throws SAXException { + if (getContentHandler() != null) { + QName qName = reader.getName(); + getContentHandler() + .endElement(qName.getNamespaceURI(), qName.getLocalPart(), QNameUtils.toQualifiedName(qName)); + for (int i = 0; i < reader.getNamespaceCount(); i++) { + String prefix = reader.getNamespacePrefix(i); + if (prefix == null) { + prefix = ""; + } + getContentHandler().endPrefixMapping(prefix); + } + } + } + + private void handleProcessingInstruction() throws SAXException { + if (getContentHandler() != null) { + getContentHandler().processingInstruction(reader.getPITarget(), reader.getPIData()); + } + } + + private void handleStartDocument() throws SAXException { + setLocator(reader.getLocation()); + if (getContentHandler() != null) { + getContentHandler().startDocument(); + } + } + + private void handleStartElement() throws SAXException { + if (getContentHandler() != null) { + for (int i = 0; i < reader.getNamespaceCount(); i++) { + String prefix = reader.getNamespacePrefix(i); + if (prefix == null) { + prefix = ""; + } + getContentHandler().startPrefixMapping(prefix, reader.getNamespaceURI(i)); + } + + QName qName = reader.getName(); + getContentHandler().startElement(qName.getNamespaceURI(), + qName.getLocalPart(), + QNameUtils.toQualifiedName(qName), + getAttributes()); + } + } + + private Attributes getAttributes() { + AttributesImpl attributes = new AttributesImpl(); + + for (int i = 0; i < reader.getAttributeCount(); i++) { + String namespace = reader.getAttributeNamespace(i); + if (namespace == null) { + namespace = ""; + } + String type = reader.getAttributeType(i); + if (type == null) { + type = ""; + } + attributes.addAttribute(namespace, + reader.getAttributeLocalName(i), + QNameUtils.toQualifiedName(reader.getAttributeName(i)), + type, + reader.getAttributeValue(i)); + } + + return attributes; + } + + +} diff --git a/xml/src/main/java/org/springframework/xml/stream/StaxXmlReader.java b/xml/src/main/java/org/springframework/xml/stream/StaxXmlReader.java new file mode 100644 index 00000000..228c9502 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/StaxXmlReader.java @@ -0,0 +1,215 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import javax.xml.stream.Location; +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; + +/** + * Abstract base class for SAX XMLReader implementations that use StAX as a basis. + * + * @author Arjen Poutsma + * @see #setContentHandler(org.xml.sax.ContentHandler) + * @see #setDTDHandler(org.xml.sax.DTDHandler) + * @see #setEntityResolver(org.xml.sax.EntityResolver) + * @see #setErrorHandler(org.xml.sax.ErrorHandler) + */ +public abstract class StaxXmlReader implements XMLReader { + + private DTDHandler dtdHandler; + + private ContentHandler contentHandler; + + private EntityResolver entityResolver; + + private ErrorHandler errorHandler; + + public ContentHandler getContentHandler() { + return contentHandler; + } + + public void setContentHandler(ContentHandler contentHandler) { + this.contentHandler = contentHandler; + } + + public DTDHandler getDtdHandler() { + return dtdHandler; + } + + public void setDtdHandler(DTDHandler dtdHandler) { + this.dtdHandler = dtdHandler; + } + + public EntityResolver getEntityResolver() { + return entityResolver; + } + + public void setEntityResolver(EntityResolver entityResolver) { + this.entityResolver = entityResolver; + } + + public ErrorHandler getErrorHandler() { + return errorHandler; + } + + public void setErrorHandler(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + /** + * Throws a SAXNotRecognizedException exception. + * + * @throws SAXNotRecognizedException always + */ + public boolean getFeature(String name) throws SAXNotRecognizedException { + throw new SAXNotRecognizedException(name); + } + + /** + * Throws a SAXNotRecognizedException exception. + * + * @throws SAXNotRecognizedException always + */ + public void setFeature(String name, boolean value) throws SAXNotRecognizedException { + throw new SAXNotRecognizedException(name); + } + + /** + * Throws a SAXNotRecognizedException exception. + * + * @throws SAXNotRecognizedException always + */ + public Object getProperty(String name) throws SAXNotRecognizedException { + throw new SAXNotRecognizedException(name); + } + + /** + * Throws a SAXNotRecognizedException exception. + * + * @throws SAXNotRecognizedException always + */ + public void setProperty(String name, Object value) throws SAXNotRecognizedException { + throw new SAXNotRecognizedException(name); + } + + public void setDTDHandler(DTDHandler dtdHandler) { + this.setDtdHandler(dtdHandler); + } + + public DTDHandler getDTDHandler() { + return getDtdHandler(); + } + + /** + * Parses the StAX XML reader passed at construction-time. + *

+ * Note that the given InputSource is not read, but ignored. + * + * @param ignored is ignored + * @throws SAXException A SAX exception, possibly wrapping a XMLStreamException + */ + public final void parse(InputSource ignored) throws SAXException { + parse(); + } + + /** + * Parses the StAX XML reader passed at construction-time. + *

+ * Note that the given system identifier is not read, but ignored. + * + * @param ignored is ignored + * @throws SAXException A SAX exception, possibly wrapping a XMLStreamException + */ + public final void parse(String ignored) throws SAXException { + parse(); + } + + private void parse() throws SAXException { + try { + parseInternal(); + } + catch (XMLStreamException ex) { + SAXParseException saxException = new SAXParseException(ex.getMessage(), null, null, + ex.getLocation().getLineNumber(), ex.getLocation().getColumnNumber(), ex); + if (errorHandler != null) { + errorHandler.fatalError(saxException); + } + else { + throw saxException; + } + } + } + + /** + * Sets the SAX Locator based on the given StAX Location. + * + * @param location the location + * @see ContentHandler#setDocumentLocator(org.xml.sax.Locator) + */ + protected void setLocator(Location location) { + if (contentHandler != null) { + contentHandler.setDocumentLocator(new StaxLocator(location)); + } + } + + /** + * Template-method that parses the StAX reader passed at construction-time. + */ + protected abstract void parseInternal() throws SAXException, XMLStreamException; + + /** + * Implementation of the Locator interface that is based on a StAX Location. + * + * @see Locator + * @see Location + */ + private static class StaxLocator implements Locator { + + private Location location; + + protected StaxLocator(Location location) { + this.location = location; + } + + public String getPublicId() { + return location.getPublicId(); + } + + public String getSystemId() { + return location.getSystemId(); + } + + public int getLineNumber() { + return location.getLineNumber(); + } + + public int getColumnNumber() { + return location.getColumnNumber(); + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/stream/XmlEventStreamReader.java b/xml/src/main/java/org/springframework/xml/stream/XmlEventStreamReader.java new file mode 100644 index 00000000..1ec62eee --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/XmlEventStreamReader.java @@ -0,0 +1,253 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.util.Iterator; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.stream.Location; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Namespace; +import javax.xml.stream.events.ProcessingInstruction; +import javax.xml.stream.events.StartDocument; +import javax.xml.stream.events.XMLEvent; + +/** + * Implementation of the XMLStreamReader interface that wraps a XMLEventReader. Useful, + * because the StAX XMLInputFactory allows one to create a event reader from a stream reader, but not + * vice-versa. + * + * @author Arjen Poutsma + */ +public class XmlEventStreamReader extends AbstractXmlStreamReader { + + private XMLEvent event; + + private final XMLEventReader eventReader; + + public XmlEventStreamReader(XMLEventReader eventReader) throws XMLStreamException { + this.eventReader = eventReader; + event = eventReader.nextEvent(); + } + + public boolean isStandalone() { + if (event.isStartDocument()) { + return ((StartDocument) event).isStandalone(); + } + else { + throw new IllegalStateException(); + } + } + + public String getVersion() { + if (event.isStartDocument()) { + return ((StartDocument) event).getVersion(); + } + else { + throw new IllegalStateException(); + } + } + + public int getTextStart() { + return 0; + } + + public String getText() { + if (event.isCharacters()) { + return event.asCharacters().getData(); + } + else { + throw new IllegalStateException(); + } + } + + public String getPITarget() { + if (event.isProcessingInstruction()) { + return ((ProcessingInstruction) event).getTarget(); + } + else { + throw new IllegalStateException(); + } + } + + public String getPIData() { + if (event.isProcessingInstruction()) { + return ((ProcessingInstruction) event).getData(); + } + else { + throw new IllegalStateException(); + } + } + + public int getNamespaceCount() { + Iterator namespaces; + if (event.isStartElement()) { + namespaces = event.asStartElement().getNamespaces(); + } + else if (event.isEndElement()) { + namespaces = event.asEndElement().getNamespaces(); + } + else { + throw new IllegalStateException(); + } + return countIterator(namespaces); + } + + public NamespaceContext getNamespaceContext() { + if (event.isStartElement()) { + return event.asStartElement().getNamespaceContext(); + } + else { + throw new IllegalStateException(); + } + } + + public QName getName() { + if (event.isStartElement()) { + return event.asStartElement().getName(); + } + else if (event.isEndElement()) { + return event.asEndElement().getName(); + } + else { + throw new IllegalStateException(); + } + } + + public Location getLocation() { + return event.getLocation(); + } + + public int getEventType() { + return event.getEventType(); + } + + public String getEncoding() { + return null; + } + + public String getCharacterEncodingScheme() { + return null; + } + + public int getAttributeCount() { + if (!event.isStartElement()) { + throw new IllegalStateException(); + } + Iterator attributes = event.asStartElement().getAttributes(); + return countIterator(attributes); + } + + public void close() throws XMLStreamException { + eventReader.close(); + } + + public QName getAttributeName(int index) { + return getAttribute(index).getName(); + } + + public String getAttributeType(int index) { + return getAttribute(index).getDTDType(); + } + + public String getAttributeValue(int index) { + return getAttribute(index).getValue(); + } + + public String getNamespacePrefix(int index) { + return getNamespace(index).getPrefix(); + } + + public String getNamespaceURI(int index) { + return getNamespace(index).getNamespaceURI(); + } + + public Object getProperty(String name) throws IllegalArgumentException { + return eventReader.getProperty(name); + } + + public boolean isAttributeSpecified(int index) { + return getAttribute(index).isSpecified(); + } + + public int next() throws XMLStreamException { + event = eventReader.nextEvent(); + return event.getEventType(); + } + + public boolean standaloneSet() { + if (event.isStartDocument()) { + return ((StartDocument) event).standaloneSet(); + } + else { + throw new IllegalStateException(); + } + } + + private int countIterator(Iterator iterator) { + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + return count; + } + + private Attribute getAttribute(int index) { + if (!event.isStartElement()) { + throw new IllegalStateException(); + } + int count = 0; + Iterator attributes = event.asStartElement().getAttributes(); + while (attributes.hasNext()) { + Attribute attribute = (Attribute) attributes.next(); + if (count == index) { + return attribute; + } + else { + count++; + } + } + throw new IllegalArgumentException(); + } + + private Namespace getNamespace(int index) { + Iterator namespaces; + if (event.isStartElement()) { + namespaces = event.asStartElement().getNamespaces(); + } + else if (event.isEndElement()) { + namespaces = event.asEndElement().getNamespaces(); + } + else { + throw new IllegalStateException(); + } + int count = 0; + while (namespaces.hasNext()) { + Namespace namespace = (Namespace) namespaces.next(); + if (count == index) { + return namespace; + } + else { + count++; + } + } + throw new IllegalArgumentException(); + } +} diff --git a/xml/src/main/java/org/springframework/xml/stream/package.html b/xml/src/main/java/org/springframework/xml/stream/package.html new file mode 100644 index 00000000..14298b80 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/stream/package.html @@ -0,0 +1,5 @@ + + +Provides classes that help with StAX: the Streaming API for XML. Mostly for internal use by the framework. + + diff --git a/xml/src/main/java/org/springframework/xml/transform/StaxResult.java b/xml/src/main/java/org/springframework/xml/transform/StaxResult.java new file mode 100644 index 00000000..ce80cc8f --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/transform/StaxResult.java @@ -0,0 +1,115 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.transform; + +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.sax.SAXResult; + +import org.springframework.xml.stream.StaxEventContentHandler; +import org.springframework.xml.stream.StaxStreamContentHandler; +import org.xml.sax.ContentHandler; + +/** + * Implementation of the Result tagging interface for StAX writers. Can be constructed with a + * XMLEventConsumer or a XMLStreamWriter. + *

+ * This class is necessary because there is no implementation of Source for StaxReaders in JAXP 1.3. There + * will be a StaxResult in JAXP 1.4 (JDK 1.6), and by the time that version is available, this class will + * probably be deprecated. + *

+ * Even though StaxResult extends from SAXResult, calling the methods of + * SAXResult is not supported. In general, the only supported operation on this class is + * to use the ContentHandler obtained via {@link #getHandler()} to parse an input source using an + * XMLReader. Calling {@link #setHandler(org.xml.sax.ContentHandler)} will result in + * UnsupportedOperationExceptions. + * + * @author Arjen Poutsma + * @see XMLEventWriter + * @see XMLStreamWriter + * @see javax.xml.transform.Transformer + */ +public class StaxResult extends SAXResult { + + private XMLEventWriter eventWriter; + + private XMLStreamWriter streamWriter; + + /** + * Constructs a new instance of the StaxResult with the specified XMLStreamWriter. + * + * @param streamWriter the XMLStreamWriter to write to + */ + public StaxResult(XMLStreamWriter streamWriter) { + super.setHandler(new StaxStreamContentHandler(streamWriter)); + this.streamWriter = streamWriter; + } + + /** + * Constructs a new instance of the StaxResult with the specified XMLEventWriter. + * + * @param eventWriter the XMLEventWriter to write to + */ + public StaxResult(XMLEventWriter eventWriter) { + super.setHandler(new StaxEventContentHandler(eventWriter)); + this.eventWriter = eventWriter; + } + + /** + * Constructs a new instance of the StaxResult with the specified XMLEventWriter and + * XMLEventFactory. + * + * @param eventWriter the XMLEventWriter to write to + * @param eventFactory the XMLEventFactory to use for creating events + */ + public StaxResult(XMLEventWriter eventWriter, XMLEventFactory eventFactory) { + super.setHandler(new StaxEventContentHandler(eventWriter, eventFactory)); + this.eventWriter = eventWriter; + } + + /** + * Returns the XMLEventWriter used by this StaxResult. If this StaxResult was + * created with an XMLStreamWriter, the result will be null. + * + * @return the StAX event writer used by this result + * @see #StaxResult(javax.xml.stream.XMLEventWriter) + */ + public XMLEventWriter getXMLEventWriter() { + return eventWriter; + } + + /** + * Returns the XMLStreamWriter used by this StaxResult. If this StaxResult + * was created with an XMLEventConsumer, the result will be null. + * + * @return the StAX stream writer used by this result + * @see #StaxResult(javax.xml.stream.XMLStreamWriter) + */ + public XMLStreamWriter getXMLStreamWriter() { + return streamWriter; + } + + /** + * Throws a UnsupportedOperationException. + * + * @throws UnsupportedOperationException always + */ + public void setHandler(ContentHandler handler) { + throw new UnsupportedOperationException("setHandler is not supported"); + } +} diff --git a/xml/src/main/java/org/springframework/xml/transform/StaxSource.java b/xml/src/main/java/org/springframework/xml/transform/StaxSource.java new file mode 100644 index 00000000..db08b5a4 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/transform/StaxSource.java @@ -0,0 +1,118 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.transform; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.sax.SAXSource; + +import org.springframework.xml.stream.StaxEventXmlReader; +import org.springframework.xml.stream.StaxStreamXmlReader; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; + +/** + * Implementation of the Source tagging interface for StAX readers. Can be constructed with a + * XMLEventReader or a XMLStreamReader. + *

+ * This class is necessary because there is no implementation of Source for StaxReaders in JAXP 1.3. There + * will be a StaxSource in JAXP 1.4 (JDK 1.6), and by the time that version is available, this class will + * probably be deprecated. + *

+ * Even though StaxSource extends from SAXSource, calling the methods of + * SAXSource is not supported. In general, the only supported operation on this class is + * to use the XMLReader obtained via {@link #getXMLReader()} to parse the input source obtained via {@link + * #getInputSource()}. Calling {@link #setXMLReader(org.xml.sax.XMLReader)} or {@link + * #setInputSource(org.xml.sax.InputSource)} will result in UnsupportedOperationExceptions. + * + * @author Arjen Poutsma + * @see XMLEventReader + * @see XMLStreamReader + * @see javax.xml.transform.Transformer + */ +public class StaxSource extends SAXSource { + + private XMLEventReader eventReader; + + private XMLStreamReader streamReader; + + /** + * Constructs a new instance of the StaxSource with the specified XMLStreamReader. The + * supplied stream reader must be in XMLStreamConstants.START_DOCUMENT or + * XMLStreamConstants.START_ELEMENT state. + * + * @param streamReader the XMLStreamReader to read from + * @throws IllegalStateException if the reader is not at the start of a document or element + */ + public StaxSource(XMLStreamReader streamReader) { + super(new StaxStreamXmlReader(streamReader), new InputSource()); + this.streamReader = streamReader; + } + + /** + * Constructs a new instance of the StaxSource with the specified XMLEventReader. The + * supplied event reader must be in XMLStreamConstants.START_DOCUMENT or + * XMLStreamConstants.START_ELEMENT state. + * + * @param eventReader the XMLEventReader to read from + * @throws IllegalStateException if the reader is not at the start of a document or element + */ + public StaxSource(XMLEventReader eventReader) { + super(new StaxEventXmlReader(eventReader), new InputSource()); + this.eventReader = eventReader; + } + + /** + * Returns the XMLEventReader used by this StaxSource. If this StaxSource was + * created with an XMLStreamReader, the result will be null. + * + * @return the StAX event reader used by this source + * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader) + */ + public XMLEventReader getXMLEventReader() { + return eventReader; + } + + /** + * Returns the XMLStreamReader used by this StaxSource. If this StaxSource + * was created with an XMLEventReader, the result will be null. + * + * @return the StAX event reader used by this source + * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader) + */ + public XMLStreamReader getXMLStreamReader() { + return streamReader; + } + + /** + * Throws a UnsupportedOperationException. + * + * @throws UnsupportedOperationException always + */ + public void setInputSource(InputSource inputSource) { + throw new UnsupportedOperationException("setInputSource is not supported"); + } + + /** + * Throws a UnsupportedOperationException. + * + * @throws UnsupportedOperationException always + */ + public void setXMLReader(XMLReader reader) { + throw new UnsupportedOperationException("setXMLReader is not supported"); + } +} diff --git a/xml/src/main/java/org/springframework/xml/transform/StringResult.java b/xml/src/main/java/org/springframework/xml/transform/StringResult.java new file mode 100644 index 00000000..3b7a748c --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/transform/StringResult.java @@ -0,0 +1,43 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.transform; + +import java.io.StringWriter; + +import javax.xml.transform.stream.StreamResult; + +/** + * Convenient subclass of StreamResult that writes to a StringWriter. The resulting string can + * be retrieved via toString(). + * + * @author Arjen Poutsma + * @see #toString() + */ +public class StringResult extends StreamResult { + + public StringResult() { + super(new StringWriter()); + } + + /** + * Returns the written XML as a string. + */ + public String toString() { + return getWriter().toString(); + } + +} diff --git a/xml/src/main/java/org/springframework/xml/transform/StringSource.java b/xml/src/main/java/org/springframework/xml/transform/StringSource.java new file mode 100644 index 00000000..44368385 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/transform/StringSource.java @@ -0,0 +1,50 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.transform; + +import java.io.StringReader; + +import javax.xml.transform.stream.StreamSource; + +/** + * Convenient subclass of StreamSource that reads from a StringReader. The string to be read + * can be set via the constructor. + * + * @author Arjen Poutsma + */ +public class StringSource extends StreamSource { + + /** + * Initializes a new instance of the StringSource with the given string content. + * + * @param content the content + */ + public StringSource(String content) { + super(new StringReader(content)); + } + + /** + * Initializes a new instance of the StringSource with the given string content and system id. + * + * @param content the content + * @param systemId a string that conforms to the URI syntax + */ + public StringSource(String content, String systemId) { + super(new StringReader(content), systemId); + } + +} diff --git a/xml/src/main/java/org/springframework/xml/transform/package.html b/xml/src/main/java/org/springframework/xml/transform/package.html new file mode 100644 index 00000000..32639a6c --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/transform/package.html @@ -0,0 +1,5 @@ + + +Provides classes that help with XML transformations. Mostly for internal use by the framework. + + diff --git a/xml/src/main/java/org/springframework/xml/validation/Jaxp10ValidatorFactory.java b/xml/src/main/java/org/springframework/xml/validation/Jaxp10ValidatorFactory.java new file mode 100644 index 00000000..7a1ca08b --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/validation/Jaxp10ValidatorFactory.java @@ -0,0 +1,179 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.springframework.core.io.Resource; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Internal class that uses JAXP 1.0 features to create XmlValidator instances. + * + * @author Arjen Poutsma + */ +abstract class Jaxp10ValidatorFactory { + + private static final String SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; + + private static final String SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource"; + + static XmlValidator createValidator(Resource[] schemaResources, String schemaLanguage) throws IOException { + InputSource[] inputSources = new InputSource[schemaResources.length]; + for (int i = 0; i < schemaResources.length; i++) { + inputSources[i] = new InputSource(schemaResources[i].getInputStream()); + inputSources[i].setSystemId(SchemaLoaderUtils.getSystemId(schemaResources[i])); + } + return new Jaxp10Validator(inputSources, schemaLanguage); + } + + private static class Jaxp10Validator implements XmlValidator { + + private SAXParserFactory parserFactory; + + private TransformerFactory transformerFactory; + + private InputSource[] schemaInputSources; + + private String schemaLanguage; + + private Jaxp10Validator(InputSource[] schemaInputSources, String schemaLanguage) { + this.schemaInputSources = schemaInputSources; + this.schemaLanguage = schemaLanguage; + transformerFactory = TransformerFactory.newInstance(); + parserFactory = SAXParserFactory.newInstance(); + parserFactory.setNamespaceAware(true); + parserFactory.setValidating(true); + } + + public SAXParseException[] validate(Source source) throws IOException { + SAXParser parser = createSAXParser(); + ValidationErrorHandler errorHandler = new ValidationErrorHandler(); + try { + if (source instanceof SAXSource) { + validateSAXSource((SAXSource) source, parser, errorHandler); + } + else if (source instanceof StreamSource) { + validateStreamSource((StreamSource) source, parser, errorHandler); + } + else if (source instanceof DOMSource) { + validateDOMSource((DOMSource) source, parser, errorHandler); + } + else { + throw new IllegalArgumentException("Source [" + source.getClass().getName() + + "] is neither SAXSource, DOMSource, nor StreamSource"); + } + return errorHandler.getErrors(); + } + catch (SAXException ex) { + throw new XmlValidationException("Could not validate source: " + ex.getMessage(), ex); + } + } + + private void validateDOMSource(DOMSource domSource, SAXParser parser, ValidationErrorHandler errorHandler) + throws IOException, SAXException { + try { + // Sadly, JAXP 1.0 DOM doesn't implement DOM level 3, so we cannot use Document.normalizeDocument() + // Instead, we write the Document to a Stream, and validate that + Transformer transformer = transformerFactory.newTransformer(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + transformer.transform(domSource, new StreamResult(outputStream)); + ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); + validateStreamSource(new StreamSource(inputStream), parser, errorHandler); + } + catch (TransformerException ex) { + throw new XmlValidationException("Could not validate DOM source: " + ex.getMessage(), ex); + } + + } + + private void validateStreamSource(StreamSource streamSource, + SAXParser parser, + ValidationErrorHandler errorHandler) throws SAXException, IOException { + if (streamSource.getInputStream() != null) { + parser.parse(streamSource.getInputStream(), errorHandler); + } + else if (streamSource.getReader() != null) { + parser.parse(new InputSource(streamSource.getReader()), errorHandler); + } + else { + throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader"); + } + } + + private void validateSAXSource(SAXSource source, SAXParser parser, ValidationErrorHandler errorHandler) + throws SAXException, IOException { + parser.parse(source.getInputSource(), errorHandler); + } + + private SAXParser createSAXParser() { + try { + SAXParser parser = parserFactory.newSAXParser(); + parser.setProperty(SCHEMA_LANGUAGE, schemaLanguage); + parser.setProperty(SCHEMA_SOURCE, schemaInputSources); + return parser; + } + catch (ParserConfigurationException ex) { + throw new XmlValidationException("Could not create SAXParser: " + ex.getMessage(), ex); + } + catch (SAXException ex) { + throw new XmlValidationException("Could not create SAXParser: " + ex.getMessage(), ex); + } + } + } + + /** + * DefaultHandler extension that stores errors and fatal errors in a list. + */ + private static class ValidationErrorHandler extends DefaultHandler { + + private List errors = new ArrayList(); + + private SAXParseException[] getErrors() { + return (SAXParseException[]) errors.toArray(new SAXParseException[errors.size()]); + } + + public void warning(SAXParseException ex) throws SAXException { + } + + public void error(SAXParseException ex) throws SAXException { + errors.add(ex); + } + + public void fatalError(SAXParseException ex) throws SAXException { + errors.add(ex); + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/validation/Jaxp13ValidatorFactory.java b/xml/src/main/java/org/springframework/xml/validation/Jaxp13ValidatorFactory.java new file mode 100644 index 00000000..4b4f0191 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/validation/Jaxp13ValidatorFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.xml.transform.Source; +import javax.xml.validation.Schema; + +import org.springframework.core.io.Resource; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Internal class that uses JAXP 1.0 features to create XmlValidator instances. + * + * @author Arjen Poutsma + */ +abstract class Jaxp13ValidatorFactory { + + static XmlValidator createValidator(Resource[] resources, String schemaLanguage) throws IOException { + try { + Schema schema = SchemaLoaderUtils.loadSchema(resources, schemaLanguage); + return new Jaxp13Validator(schema); + } + catch (SAXException ex) { + throw new XmlValidationException("Could not create Schema: " + ex.getMessage(), ex); + } + } + + private static class Jaxp13Validator implements XmlValidator { + + private Schema schema; + + public Jaxp13Validator(Schema schema) { + this.schema = schema; + } + + public SAXParseException[] validate(Source source) throws IOException { + javax.xml.validation.Validator validator = schema.newValidator(); + ValidationErrorHandler errorHandler = new ValidationErrorHandler(); + validator.setErrorHandler(errorHandler); + try { + validator.validate(source); + return errorHandler.getErrors(); + } + catch (SAXException ex) { + throw new XmlValidationException("Could not validate source: " + ex.getMessage(), ex); + } + } + } + + /** + * ErrorHandler implementation that stores errors and fatal errors in a list. + */ + private static class ValidationErrorHandler implements ErrorHandler { + + private List errors = new ArrayList(); + + private SAXParseException[] getErrors() { + return (SAXParseException[]) errors.toArray(new SAXParseException[errors.size()]); + } + + public void warning(SAXParseException ex) throws SAXException { + } + + public void error(SAXParseException ex) throws SAXException { + errors.add(ex); + } + + public void fatalError(SAXParseException ex) throws SAXException { + errors.add(ex); + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/validation/SchemaLoaderUtils.java b/xml/src/main/java/org/springframework/xml/validation/SchemaLoaderUtils.java new file mode 100644 index 00000000..9b2d86f0 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/validation/SchemaLoaderUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.IOException; +import java.io.InputStream; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.xml.sax.SAXException; + +/** + * Convenient utility methods for loading of javax.xml.validation.Schema objects, performing standard + * handling of input streams. + * + * @author Arjen Poutsma + */ +public abstract class SchemaLoaderUtils { + + /** + * Load schema from the given resource. + * + * @param resource the resource to load from + * @param schemaLanguage the language of the schema. Can be XMLConstants.W3C_XML_SCHEMA_NS_URI or + * XMLConstants.RELAXNG_NS_URI. + * @throws IOException if loading failed + * @throws SAXException if loading failed + * @see javax.xml.XMLConstants#W3C_XML_SCHEMA_NS_URI + * @see javax.xml.XMLConstants#RELAXNG_NS_URI + */ + public static Schema loadSchema(Resource resource, String schemaLanguage) throws IOException, SAXException { + return loadSchema(new Resource[]{resource}, schemaLanguage); + } + + /** + * Load schema from the given resource. + * + * @param resources the resources to load from + * @param schemaLanguage the language of the schema. Can be XMLConstants.W3C_XML_SCHEMA_NS_URI or + * XMLConstants.RELAXNG_NS_URI. + * @throws IOException if loading failed + * @throws SAXException if loading failed + * @see javax.xml.XMLConstants#W3C_XML_SCHEMA_NS_URI + * @see javax.xml.XMLConstants#RELAXNG_NS_URI + */ + public static Schema loadSchema(Resource[] resources, String schemaLanguage) throws IOException, SAXException { + Assert.notEmpty(resources, "No resources given"); + Assert.hasLength(schemaLanguage, "No schema language provided"); + StreamSource[] schemaSources = new StreamSource[resources.length]; + try { + for (int i = 0; i < resources.length; i++) { + Assert.notNull(resources[i], "Resource is null"); + Assert.isTrue(resources[i].exists(), "Resource " + resources[i] + " does not exist"); + schemaSources[i] = new StreamSource(resources[i].getInputStream(), getSystemId(resources[i])); + } + SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage); + return schemaFactory.newSchema(schemaSources); + } + finally { + for (int i = 0; i < schemaSources.length; i++) { + if (schemaSources[i] != null) { + InputStream inputStream = schemaSources[i].getInputStream(); + if (inputStream != null) { + inputStream.close(); + } + } + } + } + } + + /** + * Retrieves the URL from the given resource as System ID. Returns null if it cannot be openened. + */ + public static String getSystemId(Resource resource) { + try { + return resource.getURL().toString(); + } + catch (IOException e) { + return null; + } + } +} \ No newline at end of file diff --git a/xml/src/main/java/org/springframework/xml/validation/XmlValidationException.java b/xml/src/main/java/org/springframework/xml/validation/XmlValidationException.java new file mode 100644 index 00000000..9280f5e8 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/validation/XmlValidationException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import org.springframework.xml.XmlException; + +/** + * Expection thrown when a validation error occurs + * + * @author Arjen Poutsma + */ +public class XmlValidationException extends XmlException { + + public XmlValidationException(String s) { + super(s); + } + + public XmlValidationException(String s, Throwable throwable) { + super(s, throwable); + } +} diff --git a/xml/src/main/java/org/springframework/xml/validation/XmlValidator.java b/xml/src/main/java/org/springframework/xml/validation/XmlValidator.java new file mode 100644 index 00000000..5201fa3f --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/validation/XmlValidator.java @@ -0,0 +1,47 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.IOException; + +import javax.xml.transform.Source; + +import org.xml.sax.SAXParseException; + +/** + * Simple processor that validates a given Source. Can be created via the + * XmlValidatorFactory. + *

+ * XmlValidator instances are designed to be thread safe. + * + * @author Arjen Poutsma + * @see XmlValidatorFactory#createValidator(org.springframework.core.io.Resource, String) + */ +public interface XmlValidator { + + /** + * Validates the given Source, and returns an array of SAXParseExceptions as result. The + * array will be empty if no validation errors are found. + * + * @param source the input document + * @return an array of SAXParseExceptions + * @throws IOException if the source cannot be read + * @throws XmlValidationException if the source cannot be validated + */ + SAXParseException[] validate(Source source) throws IOException; + +} diff --git a/xml/src/main/java/org/springframework/xml/validation/XmlValidatorFactory.java b/xml/src/main/java/org/springframework/xml/validation/XmlValidatorFactory.java new file mode 100644 index 00000000..4921bbca --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/validation/XmlValidatorFactory.java @@ -0,0 +1,104 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.xml.JaxpVersion; + +/** + * Factory for XmlValidators, being aware of JAXP 1.3 XmlValidators, and JAXP 1.0 parsing + * capababilities. Mainly for internal use within the framework. + *

+ * The goal of this class is to avoid runtime dependencies on JAXP 1.3 by using the best validation implementation that + * is available. Prefers JAXP 1.3 XmlValidator implementations to a custom, SAX-based implementation. + * + * @author Arjen Poutsma + * @see XmlValidator + */ +public abstract class XmlValidatorFactory { + + private static final Log logger = LogFactory.getLog(XmlValidatorFactory.class); + + /** + * Constant that defines a W3C XML Schema. + */ + public static final String SCHEMA_W3C_XML = "http://www.w3.org/2001/XMLSchema"; + + /** + * Constant that defines a RELAX NG Schema. + */ + public static final String SCHEMA_RELAX_NG = "http://relaxng.org/ns/structure/1.0"; + + /** + * Create a XmlValidator with the given schema resource and schema language type. The schema language + * must be one of the SCHEMA_XXX constants. + * + * @param schemaResource a resource that locates the schema to validate against + * @param schemaLanguage the language of the schema + * @return a validator + * @throws IOException if the schema resource cannot be read + * @throws IllegalArgumentException if the schema language is not supported + * @throws IllegalStateException if JAXP 1.0 cannot be located + * @throws XmlValidationException if a XmlValidator cannot be created + * @see #SCHEMA_RELAX_NG + * @see #SCHEMA_W3C_XML + */ + public static XmlValidator createValidator(Resource schemaResource, String schemaLanguage) throws IOException { + return createValidator(new Resource[]{schemaResource}, schemaLanguage); + } + + /** + * Create a XmlValidator with the given schema resources and schema language type. The schema language + * must be one of the SCHEMA_XXX constants. + * + * @param schemaResources an array of resource that locate the schemas to validate against + * @param schemaLanguage the language of the schemas + * @return a validator + * @throws IOException if the schema resource cannot be read + * @throws IllegalArgumentException if the schema language is not supported + * @throws IllegalStateException if JAXP 1.0 cannot be located + * @throws XmlValidationException if a XmlValidator cannot be created + * @see #SCHEMA_RELAX_NG + * @see #SCHEMA_W3C_XML + */ + public static XmlValidator createValidator(Resource[] schemaResources, String schemaLanguage) throws IOException { + Assert.notEmpty(schemaResources, "No resources given"); + Assert.hasLength(schemaLanguage, "No schema language provided"); + Assert.isTrue(SCHEMA_W3C_XML.equals(schemaLanguage) || SCHEMA_RELAX_NG.equals(schemaLanguage), + "Invalid schema language: " + schemaLanguage); + for (int i = 0; i < schemaResources.length; i++) { + Assert.isTrue(schemaResources[i].exists(), "schema [" + schemaResources + "] does not exist"); + } + if (JaxpVersion.getJaxpVersion() >= JaxpVersion.JAXP_13) { + logger.debug("Creating JAXP 1.3 XmlValidator"); + return Jaxp13ValidatorFactory.createValidator(schemaResources, schemaLanguage); + } + else if (JaxpVersion.getJaxpVersion() >= JaxpVersion.JAXP_10) { + logger.debug("Creating JAXP 1.0 XmlValidator"); + return Jaxp10ValidatorFactory.createValidator(schemaResources, schemaLanguage); + } + else { + throw new IllegalStateException("Could not locate JAXP 1.0 or higher."); + } + } + +} diff --git a/xml/src/main/java/org/springframework/xml/validation/package.html b/xml/src/main/java/org/springframework/xml/validation/package.html new file mode 100644 index 00000000..07ed2cff --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/validation/package.html @@ -0,0 +1,5 @@ + + +Provides classes for XML validation in JAXP 1.0 and JAXP 1.3. Mostly for internal use by the framework. + + diff --git a/xml/src/main/java/org/springframework/xml/xpath/JaxenXPathExpressionFactory.java b/xml/src/main/java/org/springframework/xml/xpath/JaxenXPathExpressionFactory.java new file mode 100644 index 00000000..ad8bf0f2 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/JaxenXPathExpressionFactory.java @@ -0,0 +1,131 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import java.util.List; +import java.util.Map; + +import org.jaxen.JaxenException; +import org.jaxen.SimpleNamespaceContext; +import org.jaxen.XPath; +import org.jaxen.dom.DOMXPath; +import org.w3c.dom.Node; + +/** + * Jaxen-specific factory for creating XPathExpressions. + * + * @author Arjen Poutsma + * @see #createXPathExpression(String) + */ +abstract class JaxenXPathExpressionFactory { + + /** + * Creates a Jaxen XPathExpression from the given string expression. + * + * @param expression the XPath expression + * @return the compiled XPathExpression + * @throws XPathParseException when the given expression cannot be parsed + */ + static XPathExpression createXPathExpression(String expression) { + try { + XPath xpath = new DOMXPath(expression); + return new JaxenXpathExpression(xpath); + } + catch (JaxenException ex) { + throw new org.springframework.xml.xpath.XPathParseException( + "Could not compile [" + expression + "] to a XPathExpression: " + ex.getMessage(), ex); + } + } + + /** + * Creates a Jaxen XPathExpression from the given string expression and prefixes. + * + * @param expression the XPath expression + * @param namespaces the namespaces + * @return the compiled XPathExpression + * @throws XPathParseException when the given expression cannot be parsed + */ + public static XPathExpression createXPathExpression(String expression, Map namespaces) { + try { + XPath xpath = new DOMXPath(expression); + xpath.setNamespaceContext(new SimpleNamespaceContext(namespaces)); + return new JaxenXpathExpression(xpath); + } + catch (JaxenException ex) { + throw new org.springframework.xml.xpath.XPathParseException( + "Could not compile [" + expression + "] to a XPathExpression: " + ex.getMessage(), ex); + } + } + + /** + * Jaxen implementation of the XPathExpression interface. + */ + private static class JaxenXpathExpression implements XPathExpression { + + private XPath xpath; + + private JaxenXpathExpression(XPath xpath) { + this.xpath = xpath; + } + + public Node evaluateAsNode(Node node) { + try { + return (Node) xpath.selectSingleNode(node); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + xpath + "] :" + ex.getMessage(), ex); + } + } + + public boolean evaluateAsBoolean(Node node) { + try { + return xpath.booleanValueOf(node); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + xpath + "] :" + ex.getMessage(), ex); + } + } + + public double evaluateAsNumber(Node node) { + try { + return xpath.numberValueOf(node).doubleValue(); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + xpath + "] :" + ex.getMessage(), ex); + } + } + + public String evaluateAsString(Node node) { + try { + return xpath.stringValueOf(node); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + xpath + "] :" + ex.getMessage(), ex); + } + } + + public Node[] evaluateAsNodes(Node node) { + try { + List result = xpath.selectNodes(node); + return (Node[]) result.toArray(new Node[result.size()]); + } + catch (JaxenException ex) { + throw new XPathException("Could not evaluate XPath expression [" + xpath + "] :" + ex.getMessage(), ex); + } + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactory.java b/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactory.java new file mode 100644 index 00000000..0fa35e10 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactory.java @@ -0,0 +1,140 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import java.util.Map; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.springframework.xml.namespace.SimpleNamespaceContext; + +/** + * JAXP 1.3-specific factory creating XPathExpressions. + * + * @author Arjen Poutsma + * @see #createXPathExpression(String) + */ +abstract class Jaxp13XPathExpressionFactory { + + private static XPathFactory xpathFactory; + + static { + xpathFactory = XPathFactory.newInstance(); + } + + /** + * Creates a JAXP 1.3 XPathExpression from the given string expression. + * + * @param expression the XPath expression + * @return the compiled XPathExpression + * @throws XPathParseException when the given expression cannot be parsed + */ + static XPathExpression createXPathExpression(String expression) { + try { + XPath xpath = xpathFactory.newXPath(); + javax.xml.xpath.XPathExpression xpathExpression = xpath.compile(expression); + return new Jaxp13XPathExpression(xpathExpression); + } + catch (XPathExpressionException ex) { + throw new org.springframework.xml.xpath.XPathParseException( + "Could not compile [" + expression + "] to a XPathExpression: " + ex.getMessage(), ex); + } + } + + /** + * Creates a JAXP 1.3 XPathExpression from the given string expression and namespaces. + * + * @param expression the XPath expression + * @param namespaces the namespaces + * @return the compiled XPathExpression + * @throws XPathParseException when the given expression cannot be parsed + */ + public static XPathExpression createXPathExpression(String expression, Map namespaces) { + try { + XPath xpath = xpathFactory.newXPath(); + SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext(); + namespaceContext.setBindings(namespaces); + xpath.setNamespaceContext(namespaceContext); + javax.xml.xpath.XPathExpression xpathExpression = xpath.compile(expression); + return new Jaxp13XPathExpression(xpathExpression); + } + catch (XPathExpressionException ex) { + throw new org.springframework.xml.xpath.XPathParseException( + "Could not compile [" + expression + "] to a XPathExpression: " + ex.getMessage(), ex); + } + } + + /** + * JAXP 1.3 implementation of the XPathExpression interface. + */ + private static class Jaxp13XPathExpression implements XPathExpression { + + javax.xml.xpath.XPathExpression xpathExpression; + + private Jaxp13XPathExpression(javax.xml.xpath.XPathExpression xpathExpression) { + this.xpathExpression = xpathExpression; + } + + public String evaluateAsString(Node node) { + return (String) evaluate(node, XPathConstants.STRING); + } + + public Node[] evaluateAsNodes(Node node) { + NodeList nodeList = (NodeList) evaluate(node, XPathConstants.NODESET); + return toNodeArray(nodeList); + } + + private Object evaluate(Node node, QName returnType) { + try { + return xpathExpression.evaluate(node, returnType); + } + catch (XPathExpressionException ex) { + throw new XPathException("Could not evaluate XPath expression:" + ex.getMessage(), ex); + } + } + + private Node[] toNodeArray(NodeList nodeList) { + Node[] result = new Node[nodeList.getLength()]; + for (int i = 0; i < nodeList.getLength(); i++) { + result[i] = nodeList.item(i); + } + return result; + } + + public double evaluateAsNumber(Node node) { + Double result = (Double) evaluate(node, XPathConstants.NUMBER); + return result.doubleValue(); + } + + public boolean evaluateAsBoolean(Node node) { + Boolean result = (Boolean) evaluate(node, XPathConstants.BOOLEAN); + return result.booleanValue(); + } + + public Node evaluateAsNode(Node node) { + return (Node) evaluate(node, XPathConstants.NODE); + } + } + +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/XPathException.java b/xml/src/main/java/org/springframework/xml/xpath/XPathException.java new file mode 100644 index 00000000..d9f2f673 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/XPathException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import org.springframework.xml.XmlException; + +/** + * Exception thrown when an error occurs during XPath processing. + * + * @author Arjen Poutsma + */ +public class XPathException extends XmlException { + + /** + * Constructs a new instance of the XPathException with the specific detail message. + * + * @param message the detail message + */ + public XPathException(String message) { + super(message); + } + + /** + * Constructs a new instance of the XPathException with the specific detail message and exception. + * + * @param message the detail message + * @param throwable the wrapped exception + */ + public XPathException(String message, Throwable throwable) { + super(message, throwable); + } +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/XPathExpression.java b/xml/src/main/java/org/springframework/xml/xpath/XPathExpression.java new file mode 100644 index 00000000..4ad7cb73 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/XPathExpression.java @@ -0,0 +1,75 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Defines the contract for a precompiled XPath expression. Concrete instances can be obtained through the + * XPathExpressionFactory. + * + * @author Arjen Poutsma + * @see XPathExpressionFactory + */ +public interface XPathExpression { + + /** + * Evaluates the given expression as a Node. Returns the evaluation of the expression, or + * null if it is invalid. + * + * @param node the starting point + * @return the result of the evaluation + */ + Node evaluateAsNode(Node node); + + /** + * Evaluates the given expression as a boolean. Returns the boolean evaluation of the expression, or + * false if it is invalid. + * + * @param node the starting point + * @return the result of the evaluation + */ + boolean evaluateAsBoolean(Node node); + + /** + * Evaluates the given expression as a number (double). Returns the numeric evaluation of the + * expression, or Double.NaN if it is invalid. + * + * @param node the starting point + * @return the result of the evaluation + * @see Double#NaN + */ + double evaluateAsNumber(Node node); + + /** + * Evaluates the given expression, and returns the first Node that conforms to it. Returns + * null if no result could be found. + * + * @param node the starting point + * @return the first Node that is selected by the expression + */ + String evaluateAsString(Node node); + + /** + * Evaluates the given expression, and returns all Nodes that conform to it. Returns and empty array if + * no result could be found. + * + * @param node the starting point + * @return the Nodes that are selected by the expression + */ + Node[] evaluateAsNodes(Node node); +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/XPathExpressionFactory.java b/xml/src/main/java/org/springframework/xml/xpath/XPathExpressionFactory.java new file mode 100644 index 00000000..837393ef --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/XPathExpressionFactory.java @@ -0,0 +1,117 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import java.util.Collections; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.xml.JaxpVersion; + +/** + * Factory for compiled XPathExpressions, being aware of JAXP 1.3+ XPath functionality, Jaxen, and Xalan. + * Mainly for internal use of the framework. + *

+ * The goal of this class is to avoid runtime dependencies a specific XPath engine, simply using the best XPath + * implementation that is available. Prefers JAXP 1.3+ XPath implementations to Jaxen, and falls back to Xalan. + * + * @author Arjen Poutsma + * @see XPathExpression + */ +public abstract class XPathExpressionFactory { + + private static final Log logger = LogFactory.getLog(XPathExpressionFactory.class); + + private static final String XALAN_XPATH_CLASS_NAME = "org.apache.xpath.XPath"; + + private static boolean xalanXPathAvailable; + + private static final String JAXEN_CLASS_NAME = "org.jaxen.XPath"; + + private static boolean jaxenAvailable; + + static { + // Check whether JAXP 1.3, Resin, Jaxen, or Xalan are available + if (JaxpVersion.getJaxpVersion() >= JaxpVersion.JAXP_13) { + logger.info("JAXP 1.3 available"); + } + try { + Class.forName(JAXEN_CLASS_NAME); + jaxenAvailable = true; + logger.info("Jaxen available"); + } + catch (ClassNotFoundException ex) { + jaxenAvailable = false; + } + try { + Class.forName(XALAN_XPATH_CLASS_NAME); + xalanXPathAvailable = true; + logger.info("Xalan available"); + } + catch (ClassNotFoundException ex) { + xalanXPathAvailable = false; + } + } + + /** + * Create a compiled XPath expression using the given string. + * + * @param expression the XPath expression + * @return the compiled XPath expression + * @throws IllegalStateException if neither JAXP 1.3+, Jaxen, or Xalan are available + * @throws XPathParseException if the given expression cannot be parsed + */ + public static XPathExpression createXPathExpression(String expression) + throws IllegalStateException, XPathParseException { + return createXPathExpression(expression, Collections.EMPTY_MAP); + } + + /** + * Create a compiled XPath expression using the given string and namespaces. The namespace map should consist of + * string prefixes mapped to string namespaces. + * + * @param expression the XPath expression + * @param namespaces a map that binds string prefixes to string namespaces + * @return the compiled XPath expression + * @throws IllegalStateException if neither JAXP 1.3+, Jaxen, or Xalan are available + * @throws XPathParseException if the given expression cannot be parsed + */ + public static XPathExpression createXPathExpression(String expression, Map namespaces) + throws IllegalStateException, XPathParseException { + Assert.hasLength(expression, "expression is empty"); + if (JaxpVersion.getJaxpVersion() >= JaxpVersion.JAXP_13) { + logger.debug("Creating [javax.xml.xpath.XPathExpression]"); + return Jaxp13XPathExpressionFactory.createXPathExpression(expression, namespaces); + } + else if (jaxenAvailable) { + logger.debug("Creating [org.jaxen.XPath]"); + return JaxenXPathExpressionFactory.createXPathExpression(expression, namespaces); + } + else if (xalanXPathAvailable) { + logger.debug("Creating [org.apache.xpath.XPath]"); + return XalanXPathExpressionFactory.createXPathExpression(expression, namespaces); + } + else { + throw new IllegalStateException( + "Could not create XPathExpression: could not locate JAXP 1.3, Resin, Xalan on the class path"); + } + } + + +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/XPathParseException.java b/xml/src/main/java/org/springframework/xml/xpath/XPathParseException.java new file mode 100644 index 00000000..cbe1cebe --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/XPathParseException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +/** + * Exception throws when a XPath expression cannot be parsed. + * + * @author Arjen Poutsma + */ +public class XPathParseException extends XPathException { + + /** + * Constructs a new instance of the XPathParseException with the specific detail message. + * + * @param message the detail message + */ + public XPathParseException(String message) { + super(message); + } + + /** + * Constructs a new instance of the XPathParseException with the specific detail message and + * exception. + * + * @param message the detail message + * @param throwable the wrapped exception + */ + public XPathParseException(String message, Throwable throwable) { + super(message, throwable); + } +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/XalanXPathExpressionFactory.java b/xml/src/main/java/org/springframework/xml/xpath/XalanXPathExpressionFactory.java new file mode 100644 index 00000000..c636f54f --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/XalanXPathExpressionFactory.java @@ -0,0 +1,173 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import java.util.Map; + +import javax.xml.transform.TransformerException; + +import org.apache.xml.utils.PrefixResolver; +import org.apache.xpath.XPath; +import org.apache.xpath.XPathContext; +import org.apache.xpath.objects.XObject; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.traversal.NodeIterator; + +/** + * Xalan-specific factory creating XPathExpressions. + * + * @author Arjen Poutsma + * @see #createXPathExpression(String) + */ +abstract class XalanXPathExpressionFactory { + + /** + * Creates a Xalan XPathExpression from the given string expression. + * + * @param expression the XPath expression + * @return the compiled XPathExpression + * @throws XPathParseException when the given expression cannot be parsed + */ + static XPathExpression createXPathExpression(String expression) { + try { + XPath xpath = new XPath(expression, null, null, XPath.SELECT); + return new XalanXPathExpression(xpath); + } + catch (TransformerException ex) { + throw new org.springframework.xml.xpath.XPathParseException( + "Could not compile [" + expression + "] to a XPathExpression: " + ex.getMessage(), ex); + } + } + + /** + * Creates a Xalan XPathExpression from the given string expression and namespaces. + * + * @param expression the XPath expression + * @return the compiled XPathExpression + * @throws XPathParseException when the given expression cannot be parsed + */ + public static XPathExpression createXPathExpression(String expression, Map namespaces) { + try { + PrefixResolver prefixResolver = new SimplePrefixResolver(namespaces); + XPath xpath = new XPath(expression, null, prefixResolver, XPath.SELECT); + return new XalanXPathExpression(xpath); + } + catch (TransformerException ex) { + throw new org.springframework.xml.xpath.XPathParseException( + "Could not compile [" + expression + "] to a XPathExpression: " + ex.getMessage(), ex); + } + } + + /** + * Xalan implementation of the XPathExpression interface. + */ + private static class XalanXPathExpression implements XPathExpression { + + private XPath xpath; + + private XPathContext context = new XPathContext(); + + private XalanXPathExpression(XPath xpath) { + this.xpath = xpath; + } + + public Node evaluateAsNode(Node node) { + try { + XObject result = xpath.execute(context, node, null); + NodeIterator iterator = result.nodeset(); + return iterator.nextNode(); + } + catch (TransformerException ex) { + throw new XPathException("Could not evaluate XPath expression:" + ex.getMessage(), ex); + } + } + + public boolean evaluateAsBoolean(Node node) { + try { + XObject result = xpath.execute(context, node, null); + return result.bool(); + } + catch (TransformerException ex) { + throw new XPathException("Could not evaluate XPath expression:" + ex.getMessage(), ex); + } + } + + public double evaluateAsNumber(Node node) { + try { + XObject result = xpath.execute(context, node, null); + return result.num(); + } + catch (TransformerException ex) { + throw new XPathException("Could not evaluate XPath expression:" + ex.getMessage(), ex); + } + } + + public String evaluateAsString(Node node) { + try { + XObject result = xpath.execute(context, node, null); + return result.str(); + } + catch (TransformerException ex) { + throw new XPathException("Could not evaluate XPath expression:" + ex.getMessage(), ex); + } + } + + public Node[] evaluateAsNodes(Node node) { + try { + XObject result = xpath.execute(context, node, null); + return toNodeArray(result.nodelist()); + } + catch (TransformerException ex) { + throw new XPathException("Could not evaluate XPath expression:" + ex.getMessage(), ex); + } + } + + private Node[] toNodeArray(NodeList nodeList) { + Node[] result = new Node[nodeList.getLength()]; + for (int i = 0; i < nodeList.getLength(); i++) { + result[i] = nodeList.item(i); + } + return result; + } + } + + private static class SimplePrefixResolver implements PrefixResolver { + + private Map namespaces; + + private SimplePrefixResolver(Map namespaces) { + this.namespaces = namespaces; + } + + public String getNamespaceForPrefix(String prefix) { + return (String) namespaces.get(prefix); + } + + public String getNamespaceForPrefix(String prefix, Node node) { + return (String) namespaces.get(prefix); + } + + public String getBaseIdentifier() { + return null; + } + + public boolean handlesNullPrefixes() { + return false; + } + } +} diff --git a/xml/src/main/java/org/springframework/xml/xpath/package.html b/xml/src/main/java/org/springframework/xml/xpath/package.html new file mode 100644 index 00000000..3dfd1381 --- /dev/null +++ b/xml/src/main/java/org/springframework/xml/xpath/package.html @@ -0,0 +1,5 @@ + + +Provides classes for XPath evaluation using JAXP 1.3, Jaxen, and Xalan. Mostly for internal use by the framework. + + diff --git a/xml/src/test/java/org/springframework/xml/namespace/QNameEditorTest.java b/xml/src/test/java/org/springframework/xml/namespace/QNameEditorTest.java new file mode 100644 index 00000000..b5d962d9 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/namespace/QNameEditorTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.xml.namespace; + +import javax.xml.namespace.QName; + +import junit.framework.TestCase; + +public class QNameEditorTest extends TestCase { + + private QNameEditor editor; + + protected void setUp() throws Exception { + editor = new QNameEditor(); + } + + public void testNamespaceLocalPartPrefix() throws Exception { + QName qname = new QName("namespace", "localpart", "prefix"); + doTest(qname); + } + + public void testNamespaceLocalPart() throws Exception { + QName qname = new QName("namespace", "localpart"); + doTest(qname); + } + + public void testLocalPart() throws Exception { + QName qname = new QName("localpart"); + doTest(qname); + } + + private void doTest(QName qname) { + editor.setValue(qname); + String text = editor.getAsText(); + assertNotNull("getAsText returns null", text); + editor.setAsText(text); + QName result = (QName) editor.getValue(); + assertNotNull("getValue returns null", result); + assertEquals("Parsed QName local part is not equal to original", qname.getLocalPart(), result.getLocalPart()); + assertEquals("Parsed QName prefix is not equal to original", qname.getPrefix(), result.getPrefix()); + assertEquals("Parsed QName namespace is not equal to original", qname.getNamespaceURI(), + result.getNamespaceURI()); + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/namespace/QNameUtilsTest.java b/xml/src/test/java/org/springframework/xml/namespace/QNameUtilsTest.java new file mode 100644 index 00000000..57e84320 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/namespace/QNameUtilsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.xml.namespace; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import junit.framework.TestCase; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.springframework.util.StringUtils; + +public class QNameUtilsTest extends TestCase { + + public void testValidQNames() { + assertTrue("Namespace QName not validated", QNameUtils.validateQName("{namespace}local")); + assertTrue("No Namespace QName not validated", QNameUtils.validateQName("local")); + } + + public void testInvalidQNames() { + assertFalse("Null QName validated", QNameUtils.validateQName(null)); + assertFalse("Empty QName validated", QNameUtils.validateQName("")); + assertFalse("Invalid QName validated", QNameUtils.validateQName("{namespace}")); + assertFalse("Invalid QName validated", QNameUtils.validateQName("{namespace")); + } + + public void testGetQNameForNodeNoNamespace() throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Element element = document.createElement("localname"); + QName qName = QNameUtils.getQNameForNode(element); + assertNotNull("getQNameForNode returns null", qName); + assertEquals("QName has invalid localname", "localname", qName.getLocalPart()); + assertFalse("Qname has invalid namespace", StringUtils.hasLength(qName.getNamespaceURI())); + assertFalse("Qname has invalid prefix", StringUtils.hasLength(qName.getPrefix())); + + } + + public void testGetQNameForNodeNoPrefix() throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Element element = document.createElementNS("namespace", "localname"); + QName qName = QNameUtils.getQNameForNode(element); + assertNotNull("getQNameForNode returns null", qName); + assertEquals("QName has invalid localname", "localname", qName.getLocalPart()); + assertEquals("Qname has invalid namespace", "namespace", qName.getNamespaceURI()); + assertFalse("Qname has invalid prefix", StringUtils.hasLength(qName.getPrefix())); + } + + public void testGetQNameForNode() throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Element element = document.createElementNS("namespace", "prefix:localname"); + QName qName = QNameUtils.getQNameForNode(element); + assertNotNull("getQNameForNode returns null", qName); + assertEquals("QName has invalid localname", "localname", qName.getLocalPart()); + assertEquals("Qname has invalid namespace", "namespace", qName.getNamespaceURI()); + assertEquals("Qname has invalid prefix", "prefix", qName.getPrefix()); + } + + public void testToQualifiedNamePrefix() throws Exception { + QName qName = new QName("namespace", "localName", "prefix"); + String result = QNameUtils.toQualifiedName(qName); + assertEquals("Invalid result", "prefix:localName", result); + } + + public void testToQualifiedNameNoPrefix() throws Exception { + QName qName = new QName("localName"); + String result = QNameUtils.toQualifiedName(qName); + assertEquals("Invalid result", "localName", result); + } + + public void testToQNamePrefix() throws Exception { + QName result = QNameUtils.toQName("namespace", "prefix:localName"); + assertEquals("invalid namespace", "namespace", result.getNamespaceURI()); + assertEquals("invalid prefix", "prefix", result.getPrefix()); + assertEquals("invalid localname", "localName", result.getLocalPart()); + } + + public void testToQNameNoPrefix() throws Exception { + QName result = QNameUtils.toQName("namespace", "localName"); + assertEquals("invalid namespace", "namespace", result.getNamespaceURI()); + assertEquals("invalid prefix", "", result.getPrefix()); + assertEquals("invalid localname", "localName", result.getLocalPart()); + } +} diff --git a/xml/src/test/java/org/springframework/xml/namespace/SimpleNamespaceContextTest.java b/xml/src/test/java/org/springframework/xml/namespace/SimpleNamespaceContextTest.java new file mode 100644 index 00000000..2c72a62c --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/namespace/SimpleNamespaceContextTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2005 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 + * + * 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 org.springframework.xml.namespace; + +import java.util.Collections; +import java.util.Iterator; + +import javax.xml.XMLConstants; + +import junit.framework.TestCase; + +public class SimpleNamespaceContextTest extends TestCase { + + private SimpleNamespaceContext context; + + protected void setUp() throws Exception { + context = new SimpleNamespaceContext(); + context.bindNamespaceUri("prefix", "namespaceURI"); + } + + public void testGetNamespaceURI() { + assertEquals("Invalid namespaceURI for default namespace", "", context + .getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX)); + String defaultNamespaceUri = "defaultNamespace"; + context.bindNamespaceUri(XMLConstants.DEFAULT_NS_PREFIX, defaultNamespaceUri); + assertEquals("Invalid namespaceURI for default namespace", defaultNamespaceUri, context + .getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX)); + assertEquals("Invalid namespaceURI for bound prefix", "namespaceURI", context.getNamespaceURI("prefix")); + assertEquals("Invalid namespaceURI for unbound prefix", "", context.getNamespaceURI("unbound")); + assertEquals("Invalid namespaceURI for namespace prefix", XMLConstants.XML_NS_URI, context + .getNamespaceURI(XMLConstants.XML_NS_PREFIX)); + assertEquals("Invalid namespaceURI for attribute prefix", XMLConstants.XMLNS_ATTRIBUTE_NS_URI, context + .getNamespaceURI(XMLConstants.XMLNS_ATTRIBUTE)); + + } + + public void testGetPrefix() { + assertEquals("Invalid prefix for default namespace", XMLConstants.DEFAULT_NS_PREFIX, context.getPrefix("")); + assertEquals("Invalid prefix for bound namespace", "prefix", context.getPrefix("namespaceURI")); + assertNull("Invalid prefix for unbound namespace", context.getPrefix("unbound")); + assertEquals("Invalid prefix for namespace", XMLConstants.XML_NS_PREFIX, context + .getPrefix(XMLConstants.XML_NS_URI)); + assertEquals("Invalid prefix for attribute namespace", XMLConstants.XMLNS_ATTRIBUTE, context + .getPrefix(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)); + } + + public void testGetPrefixes() { + assertPrefixes("", XMLConstants.DEFAULT_NS_PREFIX); + assertPrefixes("namespaceURI", "prefix"); + assertFalse("Invalid prefix for unbound namespace", context.getPrefixes("unbound").hasNext()); + assertPrefixes(XMLConstants.XML_NS_URI, XMLConstants.XML_NS_PREFIX); + assertPrefixes(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE); + } + + public void testMultiplePrefixes() { + context.bindNamespaceUri("prefix1", "namespace"); + context.bindNamespaceUri("prefix2", "namespace"); + Iterator iterator = context.getPrefixes("namespace"); + assertNotNull("getPrefixes returns null", iterator); + assertTrue("iterator is empty", iterator.hasNext()); + String result = (String) iterator.next(); + assertTrue("Invalid prefix", result.equals("prefix1") || result.equals("prefix2")); + assertTrue("iterator is empty", iterator.hasNext()); + result = (String) iterator.next(); + assertTrue("Invalid prefix", result.equals("prefix1") || result.equals("prefix2")); + assertFalse("iterator contains more than two values", iterator.hasNext()); + } + + private void assertPrefixes(String namespaceUri, String prefix) { + Iterator iterator = context.getPrefixes(namespaceUri); + assertNotNull("getPrefixes returns null", iterator); + assertTrue("iterator is empty", iterator.hasNext()); + String result = (String) iterator.next(); + assertEquals("Invalid prefix", prefix, result); + assertFalse("iterator contains multiple values", iterator.hasNext()); + } + + public void testGetBoundPrefixes() throws Exception { + Iterator iterator = context.getBoundPrefixes(); + assertNotNull("getPrefixes returns null", iterator); + assertTrue("iterator is empty", iterator.hasNext()); + String result = (String) iterator.next(); + assertEquals("Invalid prefix", "prefix", result); + assertFalse("iterator contains multiple values", iterator.hasNext()); + } + + public void testSetBindings() throws Exception { + context.setBindings(Collections.singletonMap("prefix", "namespace")); + assertEquals("Invalid namespace uri", "namespace", context.getNamespaceURI("prefix")); + } +} diff --git a/xml/src/test/java/org/springframework/xml/stream/AbstractStaxContentHandlerTestCase.java b/xml/src/test/java/org/springframework/xml/stream/AbstractStaxContentHandlerTestCase.java new file mode 100644 index 00000000..37f4f1e4 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/stream/AbstractStaxContentHandlerTestCase.java @@ -0,0 +1,62 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; + +import javax.xml.stream.XMLStreamException; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +public abstract class AbstractStaxContentHandlerTestCase extends XMLTestCase { + + private static final String XML_CONTENT_HANDLER = + "content"; + + private XMLReader xmlReader; + + protected void setUp() throws Exception { + xmlReader = XMLReaderFactory.createXMLReader(); + } + + public void testContentHandler() throws Exception { + StringWriter stringWriter = new StringWriter(); + StaxContentHandler handler = createStaxContentHandler(stringWriter); + xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", false); + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(new StringReader(XML_CONTENT_HANDLER))); + assertXMLEqual("Invalid result", XML_CONTENT_HANDLER, stringWriter.toString()); + } + + public void testContentHandlerNamespacePrefixes() throws Exception { + StringWriter stringWriter = new StringWriter(); + StaxContentHandler handler = createStaxContentHandler(stringWriter); + xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(new StringReader(XML_CONTENT_HANDLER))); + assertXMLEqual("Invalid result", XML_CONTENT_HANDLER, stringWriter.toString()); + } + + protected abstract StaxContentHandler createStaxContentHandler(Writer writer) throws XMLStreamException; + + +} diff --git a/xml/src/test/java/org/springframework/xml/stream/AbstractStaxXmlReaderTestCase.java b/xml/src/test/java/org/springframework/xml/stream/AbstractStaxXmlReaderTestCase.java new file mode 100644 index 00000000..82e20089 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/stream/AbstractStaxXmlReaderTestCase.java @@ -0,0 +1,202 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Arrays; + +import javax.xml.stream.XMLStreamException; + +import junit.framework.TestCase; +import org.easymock.AbstractMatcher; +import org.easymock.MockControl; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +public abstract class AbstractStaxXmlReaderTestCase extends TestCase { + + private static final String XML_DTD_HANDLER = + ""; + + private static final String XML_CONTENT_HANDLER = + "content"; + + private static final String XML_CONTENT_HANDLER_ATTS = ""; + + private XMLReader reader; + + protected void setUp() throws Exception { + reader = XMLReaderFactory.createXMLReader(); + reader.setFeature("http://xml.org/sax/features/namespaces", true); + reader.setFeature("http://xml.org/sax/features/namespace-prefixes", false); + } + + public void testContentHandler() throws SAXException, IOException, XMLStreamException { + // record the callbacks by parsing the XML with a regular SAX parser + MockControl control = MockControl.createStrictControl(ContentHandler.class); + control.setDefaultMatcher(new SaxArgumentMatcher()); + ContentHandler mock = (ContentHandler) control.getMock(); + reader.setContentHandler(mock); + reader.parse(new InputSource(new StringReader(XML_CONTENT_HANDLER))); + control.replay(); + StaxXmlReader staxXmlReader = createStaxXmlReader(new StringReader(XML_CONTENT_HANDLER)); + staxXmlReader.setContentHandler(mock); + staxXmlReader.parse(new InputSource()); + control.verify(); + } + + public void testContentHandlerAttributes() throws SAXException, IOException, XMLStreamException { + MockControl control = MockControl.createStrictControl(ContentHandler.class); + control.setDefaultMatcher(new SaxArgumentMatcher()); + ContentHandler mock = (ContentHandler) control.getMock(); + reader.setContentHandler(mock); + reader.parse(new InputSource(new StringReader(XML_CONTENT_HANDLER_ATTS))); + control.replay(); + StaxXmlReader staxXmlReader = createStaxXmlReader(new StringReader(XML_CONTENT_HANDLER_ATTS)); + staxXmlReader.setContentHandler(mock); + staxXmlReader.parse(new InputSource()); + control.verify(); + } + + public void testDtdHandler() throws IOException, SAXException, XMLStreamException { + // record the callbacks by parsing the XML with a regular SAX parser + MockControl control = MockControl.createStrictControl(DTDHandler.class); + control.setDefaultMatcher(new SaxArgumentMatcher()); + DTDHandler mock = (DTDHandler) control.getMock(); + reader.setDTDHandler(mock); + reader.parse(new InputSource(new StringReader(XML_DTD_HANDLER))); + control.replay(); + StaxXmlReader staxXmlReader = createStaxXmlReader(new StringReader(XML_DTD_HANDLER)); + staxXmlReader.setDTDHandler(mock); + staxXmlReader.parse(new InputSource()); + control.verify(); + } + + protected abstract StaxXmlReader createStaxXmlReader(Reader reader) throws XMLStreamException; + + /** + * Easymock ArgumentMatcher implementation that matches SAX arguments. + */ + private static class SaxArgumentMatcher extends AbstractMatcher { + + public boolean matches(Object[] expected, Object[] actual) { + if (expected == actual) { + return true; + } + if (expected == null || actual == null) { + return false; + } + if (expected.length != actual.length) { + throw new IllegalArgumentException("Expected and actual arguments must have the same size"); + } + if (expected.length == 3 && (expected[0] instanceof char[]) && (expected[1] instanceof Integer) && + (expected[2] instanceof Integer)) { + // handling of the character(char[], int, int) methods + String expectedString = new String((char[]) expected[0], ((Integer) expected[1]).intValue(), + ((Integer) expected[2]).intValue()); + String actualString = new String((char[]) actual[0], ((Integer) actual[1]).intValue(), + ((Integer) actual[2]).intValue()); + return (expectedString.equals(actualString)); + } + else if (expected.length == 1 && (expected[0] instanceof Locator)) { + return true; + } + else { + return super.matches(expected, actual); + } + } + + protected boolean argumentMatches(Object expected, Object actual) { + if (expected instanceof char[]) { + return Arrays.equals((char[]) expected, (char[]) actual); + } + else if (expected instanceof Attributes) { + Attributes expectedAttributes = (Attributes) expected; + Attributes actualAttributes = (Attributes) actual; + if (expectedAttributes.getLength() != actualAttributes.getLength()) { + return false; + } + for (int i = 0; i < expectedAttributes.getLength(); i++) { + if (!expectedAttributes.getURI(i).equals(actualAttributes.getURI(i)) || + !expectedAttributes.getQName(i).equals(actualAttributes.getQName(i)) || + !expectedAttributes.getType(i).equals(actualAttributes.getType(i)) || + !expectedAttributes.getValue(i).equals(actualAttributes.getValue(i))) { + return false; + } + } + return true; + } + else if (expected instanceof Locator) { + Locator expectedLocator = (Locator) expected; + Locator actualLocator = (Locator) actual; + return (expectedLocator.getColumnNumber() == actualLocator.getColumnNumber() && + expectedLocator.getLineNumber() == actualLocator.getLineNumber()); + } + return super.argumentMatches(expected, actual); + } + + protected String argumentToString(Object argument) { + if (argument instanceof char[]) { + char[] array = (char[]) argument; + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < array.length; i++) { + buffer.append(array[i]); + } + return buffer.toString(); + } + else if (argument instanceof Attributes) { + Attributes attributes = (Attributes) argument; + StringBuffer buffer = new StringBuffer("["); + for (int i = 0; i < attributes.getLength(); i++) { + buffer.append('{'); + buffer.append(attributes.getURI(i)); + buffer.append('}'); + buffer.append(attributes.getQName(i)); + buffer.append('='); + buffer.append(attributes.getValue(i)); + if (i < attributes.getLength() - 1) { + buffer.append(", "); + } + } + buffer.append(']'); + return buffer.toString(); + } + else if (argument instanceof Locator) { + Locator locator = (Locator) argument; + StringBuffer buffer = new StringBuffer("["); + buffer.append(locator.getLineNumber()); + buffer.append(','); + buffer.append(locator.getColumnNumber()); + buffer.append(']'); + return buffer.toString(); + } + else { + return super.argumentToString(argument); + } + } + } + + +} diff --git a/xml/src/test/java/org/springframework/xml/stream/StaxEventContentHandlerTest.java b/xml/src/test/java/org/springframework/xml/stream/StaxEventContentHandlerTest.java new file mode 100644 index 00000000..3b5fa65b --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/stream/StaxEventContentHandlerTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.io.Writer; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; + +public class StaxEventContentHandlerTest extends AbstractStaxContentHandlerTestCase { + + protected StaxContentHandler createStaxContentHandler(Writer writer) throws XMLStreamException { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + return new StaxEventContentHandler(outputFactory.createXMLEventWriter(writer)); + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/stream/StaxEventXmlReaderTest.java b/xml/src/test/java/org/springframework/xml/stream/StaxEventXmlReaderTest.java new file mode 100644 index 00000000..b1e3b033 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/stream/StaxEventXmlReaderTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.io.Reader; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; + +public class StaxEventXmlReaderTest extends AbstractStaxXmlReaderTestCase { + + protected StaxXmlReader createStaxXmlReader(Reader reader) throws XMLStreamException { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + return new StaxEventXmlReader(inputFactory.createXMLEventReader(reader)); + } +} + diff --git a/xml/src/test/java/org/springframework/xml/stream/StaxStreamContentHandlerTest.java b/xml/src/test/java/org/springframework/xml/stream/StaxStreamContentHandlerTest.java new file mode 100644 index 00000000..b5141b0f --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/stream/StaxStreamContentHandlerTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.io.Writer; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; + +public class StaxStreamContentHandlerTest extends AbstractStaxContentHandlerTestCase { + + protected StaxContentHandler createStaxContentHandler(Writer writer) throws XMLStreamException { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + return new StaxStreamContentHandler(outputFactory.createXMLStreamWriter(writer)); + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/stream/StaxStreamXmlReaderTest.java b/xml/src/test/java/org/springframework/xml/stream/StaxStreamXmlReaderTest.java new file mode 100644 index 00000000..5bfa18ca --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/stream/StaxStreamXmlReaderTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.io.Reader; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; + +public class StaxStreamXmlReaderTest extends AbstractStaxXmlReaderTestCase { + + protected StaxXmlReader createStaxXmlReader(Reader reader) throws XMLStreamException { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + return new StaxStreamXmlReader(inputFactory.createXMLStreamReader(reader)); + } + +} diff --git a/xml/src/test/java/org/springframework/xml/stream/XmlEventStreamReaderTest.java b/xml/src/test/java/org/springframework/xml/stream/XmlEventStreamReaderTest.java new file mode 100644 index 00000000..72359399 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/stream/XmlEventStreamReaderTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.stream; + +import java.io.StringReader; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.springframework.xml.transform.StaxSource; +import org.springframework.xml.transform.StringResult; + +public class XmlEventStreamReaderTest extends XMLTestCase { + + private static final String XML = + "content"; + + private XmlEventStreamReader streamReader; + + protected void setUp() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(XML)); + streamReader = new XmlEventStreamReader(eventReader); + } + + public void testReadAll() throws Exception { + while (streamReader.hasNext()) { + streamReader.next(); + } + } + + public void testReadCorrect() throws Exception { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StaxSource source = new StaxSource(streamReader); + StringResult result = new StringResult(); + transformer.transform(source, result); + assertXMLEqual(XML, result.toString()); + } + +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/transform/StaxResultTest.java b/xml/src/test/java/org/springframework/xml/transform/StaxResultTest.java new file mode 100644 index 00000000..18277468 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/transform/StaxResultTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.transform; + +import java.io.StringWriter; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; + +public class StaxResultTest extends XMLTestCase { + + private static final String XML = ""; + + private Transformer transformer; + + private XMLOutputFactory inputFactory; + + protected void setUp() throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + inputFactory = XMLOutputFactory.newInstance(); + } + + public void testStreamWriterSource() throws Exception { + StringWriter stringWriter = new StringWriter(); + XMLStreamWriter streamWriter = inputFactory.createXMLStreamWriter(stringWriter); + Source source = new StringSource(XML); + StaxResult result = new StaxResult(streamWriter); + assertEquals("Invalid streamWriter returned", streamWriter, result.getXMLStreamWriter()); + assertNull("EventWriter returned", result.getXMLEventWriter()); + transformer.transform(source, result); + assertXMLEqual("Invalid result", XML, stringWriter.toString()); + } + + public void testEventWriterSource() throws Exception { + StringWriter stringWriter = new StringWriter(); + XMLEventWriter eventWriter = inputFactory.createXMLEventWriter(stringWriter); + Source source = new StringSource(XML); + StaxResult result = new StaxResult(eventWriter); + assertEquals("Invalid eventWriter returned", eventWriter, result.getXMLEventWriter()); + assertNull("StreamWriter returned", result.getXMLStreamWriter()); + transformer.transform(source, result); + assertXMLEqual("Invalid result", XML, stringWriter.toString()); + } + +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/transform/StaxSourceTest.java b/xml/src/test/java/org/springframework/xml/transform/StaxSourceTest.java new file mode 100644 index 00000000..7efd37bb --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/transform/StaxSourceTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.transform; + +import java.io.StringReader; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Result; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; + +import org.custommonkey.xmlunit.XMLTestCase; + +public class StaxSourceTest extends XMLTestCase { + + private static final String XML = ""; + + private Transformer transformer; + + private XMLInputFactory inputFactory; + + protected void setUp() throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + inputFactory = XMLInputFactory.newInstance(); + } + + public void testStreamReaderSource() throws Exception { + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(new StringReader(XML)); + StaxSource source = new StaxSource(streamReader); + assertEquals("Invalid streamReader returned", streamReader, source.getXMLStreamReader()); + assertNull("EventReader returned", source.getXMLEventReader()); + Result result = new StringResult(); + transformer.transform(source, result); + assertXMLEqual("Invalid result", XML, result.toString()); + } + + public void testEventReaderSource() throws Exception { + XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(XML)); + StaxSource source = new StaxSource(eventReader); + assertEquals("Invalid eventReader returned", eventReader, source.getXMLEventReader()); + assertNull("StreamReader returned", source.getXMLStreamReader()); + Result result = new StringResult(); + transformer.transform(source, result); + assertXMLEqual("Invalid result", XML, result.toString()); + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/transform/StringResultTest.java b/xml/src/test/java/org/springframework/xml/transform/StringResultTest.java new file mode 100644 index 00000000..e626eb07 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/transform/StringResultTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.transform; + +import java.io.IOException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +public class StringResultTest extends XMLTestCase { + + public void testStringResult() + throws TransformerException, ParserConfigurationException, IOException, SAXException { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + Element element = document.createElementNS("namespace", "prefix:localName"); + document.appendChild(element); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + StringResult result = new StringResult(); + transformer.transform(new DOMSource(document), result); + assertXMLEqual("Invalid result", "", result.toString()); + } + +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/transform/StringSourceTest.java b/xml/src/test/java/org/springframework/xml/transform/StringSourceTest.java new file mode 100644 index 00000000..bd32fcc5 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/transform/StringSourceTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.transform; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; + +import junit.framework.TestCase; +import org.w3c.dom.Element; + +public class StringSourceTest extends TestCase { + + public void testStringSource() throws TransformerException { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + String content = ""; + DOMResult result = new DOMResult(); + transformer.transform(new StringSource(content), result); + Element rootElement = (Element) result.getNode().getFirstChild(); + assertEquals("Invalid local name", "content", rootElement.getLocalName()); + assertEquals("Invalid prefix", "prefix", rootElement.getPrefix()); + assertEquals("Invalid namespace", "namespace", rootElement.getNamespaceURI()); + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/validation/AbstractValidatorFactoryTestCase.java b/xml/src/test/java/org/springframework/xml/validation/AbstractValidatorFactoryTestCase.java new file mode 100644 index 00000000..656533db --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/validation/AbstractValidatorFactoryTestCase.java @@ -0,0 +1,105 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.InputStream; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; + +import junit.framework.TestCase; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXParseException; + +public abstract class AbstractValidatorFactoryTestCase extends TestCase { + + private XmlValidator validator; + + private InputStream validInputStream; + + private InputStream invalidInputStream; + + protected void setUp() throws Exception { + Resource[] schemaResource = + new Resource[]{new ClassPathResource("schema.xsd", AbstractValidatorFactoryTestCase.class)}; + validator = createValidator(schemaResource, XmlValidatorFactory.SCHEMA_W3C_XML); + validInputStream = AbstractValidatorFactoryTestCase.class.getResourceAsStream("validDocument.xml"); + invalidInputStream = AbstractValidatorFactoryTestCase.class.getResourceAsStream("invalidDocument.xml"); + } + + protected void tearDown() throws Exception { + validInputStream.close(); + invalidInputStream.close(); + } + + protected abstract XmlValidator createValidator(Resource[] schemaResources, String schemaLanguage) throws Exception; + + public void testHandleValidMessageStream() throws Exception { + SAXParseException[] errors = validator.validate(new StreamSource(validInputStream)); + assertNotNull("Null returned for errors", errors); + assertEquals("ValidationErrors returned", 0, errors.length); + } + + public void testValidateTwice() throws Exception { + validator.validate(new StreamSource(validInputStream)); + validInputStream = AbstractValidatorFactoryTestCase.class.getResourceAsStream("validDocument.xml"); + validator.validate(new StreamSource(validInputStream)); + } + + public void testHandleInvalidMessageStream() throws Exception { + SAXParseException[] errors = validator.validate(new StreamSource(invalidInputStream)); + assertNotNull("Null returned for errors", errors); + assertEquals("ValidationErrors returned", 3, errors.length); + } + + public void testHandleValidMessageSax() throws Exception { + SAXParseException[] errors = validator.validate(new SAXSource(new InputSource(validInputStream))); + assertNotNull("Null returned for errors", errors); + assertEquals("ValidationErrors returned", 0, errors.length); + } + + public void testHandleInvalidMessageSax() throws Exception { + SAXParseException[] errors = validator.validate(new SAXSource(new InputSource(invalidInputStream))); + assertNotNull("Null returned for errors", errors); + assertEquals("ValidationErrors returned", 3, errors.length); + } + + public void testHandleValidMessageDom() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + Document document = documentBuilderFactory.newDocumentBuilder() + .parse(new InputSource(validInputStream)); + SAXParseException[] errors = validator.validate(new DOMSource(document)); + assertNotNull("Null returned for errors", errors); + assertEquals("ValidationErrors returned", 0, errors.length); + } + + public void testHandleInvalidMessageDom() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + Document document = documentBuilderFactory.newDocumentBuilder() + .parse(new InputSource(invalidInputStream)); + SAXParseException[] errors = validator.validate(new DOMSource(document)); + assertNotNull("Null returned for errors", errors); + assertEquals("ValidationErrors returned", 3, errors.length); + } + +} diff --git a/xml/src/test/java/org/springframework/xml/validation/Jaxp10ValidatorFactoryTest.java b/xml/src/test/java/org/springframework/xml/validation/Jaxp10ValidatorFactoryTest.java new file mode 100644 index 00000000..47620e26 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/validation/Jaxp10ValidatorFactoryTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.IOException; + +import org.springframework.core.io.Resource; + +public class Jaxp10ValidatorFactoryTest extends AbstractValidatorFactoryTestCase { + + protected XmlValidator createValidator(Resource[] schemaResources, String schemaLanguage) throws IOException { + return Jaxp10ValidatorFactory.createValidator(schemaResources, schemaLanguage); + } +} diff --git a/xml/src/test/java/org/springframework/xml/validation/Jaxp13ValidatorFactoryTest.java b/xml/src/test/java/org/springframework/xml/validation/Jaxp13ValidatorFactoryTest.java new file mode 100644 index 00000000..1358272c --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/validation/Jaxp13ValidatorFactoryTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.IOException; + +import org.springframework.core.io.Resource; + +public class Jaxp13ValidatorFactoryTest extends AbstractValidatorFactoryTestCase { + + protected XmlValidator createValidator(Resource[] schemaResources, String schemaLanguage) throws IOException { + return Jaxp13ValidatorFactory.createValidator(schemaResources, schemaLanguage); + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/validation/SchemaLoaderUtilsTest.java b/xml/src/test/java/org/springframework/xml/validation/SchemaLoaderUtilsTest.java new file mode 100644 index 00000000..c25fc6ff --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/validation/SchemaLoaderUtilsTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import javax.xml.XMLConstants; +import javax.xml.validation.Schema; + +import junit.framework.TestCase; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; + +public class SchemaLoaderUtilsTest extends TestCase { + + public void testLoadSchema() throws Exception { + Resource resource = new ClassPathResource("schema.xsd", getClass()); + Schema schema = SchemaLoaderUtils.loadSchema(resource, XMLConstants.W3C_XML_SCHEMA_NS_URI); + assertNotNull("No schema returned", schema); + assertFalse("Resource not closed", resource.isOpen()); + } + + public void testLoadNonExistantSchema() throws Exception { + try { + Resource nonExistant = new ClassPathResource("bla"); + SchemaLoaderUtils.loadSchema(nonExistant, XMLConstants.W3C_XML_SCHEMA_NS_URI); + fail("Should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // expected + } + } + + public void testLoadNullSchema() throws Exception { + try { + SchemaLoaderUtils.loadSchema((Resource) null, XMLConstants.W3C_XML_SCHEMA_NS_URI); + fail("Should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // expected + } + } + + public void testLoadMultipleSchemas() throws Exception { + Resource envelope = new UrlResource("http://schemas.xmlsoap.org/soap/envelope/"); + Resource encoding = new UrlResource("http://schemas.xmlsoap.org/soap/encoding/"); + Schema schema = + SchemaLoaderUtils.loadSchema(new Resource[]{envelope, encoding}, XMLConstants.W3C_XML_SCHEMA_NS_URI); + assertNotNull("No schema returned", schema); + assertFalse("Resource not closed", envelope.isOpen()); + assertFalse("Resource not closed", encoding.isOpen()); + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/validation/XmlValidatorFactoryTest.java b/xml/src/test/java/org/springframework/xml/validation/XmlValidatorFactoryTest.java new file mode 100644 index 00000000..9965e821 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/validation/XmlValidatorFactoryTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.validation; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import junit.framework.TestCase; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +public class XmlValidatorFactoryTest extends TestCase { + + public void testCreateValidator() throws Exception { + Resource resource = new ClassPathResource("schema.xsd", AbstractValidatorFactoryTestCase.class); + XmlValidator validator = XmlValidatorFactory.createValidator(resource, XmlValidatorFactory.SCHEMA_W3C_XML); + assertNotNull("No validator returned", validator); + } + + public void testNonExistentResource() throws Exception { + Resource resource = new NonExistentResource(); + try { + XmlValidatorFactory.createValidator(resource, XmlValidatorFactory.SCHEMA_W3C_XML); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException ex) { + // expected + } + } + + public void testInvalidSchemaLanguage() throws Exception { + Resource resource = new ClassPathResource("schema.xsd", AbstractValidatorFactoryTestCase.class); + try { + XmlValidatorFactory.createValidator(resource, "bla"); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException ex) { + // expected + } + } + + private static class NonExistentResource implements Resource { + + public Resource createRelative(String relativePath) throws IOException { + throw new IOException(); + } + + public boolean exists() { + return false; + } + + public String getDescription() { + return null; + } + + public File getFile() throws IOException { + throw new IOException(); + } + + public String getFilename() { + return null; + } + + public URL getURL() throws IOException { + throw new IOException(); + } + + public boolean isOpen() { + return false; + } + + public InputStream getInputStream() throws IOException { + throw new IOException(); + } + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/xpath/AbstractXPathExpressionFactoryTestCase.java b/xml/src/test/java/org/springframework/xml/xpath/AbstractXPathExpressionFactoryTestCase.java new file mode 100644 index 00000000..d535ce13 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/xpath/AbstractXPathExpressionFactoryTestCase.java @@ -0,0 +1,192 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import junit.framework.TestCase; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import org.springframework.util.StringUtils; + +public abstract class AbstractXPathExpressionFactoryTestCase extends TestCase { + + private Document noNamespacesDocument; + + private Document namespacesDocument; + + private Map namespaces = new HashMap(); + + protected void setUp() throws Exception { + namespaces.put("prefix1", "namespace1"); + namespaces.put("prefix2", "namespace2"); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + InputStream inputStream = getClass().getResourceAsStream("nonamespaces.xml"); + try { + noNamespacesDocument = documentBuilder.parse(inputStream); + } + finally { + inputStream.close(); + } + inputStream = getClass().getResourceAsStream("namespaces.xml"); + try { + namespacesDocument = documentBuilder.parse(inputStream); + } + finally { + inputStream.close(); + } + } + + public void testEvaluateAsBooleanInvalidNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/prefix1:root/prefix2:otherchild", namespaces); + boolean result = expression.evaluateAsBoolean(namespacesDocument); + assertFalse("Invalid result [" + result + "]", result); + } + + public void testEvaluateAsBooleanInvalidNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/otherchild"); + boolean result = expression.evaluateAsBoolean(noNamespacesDocument); + assertFalse("Invalid result [" + result + "]", result); + } + + public void testEvaluateAsBooleanNamespaces() throws IOException, SAXException { + XPathExpression expression = + createXPathExpression("/prefix1:root/prefix2:child/prefix2:boolean/text()", namespaces); + boolean result = expression.evaluateAsBoolean(namespacesDocument); + assertTrue("Invalid result", result); + } + + public void testEvaluateAsBooleanNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/child/boolean/text()"); + boolean result = expression.evaluateAsBoolean(noNamespacesDocument); + assertTrue("Invalid result", result); + } + + public void testEvaluateAsDoubleInvalidNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/prefix1:root/prefix2:otherchild", namespaces); + double result = expression.evaluateAsNumber(noNamespacesDocument); + assertTrue("Invalid result [" + result + "]", Double.isNaN(result)); + } + + public void testEvaluateAsDoubleInvalidNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/otherchild"); + double result = expression.evaluateAsNumber(noNamespacesDocument); + assertTrue("Invalid result [" + result + "]", Double.isNaN(result)); + } + + public void testEvaluateAsDoubleNamespaces() throws IOException, SAXException { + XPathExpression expression = + createXPathExpression("/prefix1:root/prefix2:child/prefix2:number/text()", namespaces); + double result = expression.evaluateAsNumber(namespacesDocument); + assertEquals("Invalid result", 42D, result, 0D); + } + + public void testEvaluateAsDoubleNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/child/number/text()"); + double result = expression.evaluateAsNumber(noNamespacesDocument); + assertEquals("Invalid result", 42D, result, 0D); + } + + public void testEvaluateAsNodeInvalidNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/prefix1:root/prefix2:otherchild", namespaces); + Node result = expression.evaluateAsNode(namespacesDocument); + assertNull("Invalid result [" + result + "]", result); + } + + public void testEvaluateAsNodeInvalidNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/otherchild"); + Node result = expression.evaluateAsNode(noNamespacesDocument); + assertNull("Invalid result [" + result + "]", result); + } + + public void testEvaluateAsNodeNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/prefix1:root/prefix2:child", namespaces); + Node result = expression.evaluateAsNode(namespacesDocument); + assertNotNull("Invalid result", result); + assertEquals("Invalid localname", "child", result.getLocalName()); + } + + public void testEvaluateAsNodeNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/child"); + Node result = expression.evaluateAsNode(noNamespacesDocument); + assertNotNull("Invalid result", result); + assertEquals("Invalid localname", "child", result.getLocalName()); + } + + public void testEvaluateAsNodesNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/prefix1:root/prefix2:child/*", namespaces); + Node[] results = expression.evaluateAsNodes(namespacesDocument); + assertNotNull("Invalid result", results); + assertEquals("Invalid amount of results", 3, results.length); + } + + public void testEvaluateAsNodesNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/child/*"); + Node[] results = expression.evaluateAsNodes(noNamespacesDocument); + assertNotNull("Invalid result", results); + assertEquals("Invalid amount of results", 3, results.length); + } + + public void testEvaluateAsStringInvalidNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/prefix1:root/prefix2:otherchild", namespaces); + String result = expression.evaluateAsString(namespacesDocument); + assertFalse("Invalid result [" + result + "]", StringUtils.hasText(result)); + } + + public void testEvaluateAsStringInvalidNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/otherchild"); + String result = expression.evaluateAsString(noNamespacesDocument); + assertFalse("Invalid result [" + result + "]", StringUtils.hasText(result)); + } + + public void testEvaluateAsStringNamespaces() throws IOException, SAXException { + XPathExpression expression = + createXPathExpression("/prefix1:root/prefix2:child/prefix2:text/text()", namespaces); + String result = expression.evaluateAsString(namespacesDocument); + assertEquals("Invalid result", "text", result); + } + + public void testEvaluateAsStringNoNamespaces() throws IOException, SAXException { + XPathExpression expression = createXPathExpression("/root/child/text/text()"); + String result = expression.evaluateAsString(noNamespacesDocument); + assertEquals("Invalid result", "text", result); + } + + public void testInvalidExpression() { + try { + createXPathExpression("\\"); + fail("No XPathParseException thrown"); + } + catch (XPathParseException ex) { + // Expected behaviour + } + } + + protected abstract XPathExpression createXPathExpression(String expression); + + protected abstract XPathExpression createXPathExpression(String expression, Map namespaces); +} diff --git a/xml/src/test/java/org/springframework/xml/xpath/JaxenXPathExpressionFactoryTest.java b/xml/src/test/java/org/springframework/xml/xpath/JaxenXPathExpressionFactoryTest.java new file mode 100644 index 00000000..6acab667 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/xpath/JaxenXPathExpressionFactoryTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import java.io.IOException; +import java.util.Map; + +import org.xml.sax.SAXException; + +public class JaxenXPathExpressionFactoryTest extends AbstractXPathExpressionFactoryTestCase { + + protected XPathExpression createXPathExpression(String expression) { + return JaxenXPathExpressionFactory.createXPathExpression(expression); + } + + protected XPathExpression createXPathExpression(String expression, Map namespaces) { + return JaxenXPathExpressionFactory.createXPathExpression(expression, namespaces); + } + + public void testEvaluateAsDoubleNoNamespaces() throws IOException, SAXException { + // Currently not working on Jaxen 1.1 beta 8, hence the override here + } + + public void testEvaluateAsDoubleNamespaces() throws IOException, SAXException { + // Currently not working on Jaxen 1.1 beta 8, hence the override here + } + +} diff --git a/xml/src/test/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactoryTest.java b/xml/src/test/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactoryTest.java new file mode 100644 index 00000000..c1fd088a --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/xpath/Jaxp13XPathExpressionFactoryTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import java.util.Map; + +public class Jaxp13XPathExpressionFactoryTest extends AbstractXPathExpressionFactoryTestCase { + + protected XPathExpression createXPathExpression(String expression) { + return Jaxp13XPathExpressionFactory.createXPathExpression(expression); + } + + protected XPathExpression createXPathExpression(String expression, Map namespaces) { + return Jaxp13XPathExpressionFactory.createXPathExpression(expression, namespaces); + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/xpath/XPathExpressionFactoryTest.java b/xml/src/test/java/org/springframework/xml/xpath/XPathExpressionFactoryTest.java new file mode 100644 index 00000000..f8bd51e7 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/xpath/XPathExpressionFactoryTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import junit.framework.TestCase; + +public class XPathExpressionFactoryTest extends TestCase { + + public void testCreateXPathExpression() throws Exception { + XPathExpression expression = XPathExpressionFactory.createXPathExpression("/root"); + assertNotNull("No expression returned", expression); + } + + public void testCreateEmptyXPathExpression() throws Exception { + try { + XPathExpressionFactory.createXPathExpression(""); + fail("Should have thrown an Exception"); + } + catch (IllegalArgumentException ex) { + // expected + } + } +} \ No newline at end of file diff --git a/xml/src/test/java/org/springframework/xml/xpath/XalanXPathExpressionFactoryTest.java b/xml/src/test/java/org/springframework/xml/xpath/XalanXPathExpressionFactoryTest.java new file mode 100644 index 00000000..b2a222d6 --- /dev/null +++ b/xml/src/test/java/org/springframework/xml/xpath/XalanXPathExpressionFactoryTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006 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 + * + * 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 org.springframework.xml.xpath; + +import java.util.Map; + +/** + * @author Arjen Poutsma + */ +public class XalanXPathExpressionFactoryTest extends AbstractXPathExpressionFactoryTestCase { + + protected XPathExpression createXPathExpression(String expression) { + return XalanXPathExpressionFactory.createXPathExpression(expression); + } + + protected XPathExpression createXPathExpression(String expression, Map prefixes) { + return XalanXPathExpressionFactory.createXPathExpression(expression, prefixes); + } +}