View Javadoc

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