diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractActionEndpointMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractActionEndpointMapping.java new file mode 100644 index 00000000..e19c9a8c --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractActionEndpointMapping.java @@ -0,0 +1,97 @@ +/* + * 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.util.HashMap; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.Assert; + +/** + * @author Arjen Poutsma + * @since 1.5.0 + */ +public abstract class AbstractActionEndpointMapping extends AbstractAddressingEndpointMapping + implements ApplicationContextAware { + + // keys are action URIs, values are endpoints + private final Map endpointMap = new HashMap(); + + private ApplicationContext applicationContext; + + /** + * Looks up an endpoint instance for the given action. All keys are tried in order. + * + * @param action the action URI + * @return the associated endpoint instance, or null if not found + */ + protected Object lookupEndpoint(URI action) { + return endpointMap.get(action); + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + /** + * Register the specified endpoint for the given action URI. + * + * @param action the action the bean should be mapped to + * @param endpoint the endpoint instance or endpoint bean name String (a bean name will automatically be resolved + * into the corresponding endpoint bean) + * @throws org.springframework.beans.BeansException + * if the endpoint couldn't be registered + * @throws IllegalStateException if there is a conflicting endpoint registered + */ + 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 + "]"); + } + } + } + + protected final Object getEndpointInternal(MessageAddressingProperties map) { + return getEndpointInternal(map.getTo(), map.getAction()); + } + + protected abstract Object getEndpointInternal(URI to, URI action); + + +} diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractActionMethodEndpointMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractActionMethodEndpointMapping.java new file mode 100644 index 00000000..5d3c559e --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractActionMethodEndpointMapping.java @@ -0,0 +1,78 @@ +/* + * 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 org.springframework.aop.support.AopUtils; +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 AbstractActionMethodEndpointMapping extends AbstractActionEndpointMapping { + + /** + * Helper method that registers the methods of the given bean. This method iterates over the methods of the bean, + * and calls {@link #getActionForMethod(Method)} for each. If this returns a URI, the method is registered using + * {@link #registerEndpoint(URI, Object)}. + * + * @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/AbstractWsAddressingEndpointMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractAddressingEndpointMapping.java similarity index 98% rename from sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingEndpointMapping.java rename to sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractAddressingEndpointMapping.java index 886e1194..a0f0ca86 100644 --- a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingEndpointMapping.java +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractAddressingEndpointMapping.java @@ -45,7 +45,7 @@ import org.springframework.xml.transform.TransformerObjectSupport; * @author Arjen Poutsma * @since 1.5.0 */ -public abstract class AbstractWsAddressingEndpointMapping extends TransformerObjectSupport +public abstract class AbstractAddressingEndpointMapping extends TransformerObjectSupport implements SoapEndpointMapping, InitializingBean { private String[] actorsOrRoles; @@ -63,7 +63,7 @@ public abstract class AbstractWsAddressingEndpointMapping extends TransformerObj private EndpointInterceptor[] postInterceptors = new EndpointInterceptor[0]; /** Protected constructor. Initializes the default settings. */ - protected AbstractWsAddressingEndpointMapping() { + protected AbstractAddressingEndpointMapping() { initDefaultStrategies(); } diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingMapping.java deleted file mode 100644 index 6a3a7da0..00000000 --- a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AbstractWsAddressingMapping.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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.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 AbstractWsAddressingMapping extends TransformerObjectSupport - implements SoapEndpointMapping, InitializingBean { - - private String[] actorsOrRoles; - - private boolean isUltimateReceiver = true; - - private MessageIdStrategy messageIdStrategy; - - private WebServiceMessageSender[] messageSenders; - - private WsAddressingVersion[] versions; - - private EndpointInterceptor[] preInterceptors; - - private EndpointInterceptor[] postInterceptors; - - private int wsAddressingInterceptorIdx = -1; - - private EndpointInterceptor[] allInterceptors; - - /** Protected constructor. Initializes the default settings. */ - protected AbstractWsAddressingMapping() { - 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) { - this.preInterceptors = preInterceptors; - } - - /** - * Set additional interceptors to be applied after the implicit WS-Addressing interceptor, e.g. - * PayloadLoggingInterceptor. - */ - public final void setPostInterceptors(EndpointInterceptor[] postInterceptors) { - 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 WS-Addressing " + 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 new SoapEndpointInvocationChain(endpoint, getAllEndpointInterceptors(versions[i]), actorsOrRoles, - isUltimateReceiver); - } - } - return null; - } - - 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; - } - - private EndpointInterceptor[] getAllEndpointInterceptors(WsAddressingVersion version) { - // lazy init - if (allInterceptors == null) { - if (preInterceptors == null) { - preInterceptors = new EndpointInterceptor[0]; - } - if (postInterceptors == null) { - postInterceptors = new EndpointInterceptor[0]; - } - allInterceptors = new EndpointInterceptor[preInterceptors.length + postInterceptors.length + 1]; - System.arraycopy(preInterceptors, 0, allInterceptors, 0, preInterceptors.length); - System.arraycopy(postInterceptors, 0, allInterceptors, preInterceptors.length + 1, postInterceptors.length); - } - allInterceptors[preInterceptors.length] = - new WsAddressingEndpointInterceptor(version, messageIdStrategy, messageSenders); - return allInterceptors; - } - - /** - * 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); - -} 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 index 91556904..67edb39c 100644 --- a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AddressingEndpointInterceptor.java +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AddressingEndpointInterceptor.java @@ -33,7 +33,7 @@ import org.springframework.ws.transport.WebServiceMessageSender; /** * {@link SoapEndpointInterceptor} implementation that deals with WS-Addressing headers. Stateful, and instatiated by - * the {@link AbstractWsAddressingEndpointMapping}. + * the {@link AbstractAddressingEndpointMapping}. * * @author Arjen Poutsma * @since 1.5.0 diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/AnnotationActionMethodEndpointMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AnnotationActionMethodEndpointMapping.java new file mode 100644 index 00000000..3252d0a2 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/AnnotationActionMethodEndpointMapping.java @@ -0,0 +1,94 @@ +/* + * 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.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.StringUtils; +import org.springframework.ws.server.endpoint.MethodEndpoint; +import org.springframework.ws.server.endpoint.annotation.Endpoint; +import org.springframework.ws.soap.addressing.annotation.Action; +import org.springframework.ws.soap.addressing.annotation.Address; + +/** + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class AnnotationActionMethodEndpointMapping extends AbstractActionMethodEndpointMapping + implements BeanPostProcessor { + + /** Returns the 'endpoint' annotation type. Default is {@link Endpoint}. */ + protected Class getEndpointAnnotationType() { + return Endpoint.class; + } + + public final Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public final Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (getEndpointClass(bean).getAnnotation(getEndpointAnnotationType()) != null) { + registerMethods(bean); + } + return bean; + } + + protected URI getActionForMethod(Method method) { + Action action = method.getAnnotation(Action.class); + if (action != null) { + try { + return new URI(action.value()); + } + catch (URISyntaxException e) { + // ignore + } + } + return null; + } + + protected Object getEndpointInternal(URI to, URI action) { + MethodEndpoint methodEndpoint = (MethodEndpoint) lookupEndpoint(action); + if (methodEndpoint != null) { + // respect the Address annotation, if set + Class endpointClass = methodEndpoint.getMethod().getDeclaringClass(); + Address address = AnnotationUtils.findAnnotation(endpointClass, Address.class); + if (address != null && StringUtils.hasText(address.value())) { + try { + URI addressUri = new URI(address.value()); + if (to.equals(addressUri)) { + return methodEndpoint; + } + } + catch (URISyntaxException e) { + throw new IllegalArgumentException( + "Invalid Address annotation [" + address.value() + "] on [" + endpointClass + "]"); + } + } + else { + // address not set + return methodEndpoint; + } + } + return null; + } +} diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/SimpleActionEndpointMapping.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/SimpleActionEndpointMapping.java new file mode 100644 index 00000000..da6602d4 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/SimpleActionEndpointMapping.java @@ -0,0 +1,142 @@ +/* + * 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; + +/** + * Implementation of the EndpointMapping interface to map from WS-Addressing Action Message + * Addressing Property 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 {@link #setAddress(URI) address} property should be equal to the {@link + * MessageAddressingProperties#getTo() destination} property of the incominging message. As such, it can be used to + * create multiple Endpoint References, by defining multiple SimpleActionEndpointMapping bean definitions + * with different addressproperty values. + * + * @author Arjen Poutsma + * @see MessageAddressingProperties#getAction() + * @since 1.5.0 + */ +public class SimpleActionEndpointMapping extends AbstractActionEndpointMapping { + + // contents will be copied over to endpointMap + private final Map actionMap = new HashMap(); + + private URI address; + + /** + * Map action URIs to endpoint bean names. This is the typical way of configuring this EndpointMapping. + * + * @param mappings properties with URLs as keys and bean names as values + * @see #setActionMap(java.util.Map) + */ + public void setMappings(Properties mappings) throws URISyntaxException { + setActionMap(mappings); + } + + /** + * Set a Map with action URIs as keys and handler beans (or handler bean names) as values. Convenient for population + * with bean references. + * + * @param actionMap map with action URIs as keys and beans as values + * @see #setMappings + */ + public void setActionMap(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.actionMap.put(action, entry.getValue()); + } + } + + /** + * Set the address property. If set, value of this property is compared to the {@link + * MessageAddressingProperties#getTo() destination} property of the incominging message. + * + * @param address the address URI + */ + public void setAddress(URI address) { + this.address = address; + } + + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + registerEndpoints(actionMap); + } + + protected Object getEndpointInternal(URI to, URI action) { + // MAP address much match the defined EPR address + if (address != null && !address.equals(to)) { + return null; + } + return lookupEndpoint(action); + } + + /** + * Register all endpoints specified in the action map. + * + * @param actionMap Map with action URIs as keys and endppint beans or bean names as values + * @throws BeansException if an endpoint couldn't be registered + * @throws IllegalStateException if there is a conflicting endpoint registered + */ + protected void registerEndpoints(Map actionMap) throws BeansException { + if (actionMap.isEmpty()) { + logger.warn("Neither 'actionMap' nor 'mappings' set on SimpleActionEndpointMapping"); + } + else { + for (Iterator iterator = actionMap.entrySet().iterator(); iterator.hasNext();) { + Map.Entry entry = (Map.Entry) iterator.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); + } + } + } + +} 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 deleted file mode 100644 index f45c0fe2..00000000 --- a/sandbox/src/main/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMapping.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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/WsAddressingEndpointInterceptor.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/WsAddressingEndpointInterceptor.java deleted file mode 100644 index 4fdc2221..00000000 --- a/sandbox/src/main/java/org/springframework/ws/soap/addressing/WsAddressingEndpointInterceptor.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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 AbstractWsAddressingMapping}. - * - * @author Arjen Poutsma - * @since 1.5.0 - */ -class WsAddressingEndpointInterceptor implements SoapEndpointInterceptor { - - private static final Log logger = LogFactory.getLog(WsAddressingEndpointInterceptor.class); - - private final WsAddressingVersion version; - - private final MessageIdStrategy messageIdStrategy; - - private final WebServiceMessageSender[] messageSenders; - - WsAddressingEndpointInterceptor(WsAddressingVersion version, - MessageIdStrategy messageIdStrategy, - WebServiceMessageSender[] messageSenders) { - Assert.notNull(version, "version must not be null"); - Assert.notNull(messageIdStrategy, "messageIdStrategy must not be null"); - Assert.notNull(messageSenders, "messageSenders must not be null"); - this.version = version; - this.messageIdStrategy = messageIdStrategy; - this.messageSenders = messageSenders; - } - - public 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 boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { - return handleResponseOrFault(messageContext, false); - } - - public 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()); - SoapMessage request = (SoapMessage) messageContext.getRequest(); - MessageAddressingProperties requestMap = version.getMessageAddressingProperties(request); - EndpointReference replyEpr = isFault ? requestMap.getFaultTo() : requestMap.getReplyTo(); - if (handleNoneAddress(messageContext, replyEpr)) { - return false; - } - URI responseMessageId = getMessageId(messageContext); - MessageAddressingProperties replyMap = requestMap.getReplyProperties(replyEpr, null, 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/annotation/Action.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/annotation/Action.java index dd1420f1..9a8bace3 100644 --- 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 @@ -23,6 +23,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** + * Marks an endpoint method as the handler for an incoming request. The annotation value signifies the value for the + * request WS-Addressing Action header that is handled by the method. + * * @author Arjen Poutsma * @since 1.5.0 */ @@ -31,4 +34,13 @@ import java.lang.annotation.Target; @Target(ElementType.METHOD) public @interface Action { + /** Signifies the value for the request WS-Addressing Action header that is handled by the method. */ + String value(); + + /** + * Explicit value of the WS-Addressing Action message addressing property for the output + * message of the operation. + */ + String output() default ""; + } diff --git a/sandbox/src/main/java/org/springframework/ws/soap/addressing/annotation/Address.java b/sandbox/src/main/java/org/springframework/ws/soap/addressing/annotation/Address.java new file mode 100644 index 00000000..63adfe5c --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/soap/addressing/annotation/Address.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * Marks an endpoint with a WS-Addressing Address. If this annotation is applied, the {@link #value()} is + * compared to the {@link org.springframework.ws.soap.addressing.MessageAddressingProperties#getTo() destination} + * property of the incominging message. + *

+ * as the handler for an incoming request. The annotation value signifies the value for the request WS-Addressing + * Action header that is handled by the method. + * + * @author Arjen Poutsma + * @since 1.5.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Address { + + /** + * The value may indicate a suggestion for a logical component name, to be turned into a Spring bean in case of an + * autodetected component. + * + * @return the suggested component name, if any + */ + String value(); + +} diff --git a/sandbox/src/test/java/org/springframework/ws/soap/addressing/AnnotationActionMethodEndpointMappingTest.java b/sandbox/src/test/java/org/springframework/ws/soap/addressing/AnnotationActionMethodEndpointMappingTest.java new file mode 100644 index 00000000..97c8c7a9 --- /dev/null +++ b/sandbox/src/test/java/org/springframework/ws/soap/addressing/AnnotationActionMethodEndpointMappingTest.java @@ -0,0 +1,90 @@ +/* + * 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.io.IOException; +import javax.xml.soap.SOAPException; + +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.ws.context.DefaultMessageContext; +import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInvocationChain; +import org.springframework.ws.server.endpoint.MethodEndpoint; +import org.springframework.ws.server.endpoint.annotation.Endpoint; +import org.springframework.ws.soap.addressing.annotation.Action; +import org.springframework.ws.soap.addressing.annotation.Address; +import org.springframework.ws.soap.saaj.SaajSoapMessage; +import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; + +public class AnnotationActionMethodEndpointMappingTest extends AbstractWsAddressingTestCase { + + private StaticApplicationContext applicationContext; + + private AnnotationActionMethodEndpointMapping mapping; + + protected void onSetUp() throws Exception { + applicationContext = new StaticApplicationContext(); + applicationContext.registerSingleton("mapping", AnnotationActionMethodEndpointMapping.class); + mapping = (AnnotationActionMethodEndpointMapping) applicationContext.getBean("mapping"); + } + + public void testNoAddress() throws Exception { + applicationContext.registerSingleton("endpoint", Endpoint1.class); + applicationContext.refresh(); + MessageContext messageContext = createMessageContext(); + + EndpointInvocationChain chain = mapping.getEndpoint(messageContext); + assertNotNull("MethodEndpoint not registered", chain); + MethodEndpoint expected = new MethodEndpoint(applicationContext.getBean("endpoint"), "doIt", new Class[0]); + assertEquals("Invalid endpoint registered", expected, chain.getEndpoint()); + } + + public void testAddress() throws Exception { + applicationContext.registerSingleton("endpoint", Endpoint2.class); + applicationContext.refresh(); + MessageContext messageContext = createMessageContext(); + + EndpointInvocationChain chain = mapping.getEndpoint(messageContext); + assertNotNull("MethodEndpoint not registered", chain); + MethodEndpoint expected = new MethodEndpoint(applicationContext.getBean("endpoint"), "doIt", new Class[0]); + assertEquals("Invalid endpoint registered", expected, chain.getEndpoint()); + } + + private MessageContext createMessageContext() throws SOAPException, IOException { + SaajSoapMessage message = loadSaajMessage("200408/valid.xml"); + return new DefaultMessageContext(message, new SaajSoapMessageFactory(messageFactory)); + } + + @Endpoint + private static class Endpoint1 { + + @Action("http://fabrikam123.example/mail/Delete") + public void doIt() { + + } + } + + @Endpoint + @Address("mailto:joe@fabrikam123.example") + private static class Endpoint2 { + + @Action("http://fabrikam123.example/mail/Delete") + public void doIt() { + + } + } +} \ No newline at end of file diff --git a/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMappingTest.java b/sandbox/src/test/java/org/springframework/ws/soap/addressing/SimpleActionEndpointMappingTest.java similarity index 93% rename from sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMappingTest.java rename to sandbox/src/test/java/org/springframework/ws/soap/addressing/SimpleActionEndpointMappingTest.java index cad9585d..681c0dde 100644 --- a/sandbox/src/test/java/org/springframework/ws/soap/addressing/WsAddressingActionEndpointMappingTest.java +++ b/sandbox/src/test/java/org/springframework/ws/soap/addressing/SimpleActionEndpointMappingTest.java @@ -28,16 +28,16 @@ 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 { +public class SimpleActionEndpointMappingTest extends AbstractWsAddressingTestCase { - private WsAddressingActionEndpointMapping mapping; + private SimpleActionEndpointMapping mapping; private Endpoint1 endpoint1; private Endpoint2 endpoint2; protected void onSetUp() throws Exception { - mapping = new WsAddressingActionEndpointMapping(); + mapping = new SimpleActionEndpointMapping(); Map map = new HashMap(); endpoint1 = new Endpoint1(); endpoint2 = new Endpoint2(); @@ -46,7 +46,6 @@ public class WsAddressingActionEndpointMappingTest extends AbstractWsAddressingT mapping.setPreInterceptors(new EndpointInterceptor[]{new PayloadLoggingInterceptor()}); mapping.setPostInterceptors(new EndpointInterceptor[]{new PayloadValidatingInterceptor()}); mapping.setActionMap(map); - mapping.setApplicationContext(null); mapping.afterPropertiesSet(); }