1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.kuali.rice.kew.config;
18
19
20 import java.io.IOException;
21 import java.net.SocketException;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.Map;
26 import java.util.Properties;
27
28 import javax.xml.namespace.QName;
29
30 import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
31 import org.apache.commons.httpclient.HostConfiguration;
32 import org.apache.commons.httpclient.HttpClient;
33 import org.apache.commons.httpclient.HttpMethod;
34 import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
35 import org.apache.commons.httpclient.cookie.CookiePolicy;
36 import org.apache.commons.httpclient.params.HttpClientParams;
37 import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
38 import org.apache.commons.httpclient.params.HttpMethodParams;
39 import org.apache.commons.httpclient.params.HttpParams;
40 import org.apache.commons.httpclient.util.IdleConnectionTimeoutThread;
41 import org.apache.commons.lang.StringUtils;
42 import org.kuali.rice.core.api.config.CoreConfigHelper;
43 import org.kuali.rice.core.api.config.property.ConfigContext;
44 import org.kuali.rice.core.framework.resourceloader.BaseResourceLoader;
45 import org.kuali.rice.kew.util.KEWConstants;
46 import org.kuali.rice.kim.api.group.GroupService;
47 import org.kuali.rice.kim.api.identity.IdentityService;
48 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
49 import org.kuali.rice.ksb.messaging.HttpClientHelper;
50 import org.kuali.rice.ksb.messaging.KSBHttpInvokerRequestExecutor;
51 import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean;
52
53
54
55
56
57
58
59
60
61 public class ThinClientResourceLoader extends BaseResourceLoader {
62 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ThinClientResourceLoader.class);
63
64 private static final String DEFAULT_MAX_CONNECTIONS = "40";
65 private static final String DEFAULT_CONNECTION_TIMEOUT = "60000";
66 private static final String DEFAULT_CONNECTION_MANAGER_TIMEOUT = "60000";
67 public static final String MAX_CONNECTIONS = "kew." + HttpConnectionManagerParams.MAX_TOTAL_CONNECTIONS;
68 public static final String CONNECTION_TIMEOUT = "kew." + HttpConnectionManagerParams.CONNECTION_TIMEOUT;
69 public static final String CONNECTION_MANAGER_TIMEOUT = "kew." + HttpClientParams.CONNECTION_MANAGER_TIMEOUT;
70 public static final String DOCUMENT_ENDPOINT = "workflowdocument.javaservice.endpoint";
71 public static final String SECURE_DOCUMENT_ENDPOINT = "secure.workflowdocument.javaservice.endpoint";
72 public static final String UTILITY_ENDPOINT = "workflowutility.javaservice.endpoint";
73 public static final String SECURE_UTILITY_ENDPOINT = "secure.workflowutility.javaservice.endpoint";
74 public static final String IDENTITY_ENDPOINT = "identity.javaservice.endpoint";
75 public static final String SECURE_IDENTITY_ENDPOINT = "secure.identity.javaservice.endpoint";
76 public static final String GROUP_ENDPOINT = "group.javaservice.endpoint";
77 public static final String SECURE_GROUP_ENDPOINT = "secure.group.javaservice.endpoint";
78
79
80 private static final String IDLE_CONNECTION_THREAD_INTERVAL_PROPERTY = "ksb.thinClient.idleConnectionThreadInterval";
81 private static final String IDLE_CONNECTION_TIMEOUT_PROPERTY = "ksb.thinClient.idleConnectionTimeout";
82 private static final String DEFAULT_IDLE_CONNECTION_THREAD_INTERVAL = "7500";
83 private static final String DEFAULT_IDLE_CONNECTION_TIMEOUT = "5000";
84 private static final String RETRY_SOCKET_EXCEPTION_PROPERTY = "ksb.thinClient.retrySocketException";
85
86 private Map<String, Object> services = Collections.synchronizedMap(new HashMap<String, Object>());
87
88 private IdleConnectionTimeoutThread ictt;
89
90 public ThinClientResourceLoader() {
91 super(new QName(CoreConfigHelper.getApplicationId(), "ThinClientResourceLoader"));
92 ictt = new IdleConnectionTimeoutThread();
93 }
94
95 @Override
96 public void start() throws Exception {
97 super.start();
98 initializeHttpClientParams();
99 runIdleConnectionTimeout();
100
101 }
102
103
104
105 @Override
106 public void stop() throws Exception {
107 super.stop();
108 if (ictt != null) {
109 ictt.shutdown();
110 }
111
112 }
113
114 public Object getService(QName serviceQName) {
115 String serviceName = serviceQName.getLocalPart();
116 Object cachedService = services.get(serviceName);
117 if (cachedService != null) {
118 return cachedService;
119 }
120 if (serviceName.equals(KEWConstants.WORKFLOW_UTILITY_SERVICE)) {
121 throw new UnsupportedOperationException("Reimplement me! - see KULRICE-5061");
122
123
124
125
126
127
128
129 } else if (serviceName.equals(KimApiServiceLocator.KIM_IDENTITY_SERVICE)) {
130 IdentityService identityService = getIdentityService();
131 services.put(serviceName, identityService);
132 return identityService;
133 } else if (serviceName.equals(KimApiServiceLocator.KIM_GROUP_SERVICE)) {
134 GroupService groupService = getGroupService();
135 services.put(serviceName, groupService);
136 return groupService;
137 }
138 return null;
139 }
140
141
142
143
144
145
146
147
148
149 public IdentityService getIdentityService() {
150 return (IdentityService)getServiceProxy(IdentityService.class, IDENTITY_ENDPOINT, SECURE_IDENTITY_ENDPOINT);
151 }
152
153 public GroupService getGroupService() {
154 return (GroupService)getServiceProxy(GroupService.class, GROUP_ENDPOINT, SECURE_GROUP_ENDPOINT);
155 }
156
157 protected Object getServiceProxy(Class serviceInterface, String endpointParam, String secureEndpointParam) {
158 HttpInvokerProxyFactoryBean proxyFactory = new HttpInvokerProxyFactoryBean();
159 String serviceUrl = ConfigContext.getCurrentContextConfig().getProperty(endpointParam);
160 if (StringUtils.isEmpty(serviceUrl)) {
161 throw new IllegalArgumentException("The " + endpointParam + " configuration parameter was not defined but is required.");
162 }
163 proxyFactory.setServiceUrl(serviceUrl);
164 proxyFactory.setServiceInterface(serviceInterface);
165 String secureProp = ConfigContext.getCurrentContextConfig().getProperty(secureEndpointParam);
166 Boolean secureIt = null;
167 secureIt = secureProp == null || Boolean.valueOf(secureProp);
168 KSBHttpInvokerRequestExecutor executor = new KSBHttpInvokerRequestExecutor(getHttpClient());
169 executor.setSecure(secureIt);
170 proxyFactory.setHttpInvokerRequestExecutor(executor);
171 proxyFactory.afterPropertiesSet();
172 return proxyFactory.getObject();
173 }
174
175
176
177
178
179 private HttpClientParams httpClientParams;
180
181
182
183
184
185
186
187
188 protected HttpClient getHttpClient() {
189 return new HttpClient(httpClientParams);
190 }
191
192 protected void initializeHttpClientParams() {
193 httpClientParams = new HttpClientParams();
194 configureDefaultHttpClientParams(httpClientParams);
195 Properties configProps = ConfigContext.getCurrentContextConfig().getProperties();
196 for (Iterator iterator = configProps.keySet().iterator(); iterator.hasNext();) {
197 String paramName = (String) iterator.next();
198 if (paramName.startsWith("http.")) {
199 HttpClientHelper.setParameter(httpClientParams, paramName, (String) configProps.get(paramName));
200 }
201 }
202
203 String maxConnectionsValue = configProps.getProperty(MAX_CONNECTIONS);
204 if (!StringUtils.isEmpty(maxConnectionsValue)) {
205 Integer maxConnections = new Integer(maxConnectionsValue);
206 Map<HostConfiguration, Integer> maxHostConnectionsMap = new HashMap<HostConfiguration, Integer>();
207 maxHostConnectionsMap.put(HostConfiguration.ANY_HOST_CONFIGURATION, maxConnections);
208 httpClientParams.setParameter(HttpConnectionManagerParams.MAX_HOST_CONNECTIONS, maxHostConnectionsMap);
209 httpClientParams.setIntParameter(HttpConnectionManagerParams.MAX_TOTAL_CONNECTIONS, maxConnections);
210 }
211
212 String connectionManagerTimeoutValue = configProps.getProperty(CONNECTION_MANAGER_TIMEOUT);
213 if (!StringUtils.isEmpty(connectionManagerTimeoutValue)) {
214 httpClientParams.setLongParameter(HttpClientParams.CONNECTION_MANAGER_TIMEOUT, new Long(connectionManagerTimeoutValue));
215 }
216
217 String connectionTimeoutValue = configProps.getProperty(CONNECTION_TIMEOUT);
218 if (!StringUtils.isEmpty(connectionTimeoutValue)) {
219 httpClientParams.setIntParameter(HttpConnectionManagerParams.CONNECTION_TIMEOUT, new Integer(connectionTimeoutValue));
220 }
221 }
222
223 protected void configureDefaultHttpClientParams(HttpParams params) {
224 params.setParameter(HttpClientParams.CONNECTION_MANAGER_CLASS, MultiThreadedHttpConnectionManager.class);
225 params.setParameter(HttpMethodParams.COOKIE_POLICY, CookiePolicy.RFC_2109);
226 params.setLongParameter(HttpClientParams.CONNECTION_MANAGER_TIMEOUT, new Long(DEFAULT_CONNECTION_MANAGER_TIMEOUT));
227 Map<HostConfiguration, Integer> maxHostConnectionsMap = new HashMap<HostConfiguration, Integer>();
228 maxHostConnectionsMap.put(HostConfiguration.ANY_HOST_CONFIGURATION, new Integer(DEFAULT_MAX_CONNECTIONS));
229 params.setParameter(HttpConnectionManagerParams.MAX_HOST_CONNECTIONS, maxHostConnectionsMap);
230 params.setIntParameter(HttpConnectionManagerParams.MAX_TOTAL_CONNECTIONS, new Integer(DEFAULT_MAX_CONNECTIONS));
231 params.setIntParameter(HttpConnectionManagerParams.CONNECTION_TIMEOUT, new Integer(DEFAULT_CONNECTION_TIMEOUT));
232
233 boolean retrySocketException = new Boolean(ConfigContext.getCurrentContextConfig().getProperty(RETRY_SOCKET_EXCEPTION_PROPERTY));
234 if (retrySocketException) {
235 LOG.info("Installing custom HTTP retry handler to retry requests in face of SocketExceptions");
236 params.setParameter(HttpMethodParams.RETRY_HANDLER, new CustomHttpMethodRetryHandler());
237 }
238 }
239
240
241
242
243
244
245
246 private void runIdleConnectionTimeout() {
247 if (ictt != null) {
248 String timeoutInterval = ConfigContext.getCurrentContextConfig().getProperty(IDLE_CONNECTION_THREAD_INTERVAL_PROPERTY);
249 if (StringUtils.isBlank(timeoutInterval)) {
250 timeoutInterval = DEFAULT_IDLE_CONNECTION_THREAD_INTERVAL;
251 }
252 String connectionTimeout = ConfigContext.getCurrentContextConfig().getProperty(IDLE_CONNECTION_TIMEOUT_PROPERTY);
253 if (StringUtils.isBlank(connectionTimeout)) {
254 connectionTimeout = DEFAULT_IDLE_CONNECTION_TIMEOUT;
255 }
256
257 ictt.addConnectionManager(getHttpClient().getHttpConnectionManager());
258 ictt.setTimeoutInterval(new Integer(timeoutInterval));
259 ictt.setConnectionTimeout(new Integer(connectionTimeout));
260
261 ictt.start();
262 }
263 }
264
265 private static final class CustomHttpMethodRetryHandler extends DefaultHttpMethodRetryHandler {
266
267 @Override
268 public boolean retryMethod(HttpMethod method, IOException exception, int executionCount) {
269 boolean shouldRetry = super.retryMethod(method, exception, executionCount);
270 if (!shouldRetry && exception instanceof SocketException) {
271 LOG.warn("Retrying request because of SocketException!", exception);
272 shouldRetry = true;
273 }
274 return shouldRetry;
275 }
276
277 }
278
279 }