View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.ksb.messaging.serviceconnectors;
17  
18  import java.io.IOException;
19  import java.net.SocketException;
20  import java.net.SocketTimeoutException;
21  import java.net.URL;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Properties;
26  
27  import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
28  import org.apache.commons.httpclient.HostConfiguration;
29  import org.apache.commons.httpclient.HttpClient;
30  import org.apache.commons.httpclient.HttpMethod;
31  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
32  import org.apache.commons.httpclient.cookie.CookiePolicy;
33  import org.apache.commons.httpclient.params.HttpClientParams;
34  import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
35  import org.apache.commons.httpclient.params.HttpConnectionParams;
36  import org.apache.commons.httpclient.params.HttpMethodParams;
37  import org.apache.commons.httpclient.params.HttpParams;
38  import org.apache.commons.httpclient.util.IdleConnectionTimeoutThread;
39  import org.apache.commons.lang.StringUtils;
40  import org.apache.log4j.Logger;
41  import org.kuali.rice.core.api.config.property.ConfigContext;
42  import org.kuali.rice.ksb.api.bus.support.JavaServiceConfiguration;
43  import org.kuali.rice.ksb.messaging.HttpClientHelper;
44  import org.kuali.rice.ksb.messaging.KSBHttpInvokerProxyFactoryBean;
45  import org.kuali.rice.ksb.messaging.KSBHttpInvokerRequestExecutor;
46  import org.kuali.rice.ksb.security.httpinvoker.AuthenticationCommonsHttpInvokerRequestExecutor;
47  
48  
49  /**
50   * @author Kuali Rice Team (rice.collab@kuali.org)
51   * @since 0.9
52   */
53  public class HttpInvokerConnector extends AbstractServiceConnector {
54  
55  	private static final Logger LOG = Logger.getLogger(HttpInvokerConnector.class);
56  
57  	private HttpClientParams httpClientParams;
58  
59  	private boolean httpClientInitialized = false;
60  
61  	private static final String IDLE_CONNECTION_THREAD_INTERVAL_PROPERTY = "ksb.thinClient.idleConnectionThreadInterval";
62  	private static final String IDLE_CONNECTION_TIMEOUT_PROPERTY = "ksb.thinClient.idleConnectionTimeout";
63  	private static final String DEFAULT_IDLE_CONNECTION_THREAD_INTERVAL = "7500";
64  	private static final String DEFAULT_IDLE_CONNECTION_TIMEOUT = "5000";
65  	private static final String RETRY_SOCKET_EXCEPTION_PROPERTY = "ksb.thinClient.retrySocketException";
66  	
67  	private static IdleConnectionTimeoutThread ictt;
68  	
69  	public HttpInvokerConnector(final JavaServiceConfiguration serviceConfiguration, final URL alternateEndpointUrl) {
70  		super(serviceConfiguration, alternateEndpointUrl);
71  		initializeHttpClientParams();
72  	}
73  
74      @Override
75  	public JavaServiceConfiguration getServiceConfiguration() {
76  		return (JavaServiceConfiguration) super.getServiceConfiguration();
77  	}
78  	
79  	public Object getService() {
80  	    LOG.debug("Getting connector for endpoint " + getActualEndpointUrl());
81  		KSBHttpInvokerProxyFactoryBean client = new KSBHttpInvokerProxyFactoryBean();
82  		client.setServiceUrl(getActualEndpointUrl().toExternalForm());
83  		client.setServiceConfiguration(getServiceConfiguration());
84  		
85  		KSBHttpInvokerRequestExecutor executor;
86  		
87  		if (getCredentialsSource() != null) {
88  		    executor = new AuthenticationCommonsHttpInvokerRequestExecutor(getHttpClient(), getCredentialsSource(), getServiceConfiguration());
89  		} else {
90  		    executor = new KSBHttpInvokerRequestExecutor(getHttpClient());
91  		}
92  		executor.setSecure(getServiceConfiguration().getBusSecurity());
93  		client.setHttpInvokerRequestExecutor(executor);	
94  		client.afterPropertiesSet();
95  		return getServiceProxyWithFailureMode(client.getObject(), getServiceConfiguration());
96  	}
97  
98  	/**
99  	 * Creates a commons HttpClient for service invocation. Config parameters
100 	 * that start with http.* are used to configure the client.
101 	 * 
102 	 * TODO we need to add support for other invocation protocols and
103 	 * implementations, but for now...
104 	 */
105 	public HttpClient getHttpClient() {
106 		return new HttpClient(this.httpClientParams);
107 	}
108 
109 	protected void initializeHttpClientParams() {
110 		synchronized (HttpInvokerConnector.class) {
111 		if (! this.httpClientInitialized) {
112 		    this.httpClientParams = new HttpClientParams();
113 			configureDefaultHttpClientParams(this.httpClientParams);
114 			Properties configProps = ConfigContext.getCurrentContextConfig().getProperties();
115 			for (Iterator<Object> iterator = configProps.keySet().iterator(); iterator.hasNext();) {
116 				String paramName = (String) iterator.next();
117 				if (paramName.startsWith("http.")) {
118 					HttpClientHelper.setParameter(this.httpClientParams, paramName, (String) configProps.get(paramName));
119 				}
120 			}
121 				runIdleConnectionTimeout();
122 			this.httpClientInitialized = true;
123 		}
124 	}
125 	}
126 
127 	protected void configureDefaultHttpClientParams(HttpParams params) {
128 		params.setParameter(HttpClientParams.CONNECTION_MANAGER_CLASS, MultiThreadedHttpConnectionManager.class);
129 		params.setParameter(HttpMethodParams.COOKIE_POLICY, CookiePolicy.RFC_2109);
130 		params.setLongParameter(HttpClientParams.CONNECTION_MANAGER_TIMEOUT, 10000);
131 		Map<HostConfiguration, Integer> maxHostConnectionsMap = new HashMap<HostConfiguration, Integer>();
132 		maxHostConnectionsMap.put(HostConfiguration.ANY_HOST_CONFIGURATION, new Integer(20));
133 		params.setParameter(HttpConnectionManagerParams.MAX_HOST_CONNECTIONS, maxHostConnectionsMap);
134 		params.setIntParameter(HttpConnectionManagerParams.MAX_TOTAL_CONNECTIONS, 20);
135 		params.setIntParameter(HttpConnectionParams.CONNECTION_TIMEOUT, 10000);
136 		params.setIntParameter(HttpConnectionParams.SO_TIMEOUT, 2*60*1000);
137 		
138 
139 		boolean retrySocketException = new Boolean(ConfigContext.getCurrentContextConfig().getProperty(RETRY_SOCKET_EXCEPTION_PROPERTY));
140 		if (retrySocketException) {
141 		    LOG.info("Installing custom HTTP retry handler to retry requests in face of SocketExceptions");
142 		    params.setParameter(HttpMethodParams.RETRY_HANDLER, new CustomHttpMethodRetryHandler());
143 		}
144 
145 		
146 	}
147 	
148 
149 	
150 	/**
151 	 * Idle connection timeout thread added as a part of the fix for ensuring that 
152 	 * threads that timed out need to be cleaned or and send back to the pool so that 
153 	 * other clients can use it.
154 	 *
155 	 */
156 	private void runIdleConnectionTimeout() {
157 	    if (ictt != null) {
158 		    String timeoutInterval = ConfigContext.getCurrentContextConfig().getProperty(IDLE_CONNECTION_THREAD_INTERVAL_PROPERTY);
159 		    if (StringUtils.isBlank(timeoutInterval)) {
160 			timeoutInterval = DEFAULT_IDLE_CONNECTION_THREAD_INTERVAL;
161 		    }
162 		    String connectionTimeout = ConfigContext.getCurrentContextConfig().getProperty(IDLE_CONNECTION_TIMEOUT_PROPERTY);
163 		    if (StringUtils.isBlank(connectionTimeout)) {
164 			connectionTimeout = DEFAULT_IDLE_CONNECTION_TIMEOUT;
165 		    }
166 		    
167 		    ictt.addConnectionManager(getHttpClient().getHttpConnectionManager());
168 		    ictt.setTimeoutInterval(new Integer(timeoutInterval));
169 		    ictt.setConnectionTimeout(new Integer(connectionTimeout));
170 		    //start the thread
171 		    ictt.start();
172 	    }
173 	}
174 	
175 	public static void shutdownIdleConnectionTimeout() {
176 		if (ictt != null) {
177 			try {
178 				ictt.shutdown();
179 			} catch (Exception e) {
180 				LOG.error("Failed to shutdown idle connection thread.", e);
181 			}
182 		}
183 	}
184 	
185 	private static final class CustomHttpMethodRetryHandler extends DefaultHttpMethodRetryHandler {
186 
187 		private static final int MAX_RETRIES = 1;
188 		
189 		public CustomHttpMethodRetryHandler() {
190 			super(MAX_RETRIES, true);
191 	}
192 
193 		@Override
194 	    public boolean retryMethod(HttpMethod method, IOException exception, int executionCount) {
195 		boolean shouldRetry = super.retryMethod(method, exception, executionCount);
196 		if (!shouldRetry && executionCount < MAX_RETRIES) {
197 			if (exception instanceof SocketException) {
198 				LOG.warn("Retrying request because of SocketException!", exception);
199 				shouldRetry = true;
200 			} else if (exception instanceof SocketTimeoutException) {
201 				LOG.warn("Retrying request because of SocketTimeoutException!", exception);
202 				shouldRetry = true;
203 			}
204 		}
205 		return shouldRetry;
206 	    }
207 	    
208 	}
209 	
210 
211 	
212 }