View Javadoc

1   /**
2    * Copyright 2005-2012 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.config;
17  
18  import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
19  import org.apache.commons.httpclient.protocol.Protocol;
20  import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
21  import org.kuali.rice.core.api.config.CoreConfigHelper;
22  import org.kuali.rice.core.api.config.module.RunMode;
23  import org.kuali.rice.core.api.config.property.Config;
24  import org.kuali.rice.core.api.config.property.ConfigContext;
25  import org.kuali.rice.core.api.exception.RiceRuntimeException;
26  import org.kuali.rice.core.api.lifecycle.BaseLifecycle;
27  import org.kuali.rice.core.api.lifecycle.Lifecycle;
28  import org.kuali.rice.core.api.resourceloader.ResourceLoader;
29  import org.kuali.rice.core.api.util.ClassLoaderUtils;
30  import org.kuali.rice.core.api.util.RiceConstants;
31  import org.kuali.rice.core.framework.config.module.ModuleConfigurer;
32  import org.kuali.rice.core.framework.config.module.WebModuleConfiguration;
33  import org.kuali.rice.core.framework.lifecycle.ServiceDelegatingLifecycle;
34  import org.kuali.rice.core.framework.persistence.jpa.OrmUtils;
35  import org.kuali.rice.ksb.api.KsbApiConstants;
36  import org.kuali.rice.ksb.api.KsbApiServiceLocator;
37  import org.kuali.rice.ksb.api.bus.ServiceDefinition;
38  import org.kuali.rice.ksb.messaging.AlternateEndpoint;
39  import org.kuali.rice.ksb.messaging.AlternateEndpointLocation;
40  import org.kuali.rice.ksb.messaging.MessageFetcher;
41  import org.kuali.rice.ksb.messaging.resourceloader.KSBResourceLoaderFactory;
42  import org.kuali.rice.ksb.messaging.serviceconnectors.HttpInvokerConnector;
43  import org.kuali.rice.ksb.service.KSBServiceLocator;
44  import org.kuali.rice.ksb.util.KSBConstants;
45  import org.quartz.Scheduler;
46  import org.springframework.context.ApplicationEvent;
47  import org.springframework.context.event.ContextRefreshedEvent;
48  import org.springframework.context.event.ContextStoppedEvent;
49  import org.springframework.context.event.SmartApplicationListener;
50  import org.springframework.core.Ordered;
51  import org.springframework.transaction.PlatformTransactionManager;
52  
53  import javax.sql.DataSource;
54  import java.util.ArrayList;
55  import java.util.Arrays;
56  import java.util.Collection;
57  import java.util.Collections;
58  import java.util.LinkedList;
59  import java.util.List;
60  
61  
62  /**
63   * Used to configure the embedded workflow. This could be used to configure
64   * embedded workflow programmatically but mostly this is a base class by which
65   * to hang specific configuration behavior off of through subclassing
66   * 
67   * @author Kuali Rice Team (rice.collab@kuali.org)
68   * 
69   */
70  public class KSBConfigurer extends ModuleConfigurer implements SmartApplicationListener {
71  	
72  	private static final String SERVICE_BUS_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbServiceBusClientSpringBeans.xml";
73  	private static final String MESSAGE_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbMessageClientSpringBeans.xml";
74  	private static final String OJB_MESSAGE_CLIENT_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbMessageClientSpringBeans.xml";
75  	private static final String BAM_SPRING = "classpath:org/kuali/rice/ksb/config/KsbBamSpringBeans.xml";
76  	private static final String OJB_BAM_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbBamSpringBeans.xml";
77  	private static final String REGISTRY_SERVER_SPRING = "classpath:org/kuali/rice/ksb/config/KsbRegistryServerSpringBeans.xml";
78  	private static final String OJB_REGISTRY_SPRING = "classpath:org/kuali/rice/ksb/config/KsbOjbRegistrySpringBeans.xml";
79  	private static final String WEB_SPRING = "classpath:org/kuali/rice/ksb/config/KsbWebSpringBeans.xml";
80  
81  	private List<ServiceDefinition> services = new ArrayList<ServiceDefinition>();
82  	
83      private List<AlternateEndpointLocation> alternateEndpointLocations = new ArrayList<AlternateEndpointLocation>();
84  
85  	private List<AlternateEndpoint> alternateEndpoints = new ArrayList<AlternateEndpoint>();
86  
87  	private DataSource registryDataSource;
88  
89  	private DataSource messageDataSource;
90  	
91  	private DataSource nonTransactionalMessageDataSource;
92  	
93  	private DataSource bamDataSource;
94  
95  	private Scheduler exceptionMessagingScheduler;
96  
97  	private PlatformTransactionManager platformTransactionManager;
98  	
99  	private List<Lifecycle> internalLifecycles;
100 	
101 	public KSBConfigurer() {
102 		super(KsbApiConstants.KSB_MODULE_NAME);
103 		setValidRunModes(Arrays.asList(RunMode.THIN, RunMode.REMOTE, RunMode.LOCAL));
104 		this.internalLifecycles = new ArrayList<Lifecycle>();
105 	}
106 	
107 	@Override
108 	public void addAdditonalToConfig() {
109 		configureDataSource();
110 		configureScheduler();
111 		configurePlatformTransactionManager();
112 		configureAlternateEndpoints();
113 	}
114 
115 	@Override
116 	public List<String> getPrimarySpringFiles(){
117 		final List<String> springFileLocations = new ArrayList<String>();
118 
119 		springFileLocations.add(SERVICE_BUS_CLIENT_SPRING);
120 
121         if (getRunMode() != RunMode.THIN) {
122 
123             boolean isJpa = OrmUtils.isJpaEnabled("rice.ksb");
124             if (isJpa) {
125                 // TODO redo this once we're back to JPA
126                 // springFileLocations.add("classpath:org/kuali/rice/ksb/config/KSBJPASpringBeans.xml");
127                 throw new UnsupportedOperationException("JPA not currently supported for KSB");
128             }
129 
130             // Loading these beans unconditionally now, see:
131             // KULRICE-6574: Some KSB beans not defined unless message persistence turned on
132             //
133 		    // if (isMessagePersistenceEnabled()) {
134 			    springFileLocations.add(MESSAGE_CLIENT_SPRING);
135 			    springFileLocations.add(OJB_MESSAGE_CLIENT_SPRING);
136 		    // }
137 
138             if (isBamEnabled()) {
139         	    springFileLocations.add(BAM_SPRING);
140         	    springFileLocations.add(OJB_BAM_SPRING);
141             }
142         }
143 
144         if (getRunMode().equals( RunMode.LOCAL )) {
145         	springFileLocations.add(REGISTRY_SERVER_SPRING);
146         	springFileLocations.add(OJB_REGISTRY_SPRING);
147         }
148         
149         return springFileLocations;
150 	}
151 
152     @Override
153     public boolean hasWebInterface() {
154         return true;
155     }
156 
157     // See KULRICE-7093: KSB Module UI is not available on client applications
158     @Override
159     public boolean shouldRenderWebInterface() {
160         if (ConfigContext.getCurrentContextConfig().getBooleanProperty(KSBConstants.Config.WEB_FORCE_ENABLE)) {
161             return true;
162         }
163         return super.shouldRenderWebInterface();
164     }
165 
166     @Override
167     protected WebModuleConfiguration loadWebModule() {
168         WebModuleConfiguration configuration = super.loadWebModule();
169         configuration.getWebSpringFiles().add(WEB_SPRING);
170         return configuration;
171     }
172 
173     @Override
174 	public Collection<ResourceLoader> getResourceLoadersToRegister() throws Exception{
175 		ResourceLoader ksbRemoteResourceLoader = KSBResourceLoaderFactory.createRootKSBRemoteResourceLoader();
176 		ksbRemoteResourceLoader.start();
177 		return Collections.singletonList(ksbRemoteResourceLoader);
178 	}
179 	
180 	@Override
181 	public List<Lifecycle> loadLifecycles() throws Exception {
182 		List<Lifecycle> lifecycles = new LinkedList<Lifecycle>();
183 		// this validation of our service list needs to happen after we've
184 		// loaded our configs so it's a lifecycle
185 		lifecycles.add(new BaseLifecycle() {
186 
187 			@Override
188 			public void start() throws Exception {
189 				// first check if we want to allow self-signed certificates for SSL communication
190 				if (Boolean.valueOf(ConfigContext.getCurrentContextConfig().getProperty(KSBConstants.Config.KSB_ALLOW_SELF_SIGNED_SSL)).booleanValue()) {
191 				    Protocol.registerProtocol("https", new Protocol("https",
192 					    (ProtocolSocketFactory) new EasySSLProtocolSocketFactory(), 443));
193 				}
194 				super.start();
195 			}
196 		});
197 		return lifecycles;
198 	}
199 	
200     protected void validateServices(List<ServiceDefinition> services) {
201         for (final ServiceDefinition serviceDef : this.services) {
202 			serviceDef.validate();
203 		}
204     }
205 
206     @Override
207     public void onApplicationEvent(ApplicationEvent applicationEvent) {
208         if (applicationEvent instanceof ContextRefreshedEvent) {
209             doAdditionalContextStartedLogic();
210         } else if (applicationEvent instanceof ContextStoppedEvent) {
211             doAdditionalContextStoppedLogic();
212         }
213     }
214 
215 	protected void doAdditionalContextStartedLogic() {
216         validateServices(getServices());
217 		ServicePublisher servicePublisher = new ServicePublisher(getServices());
218 		Lifecycle serviceBus = new ServiceDelegatingLifecycle(KsbApiServiceLocator.SERVICE_BUS);
219 		Lifecycle threadPool = new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.THREAD_POOL_SERVICE);
220 		Lifecycle scheduledThreadPool = new ServiceDelegatingLifecycle(KSBConstants.ServiceNames.SCHEDULED_THREAD_POOL_SERVICE);
221 		
222 		try {
223 			servicePublisher.start();
224 			internalLifecycles.add(servicePublisher);
225 			serviceBus.start();
226 			internalLifecycles.add(serviceBus);
227 			threadPool.start();
228 			internalLifecycles.add(threadPool);
229 			scheduledThreadPool.start();
230 			internalLifecycles.add(scheduledThreadPool);
231 		} catch (Exception e) {
232 			if (e instanceof RuntimeException) {
233 				throw (RuntimeException)e;
234 			}
235 			throw new RiceRuntimeException("Failed to initialize KSB on context startup");
236 		}
237 
238 		requeueMessages();
239 	}
240 
241     protected void doAdditionalContextStoppedLogic() {
242         try {
243             HttpInvokerConnector.shutdownIdleConnectionTimeout();
244         } catch (Exception e) {
245             LOG.error("Failed to shutdown idle connection timeout evictor thread.", e);
246         }
247         cleanUpConfiguration();
248     }
249 
250     @Override
251     public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
252         return true;
253     }
254 
255     @Override
256     public boolean supportsSourceType(Class<?> aClass) {
257         return true;
258     }
259 
260     @Override
261     public int getOrder() {
262         return Ordered.LOWEST_PRECEDENCE;
263     }
264 
265     @Override
266     protected void doAdditionalModuleStartLogic() throws Exception {
267         // this allows us to become aware of remote services, in case the application needs to use any of them during startup
268         LOG.info("Synchronizing remote services with service bus after KSB startup...");
269         long startTime = System.currentTimeMillis();
270         KsbApiServiceLocator.getServiceBus().synchronizeRemoteServices();
271         long endTime = System.currentTimeMillis();
272         LOG.info("...total time to synchronize remote services with service bus after KSB startup: " + (endTime - startTime));
273     }
274 
275     @Override
276 	protected void doAdditionalModuleStopLogic() throws Exception {
277 		for (int index = internalLifecycles.size() - 1; index >= 0; index--) {
278 			try {
279 				internalLifecycles.get(index).stop();
280 			} catch (Exception e) {
281 				LOG.error("Failed to properly execute shutdown logic.", e);
282 			}
283 		}
284 	}
285 
286 	/**
287      * Used to refresh the service registry after the Application Context is initialized.  This way any services that were exported on startup
288      * will be available in the service registry once startup is complete.
289      */
290     private void requeueMessages() {
291         LOG.info("Refreshing Service Registry to export services to the bus.");
292         KsbApiServiceLocator.getServiceBus().synchronizeLocalServices();
293         
294 		//automatically requeue documents sitting with status of 'R'
295 		MessageFetcher messageFetcher = new MessageFetcher((Integer) null);
296 		KSBServiceLocator.getThreadPool().execute(messageFetcher);
297     }
298     
299     protected boolean isMessagePersistenceEnabled() {
300     	return ConfigContext.getCurrentContextConfig().getBooleanProperty(KSBConstants.Config.MESSAGE_PERSISTENCE, true);
301     }
302     
303     protected boolean isBamEnabled() {
304     	return ConfigContext.getCurrentContextConfig().getBooleanProperty(Config.BAM_ENABLED, false);
305     }
306 
307 	protected void configureScheduler() {
308 		if (this.getExceptionMessagingScheduler() != null) {
309 			LOG.info("Configuring injected exception messaging Scheduler");
310 			ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.INJECTED_EXCEPTION_MESSAGE_SCHEDULER_KEY, this.getExceptionMessagingScheduler());
311 		}
312 	}
313 
314 	protected void configureDataSource() {
315 		if (isMessagePersistenceEnabled()) {
316 			if (getMessageDataSource() != null) {
317 				ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_MESSAGE_DATASOURCE, getMessageDataSource());
318 			}
319 			if (getNonTransactionalMessageDataSource() != null) {
320 	            ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_MESSAGE_NON_TRANSACTIONAL_DATASOURCE, getNonTransactionalMessageDataSource());
321 			}
322 		}
323         if (getRunMode().equals(RunMode.LOCAL)) {
324         	if (getRegistryDataSource() != null) {
325                 ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_REGISTRY_DATASOURCE, getRegistryDataSource());
326             }
327         }
328         if (isBamEnabled()) {
329         	if (getBamDataSource() != null) {
330         		ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_BAM_DATASOURCE, getBamDataSource());
331         	}
332         }
333     }
334 
335 	protected void configurePlatformTransactionManager() {
336 		if (getPlatformTransactionManager() == null) {
337 			return;
338 		}
339 		ConfigContext.getCurrentContextConfig().putObject(RiceConstants.SPRING_TRANSACTION_MANAGER, getPlatformTransactionManager());
340 	}
341 	
342 	protected void configureAlternateEndpoints() {
343 		ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINT_LOCATIONS, getAlternateEndpointLocations());
344 		ConfigContext.getCurrentContextConfig().putObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINTS, getAlternateEndpoints());
345 	}
346 
347 	/**
348      * Because our configuration is global, shutting down Rice does not get rid of objects stored there.  For that reason
349      * we need to manually clean these up.  This is most important in the case of the service bus because the configuration
350      * is used to store services to be exported.  If we don't clean this up then a shutdown/startup within the same
351      * class loading context causes the service list to be doubled and results in "multiple endpoint" error messages.
352      *
353      */
354     protected void cleanUpConfiguration() {
355         ConfigContext.getCurrentContextConfig().removeObject(KSBConstants.Config.KSB_ALTERNATE_ENDPOINTS);
356     }
357 
358 	public List<ServiceDefinition> getServices() {
359 		return this.services;
360 	}
361 
362 	public void setServices(List<ServiceDefinition> javaServices) {
363 		this.services = javaServices;
364 	}
365 
366 	public DataSource getMessageDataSource() {
367 		return this.messageDataSource;
368 	}
369 
370 	public void setMessageDataSource(DataSource messageDataSource) {
371 		this.messageDataSource = messageDataSource;
372 	}
373 
374     public DataSource getNonTransactionalMessageDataSource() {
375         return this.nonTransactionalMessageDataSource;
376     }
377 
378     public void setNonTransactionalMessageDataSource(DataSource nonTransactionalMessageDataSource) {
379         this.nonTransactionalMessageDataSource = nonTransactionalMessageDataSource;
380     }
381 
382     public DataSource getRegistryDataSource() {
383 		return this.registryDataSource;
384 	}
385 
386 	public void setRegistryDataSource(DataSource registryDataSource) {
387 		this.registryDataSource = registryDataSource;
388 	}
389 	
390 	public DataSource getBamDataSource() {
391 		return this.bamDataSource;
392 	}
393 
394 	public void setBamDataSource(DataSource bamDataSource) {
395 		this.bamDataSource = bamDataSource;
396 	}
397 
398 	public Scheduler getExceptionMessagingScheduler() {
399 		return this.exceptionMessagingScheduler;
400 	}
401 
402 	public void setExceptionMessagingScheduler(Scheduler exceptionMessagingScheduler) {
403 		this.exceptionMessagingScheduler = exceptionMessagingScheduler;
404 	}
405 
406 	public PlatformTransactionManager getPlatformTransactionManager() {
407 		return platformTransactionManager;
408 	}
409 
410 	public void setPlatformTransactionManager(PlatformTransactionManager springTransactionManager) {
411 		this.platformTransactionManager = springTransactionManager;
412 	}
413 
414     public List<AlternateEndpointLocation> getAlternateEndpointLocations() {
415 	    return this.alternateEndpointLocations;
416     }
417 
418     public void setAlternateEndpointLocations(List<AlternateEndpointLocation> alternateEndpointLocations) {
419 	    this.alternateEndpointLocations = alternateEndpointLocations;
420 	}
421 
422     public List<AlternateEndpoint> getAlternateEndpoints() {
423         return this.alternateEndpoints;
424     }
425 
426     public void setAlternateEndpoints(List<AlternateEndpoint> alternateEndpoints) {
427         this.alternateEndpoints = alternateEndpoints;
428     }
429     
430     private final class ServicePublisher extends BaseLifecycle {
431 
432     	private final List<ServiceDefinition> serviceDefinitions;
433     	
434     	ServicePublisher(List<ServiceDefinition> serviceDefinitions) {
435     		this.serviceDefinitions = serviceDefinitions;
436     	}
437     	
438 		@Override
439 		public void start() throws Exception {
440 			if (serviceDefinitions != null && !serviceDefinitions.isEmpty()) {
441 				LOG.debug("Configuring " + serviceDefinitions.size() + " services for application id " + CoreConfigHelper.getApplicationId() + " using config for classloader " + ClassLoaderUtils.getDefaultClassLoader());
442 				KsbApiServiceLocator.getServiceBus().publishServices(serviceDefinitions, true);
443 				super.start();
444 			}
445 		}
446     	
447     }
448     
449 }