Java tutorial
/** * Copyright 2005-2014 The Kuali Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php * * 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.kuali.rice.ksb.messaging.serviceconnectors; import java.io.IOException; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.cookie.CookiePolicy; import org.apache.commons.httpclient.params.HttpClientParams; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; import org.apache.commons.httpclient.params.HttpConnectionParams; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.httpclient.params.HttpParams; import org.apache.commons.httpclient.util.IdleConnectionTimeoutThread; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.kuali.rice.core.api.config.property.ConfigContext; import org.kuali.rice.ksb.api.bus.support.JavaServiceConfiguration; import org.kuali.rice.ksb.messaging.HttpClientHelper; import org.kuali.rice.ksb.messaging.KSBHttpInvokerProxyFactoryBean; import org.kuali.rice.ksb.messaging.KSBHttpInvokerRequestExecutor; import org.kuali.rice.ksb.security.httpinvoker.AuthenticationCommonsHttpInvokerRequestExecutor; /** * @author Kuali Rice Team (rice.collab@kuali.org) * @since 0.9 */ public class HttpInvokerConnector extends AbstractServiceConnector { private static final Logger LOG = Logger.getLogger(HttpInvokerConnector.class); private HttpClientParams httpClientParams; private boolean httpClientInitialized = false; private static final String IDLE_CONNECTION_THREAD_INTERVAL_PROPERTY = "ksb.thinClient.idleConnectionThreadInterval"; private static final String IDLE_CONNECTION_TIMEOUT_PROPERTY = "ksb.thinClient.idleConnectionTimeout"; private static final String DEFAULT_IDLE_CONNECTION_THREAD_INTERVAL = "7500"; private static final String DEFAULT_IDLE_CONNECTION_TIMEOUT = "5000"; private static final String RETRY_SOCKET_EXCEPTION_PROPERTY = "ksb.thinClient.retrySocketException"; private static IdleConnectionTimeoutThread ictt; public HttpInvokerConnector(final JavaServiceConfiguration serviceConfiguration, final URL alternateEndpointUrl) { super(serviceConfiguration, alternateEndpointUrl); initializeHttpClientParams(); } @Override public JavaServiceConfiguration getServiceConfiguration() { return (JavaServiceConfiguration) super.getServiceConfiguration(); } public Object getService() { LOG.debug("Getting connector for endpoint " + getActualEndpointUrl()); KSBHttpInvokerProxyFactoryBean client = new KSBHttpInvokerProxyFactoryBean(); client.setServiceUrl(getActualEndpointUrl().toExternalForm()); client.setServiceConfiguration(getServiceConfiguration()); KSBHttpInvokerRequestExecutor executor; if (getCredentialsSource() != null) { executor = new AuthenticationCommonsHttpInvokerRequestExecutor(getHttpClient(), getCredentialsSource(), getServiceConfiguration()); } else { executor = new KSBHttpInvokerRequestExecutor(getHttpClient()); } executor.setSecure(getServiceConfiguration().getBusSecurity()); client.setHttpInvokerRequestExecutor(executor); client.afterPropertiesSet(); return getServiceProxyWithFailureMode(client.getObject(), getServiceConfiguration()); } /** * Creates a commons HttpClient for service invocation. Config parameters * that start with http.* are used to configure the client. * * TODO we need to add support for other invocation protocols and * implementations, but for now... */ public HttpClient getHttpClient() { return new HttpClient(this.httpClientParams); } protected void initializeHttpClientParams() { synchronized (HttpInvokerConnector.class) { if (!this.httpClientInitialized) { this.httpClientParams = new HttpClientParams(); configureDefaultHttpClientParams(this.httpClientParams); Properties configProps = ConfigContext.getCurrentContextConfig().getProperties(); for (Iterator<Object> iterator = configProps.keySet().iterator(); iterator.hasNext();) { String paramName = (String) iterator.next(); if (paramName.startsWith("http.")) { HttpClientHelper.setParameter(this.httpClientParams, paramName, (String) configProps.get(paramName)); } } runIdleConnectionTimeout(); this.httpClientInitialized = true; } } } protected void configureDefaultHttpClientParams(HttpParams params) { params.setParameter(HttpClientParams.CONNECTION_MANAGER_CLASS, MultiThreadedHttpConnectionManager.class); params.setParameter(HttpMethodParams.COOKIE_POLICY, CookiePolicy.RFC_2109); params.setLongParameter(HttpClientParams.CONNECTION_MANAGER_TIMEOUT, 10000); Map<HostConfiguration, Integer> maxHostConnectionsMap = new HashMap<HostConfiguration, Integer>(); maxHostConnectionsMap.put(HostConfiguration.ANY_HOST_CONFIGURATION, new Integer(20)); params.setParameter(HttpConnectionManagerParams.MAX_HOST_CONNECTIONS, maxHostConnectionsMap); params.setIntParameter(HttpConnectionManagerParams.MAX_TOTAL_CONNECTIONS, 20); params.setIntParameter(HttpConnectionParams.CONNECTION_TIMEOUT, 10000); params.setIntParameter(HttpConnectionParams.SO_TIMEOUT, 2 * 60 * 1000); boolean retrySocketException = new Boolean( ConfigContext.getCurrentContextConfig().getProperty(RETRY_SOCKET_EXCEPTION_PROPERTY)); if (retrySocketException) { LOG.info("Installing custom HTTP retry handler to retry requests in face of SocketExceptions"); params.setParameter(HttpMethodParams.RETRY_HANDLER, new CustomHttpMethodRetryHandler()); } } /** * Idle connection timeout thread added as a part of the fix for ensuring that * threads that timed out need to be cleaned or and send back to the pool so that * other clients can use it. * */ private void runIdleConnectionTimeout() { if (ictt != null) { String timeoutInterval = ConfigContext.getCurrentContextConfig() .getProperty(IDLE_CONNECTION_THREAD_INTERVAL_PROPERTY); if (StringUtils.isBlank(timeoutInterval)) { timeoutInterval = DEFAULT_IDLE_CONNECTION_THREAD_INTERVAL; } String connectionTimeout = ConfigContext.getCurrentContextConfig() .getProperty(IDLE_CONNECTION_TIMEOUT_PROPERTY); if (StringUtils.isBlank(connectionTimeout)) { connectionTimeout = DEFAULT_IDLE_CONNECTION_TIMEOUT; } ictt.addConnectionManager(getHttpClient().getHttpConnectionManager()); ictt.setTimeoutInterval(new Integer(timeoutInterval)); ictt.setConnectionTimeout(new Integer(connectionTimeout)); //start the thread ictt.start(); } } public static void shutdownIdleConnectionTimeout() { if (ictt != null) { try { ictt.shutdown(); } catch (Exception e) { LOG.error("Failed to shutdown idle connection thread.", e); } } } private static final class CustomHttpMethodRetryHandler extends DefaultHttpMethodRetryHandler { private static final int MAX_RETRIES = 1; public CustomHttpMethodRetryHandler() { super(MAX_RETRIES, true); } @Override public boolean retryMethod(HttpMethod method, IOException exception, int executionCount) { boolean shouldRetry = super.retryMethod(method, exception, executionCount); if (!shouldRetry && executionCount < MAX_RETRIES) { if (exception instanceof SocketException) { LOG.warn("Retrying request because of SocketException!", exception); shouldRetry = true; } else if (exception instanceof SocketTimeoutException) { LOG.warn("Retrying request because of SocketTimeoutException!", exception); shouldRetry = true; } } return shouldRetry; } } }