diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMethodEndpointMapping.java b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMethodEndpointMapping.java index 8f08d5d5..cd6a3754 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMethodEndpointMapping.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/AbstractMethodEndpointMapping.java @@ -112,7 +112,7 @@ public abstract class AbstractMethodEndpointMapping extends AbstractEndpointMapp Assert.notNull(endpoint, "'endpoint' must not be null"); Method[] methods = getEndpointClass(endpoint).getMethods(); for (int i = 0; i < methods.length; i++) { - if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_15 && methods[i].isSynthetic() || + if (JdkVersion.isAtLeastJava15() && methods[i].isSynthetic() || methods[i].getDeclaringClass().equals(Object.class)) { continue; } diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingEndpointMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingEndpointMapping.java new file mode 100644 index 00000000..886e1194 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingEndpointMapping.java @@ -0,0 +1,210 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.addressing; + +import java.net.URI; +import java.util.Arrays; +import java.util.Iterator; +import javax.xml.transform.TransformerException; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.JdkVersion; +import org.springframework.util.Assert; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.EndpointInvocationChain; +import org.springframework.ws.server.EndpointMapping; +import org.springframework.ws.soap.SoapHeader; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.addressing.messageid.MessageIdStrategy; +import org.springframework.ws.soap.addressing.messageid.RandomGuidMessageIdStrategy; +import org.springframework.ws.soap.addressing.messageid.UuidMessageIdStrategy; +import org.springframework.ws.soap.server.SoapEndpointInvocationChain; +import org.springframework.ws.soap.server.SoapEndpointMapping; +import org.springframework.ws.transport.WebServiceMessageSender; +import org.springframework.xml.transform.TransformerObjectSupport; + +/** + * Abstract base class for {@link EndpointMapping} implementations that implement WS-Addressing. + * + * @author Arjen Poutsma + * @since 1.5.0 + */ +public abstract class AbstractWsAddressingEndpointMapping extends TransformerObjectSupport + implements SoapEndpointMapping, InitializingBean { + + private String[] actorsOrRoles; + + private boolean isUltimateReceiver = true; + + private MessageIdStrategy messageIdStrategy; + + private WebServiceMessageSender[] messageSenders; + + private WsAddressingVersion[] versions; + + private EndpointInterceptor[] preInterceptors = new EndpointInterceptor[0]; + + private EndpointInterceptor[] postInterceptors = new EndpointInterceptor[0]; + + /** Protected constructor. Initializes the default settings. */ + protected AbstractWsAddressingEndpointMapping() { + initDefaultStrategies(); + } + + /** + * Initializes the default implementation for this mapping's strategies: the {@link WsAddressing200408} and {@link + * WsAddressing200605} versions of the specication, and the {@link UuidMessageIdStrategy} on Java 5 and higher; the + * {@link RandomGuidMessageIdStrategy} on Java 1.4. + */ + protected void initDefaultStrategies() { + this.versions = new WsAddressingVersion[]{new WsAddressing200408(), new WsAddressing200605()}; + if (JdkVersion.isAtLeastJava15()) { + messageIdStrategy = new UuidMessageIdStrategy(); + } + else { + messageIdStrategy = new RandomGuidMessageIdStrategy(); + } + } + + 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; + } + + public final void setUltimateReceiver(boolean ultimateReceiver) { + this.isUltimateReceiver = ultimateReceiver; + } + + /** + * Set additional interceptors to be applied before the implicit WS-Addressing interceptor, e.g. + * XwsSecurityInterceptor. + */ + public final void setPreInterceptors(EndpointInterceptor[] preInterceptors) { + Assert.notNull(preInterceptors, "'preInterceptors' must not be null"); + this.preInterceptors = preInterceptors; + } + + /** + * Set additional interceptors to be applied after the implicit WS-Addressing interceptor, e.g. + * PayloadLoggingInterceptor. + */ + public final void setPostInterceptors(EndpointInterceptor[] postInterceptors) { + Assert.notNull(postInterceptors, "'postInterceptors' must not be null"); + this.postInterceptors = postInterceptors; + } + + /** + * Sets the message id provider used for creating WS-Addressing MessageIds. + *

+ * By default, the {@link UuidMessageIdStrategy} is used on Java 5 and higher, and the {@link + * RandomGuidMessageIdStrategy} on Java 1.4. + */ + public final void setMessageIdProvider(MessageIdStrategy messageIdStrategy) { + this.messageIdStrategy = messageIdStrategy; + } + + public final void setMessageSenders(WebServiceMessageSender[] messageSenders) { + this.messageSenders = messageSenders; + } + + /** + * Sets the WS-Addressing versions to be supported by this mapping. + *

+ * By default, this array is set to support {@link WsAddressing200408 the August 2004} and the {@link + * WsAddressing200605 May 2006} versions of the specification. + */ + public final void setVersions(WsAddressingVersion[] versions) { + this.versions = versions; + } + + public void afterPropertiesSet() throws Exception { + if (logger.isInfoEnabled()) { + logger.info("Supporting versions " + Arrays.asList(versions)); + } + } + + public final EndpointInvocationChain getEndpoint(MessageContext messageContext) throws TransformerException { + Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest()); + SoapMessage request = (SoapMessage) messageContext.getRequest(); + for (int i = 0; i < versions.length; i++) { + if (supports(versions[i], request)) { + MessageAddressingProperties requestMap = versions[i].getMessageAddressingProperties(request); + if (requestMap == null) { + return null; + } + Object endpoint = getEndpointInternal(requestMap); + if (endpoint == null) { + return null; + } + return getEndpointInvocationChain(endpoint, versions[i]); + } + } + return null; + } + + /** Creates a {@link SoapEndpointInvocationChain} based on the given endpoint and {@link WsAddressingVersion}. */ + private EndpointInvocationChain getEndpointInvocationChain(Object endpoint, WsAddressingVersion version) { + URI responseAction = getResponseAction(endpoint); + URI faultAction = getFaultAction(endpoint); + EndpointInterceptor[] interceptors = + new EndpointInterceptor[preInterceptors.length + postInterceptors.length + 1]; + System.arraycopy(preInterceptors, 0, interceptors, 0, preInterceptors.length); + AddressingEndpointInterceptor interceptor = new AddressingEndpointInterceptor(version, messageIdStrategy, + messageSenders, responseAction, faultAction); + interceptors[preInterceptors.length] = interceptor; + System.arraycopy(postInterceptors, 0, interceptors, preInterceptors.length + 1, postInterceptors.length); + return new SoapEndpointInvocationChain(endpoint, interceptors, actorsOrRoles, isUltimateReceiver); + } + + private boolean supports(WsAddressingVersion version, SoapMessage request) { + SoapHeader header = request.getSoapHeader(); + if (header != null) { + for (Iterator iterator = header.examineAllHeaderElements(); iterator.hasNext();) { + SoapHeaderElement headerElement = (SoapHeaderElement) iterator.next(); + if (version.understands(headerElement)) { + return true; + } + } + } + return false; + } + + /** + * Lookup an endpoint for the given {@link MessageAddressingProperties}, returning null if no specific + * one is found. This template method is called by {@link #getEndpoint(MessageContext)}. + * + * @param map the message addressing properties + * @return the endpoint, or null + */ + protected abstract Object getEndpointInternal(MessageAddressingProperties map); + + protected URI getResponseAction(Object endpoint) { + return null; + } + + protected URI getFaultAction(Object endpoint) { + return null; + } + +} diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingMethodEndpointMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingMethodEndpointMapping.java new file mode 100644 index 00000000..2518854d --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingMethodEndpointMapping.java @@ -0,0 +1,129 @@ +/* + * Copyright 2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.addressing; + +import java.lang.reflect.Method; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContextException; +import org.springframework.core.JdkVersion; +import org.springframework.util.Assert; +import org.springframework.ws.server.endpoint.MethodEndpoint; + +/** + * @author Arjen Poutsma + * @since 1.5.0 + */ +public abstract class AbstractWsAddressingMethodEndpointMapping extends AbstractWsAddressingEndpointMapping { + + /** Keys are URI Actions, values are {@link MethodEndpoint}s. */ + private final Map endpointMap = new HashMap(); + + protected Object getEndpointInternal(MessageAddressingProperties map) { + URI action = map.getAction(); + if (logger.isDebugEnabled()) { + logger.debug("Looking up endpoint for [" + key + "]"); + } + return lookupEndpoint(key); + + //TODO implement + throw new UnsupportedOperationException("Not implemented"); + } + + /** + * Looks up an endpoint instance for the given Action URI. + * + * @param action the Addressing action being mapped to + * @return the associated endpoint instance, or null if not found + */ + protected MethodEndpoint lookupEndpoint(URI action) { + return (MethodEndpoint) endpointMap.get(action); + } + + /** + * Register the given endpoint instance under the action URI. + * + * @param action the lookup key + * @param endpoint the method endpoint instance + * @throws BeansException if the endpoint could not be registered + */ + protected void registerEndpoint(URI action, MethodEndpoint endpoint) throws BeansException { + Object mappedEndpoint = endpointMap.get(action); + if (mappedEndpoint != null) { + throw new ApplicationContextException("Cannot map endpoint [" + endpoint + "] on action [" + action + + "]: there's already endpoint [" + mappedEndpoint + "] mapped"); + } + if (endpoint == null) { + throw new ApplicationContextException("Could not find endpoint for action [" + action + "]"); + } + endpointMap.put(action, endpoint); + if (logger.isDebugEnabled()) { + logger.debug("Mapped action [" + action + "] onto endpoint [" + endpoint + "]"); + } + } + + /** + * Helper method that registers the methods of the given bean. This method iterates over the methods of the bean, + * and calls {@link #getActionForMethod(java.lang.reflect.Method)} for each. If this returns a string, the method is + * registered using {@link #registerEndpoint(URI, MethodEndpoint)}. + * + * @see #getActionForMethod (java.lang.reflect.Method) + */ + protected void registerMethods(Object endpoint) { + Assert.notNull(endpoint, "'endpoint' must not be null"); + Method[] methods = getEndpointClass(endpoint).getMethods(); + for (int i = 0; i < methods.length; i++) { + if (JdkVersion.isAtLeastJava15() && methods[i].isSynthetic() || + methods[i].getDeclaringClass().equals(Object.class)) { + continue; + } + URI action = getActionForMethod(methods[i]); + if (action != null) { + registerEndpoint(action, new MethodEndpoint(endpoint, methods[i])); + } + } + } + + /** + * Returns the the action URI for the given method. Returns null if the method is not to be registered, + * which is the default. + * + * @param method the method + * @return the action URI, or null if the method is not to be registered + */ + protected URI getActionForMethod(Method method) { + return null; + } + + /** + * Return the class or interface to use for method reflection. + *

+ * Default implementation delegates to {@link AopUtils#getTargetClass(Object)}. + * + * @param endpoint the bean instance (might be an AOP proxy) + * @return the bean class to expose + */ + protected Class getEndpointClass(Object endpoint) { + return AopUtils.getTargetClass(endpoint); + } + + +} diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingVersion.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingVersion.java index 179d694b..0af7bd2d 100644 --- a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingVersion.java +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingVersion.java @@ -190,6 +190,10 @@ public abstract class AbstractWsAddressingVersion extends TransformerObjectSuppo SoapHeaderElement to = header.addHeaderElement(getToName()); to.setText(map.getTo().toString()); to.setMustUnderstand(true); + if (map.getAction() != null) { + SoapHeaderElement action = header.addHeaderElement(getActionName()); + action.setText(map.getAction().toString()); + } try { Transformer transformer = createTransformer(); for (Iterator iterator = map.getReferenceParameters().iterator(); iterator.hasNext();) { diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AddressingEndpointInterceptor.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AddressingEndpointInterceptor.java new file mode 100644 index 00000000..91556904 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AddressingEndpointInterceptor.java @@ -0,0 +1,181 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.addressing; + +import java.io.IOException; +import java.net.URI; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.Assert; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.soap.SoapHeaderElement; +import org.springframework.ws.soap.SoapMessage; +import org.springframework.ws.soap.addressing.messageid.MessageIdStrategy; +import org.springframework.ws.soap.server.SoapEndpointInterceptor; +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.WebServiceMessageSender; + +/** + * {@link SoapEndpointInterceptor} implementation that deals with WS-Addressing headers. Stateful, and instatiated by + * the {@link AbstractWsAddressingEndpointMapping}. + * + * @author Arjen Poutsma + * @since 1.5.0 + */ +class AddressingEndpointInterceptor implements SoapEndpointInterceptor { + + private static final Log logger = LogFactory.getLog(AddressingEndpointInterceptor.class); + + private final WsAddressingVersion version; + + private final MessageIdStrategy messageIdStrategy; + + private final WebServiceMessageSender[] messageSenders; + + private URI replyAction; + + private URI faultAction; + + AddressingEndpointInterceptor(WsAddressingVersion version, + MessageIdStrategy messageIdStrategy, + WebServiceMessageSender[] messageSenders, + URI replyAction, + URI faultAction) { + Assert.notNull(version, "version must not be null"); + Assert.notNull(messageIdStrategy, "messageIdStrategy must not be null"); + if (messageSenders == null) { + messageSenders = new WebServiceMessageSender[0]; + } + this.version = version; + this.messageIdStrategy = messageIdStrategy; + this.messageSenders = messageSenders; + this.replyAction = replyAction; + this.faultAction = faultAction; + } + + public final boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { + Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest()); + SoapMessage request = (SoapMessage) messageContext.getRequest(); + MessageAddressingProperties requestMap = version.getMessageAddressingProperties(request); + if (!requestMap.hasRequiredProperties()) { + version.addMessageAddressingHeaderRequiredFault((SoapMessage) messageContext.getResponse()); + return false; + } + if (!requestMap.isValid() || messageIdStrategy.isDuplicate(requestMap.getMessageId())) { + version.addInvalidAddressingHeaderFault((SoapMessage) messageContext.getResponse()); + return false; + } + return true; + } + + public final boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { + return handleResponseOrFault(messageContext, false); + } + + public final boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception { + return handleResponseOrFault(messageContext, true); + } + + private boolean handleResponseOrFault(MessageContext messageContext, boolean isFault) throws Exception { + Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest()); + Assert.isInstanceOf(SoapMessage.class, messageContext.getResponse()); + MessageAddressingProperties requestMap = + version.getMessageAddressingProperties((SoapMessage) messageContext.getRequest()); + EndpointReference replyEpr = !isFault ? requestMap.getReplyTo() : requestMap.getFaultTo(); + if (handleNoneAddress(messageContext, replyEpr)) { + return false; + } + URI responseMessageId = getMessageId(messageContext); + URI action = !isFault ? replyAction : faultAction; + MessageAddressingProperties replyMap = requestMap.getReplyProperties(replyEpr, action, responseMessageId); + version.addAddressingHeaders((SoapMessage) messageContext.getResponse(), replyMap); + if (handleAnonymousAddress(messageContext, replyEpr)) { + return true; + } + else { + sendOutOfBand(messageContext, replyEpr); + return false; + } + } + + private boolean handleNoneAddress(MessageContext messageContext, EndpointReference replyEpr) { + if (replyEpr == null || version.hasNoneAddress(replyEpr)) { + if (logger.isDebugEnabled()) { + logger.debug("Request " + messageContext.getRequest() + "] has [" + replyEpr + + "] reply address; reply [" + messageContext.getResponse() + "] discarded"); + } + messageContext.clearResponse(); + return true; + } + return false; + } + + private boolean handleAnonymousAddress(MessageContext messageContext, EndpointReference replyEpr) { + if (version.hasAnonymousAddress(replyEpr)) { + if (logger.isDebugEnabled()) { + logger.debug("Request " + messageContext.getRequest() + "] has [" + replyEpr + + "] reply address; sending in-band reply [" + messageContext.getResponse() + "]"); + } + return true; + } + return false; + } + + private void sendOutOfBand(MessageContext messageContext, EndpointReference replyEpr) throws IOException { + if (logger.isDebugEnabled()) { + logger.debug("Request " + messageContext.getRequest() + "] has [" + replyEpr + + "] reply address; sending out-of-band reply [" + messageContext.getResponse() + "]"); + } + + boolean supported = false; + for (int i = 0; i < messageSenders.length; i++) { + if (messageSenders[i].supports(replyEpr.getAddress())) { + supported = true; + WebServiceConnection connection = null; + try { + connection = messageSenders[i].createConnection(replyEpr.getAddress()); + connection.send(messageContext.getResponse()); + break; + } + finally { + messageContext.clearResponse(); + if (connection != null) { + connection.close(); + } + } + } + } + if (!supported) { + logger.warn("Could not send out-of-band response to [" + replyEpr.getAddress() + "]. " + + "Configure WebServiceMessageSenders which support this uri."); + } + } + + private URI getMessageId(MessageContext messageContext) { + URI responseMessageId = messageIdStrategy.newMessageId(messageContext); + if (logger.isTraceEnabled()) { + logger.trace("Generated reply MessageID [" + responseMessageId + "] for [" + messageContext + "]"); + } + return responseMessageId; + } + + public boolean understands(SoapHeaderElement header) { + return version.understands(header); + } +} diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/EndpointReferenceEditor.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/EndpointReferenceEditor.java new file mode 100644 index 00000000..9cf18d4e --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/EndpointReferenceEditor.java @@ -0,0 +1,55 @@ +/* + * Copyright 2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.addressing; + +import java.beans.PropertyEditorSupport; +import java.net.URI; +import java.net.URISyntaxException; + +import org.springframework.util.StringUtils; + +/** + * Editor for EndpointReference, to directly populate a EPR property instead of using a String property as + * bridge. + * + * @author Arjen Poutsma + * @see EndpointReference + * @since 1.5.0 + */ +public class EndpointReferenceEditor extends PropertyEditorSupport { + + public void setAsText(String text) throws IllegalArgumentException { + if (StringUtils.hasText(text)) { + String uri = text.trim(); + try { + URI address = new URI(uri); + setValue(new EndpointReference(address)); + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Invalid URI syntax: " + ex); + } + } + else { + setValue(null); + } + } + + public String getAsText() { + EndpointReference value = (EndpointReference) getValue(); + return (value != null ? value.toString() : ""); + } +} diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMapping.java new file mode 100644 index 00000000..f45c0fe2 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMapping.java @@ -0,0 +1,148 @@ +/* + * Copyright 2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.addressing; + +import java.net.URI; +import java.net.URISyntaxException; +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.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.Assert; + +/** + * Implementation of the EndpointMapping interface to map from WS-Addressing Action headers to + * endpoint beans. Supports both mapping to bean instances and mapping to bean names. + *

+ * 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 WS_ADDRESSING_ACTION=ENDPOINT_BEAN_NAME. + *

+ * If set, the destination property is used suitable for further endpoint determination. can be used to set + * an EndpointReference destination (i.e. the To element). If this propert + * + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class WsAddressingActionEndpointMapping extends AbstractWsAddressingEndpointMapping + implements ApplicationContextAware { + + // keys are action URIs, values are endpoints + private final Map endpointMap = new HashMap(); + + // contents will be copied over to endpointMap + private final Map temporaryEndpointMap = new HashMap(); + + private URI destination; + + private ApplicationContext applicationContext; + + public void setMappings(Properties mappings) throws URISyntaxException { + setEndpointMap(mappings); + } + + public void setEndpointMap(Map actionMap) throws URISyntaxException { + Iterator it = actionMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + URI action; + if (entry.getKey() instanceof String) { + action = new URI((String) entry.getKey()); + } + else if (entry.getKey() instanceof URI) { + action = (URI) entry.getKey(); + } + else { + throw new IllegalArgumentException("Invalid key [" + entry.getKey() + "]; expected String or URI"); + } + this.temporaryEndpointMap.put(action, entry.getValue()); + } + } + + public void setDestination(URI destination) { + this.destination = destination; + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + registerEndpoints(temporaryEndpointMap); + } + + protected Object getEndpointInternal(MessageAddressingProperties map) { + // MAP address much match the defined EPR address + if (destination != null && !destination.equals(map.getTo())) { + return null; + } + return endpointMap.get(map.getAction()); + } + + protected void registerEndpoints(Map endpointMap) throws BeansException { + if (endpointMap.isEmpty()) { + logger.warn("Neither 'actionMap' nor 'mappings' set on WsAddressingActionEndpointMapping"); + } + else { + Iterator it = endpointMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + URI action = (URI) entry.getKey(); + Object endpoint = entry.getValue(); + // Remove whitespace from endpoint bean name. + if (endpoint instanceof String) { + endpoint = ((String) endpoint).trim(); + } + registerEndpoint(action, endpoint); + } + } + } + + protected void registerEndpoint(URI action, Object endpoint) throws BeansException, IllegalStateException { + Assert.notNull(action, "Action must not be null"); + Assert.notNull(endpoint, "Endpoint object must not be null"); + Object resolvedEndpoint = endpoint; + + if (endpoint instanceof String) { + String endpointName = (String) endpoint; + if (applicationContext.isSingleton(endpointName)) { + resolvedEndpoint = applicationContext.getBean(endpointName); + } + } + Object mappedEndpoint = this.endpointMap.get(action); + if (mappedEndpoint != null) { + if (mappedEndpoint != resolvedEndpoint) { + throw new IllegalStateException("Cannot map endpoint [" + endpoint + "] to action [" + action + + "]: There is already endpoint [" + resolvedEndpoint + "] mapped."); + } + } + else { + this.endpointMap.put(action, resolvedEndpoint); + if (logger.isDebugEnabled()) { + logger.debug("Mapped Action [" + action + "] onto endpoint [" + resolvedEndpoint + "]"); + } + } + } +} diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/annotation/Action.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/annotation/Action.java new file mode 100644 index 00000000..dd1420f1 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/annotation/Action.java @@ -0,0 +1,34 @@ +/* + * Copyright 2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.addressing.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Arjen Poutsma + * @since 1.5.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Action { + +} diff --git a/sandbox/src/test/java/org/springframework/ws/soap/addressing/AbstractWsAddressingInterceptorTestCase.java b/sandbox/src/test/java/org/springframework/ws/soap/addressing/AbstractAddressingInterceptorTestCase.java similarity index 91% rename from sandbox/src/test/java/org/springframework/ws/soap/addressing/AbstractWsAddressingInterceptorTestCase.java rename to sandbox/src/test/java/org/springframework/ws/soap/addressing/AbstractAddressingInterceptorTestCase.java index 02e422b8..83b3cd48 100644 --- a/sandbox/src/test/java/org/springframework/ws/soap/addressing/AbstractWsAddressingInterceptorTestCase.java +++ b/sandbox/src/test/java/org/springframework/ws/soap/addressing/AbstractAddressingInterceptorTestCase.java @@ -19,9 +19,9 @@ import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; import org.springframework.ws.transport.WebServiceConnection; import org.springframework.ws.transport.WebServiceMessageSender; -public abstract class AbstractWsAddressingInterceptorTestCase extends AbstractWsAddressingTestCase { +public abstract class AbstractAddressingInterceptorTestCase extends AbstractWsAddressingTestCase { - private WsAddressingEndpointInterceptor interceptor; + private AddressingEndpointInterceptor interceptor; private MockControl strategyControl; @@ -31,7 +31,10 @@ public abstract class AbstractWsAddressingInterceptorTestCase extends AbstractWs strategyControl = MockControl.createControl(MessageIdStrategy.class); strategyMock = (MessageIdStrategy) strategyControl.getMock(); strategyControl.expectAndDefaultReturn(strategyMock.isDuplicate(null), false); - interceptor = new WsAddressingEndpointInterceptor(getVersion(), strategyMock, new WebServiceMessageSender[0]); + URI replyAction = new URI("urn:replyAction"); + URI faultAction = new URI("urn:faultAction"); + interceptor = new AddressingEndpointInterceptor(getVersion(), strategyMock, new WebServiceMessageSender[0], + replyAction, faultAction); } public void testUnderstands() throws Exception { @@ -128,8 +131,10 @@ public abstract class AbstractWsAddressingInterceptorTestCase extends AbstractWs MockControl senderControl = MockControl.createControl(WebServiceMessageSender.class); WebServiceMessageSender senderMock = (WebServiceMessageSender) senderControl.getMock(); - interceptor = new WsAddressingEndpointInterceptor(getVersion(), strategyMock, - new WebServiceMessageSender[]{senderMock}); + URI replyAction = new URI("urn:replyAction"); + URI faultAction = new URI("urn:replyAction"); + interceptor = new AddressingEndpointInterceptor(getVersion(), strategyMock, + new WebServiceMessageSender[]{senderMock}, replyAction, faultAction); MockControl connectionControl = MockControl.createControl(WebServiceConnection.class); WebServiceConnection connectionMock = (WebServiceConnection) connectionControl.getMock(); diff --git a/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingInterceptor200408Test.java b/sandbox/src/test/java/org/springframework/ws/soap/addressing/AddressingInterceptor200408Test.java similarity index 80% rename from sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingInterceptor200408Test.java rename to sandbox/src/test/java/org/springframework/ws/soap/addressing/AddressingInterceptor200408Test.java index c300b9cf..0a5da3ea 100644 --- a/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingInterceptor200408Test.java +++ b/sandbox/src/test/java/org/springframework/ws/soap/addressing/AddressingInterceptor200408Test.java @@ -4,7 +4,7 @@ package org.springframework.ws.soap.addressing; -public class WsAddressingInterceptor200408Test extends AbstractWsAddressingInterceptorTestCase { +public class AddressingInterceptor200408Test extends AbstractAddressingInterceptorTestCase { protected WsAddressingVersion getVersion() { return new WsAddressing200408(); diff --git a/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingInterceptor200605Test.java b/sandbox/src/test/java/org/springframework/ws/soap/addressing/AddressingInterceptor200605Test.java similarity index 74% rename from sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingInterceptor200605Test.java rename to sandbox/src/test/java/org/springframework/ws/soap/addressing/AddressingInterceptor200605Test.java index db468030..47169f44 100644 --- a/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingInterceptor200605Test.java +++ b/sandbox/src/test/java/org/springframework/ws/soap/addressing/AddressingInterceptor200605Test.java @@ -4,7 +4,7 @@ package org.springframework.ws.soap.addressing; -public class WsAddressingInterceptor200605Test extends AbstractWsAddressingInterceptorTestCase { +public class AddressingInterceptor200605Test extends AbstractAddressingInterceptorTestCase { protected WsAddressingVersion getVersion() { return new WsAddressing200605(); diff --git a/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMappingTest.java b/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMappingTest.java new file mode 100644 index 00000000..cad9585d --- /dev/null +++ b/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMappingTest.java @@ -0,0 +1,82 @@ +/* + * Copyright ${YEAR} the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.soap.addressing; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.EndpointInvocationChain; +import org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; +import org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor; + +public class WsAddressingActionEndpointMappingTest extends AbstractWsAddressingTestCase { + + private WsAddressingActionEndpointMapping mapping; + + private Endpoint1 endpoint1; + + private Endpoint2 endpoint2; + + protected void onSetUp() throws Exception { + mapping = new WsAddressingActionEndpointMapping(); + Map map = new HashMap(); + endpoint1 = new Endpoint1(); + endpoint2 = new Endpoint2(); + map.put("http://fabrikam123.example/mail/Delete", endpoint1); + map.put("http://fabrikam123.example/mail/Add", endpoint2); + mapping.setPreInterceptors(new EndpointInterceptor[]{new PayloadLoggingInterceptor()}); + mapping.setPostInterceptors(new EndpointInterceptor[]{new PayloadValidatingInterceptor()}); + mapping.setActionMap(map); + mapping.setApplicationContext(null); + mapping.afterPropertiesSet(); + } + + public void testMatch() throws Exception { + SaajSoapMessage message = loadSaajMessage("200408/valid.xml"); + MessageContext messageContext = new DefaultMessageContext(message, new SaajSoapMessageFactory(messageFactory)); + + EndpointInvocationChain endpoint = mapping.getEndpoint(messageContext); + assertNotNull("No endpoint returned", endpoint); + assertEquals("Invalid endpoint returned", endpoint1, endpoint.getEndpoint()); + EndpointInterceptor[] interceptors = endpoint.getInterceptors(); + assertEquals("Invalid amount of interceptors returned", 3, interceptors.length); + assertTrue("Invalid first interceptor", interceptors[0] instanceof PayloadLoggingInterceptor); + assertTrue("Invalid first interceptor", interceptors[1] instanceof AddressingEndpointInterceptor); + assertTrue("Invalid first interceptor", interceptors[2] instanceof PayloadValidatingInterceptor); + } + + public void testNoMatch() throws Exception { + SaajSoapMessage message = loadSaajMessage("200408/response-no-message-id.xml"); + MessageContext messageContext = new DefaultMessageContext(message, new SaajSoapMessageFactory(messageFactory)); + + EndpointInvocationChain endpoint = mapping.getEndpoint(messageContext); + assertNull("Endpoint returned", endpoint); + } + + private static class Endpoint1 { + + } + + private static class Endpoint2 { + + } +} \ No newline at end of file diff --git a/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200408/response-anonymous.xml b/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200408/response-anonymous.xml index 8dde1531..99276263 100644 --- a/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200408/response-anonymous.xml +++ b/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200408/response-anonymous.xml @@ -4,6 +4,7 @@ uid:1234 uuid:aaaabbbb-cccc-dddd-eeee-ffffffffffff http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + urn:replyAction diff --git a/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200408/response-fault-to.xml b/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200408/response-fault-to.xml index 23499f69..4751eb0e 100644 --- a/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200408/response-fault-to.xml +++ b/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200408/response-fault-to.xml @@ -4,6 +4,7 @@ uid:1234 uuid:aaaabbbb-cccc-dddd-eeee-ffffffffffff http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + urn:faultAction diff --git a/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200605/response-anonymous.xml b/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200605/response-anonymous.xml index f7588230..2ba12041 100644 --- a/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200605/response-anonymous.xml +++ b/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200605/response-anonymous.xml @@ -3,6 +3,7 @@ uid:1234 http://example.com/someuniquestring http://www.w3.org/2005/08/addressing/anonymous + urn:replyAction diff --git a/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200605/response-fault-to.xml b/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200605/response-fault-to.xml index c218911b..4e0392db 100644 --- a/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200605/response-fault-to.xml +++ b/sandbox/src/test/resources/org/springframework/ws/soap/addressing/200605/response-fault-to.xml @@ -3,6 +3,7 @@ uid:1234 http://example.com/someuniquestring http://www.w3.org/2005/08/addressing/anonymous + urn:faultAction