View Javadoc

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