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