diff --git a/core/src/main/java/org/springframework/ws/transport/TransportException.java b/core/src/main/java/org/springframework/ws/transport/TransportException.java index a4d1ffec..056d473a 100644 --- a/core/src/main/java/org/springframework/ws/transport/TransportException.java +++ b/core/src/main/java/org/springframework/ws/transport/TransportException.java @@ -30,4 +30,9 @@ public abstract class TransportException extends IOException { super(msg); } + protected TransportException(String msg, Throwable cause) { + super(msg); + initCause(cause); + } + } diff --git a/core/src/main/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSender.java b/core/src/main/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSender.java index 81a2d311..71d6ac0d 100644 --- a/core/src/main/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSender.java +++ b/core/src/main/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSender.java @@ -18,6 +18,9 @@ package org.springframework.ws.transport.http; import java.net.URI; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.ws.transport.WebServiceMessageSender; /** @@ -29,6 +32,11 @@ import org.springframework.ws.transport.WebServiceMessageSender; */ public abstract class AbstractHttpWebServiceMessageSender implements WebServiceMessageSender { + /** + * Logger available to subclasses. + */ + protected final Log logger = LogFactory.getLog(getClass()); + private boolean acceptGzipEncoding = true; /** diff --git a/core/src/main/java/org/springframework/ws/transport/http/HttpTransportException.java b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportException.java index b4910078..767e62f1 100644 --- a/core/src/main/java/org/springframework/ws/transport/http/HttpTransportException.java +++ b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportException.java @@ -29,4 +29,10 @@ public class HttpTransportException extends TransportException { public HttpTransportException(String msg) { super(msg); } + + protected HttpTransportException(String msg, Throwable cause) { + super(msg); + initCause(cause); + } + } diff --git a/core/src/main/java/org/springframework/ws/transport/http/HttpUrlConnectionMessageSender.java b/core/src/main/java/org/springframework/ws/transport/http/HttpUrlConnectionMessageSender.java index 19dd357b..61190afa 100644 --- a/core/src/main/java/org/springframework/ws/transport/http/HttpUrlConnectionMessageSender.java +++ b/core/src/main/java/org/springframework/ws/transport/http/HttpUrlConnectionMessageSender.java @@ -46,16 +46,31 @@ public class HttpUrlConnectionMessageSender extends AbstractHttpWebServiceMessag } else { HttpURLConnection httpURLConnection = (HttpURLConnection) connection; - httpURLConnection.setRequestMethod(HttpTransportConstants.METHOD_POST); - httpURLConnection.setUseCaches(false); - httpURLConnection.setDoInput(true); - httpURLConnection.setDoOutput(true); - if (isAcceptGzipEncoding()) { - httpURLConnection.setRequestProperty(HttpTransportConstants.HEADER_ACCEPT_ENCODING, - HttpTransportConstants.CONTENT_ENCODING_GZIP); - } + prepareConnection(httpURLConnection); return new HttpUrlConnection(httpURLConnection); } } + /** + * Template method for preparing the given {@link java.net.HttpURLConnection}. + *

+ * The default implementation prepares the connection for input and output, sets the HTTP method to POST, disables + * caching, and sets the {@code Accept-Encoding} header to gzip, if {@linkplain #setAcceptGzipEncoding(boolean) + * applicable}. + * + * @param connection the connection to prepare + * @throws IOException in case of I/O errors + */ + protected void prepareConnection(HttpURLConnection connection) throws IOException { + connection.setRequestMethod(HttpTransportConstants.METHOD_POST); + connection.setUseCaches(false); + connection.setDoInput(true); + connection.setDoOutput(true); + if (isAcceptGzipEncoding()) { + connection.setRequestProperty(HttpTransportConstants.HEADER_ACCEPT_ENCODING, + HttpTransportConstants.CONTENT_ENCODING_GZIP); + } + } + + } diff --git a/core/src/test/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSenderIntegrationTestCase.java b/core/src/test/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSenderIntegrationTestCase.java index 4bb4f2d9..715ca575 100644 --- a/core/src/test/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSenderIntegrationTestCase.java +++ b/core/src/test/java/org/springframework/ws/transport/http/AbstractHttpWebServiceMessageSenderIntegrationTestCase.java @@ -19,6 +19,7 @@ package org.springframework.ws.transport.http; import java.io.IOException; import java.io.OutputStream; import java.net.URI; +import java.net.URISyntaxException; import java.util.zip.GZIPOutputStream; import javax.servlet.Servlet; import javax.servlet.ServletException; @@ -50,6 +51,7 @@ import org.springframework.ws.transport.FaultAwareWebServiceConnection; import org.springframework.ws.transport.WebServiceConnection; import org.springframework.xml.transform.StringResult; import org.springframework.xml.transform.StringSource; +import org.springframework.beans.factory.InitializingBean; public abstract class AbstractHttpWebServiceMessageSenderIntegrationTestCase extends XMLTestCase { @@ -91,6 +93,9 @@ public abstract class AbstractHttpWebServiceMessageSenderIntegrationTestCase ext jettyServer = new Server(8888); jettyContext = new Context(jettyServer, "/"); messageSender = createMessageSender(); + if (messageSender instanceof InitializingBean) { + ((InitializingBean) messageSender).afterPropertiesSet(); + } XMLUnit.setIgnoreWhitespace(true); saajMessageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL); messageFactory = new SaajSoapMessageFactory(saajMessageFactory); @@ -105,6 +110,10 @@ public abstract class AbstractHttpWebServiceMessageSenderIntegrationTestCase ext } } + public void testSupports() throws URISyntaxException { + assertTrue("Message sender does not support HTTP url", messageSender.supports(new URI(URI_STRING))); + } + public void testSendAndReceiveResponse() throws Exception { MyServlet servlet = new MyServlet(); servlet.setResponse(true); diff --git a/support/src/main/java/org/springframework/ws/transport/http/HttpsTransportException.java b/support/src/main/java/org/springframework/ws/transport/http/HttpsTransportException.java new file mode 100644 index 00000000..9f721696 --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/http/HttpsTransportException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.transport.http; + +/** + * Exception that is thrown when an error occurs in the HTTP transport. + * + * @author Arjen Poutsma + * @since 1.5.8 + */ +public class HttpsTransportException extends HttpTransportException { + + public HttpsTransportException(String msg) { + super(msg); + } + + public HttpsTransportException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/support/src/main/java/org/springframework/ws/transport/http/HttpsUrlConnectionMessageSender.java b/support/src/main/java/org/springframework/ws/transport/http/HttpsUrlConnectionMessageSender.java new file mode 100644 index 00000000..e5ba67ef --- /dev/null +++ b/support/src/main/java/org/springframework/ws/transport/http/HttpsUrlConnectionMessageSender.java @@ -0,0 +1,168 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ws.transport.http; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.security.GeneralSecurityException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.util.Arrays; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Extension of {@link HttpUrlConnectionMessageSender} that adds support for (self-signed) HTTPS certificates. + * + * @author Alex Marshall + * @author Arjen Poutsma + * @since 1.5.8 + */ +public class HttpsUrlConnectionMessageSender extends HttpUrlConnectionMessageSender implements InitializingBean { + + /** The default SSL protocol. */ + public static final String DEFAULT_SSL_PROTOCOL = "ssl"; + + private String sslProtocol = DEFAULT_SSL_PROTOCOL; + + private String sslProvider; + + private KeyManager[] keyManagers; + + private TrustManager[] trustManagers; + + private HostnameVerifier hostnameVerifier; + + private SecureRandom rnd; + + /** + * Sets the SSL protocol to use. Default is {@code ssl}. + * + * @see SSLContext#getInstance(String, String) + */ + public void setSslProtocol(String sslProtocol) { + Assert.hasLength(sslProtocol, "'sslProtocol' must not be empty"); + this.sslProtocol = sslProtocol; + } + + /** + * Sets the SSL provider to use. Default is empty, to use the default provider. + * + * @see SSLContext#getInstance(String, String) + */ + public void setSslProvider(String sslProvider) { + this.sslProvider = sslProvider; + } + + /** + * Specifies the key managers to use for this message sender. + *

+ * Setting either this property or {@link #setTrustManagers(TrustManager[]) trustManagers} is required. + * + * @see SSLContext#init(KeyManager[], TrustManager[], SecureRandom) + */ + public void setKeyManagers(KeyManager[] keyManagers) { + this.keyManagers = keyManagers; + } + + /** + * Specifies the trust managers to use for this message sender. + *

+ * Setting either this property or {@link #setKeyManagers(KeyManager[]) keyManagers} is required. + * + * @see SSLContext#init(KeyManager[], TrustManager[], SecureRandom) + */ + public void setTrustManagers(TrustManager[] trustManagers) { + this.trustManagers = trustManagers; + } + + /** + * Specifies the host name verifier to use for this message sender. + * + * @see HttpsURLConnection#setHostnameVerifier(HostnameVerifier) + */ + public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + + /** + * Specifies the secure random to use for this message sender. + * + * @see SSLContext#init(KeyManager[], TrustManager[], SecureRandom) + */ + public void setSecureRandom(SecureRandom rnd) { + this.rnd = rnd; + } + + public void afterPropertiesSet() throws Exception { + Assert.isTrue(!(ObjectUtils.isEmpty(keyManagers) && ObjectUtils.isEmpty(trustManagers)), + "Setting either 'keyManagers' or 'trustManagers' is required"); + } + + protected void prepareConnection(HttpURLConnection connection) throws IOException { + super.prepareConnection(connection); + if (connection instanceof HttpsURLConnection) { + HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; + try { + SSLContext sslContext = createSslContext(sslProtocol, sslProvider); + sslContext.init(keyManagers, trustManagers, rnd); + if (logger.isDebugEnabled()) { + logger.debug("Initialized SSL Context with key managers [" + + StringUtils.arrayToCommaDelimitedString(keyManagers) + "] trust managers [" + + StringUtils.arrayToCommaDelimitedString(trustManagers) + "] secure random [" + rnd + "]"); + } + + httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); + + if (hostnameVerifier != null) { + httpsConnection.setHostnameVerifier(hostnameVerifier); + } + } + catch (NoSuchProviderException ex) { + throw new HttpsTransportException("Could not create SSLContext: " + ex.getMessage(), ex); + } + catch (NoSuchAlgorithmException ex) { + throw new HttpsTransportException("Could not create SSLContext: " + ex.getMessage(), ex); + } + catch (KeyManagementException ex) { + throw new HttpsTransportException("Could not initialize SSLContext: " + ex.getMessage(), ex); + } + } + } + + private SSLContext createSslContext(String protocol, String provider) + throws NoSuchProviderException, NoSuchAlgorithmException { + if (!StringUtils.hasLength(provider)) { + return SSLContext.getInstance(protocol); + } + else { + return SSLContext.getInstance(protocol, provider); + } + } + +}