commit 66d3aef26cd9b519337d95c4b0503091c30e44dc
Author: Arjen Poutsma 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 EndpointMapping implementation - whether standard,
+ * or provided as part of an application - to control the routing of request messages to endpoint objects. Endpoint
+ * mappings can be registerd using the endpointMappings property.EndpointAdapter; this allows to use any endpoint interface or form. Default are
+ * MessageEndpointAdapter and PayloadEndpointAdapter, for MessageEndpoint and
+ * PayloadEndpoint, respectively. Additional endpoint adapters can be added through the
+ * endpointAdapters property.EndpointExceptionResolver, for example mapping certain exceptions to SOAP Faults. Default is none.
+ * Additional exception resolvers can be added through the endpointExceptionResolvers property.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 null
+ * @return the added 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("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 = "fairy tale
+ architecture
: one is made to believe things that simply are not true. To quote
+
+
+
+ The alternative of contract-last development is
+
+ Marshaller interface for JAXB 2.0.
+ *
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 =
+ "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.
+ *
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 =
+ "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 @@
+ABC
+ DEF
+ 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 @@
+
+
+
+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 @@
+
+
+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 certificate | + *trustStore |
| Encryption based on symmetric key | + *symmetricStore |
| Signing | keyStore |
| Signature verification | trustStore |
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()}.
+ *
+ * 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> + *+ * + *
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: 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. javax.net.ssl.trustStore system property was not
+ * specified, but if the file $JAVA_HOME/lib/security/jssecacerts exists, that file is used. $JAVA_HOME/lib/security/cacerts exists, that file is used. 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 @@
+
+(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 =
+ "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 =
+ "