diff --git a/core/src/main/java/org/springframework/ws/soap/addressing/client/ActionCallback.java b/core/src/main/java/org/springframework/ws/soap/addressing/client/ActionCallback.java
new file mode 100644
index 00000000..754d948e
--- /dev/null
+++ b/core/src/main/java/org/springframework/ws/soap/addressing/client/ActionCallback.java
@@ -0,0 +1,193 @@
+/*
+ * 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.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import javax.xml.transform.TransformerException;
+
+import org.springframework.core.JdkVersion;
+import org.springframework.util.Assert;
+import org.springframework.ws.WebServiceMessage;
+import org.springframework.ws.client.core.WebServiceMessageCallback;
+import org.springframework.ws.soap.SoapMessage;
+import org.springframework.ws.soap.addressing.core.EndpointReference;
+import org.springframework.ws.soap.addressing.core.MessageAddressingProperties;
+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.addressing.version.Addressing10;
+import org.springframework.ws.soap.addressing.version.AddressingVersion;
+import org.springframework.ws.transport.context.TransportContext;
+import org.springframework.ws.transport.context.TransportContextHolder;
+
+/**
+ * {@link WebServiceMessageCallback} implementation that sets the WS-Addressing Action header on the
+ * message.
+ *
+ * WebServiceTemplate template = new WebServiceTemplate(messageFactory);
+ * Result result = new DOMResult();
+ * template.sendSourceAndReceiveToResult(
+ * new StringSource("<content xmlns=\"http://tempuri.org\"/>"),
+ * new ActionCallback(new URI("http://tempuri.org/Action")),
+ * result);
+ *
+ *
+ * @author Arjen Poutsma
+ * @since 1.0.0
+ */
+public class ActionCallback implements WebServiceMessageCallback {
+
+ private final AddressingVersion version;
+
+ private final URI action;
+
+ private final URI to;
+
+ private MessageIdStrategy messageIdStrategy;
+
+ private EndpointReference from;
+
+ private EndpointReference replyTo;
+
+ private EndpointReference faultTo;
+
+ /**
+ * Create a new ActionCallback with the given Action.
+ *
+ * The To header of the outgoing message will reflect the {@link org.springframework.ws.transport.WebServiceConnection#getUri()
+ * connection URI}.
+ *
+ * The {@link AddressingVersion} is set to {@link Addressing10}.
+ *
+ * @param action the value of the action property to set
+ */
+ public ActionCallback(String action) throws URISyntaxException {
+ this(new URI(action), new Addressing10(), null);
+ }
+
+ /**
+ * Create a new ActionCallback with the given Action.
+ *
+ * The To header of the outgoing message will reflect the {@link org.springframework.ws.transport.WebServiceConnection#getUri()
+ * connection URI}.
+ *
+ * The {@link AddressingVersion} is set to {@link Addressing10}.
+ *
+ * @param action the value of the action property to set
+ */
+ public ActionCallback(URI action) {
+ this(action, new Addressing10(), null);
+ }
+
+ /**
+ * Create a new ActionCallback with the given version and Action.
+ *
+ * The To header of the outgoing message will reflect the {@link org.springframework.ws.transport.WebServiceConnection#getUri()
+ * connection URI}.
+ *
+ * @param action the value of the action property to set
+ * @param version the WS-Addressing version to use
+ */
+ public ActionCallback(URI action, AddressingVersion version) {
+ this(action, version, null);
+ }
+
+ /**
+ * Create a new ActionCallback with the given version, Action, and optional
+ * To.
+ *
+ * @param action the value of the action property
+ * @param version the WS-Addressing version to use
+ * @param action the value of the destination property
+ */
+ public ActionCallback(URI action, AddressingVersion version, URI to) {
+ Assert.notNull(action, "'action' must not be null");
+ Assert.notNull(version, "'version' must not be null");
+ this.action = action;
+ this.version = version;
+ this.to = to;
+ if (JdkVersion.isAtLeastJava15()) {
+ messageIdStrategy = new UuidMessageIdStrategy();
+ }
+ else {
+ messageIdStrategy = new RandomGuidMessageIdStrategy();
+ }
+ }
+
+ /**
+ * Sets the message id strategy 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 void setMessageIdStrategy(MessageIdStrategy messageIdStrategy) {
+ Assert.notNull(messageIdStrategy, "'messageIdStrategy' must not be null");
+ this.messageIdStrategy = messageIdStrategy;
+ }
+
+ public void setFrom(EndpointReference from) {
+ this.from = from;
+ }
+
+ public void setReplyTo(EndpointReference replyTo) {
+ this.replyTo = replyTo;
+ }
+
+ public void setFaultTo(EndpointReference faultTo) {
+ this.faultTo = faultTo;
+ }
+
+ /**
+ * Returns the Destination for outgoing messages.
+ *
+ * Defaults to the {@link org.springframework.ws.transport.WebServiceConnection#getUri() connection URI} if no
+ * destination was set.
+ */
+ protected URI getTo() {
+ if (to == null) {
+ TransportContext transportContext = TransportContextHolder.getTransportContext();
+ if (transportContext != null && transportContext.getConnection() != null) {
+ try {
+ return transportContext.getConnection().getUri();
+ }
+ catch (URISyntaxException ex) {
+ // ignore
+ }
+ }
+ throw new IllegalStateException("Could not obtain connection URI from Transport Context");
+ }
+ else {
+ return to;
+ }
+ }
+
+ public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
+ Assert.isInstanceOf(SoapMessage.class, message);
+ SoapMessage soapMessage = (SoapMessage) message;
+ URI to = getTo();
+ URI messageId = messageIdStrategy.newMessageId(soapMessage);
+ MessageAddressingProperties map =
+ new MessageAddressingProperties(to, from, replyTo, faultTo, action, messageId);
+ version.addAddressingHeaders(soapMessage, map);
+ }
+
+
+}
diff --git a/core/src/test/java/org/springframework/ws/soap/addressing/client/AbstractActionCallbackTestCase.java b/core/src/test/java/org/springframework/ws/soap/addressing/client/AbstractActionCallbackTestCase.java
new file mode 100644
index 00000000..9ad1fa2d
--- /dev/null
+++ b/core/src/test/java/org/springframework/ws/soap/addressing/client/AbstractActionCallbackTestCase.java
@@ -0,0 +1,120 @@
+/*
+ * 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.client;
+
+import java.net.URI;
+import javax.xml.namespace.QName;
+import javax.xml.soap.SOAPBody;
+import javax.xml.soap.SOAPBodyElement;
+import javax.xml.soap.SOAPElement;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPMessage;
+
+import org.easymock.MockControl;
+
+import org.springframework.ws.soap.addressing.AbstractWsAddressingTestCase;
+import org.springframework.ws.soap.addressing.core.EndpointReference;
+import org.springframework.ws.soap.addressing.messageid.MessageIdStrategy;
+import org.springframework.ws.soap.addressing.version.AddressingVersion;
+import org.springframework.ws.soap.saaj.SaajSoapMessage;
+import org.springframework.ws.transport.WebServiceConnection;
+import org.springframework.ws.transport.context.DefaultTransportContext;
+import org.springframework.ws.transport.context.TransportContext;
+import org.springframework.ws.transport.context.TransportContextHolder;
+
+public abstract class AbstractActionCallbackTestCase extends AbstractWsAddressingTestCase {
+
+ private ActionCallback callback;
+
+ private MockControl strategyControl;
+
+ private MessageIdStrategy strategyMock;
+
+ private MockControl connectionControl;
+
+ private WebServiceConnection connectionMock;
+
+ protected final void onSetUp() throws Exception {
+ strategyControl = MockControl.createControl(MessageIdStrategy.class);
+ strategyMock = (MessageIdStrategy) strategyControl.getMock();
+
+ connectionControl = MockControl.createControl(WebServiceConnection.class);
+ connectionMock = (WebServiceConnection) connectionControl.getMock();
+ TransportContext transportContext = new DefaultTransportContext(connectionMock);
+ TransportContextHolder.setTransportContext(transportContext);
+ }
+
+ protected void tearDown() throws Exception {
+ TransportContextHolder.setTransportContext(null);
+ }
+
+ public void testValid() throws Exception {
+ URI action = new URI("http://example.com/fabrikam/mail/Delete");
+ URI to = new URI("mailto:fabrikam@example.com");
+ callback = new ActionCallback(action, getVersion(), to);
+ callback.setMessageIdStrategy(strategyMock);
+ SaajSoapMessage message = createDeleteMessage();
+ strategyControl
+ .expectAndReturn(strategyMock.newMessageId(message), new URI("http://example.com/someuniquestring"));
+ callback.setReplyTo(new EndpointReference(new URI("http://example.com/business/client1")));
+ strategyControl.replay();
+ connectionControl.replay();
+
+ callback.doWithMessage(message);
+
+ SaajSoapMessage expected = loadSaajMessage(getTestPath() + "/valid.xml");
+ assertXMLEqual("Invalid message", expected, message);
+ strategyControl.verify();
+ connectionControl.verify();
+ }
+
+ public void testDefaults() throws Exception {
+ URI action = new URI("http://example.com/fabrikam/mail/Delete");
+ URI connectionUri = new URI("mailto:fabrikam@example.com");
+ callback = new ActionCallback(action, getVersion());
+ callback.setMessageIdStrategy(strategyMock);
+ connectionControl.expectAndReturn(connectionMock.getUri(), connectionUri);
+ connectionControl.replay();
+
+ SaajSoapMessage message = createDeleteMessage();
+ strategyControl
+ .expectAndReturn(strategyMock.newMessageId(message), new URI("http://example.com/someuniquestring"));
+ callback.setReplyTo(new EndpointReference(new URI("http://example.com/business/client1")));
+ strategyControl.replay();
+
+ callback.doWithMessage(message);
+
+ SaajSoapMessage expected = loadSaajMessage(getTestPath() + "/valid.xml");
+ assertXMLEqual("Invalid message", expected, message);
+ strategyControl.verify();
+ connectionControl.verify();
+ }
+
+ private SaajSoapMessage createDeleteMessage() throws SOAPException {
+ SOAPMessage saajMessage = messageFactory.createMessage();
+ SOAPBody saajBody = saajMessage.getSOAPBody();
+ SOAPBodyElement delete = saajBody.addBodyElement(new QName("http://example.com/fabrikam", "Delete"));
+ SOAPElement maxCount = delete.addChildElement(new QName("maxCount"));
+ maxCount.setTextContent("42");
+ return new SaajSoapMessage(saajMessage);
+ }
+
+ protected abstract AddressingVersion getVersion();
+
+ protected abstract String getTestPath();
+
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/springframework/ws/soap/addressing/client/ActionCallback10Test.java b/core/src/test/java/org/springframework/ws/soap/addressing/client/ActionCallback10Test.java
new file mode 100644
index 00000000..465f44eb
--- /dev/null
+++ b/core/src/test/java/org/springframework/ws/soap/addressing/client/ActionCallback10Test.java
@@ -0,0 +1,31 @@
+/*
+ * 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.client;
+
+import org.springframework.ws.soap.addressing.version.Addressing10;
+import org.springframework.ws.soap.addressing.version.AddressingVersion;
+
+public class ActionCallback10Test extends AbstractActionCallbackTestCase {
+
+ protected AddressingVersion getVersion() {
+ return new Addressing10();
+ }
+
+ protected String getTestPath() {
+ return "10";
+ }
+}
diff --git a/core/src/test/java/org/springframework/ws/soap/addressing/client/ActionCallback200408Test.java b/core/src/test/java/org/springframework/ws/soap/addressing/client/ActionCallback200408Test.java
new file mode 100644
index 00000000..0b6fc7af
--- /dev/null
+++ b/core/src/test/java/org/springframework/ws/soap/addressing/client/ActionCallback200408Test.java
@@ -0,0 +1,31 @@
+/*
+ * 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.client;
+
+import org.springframework.ws.soap.addressing.version.Addressing200408;
+import org.springframework.ws.soap.addressing.version.AddressingVersion;
+
+public class ActionCallback200408Test extends AbstractActionCallbackTestCase {
+
+ protected AddressingVersion getVersion() {
+ return new Addressing200408();
+ }
+
+ protected String getTestPath() {
+ return "200408";
+ }
+}
\ No newline at end of file