001 /* 002 * Copyright 2006-2011 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.kuali.rice.ksb.messaging.config; 018 019 import java.util.ArrayList; 020 import java.util.Arrays; 021 import java.util.Collection; 022 import java.util.Collections; 023 import java.util.LinkedList; 024 import java.util.List; 025 026 import javax.sql.DataSource; 027 028 import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory; 029 import org.apache.commons.httpclient.protocol.Protocol; 030 import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; 031 import org.kuali.rice.core.api.config.CoreConfigHelper; 032 import org.kuali.rice.core.api.config.module.RunMode; 033 import org.kuali.rice.core.api.config.property.Config; 034 import org.kuali.rice.core.api.config.property.ConfigContext; 035 import org.kuali.rice.core.api.exception.RiceRuntimeException; 036 import org.kuali.rice.core.api.lifecycle.BaseLifecycle; 037 import org.kuali.rice.core.api.lifecycle.Lifecycle; 038 import org.kuali.rice.core.api.resourceloader.ResourceLoader; 039 import org.kuali.rice.core.framework.persistence.jpa.OrmUtils; 040 import org.kuali.rice.core.impl.config.module.ModuleConfigurer; 041 import org.kuali.rice.core.impl.lifecycle.ServiceDelegatingLifecycle; 042 import org.kuali.rice.core.util.ClassLoaderUtils; 043 import org.kuali.rice.core.util.RiceConstants; 044 import org.kuali.rice.ksb.api.KsbApiConstants; 045 import org.kuali.rice.ksb.api.KsbApiServiceLocator; 046 import org.kuali.rice.ksb.api.bus.ServiceDefinition; 047 import org.kuali.rice.ksb.messaging.AlternateEndpoint; 048 import org.kuali.rice.ksb.messaging.AlternateEndpointLocation; 049 import org.kuali.rice.ksb.messaging.MessageFetcher; 050 import org.kuali.rice.ksb.messaging.resourceloader.KSBResourceLoaderFactory; 051 import org.kuali.rice.ksb.messaging.serviceconnectors.HttpInvokerConnector; 052 import org.kuali.rice.ksb.service.KSBServiceLocator; 053 import org.kuali.rice.ksb.util.KSBConstants; 054 import org.quartz.Scheduler; 055 import org.springframework.transaction.PlatformTransactionManager; 056 057 058 /** 059 * Used to configure the embedded workflow. This could be used to configure 060 * embedded workflow programmatically but mostly this is a base class by which 061 * to hang specific configuration behavior off of through subclassing 062 * 063 * @author Kuali Rice Team (rice.collab@kuali.org) 064 * 065 */ 066 public class KSBConfigurer extends ModuleConfigurer { 067 068 private static final String SERVICE_BUS_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbServiceBusClientSpringBeans.xml"; 069 private static final String MESSAGE_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbMessageClientSpringBeans.xml"; 070 private static final String OJB_MESSAGE_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbMessageClientSpringBeans.xml"; 071 private static final String BAM_SPRING = "classpath:org/kuali/rice/ksb/config/KsbBamSpringBeans.xml"; 072 private static final String OJB_BAM_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbBamSpringBeans.xml"; 073 private static final String MODULE_SPRING = "classpath:org/kuali/rice/ksb/config/KsbModuleConfigurationSpringBeans.xml"; 074 private static final String REGISTRY_SERVER_SPRING = "classpath:org/kuali/rice/ksb/config/KsbRegistryServerSpringBeans.xml"; 075 private static final String OJB_REGISTRY_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbRegistrySpringBeans.xml"; 076 private static final String WEB_SPRING = "classpath:org/kuali/rice/ksb/config/KsbWebSpringBeans.xml"; 077 078 private List<ServiceDefinition> services = new ArrayList<ServiceDefinition>(); 079 080 private List<AlternateEndpointLocation> alternateEndpointLocations = new ArrayList<AlternateEndpointLocation>(); 081 082 private List<AlternateEndpoint> alternateEndpoints = new ArrayList<AlternateEndpoint>(); 083 084 private DataSource registryDataSource; 085 086 private DataSource messageDataSource; 087 088 private DataSource nonTransactionalMessageDataSource; 089 090 private DataSource bamDataSource; 091 092 private Scheduler exceptionMessagingScheduler; 093 094 private PlatformTransactionManager platformTransactionManager; 095 096 private List<Lifecycle> internalLifecycles; 097 098 public KSBConfigurer() { 099 super(KsbApiConstants.KSB_MODULE_NAME); 100 setValidRunModes(Arrays.asList(RunMode.REMOTE, RunMode.LOCAL)); 101 this.internalLifecycles = new ArrayList<Lifecycle>(); 102 } 103 104 @Override 105 public void addAdditonalToConfig() { 106 configureDataSource(); 107 configureScheduler(); 108 configurePlatformTransactionManager(); 109 configureAlternateEndpoints(); 110 } 111 112 @Override 113 public List<String> getPrimarySpringFiles(){ 114 final List<String> springFileLocations = new ArrayList<String>(); 115 116 boolean isJpa = OrmUtils.isJpaEnabled("rice.ksb"); 117 if (isJpa) { 118 // TODO redo this once we're back to JPA 119 // springFileLocations.add("classpath:org/kuali/rice/ksb/config/KSBJPASpringBeans.xml"); 120 throw new UnsupportedOperationException("JPA not currently supported for KSB"); 121 } 122 123 springFileLocations.add(SERVICE_BUS_CLIENT_SPRING); 124 125 if (isMessagePersistenceEnabled()) { 126 springFileLocations.add(MESSAGE_CLIENT_SPRING); 127 springFileLocations.add(OJB_MESSAGE_CLIENT_SPRING); 128 } 129 130 if (isBamEnabled()) { 131 springFileLocations.add(BAM_SPRING); 132 springFileLocations.add(OJB_BAM_SPRING); 133 } 134 135 if (getRunMode().equals( RunMode.LOCAL )) { 136 // TODO hack 'cause KSB used KNS - this needs to be fixed!!! 137 springFileLocations.add("classpath:org/kuali/rice/krad/config/KRADSpringBeans.xml"); 138 springFileLocations.add("classpath:org/kuali/rice/kns/config/KNSSpringBeans.xml"); 139 springFileLocations.add(REGISTRY_SERVER_SPRING); 140 springFileLocations.add(OJB_REGISTRY_SPRING); 141 if (ConfigContext.getCurrentContextConfig().getBooleanProperty(KSBConstants.Config.LOAD_KRAD_MODULE_CONFIGURATION, false)) { 142 springFileLocations.add(MODULE_SPRING); 143 springFileLocations.add(WEB_SPRING); 144 } 145 } 146 147 return springFileLocations; 148 } 149 150 /** 151 * Returns true - KSB UI should always be included. 152 * 153 * @see org.kuali.rice.core.ConfigContext.getCurrentContextConfig().ModuleConfigurer#shouldRenderWebInterface() 154 */ 155 @Override 156 public boolean shouldRenderWebInterface() { 157 return true; 158 } 159 160 @Override 161 public Collection<ResourceLoader> getResourceLoadersToRegister() throws Exception{ 162 ResourceLoader ksbRemoteResourceLoader = KSBResourceLoaderFactory.createRootKSBRemoteResourceLoader(); 163 ksbRemoteResourceLoader.start(); 164 return Collections.singletonList(ksbRemoteResourceLoader); 165 } 166 167 @Override 168 public List<Lifecycle> loadLifecycles() throws Exception { 169 List<Lifecycle> lifecycles = new LinkedList<Lifecycle>(); 170 // this validation of our service list needs to happen after we've 171 // loaded our configs so it's a lifecycle 172 lifecycles.add(new BaseLifecycle() { 173 174 @Override 175 public void start() throws Exception { 176 // first check if we want to allow self-signed certificates for SSL communication 177 if (Boolean.valueOf(ConfigContext.getCurrentContextConfig().getProperty(KSBConstants.Config.KSB_ALLOW_SELF_SIGNED_SSL)).booleanValue()) { 178 Protocol.registerProtocol("https", new Protocol("https", 179 (ProtocolSocketFactory) new EasySSLProtocolSocketFactory(), 443)); 180 } 181 super.start(); 182 } 183 }); 184 lifecycles.add(new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.BUS_ADMIN_SERVICE)); 185 return lifecycles; 186 } 187 188 189 190 @Override 191 public void doAdditonalConfigurerValidations() { 192 for (final ServiceDefinition serviceDef : KSBConfigurer.this.services) { 193 serviceDef.validate(); 194 } 195 } 196 197 @Override 198 public void doAdditionalContextStartedLogic() { 199 ServicePublisher servicePublisher = new ServicePublisher(getServices()); 200 Lifecycle serviceBus = new ServiceDelegatingLifecycle(KsbApiServiceLocator.SERVICE_BUS); 201 Lifecycle threadPool = new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.THREAD_POOL_SERVICE); 202 Lifecycle scheduledThreadPool = new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.SCHEDULED_THREAD_POOL_SERVICE); 203 204 try { 205 servicePublisher.start(); 206 internalLifecycles.add(servicePublisher); 207 serviceBus.start(); 208 internalLifecycles.add(serviceBus); 209 threadPool.start(); 210 internalLifecycles.add(threadPool); 211 scheduledThreadPool.start(); 212 internalLifecycles.add(scheduledThreadPool); 213 } catch (Exception e) { 214 if (e instanceof RuntimeException) { 215 throw (RuntimeException)e; 216 } 217 throw new RiceRuntimeException("Failed to initialize KSB on context startup"); 218 } 219 220 requeueMessages(); 221 } 222 223 @Override 224 protected void doAdditionalModuleStopLogic() throws Exception { 225 for (int index = internalLifecycles.size() - 1; index >= 0; index--) { 226 try { 227 internalLifecycles.get(index).stop(); 228 } catch (Exception e) { 229 LOG.error("Failed to properly execute shutdown logic.", e); 230 } 231 } 232 } 233 234 /** 235 * Used to refresh the service registry after the Application Context is initialized. This way any services that were exported on startup 236 * will be available in the service registry once startup is complete. 237 */ 238 private void requeueMessages() { 239 LOG.info("Refreshing Service Registry to export services to the bus."); 240 KsbApiServiceLocator.getServiceBus().synchronize(); 241 242 //automatically requeue documents sitting with status of 'R' 243 MessageFetcher messageFetcher = new MessageFetcher((Integer) null); 244 KSBServiceLocator.getThreadPool().execute(messageFetcher); 245 } 246 247 protected boolean isMessagePersistenceEnabled() { 248 return ConfigContext.getCurrentContextConfig().getBooleanProperty(KSBConstants.Config.MESSAGE_PERSISTENCE, true); 249 } 250 251 protected boolean isBamEnabled() { 252 return ConfigContext.getCurrentContextConfig().getBooleanProperty(Config.BAM_ENABLED, false); 253 } 254 255 protected void configureScheduler() { 256 if (this.getExceptionMessagingScheduler() != null) { 257 LOG.info("Configuring injected exception messaging Scheduler"); 258 ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.INJECTED_EXCEPTION_MESSAGE_SCHEDULER_KEY, this.getExceptionMessagingScheduler()); 259 } 260 } 261 262 protected void configureDataSource() { 263 if (isMessagePersistenceEnabled()) { 264 if (getMessageDataSource() != null) { 265 ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_MESSAGE_DATASOURCE, getMessageDataSource()); 266 } 267 if (getNonTransactionalMessageDataSource() != null) { 268 ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_MESSAGE_NON_TRANSACTIONAL_DATASOURCE, getNonTransactionalMessageDataSource()); 269 } 270 } 271 if (getRunMode().equals(RunMode.LOCAL)) { 272 if (getRegistryDataSource() != null) { 273 ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_REGISTRY_DATASOURCE, getRegistryDataSource()); 274 } 275 } 276 if (isBamEnabled()) { 277 if (getBamDataSource() != null) { 278 ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_BAM_DATASOURCE, getBamDataSource()); 279 } 280 } 281 } 282 283 protected void configurePlatformTransactionManager() { 284 if (getPlatformTransactionManager() == null) { 285 return; 286 } 287 ConfigContext.getCurrentContextConfig().putObject(RiceConstants.SPRING_TRANSACTION_MANAGER, getPlatformTransactionManager()); 288 } 289 290 protected void configureAlternateEndpoints() { 291 ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINT_LOCATIONS, getAlternateEndpointLocations()); 292 ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINTS, getAlternateEndpoints()); 293 } 294 295 @Override 296 public void doAdditionalContextStoppedLogic() { 297 try { 298 HttpInvokerConnector.shutdownIdleConnectionTimeout(); 299 } catch (Exception e) { 300 LOG.error("Failed to shutdown idle connection timeout evictor thread.", e); 301 } 302 cleanUpConfiguration(); 303 } 304 305 /** 306 * Because our configuration is global, shutting down Rice does not get rid of objects stored there. For that reason 307 * we need to manually clean these up. This is most important in the case of the service bus because the configuration 308 * is used to store services to be exported. If we don't clean this up then a shutdown/startup within the same 309 * class loading context causes the service list to be doubled and results in "multiple endpoint" error messages. 310 * 311 */ 312 protected void cleanUpConfiguration() { 313 ConfigContext.getCurrentContextConfig().removeObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINTS); 314 } 315 316 public List<ServiceDefinition> getServices() { 317 return this.services; 318 } 319 320 public void setServices(List<ServiceDefinition> javaServices) { 321 this.services = javaServices; 322 } 323 324 public DataSource getMessageDataSource() { 325 return this.messageDataSource; 326 } 327 328 public void setMessageDataSource(DataSource messageDataSource) { 329 this.messageDataSource = messageDataSource; 330 } 331 332 public DataSource getNonTransactionalMessageDataSource() { 333 return this.nonTransactionalMessageDataSource; 334 } 335 336 public void setNonTransactionalMessageDataSource(DataSource nonTransactionalMessageDataSource) { 337 this.nonTransactionalMessageDataSource = nonTransactionalMessageDataSource; 338 } 339 340 public DataSource getRegistryDataSource() { 341 return this.registryDataSource; 342 } 343 344 public void setRegistryDataSource(DataSource registryDataSource) { 345 this.registryDataSource = registryDataSource; 346 } 347 348 public DataSource getBamDataSource() { 349 return this.bamDataSource; 350 } 351 352 public void setBamDataSource(DataSource bamDataSource) { 353 this.bamDataSource = bamDataSource; 354 } 355 356 public Scheduler getExceptionMessagingScheduler() { 357 return this.exceptionMessagingScheduler; 358 } 359 360 public void setExceptionMessagingScheduler(Scheduler exceptionMessagingScheduler) { 361 this.exceptionMessagingScheduler = exceptionMessagingScheduler; 362 } 363 364 public PlatformTransactionManager getPlatformTransactionManager() { 365 return platformTransactionManager; 366 } 367 368 public void setPlatformTransactionManager(PlatformTransactionManager springTransactionManager) { 369 this.platformTransactionManager = springTransactionManager; 370 } 371 372 public List<AlternateEndpointLocation> getAlternateEndpointLocations() { 373 return this.alternateEndpointLocations; 374 } 375 376 public void setAlternateEndpointLocations(List<AlternateEndpointLocation> alternateEndpointLocations) { 377 this.alternateEndpointLocations = alternateEndpointLocations; 378 } 379 380 public List<AlternateEndpoint> getAlternateEndpoints() { 381 return this.alternateEndpoints; 382 } 383 384 public void setAlternateEndpoints(List<AlternateEndpoint> alternateEndpoints) { 385 this.alternateEndpoints = alternateEndpoints; 386 } 387 388 private final class ServicePublisher extends BaseLifecycle { 389 390 private final List<ServiceDefinition> serviceDefinitions; 391 392 ServicePublisher(List<ServiceDefinition> serviceDefinitions) { 393 this.serviceDefinitions = serviceDefinitions; 394 } 395 396 @Override 397 public void start() throws Exception { 398 if (serviceDefinitions != null && !serviceDefinitions.isEmpty()) { 399 LOG.debug("Configuring " + serviceDefinitions.size() + " services for application id " + CoreConfigHelper.getApplicationId() + " using config for classloader " + ClassLoaderUtils.getDefaultClassLoader()); 400 KsbApiServiceLocator.getServiceBus().publishServices(serviceDefinitions, true); 401 super.start(); 402 } 403 } 404 405 } 406 407 }