Java tutorial
/** * Copyright (c) 2012-2013 Reficio (TM) - Reestablish your software!. All Rights Reserved. * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.allstate.client.core; import com.allstate.client.SoapClientException; import com.allstate.client.TransmissionException; import com.allstate.client.ssl.SSLUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.*; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.NTCredentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.routing.HttpRoutePlanner; import org.apache.http.conn.routing.RouteInfo; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeLayeredSocketFactory; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import com.allstate.client.SoapException; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.security.GeneralSecurityException; import static com.allstate.client.core.SoapConstants.*; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * SOAP client enables the user to communicate with a SOAP server on a purely XML level. * It supports SSL/TLS, basic-authentication and java.net.Proxy. * When it comes to SOAP it supports version 1.1 and 1.2 - SOAPAction attribute is automatically properly placed, * either in the header (SOAP 1.1) or in the content (SOAP 1.2). * SOAP version recognition is based on the SOAP namespace included in the payload. * This class may throw an unchecked @see SoapClientException * */ public final class SoapClient { private final static Log log = LogFactory.getLog(SoapClient.class); private final static String NULL_SOAP_ACTION = null; private int readTimeoutInMillis; private int connectTimeoutInMillis; private URI endpointUri; private Security endpointProperties; private boolean endpointTlsEnabled; private URI proxyUri; private Security proxyProperties; private boolean proxyTlsEnabled; private DefaultHttpClient client; // ---------------------------------------------------------------- // PUBLIC API // ---------------------------------------------------------------- /** * Post the SOAP message to the SOAP server without specifying the SOAPAction * * @param requestEnvelope SOAP message envelope * @return The result returned by the SOAP server */ public String post(String requestEnvelope) { return post(NULL_SOAP_ACTION, requestEnvelope); } /** * Post the SOAP message to the SOAP server specifying the SOAPAction * * @param soapAction SOAPAction attribute * @param requestEnvelope SOAP message envelope * @return The result returned by the SOAP server */ public String post(String soapAction, String requestEnvelope) { log.debug(String.format("Sending request to host=[%s] action=[%s] request:%n%s", endpointUri.toString(), soapAction, requestEnvelope)); String response = transmit(soapAction, requestEnvelope); log.debug("Received response:\n" + requestEnvelope); return response; } /** * Disconnects from the SOAP server * Underlying connection is a persistent connection by default: * * @link http://docs.oracle.com/javase/1.5.0/docs/guide/net/http-keepalive.html */ public void disconnect() { if (client != null) { client.getConnectionManager().shutdown(); } } // ---------------------------------------------------------------- // TRANSMISSION API // ---------------------------------------------------------------- private HttpPost generatePost(String soapAction, String requestEnvelope) { try { HttpPost post = new HttpPost(endpointUri.toString()); StringEntity contentEntity = new StringEntity(requestEnvelope); post.setEntity(contentEntity); if (requestEnvelope.contains(SOAP_1_1_NAMESPACE)) { soapAction = soapAction != null ? "\"" + soapAction + "\"" : ""; post.addHeader(PROP_SOAP_ACTION_11, soapAction); post.addHeader(PROP_CONTENT_TYPE, MIMETYPE_TEXT_XML); client.getParams().setParameter(PROP_CONTENT_TYPE, MIMETYPE_TEXT_XML); } else if (requestEnvelope.contains(SOAP_1_2_NAMESPACE)) { String contentType = MIMETYPE_APPLICATION_XML; if (soapAction != null) { contentType = contentType + PROP_DELIMITER + PROP_SOAP_ACTION_12 + "\"" + soapAction + "\""; } post.addHeader(PROP_CONTENT_TYPE, contentType); } return post; } catch (UnsupportedEncodingException ex) { throw new SoapClientException(ex); } } private String transmit(String soapAction, String data) { HttpPost post = generatePost(soapAction, data); return executePost(post); } private String executePost(HttpPost post) { try { HttpResponse response = client.execute(post); StatusLine statusLine = response.getStatusLine(); HttpEntity entity = response.getEntity(); if (statusLine.getStatusCode() >= 300) { EntityUtils.consume(entity); throw new TransmissionException(statusLine.getReasonPhrase(), statusLine.getStatusCode()); } return entity == null ? null : EntityUtils.toString(entity); } catch (SoapException ex) { throw ex; } catch (ConnectTimeoutException ex) { throw new TransmissionException("Connection timed out", ex); } catch (IOException ex) { throw new TransmissionException("Transmission failed", ex); } catch (RuntimeException ex) { post.abort(); throw new TransmissionException("Transmission aborted", ex); } } // ---------------------------------------------------------------- // INITIALIZATION API // ---------------------------------------------------------------- private void initialize() { configureClient(); configureAuthentication(); configureTls(); configureProxy(); } private void configureClient() { client = new DefaultHttpClient(); HttpParams httpParameters = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParameters, connectTimeoutInMillis); HttpConnectionParams.setSoTimeout(httpParameters, readTimeoutInMillis); client.setParams(httpParameters); } private void configureAuthentication() { configureAuthentication(endpointUri, endpointProperties); configureAuthentication(proxyUri, proxyProperties); } private void configureAuthentication(URI uri, Security security) { if (security.isAuthEnabled()) { AuthScope scope = new AuthScope(uri.getHost(), uri.getPort()); Credentials credentials = null; if (security.isAuthBasic()) { credentials = new UsernamePasswordCredentials(security.getAuthUsername(), security.getAuthPassword()); } else if (security.isAuthDigest()) { credentials = new UsernamePasswordCredentials(security.getAuthUsername(), security.getAuthPassword()); } else if (security.isAuthNtlm()) { // TODO credentials = new NTCredentials(security.getAuthUsername(), security.getAuthPassword(), null, null); } else if (security.isAuthSpnego()) { // TODO } client.getCredentialsProvider().setCredentials(scope, credentials); } } private void configureTls() { SSLSocketFactory factory; int port; try { if (endpointTlsEnabled && proxyTlsEnabled) { factory = SSLUtils.getMergedSocketFactory(endpointProperties, proxyProperties); registerTlsScheme(factory, proxyUri.getPort()); } else if (endpointTlsEnabled) { factory = SSLUtils.getFactory(endpointProperties); port = endpointUri.getPort(); registerTlsScheme(factory, port); } else if (proxyTlsEnabled) { factory = SSLUtils.getFactory(proxyProperties); port = proxyUri.getPort(); registerTlsScheme(factory, port); } } catch (GeneralSecurityException ex) { throw new SoapClientException(ex); } } private void registerTlsScheme(SchemeLayeredSocketFactory factory, int port) { Scheme sch = new Scheme(HTTPS, port, factory); client.getConnectionManager().getSchemeRegistry().register(sch); } private void configureProxy() { if (proxyUri == null) { return; } if (proxyTlsEnabled) { final HttpHost proxy = new HttpHost(proxyUri.getHost(), proxyUri.getPort(), HTTPS); // https://issues.apache.org/jira/browse/HTTPCLIENT-1318 // http://stackoverflow.com/questions/15048102/httprouteplanner-how-does-it-work-with-an-https-proxy // To make the HttpClient talk to a HTTP End-site through an HTTPS Proxy, the route should be secure, // but there should not be any Tunnelling or Layering. if (!endpointTlsEnabled) { client.setRoutePlanner(new HttpRoutePlanner() { @Override public HttpRoute determineRoute(HttpHost target, HttpRequest request, HttpContext context) { return new HttpRoute(target, null, proxy, true, RouteInfo.TunnelType.PLAIN, RouteInfo.LayerType.PLAIN); } }); } client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } else { HttpHost proxy = new HttpHost(proxyUri.getHost(), proxyUri.getPort()); client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } } // ---------------------------------------------------------------- // BUILDER API // ---------------------------------------------------------------- private SoapClient() { } /** * Builder to construct a properly populated SoapClient */ public static class Builder { private Integer readTimeoutInMillis = INFINITE_TIMEOUT; private Integer connectTimeoutInMillis = INFINITE_TIMEOUT; private URI endpointUri; private Security endpointProperties; private boolean endpointTlsEnabled; private URI proxyUri; private Security proxyProperties; private boolean proxyTlsEnabled; /** * @param value URL of the SOAP endpoint to whom the client should send messages. Null is not accepted. * @return builder */ public Builder endpointUri(String value) { checkNotNull(value); try { URI uri = new URI(value); return endpointUri(uri); } catch (URISyntaxException ex) { throw new SoapClientException(String.format("URI [%s] is malformed", value), ex); } } /** * @param value URL of the SOAP endpoint to whom the client should send messages. Null is not accepted. * @return builder */ public Builder endpointUri(URI value) { endpointUri = checkNotNull(value); endpointTlsEnabled = value.getScheme().equalsIgnoreCase(HTTPS); return this; } /** * @param value URL of the SOAP endpoint to whom the client should send messages. Null is not accepted. * @return builder */ public Builder proxyUri(String value) { checkNotNull(value); try { URI uri = new URI(value); return proxyUri(uri); } catch (URISyntaxException ex) { throw new SoapClientException(String.format("URI [%s] is malformed", value), ex); } } /** * @param value URL of the SOAP endpoint to whom the client should send messages. Null is not accepted. * @return builder */ public Builder proxyUri(URI value) { proxyUri = checkNotNull(value); proxyTlsEnabled = value.getScheme().equalsIgnoreCase(HTTPS); return this; } public Builder endpointSecurity(Security value) { this.endpointProperties = checkNotNull(value); return this; } public Builder proxySecurity(Security value) { this.proxyProperties = checkNotNull(value); return this; } /** * @param value Specifies the timeout in millisecond for the read operation. Has to be not negative. * @return builder */ public Builder readTimeoutInMillis(int value) { checkArgument(value >= 0); readTimeoutInMillis = value; return this; } /** * @param value Specifies the timeout in millisecond for the connect operation. Has to be not negative. * @return builder */ public Builder connectTimeoutInMillis(int value) { checkArgument(value >= 0); connectTimeoutInMillis = value; return this; } /** * Constructs properly populated soap client * * @return properly populated soap clients */ public SoapClient build() { return initializeClient(); } private SoapClient initializeClient() { SoapClient client = new SoapClient(); client.endpointUri = endpointUri; if (endpointProperties == null) { endpointProperties = Security.builder().build(); } client.endpointProperties = endpointProperties; client.endpointTlsEnabled = endpointTlsEnabled; client.proxyUri = proxyUri; if (proxyProperties == null) { proxyProperties = Security.builder().build(); } client.proxyProperties = proxyProperties; client.proxyTlsEnabled = proxyTlsEnabled; client.readTimeoutInMillis = readTimeoutInMillis; client.connectTimeoutInMillis = connectTimeoutInMillis; client.initialize(); return client; } } /** * @return a new instance of a SoapClient Builder */ public static Builder builder() { return new Builder(); } }