001/* 002 * Copyright 2007 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 */ 016package org.kuali.ole.sys.context; 017 018import java.io.File; 019import java.io.IOException; 020import java.text.DateFormat; 021import java.text.SimpleDateFormat; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.Comparator; 026import java.util.Date; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033import javax.xml.namespace.QName; 034 035import org.apache.commons.io.FileUtils; 036import org.apache.commons.lang.StringUtils; 037import org.apache.commons.lang.math.NumberUtils; 038import org.apache.log4j.Logger; 039import org.kuali.ole.batch.service.OLEBatchSchedulerService; 040import org.kuali.ole.sys.MemoryMonitor; 041import org.kuali.ole.sys.OLEConstants; 042import org.kuali.ole.sys.batch.Step; 043import org.kuali.ole.sys.batch.service.SchedulerService; 044import org.kuali.rice.core.api.config.property.ConfigurationService; 045import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 046import org.kuali.rice.core.framework.resourceloader.SpringResourceLoader; 047import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator; 048import org.kuali.rice.coreservice.api.component.Component; 049import org.kuali.rice.krad.service.*; 050import org.quartz.Scheduler; 051import org.quartz.SchedulerException; 052import org.springframework.aop.support.AopUtils; 053import org.springframework.beans.factory.NoSuchBeanDefinitionException; 054import org.springframework.context.ConfigurableApplicationContext; 055 056@SuppressWarnings("deprecation") 057public class SpringContext { 058 private static final Logger LOG = Logger.getLogger(SpringContext.class); 059 protected static final String MEMORY_MONITOR_THRESHOLD_KEY = "memory.monitor.threshold"; 060 protected static final String USE_QUARTZ_SCHEDULING_KEY = "use.quartz.scheduling"; 061 protected static final String OLE_BATCH_STEP_COMPONENT_SET_ID = "STEP:OLE"; 062 protected static ConfigurableApplicationContext applicationContext; 063 protected static Set<Class<? extends Object>> SINGLETON_TYPES = new HashSet<Class<? extends Object>>(); 064 protected static Map<Class<? extends Object>, Object> SINGLETON_BEANS_BY_TYPE_CACHE = new HashMap<Class<? extends Object>, Object>(); 065 protected static Map<String, Object> SINGLETON_BEANS_BY_NAME_CACHE = new HashMap<String, Object>(); 066 @SuppressWarnings("rawtypes") 067 protected static Map<Class<? extends Object>, Map> SINGLETON_BEANS_OF_TYPE_CACHE = new HashMap<Class<? extends Object>, Map>(); 068 protected static Thread processWatchThread = null; 069 protected static MemoryMonitor memoryMonitor; 070 /** 071 * Use this method to retrieve a service which may or may not be implemented locally. (That is, defined in the main Spring 072 * ApplicationContext created by Rice. 073 */ 074 public static Object getService(String serviceName) { 075 return GlobalResourceLoader.getService(serviceName); 076 } 077 078 /** 079 * Use this method to retrieve a spring bean when one of the following is the case. Pass in the type of the service interface, 080 * NOT the service implementation. 1. there is only one bean of the specified type in our spring context 2. there is only one 081 * bean of the specified type in our spring context, but you want the one whose bean id is the same as type.getSimpleName() with 082 * the exception of the first letter being lower case in the former and upper case in the latter, For example, there are two 083 * beans of type DateTimeService in our context dateTimeService and testDateTimeService. To retrieve the former, you should 084 * specific DateTimeService.class as the type. To retrieve the latter, you should specify ConfigurableDateService.class as the 085 * type. Unless you are writing a unit test and need to down cast to an implementation, you do not need to cast the result of 086 * this method. 087 * 088 * @param <T> 089 * @param type 090 * @return an object that has been defined as a bean in our spring context and is of the specified type 091 */ 092 public static <T> T getBean(Class<T> type) { 093 verifyProperInitialization(); 094 T bean = null; 095 if (SINGLETON_BEANS_BY_TYPE_CACHE.containsKey(type)) { 096 bean = (T) SINGLETON_BEANS_BY_TYPE_CACHE.get(type); 097 } 098 else { 099 if ( LOG.isDebugEnabled() ) { 100 LOG.debug("Bean not already in cache: " + type + " - calling getBeansOfType() "); 101 } 102 Collection<T> beansOfType = getBeansOfType(type).values(); 103 if ( !beansOfType.isEmpty() ) { 104 if (beansOfType.size() > 1) { 105 bean = getBean(type, StringUtils.uncapitalize(type.getSimpleName()) ); 106 } 107 else { 108 bean = beansOfType.iterator().next(); 109 } 110 } 111 else { 112 try { 113 bean = getBean(type, StringUtils.uncapitalize(type.getSimpleName()) ); 114 } 115 catch (Exception ex) { 116 // do nothing, let fall through 117 } 118 if ( bean == null ) { // unable to find bean - check GRL 119 // this is needed in case no beans of the given type exist locally 120 if ( LOG.isDebugEnabled() ) { 121 LOG.debug("Bean not found in local context: " + type.getName() + " - calling GRL"); 122 } 123 Object remoteServiceBean = getService( StringUtils.uncapitalize(type.getSimpleName()) ); 124 if ( remoteServiceBean != null ) { 125 if ( type.isAssignableFrom( remoteServiceBean.getClass() ) ) { 126 bean = (T)remoteServiceBean; 127 } 128 } 129 } 130 } 131 if ( bean != null ) { 132 synchronized( SINGLETON_TYPES ) { 133 if (SINGLETON_TYPES.contains(type) || hasSingletonSuperType(type,SINGLETON_TYPES)) { 134 SINGLETON_TYPES.add(type); 135 SINGLETON_BEANS_BY_TYPE_CACHE.put(type, bean); 136 } 137 } 138 } 139 else { 140 throw new RuntimeException( "Request for non-existent bean. Unable to find in local context or on the GRL: " + type.getName() ); 141 } 142 } 143 return bean; 144 } 145 146 /** 147 * Use this method to retrieve all beans of a give type in our spring context. Pass in the type of the service interface, NOT 148 * the service implementation. 149 * 150 * @param <T> 151 * @param type 152 * @return a map of the spring bean ids / beans that are of the specified type 153 */ 154 public static <T> Map<String, T> getBeansOfType(Class<T> type) { 155 verifyProperInitialization(); 156 Map<String, T> beansOfType = null; 157 if (SINGLETON_BEANS_OF_TYPE_CACHE.containsKey(type)) { 158 beansOfType = SINGLETON_BEANS_OF_TYPE_CACHE.get(type); 159 } 160 else { 161 if ( LOG.isDebugEnabled() ) { 162 LOG.debug("Bean not already in \"OF_TYPE\" cache: " + type + " - calling getBeansOfType() on Spring context"); 163 } 164 boolean allOfTypeAreSingleton = true; 165 beansOfType = applicationContext.getBeansOfType(type); 166 for ( String key : beansOfType.keySet() ) { 167 if ( !applicationContext.isSingleton(key) ) { 168 allOfTypeAreSingleton = false; 169 } 170 } 171 if ( allOfTypeAreSingleton ) { 172 synchronized( SINGLETON_TYPES ) { 173 SINGLETON_TYPES.add(type); 174 SINGLETON_BEANS_OF_TYPE_CACHE.put(type, beansOfType); 175 } 176 } 177 } 178 return beansOfType; 179 } 180 181 public static <T> T getBean(Class<T> type, String name) { 182 T bean = null; 183 if (SINGLETON_BEANS_BY_NAME_CACHE.containsKey(name)) { 184 bean = (T) SINGLETON_BEANS_BY_NAME_CACHE.get(name); 185 } 186 else { 187 try { 188 bean = (T) applicationContext.getBean(name); 189 if ( applicationContext.isSingleton(name) ) { 190 synchronized( SINGLETON_BEANS_BY_NAME_CACHE ) { 191 SINGLETON_BEANS_BY_NAME_CACHE.put(name, bean); 192 } 193 } 194 } 195 catch (NoSuchBeanDefinitionException nsbde) { 196 if ( LOG.isDebugEnabled() ) { 197 LOG.debug("Bean with name and type not found in local context: " + name + "/" + type.getName() + " - calling GRL"); 198 } 199 Object remoteServiceBean = getService( name ); 200 if ( remoteServiceBean != null ) { 201 if ( type.isAssignableFrom( AopUtils.getTargetClass(remoteServiceBean) ) ) { 202 bean = (T)remoteServiceBean; 203 // assume remote beans are services and thus singletons 204 synchronized( SINGLETON_BEANS_BY_NAME_CACHE ) { 205 SINGLETON_BEANS_BY_NAME_CACHE.put(name, bean); 206 } 207 } 208 } 209 if (bean == null) { 210 throw new RuntimeException("No bean of this type and name exist in the application context or from the GRL: " + type.getName() + ", " + name); 211 } 212 } 213 } 214 return bean; 215 } 216 217 private static boolean hasSingletonSuperType(Class<? extends Object> type, Set<Class<? extends Object>> knownSingletonTypes ) { 218 for (Class<? extends Object> singletonType : knownSingletonTypes) { 219 if (singletonType.isAssignableFrom(type)) { 220 return true; 221 } 222 } 223 return false; 224 } 225 226 public static Object getBean(String beanName) { 227 return getBean(Object.class, beanName); 228 } 229 230 protected static String[] getBeanNames() { 231 verifyProperInitialization(); 232 return applicationContext.getBeanDefinitionNames(); 233 } 234 235 protected static void close() { 236 if ( processWatchThread != null ) { 237 LOG.info("Shutting down the ProcessWatch thread" ); 238 if ( processWatchThread.isAlive() ) { 239 processWatchThread.stop(); 240 } 241 processWatchThread = null; 242 } 243 244 try { 245 if ( isInitialized() && getBean(Scheduler.class) != null ) { 246 if ( getBean(Scheduler.class).isStarted() ) { 247 LOG.info( "Shutting Down scheduler" ); 248 getBean(Scheduler.class).shutdown(); 249 } 250 } 251 } catch (SchedulerException ex) { 252 LOG.error( "Exception while shutting down the scheduler", ex ); 253 } 254 LOG.info( "Stopping the MemoryMonitor thread" ); 255 if ( memoryMonitor != null ) { 256 memoryMonitor.stop(); 257 } 258 } 259 260 public static boolean isInitialized() { 261 return applicationContext != null; 262 } 263 264 private static void verifyProperInitialization() { 265 if (!isInitialized()) { 266 LOG.fatal( "*****************************************************************" ); 267 LOG.fatal( "*****************************************************************" ); 268 LOG.fatal( "*****************************************************************" ); 269 LOG.fatal( "*****************************************************************" ); 270 LOG.fatal( "*****************************************************************" ); 271 LOG.fatal( "Spring not initialized properly. Initialization has begun and the application context is null. Probably spring loaded bean is trying to use SpringContext.getBean() before the application context is initialized.", new IllegalStateException() ); 272 LOG.fatal( "*****************************************************************" ); 273 LOG.fatal( "*****************************************************************" ); 274 LOG.fatal( "*****************************************************************" ); 275 LOG.fatal( "*****************************************************************" ); 276 LOG.fatal( "*****************************************************************" ); 277 throw new IllegalStateException("Spring not initialized properly. Initialization has begun and the application context is null. Probably spring loaded bean is trying to use SpringContext.getBean() before the application context is initialized."); 278 } 279 } 280 281 static void initMemoryMonitor() { 282 ConfigurationService configurationService = GlobalResourceLoader.getService("kualiConfigurationService"); 283 if ( NumberUtils.isNumber(configurationService.getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY))) { 284 if (Double.valueOf(configurationService.getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY)) > 0) { 285 LOG.info( "Starting up MemoryMonitor thread" ); 286 MemoryMonitor.setPercentageUsageThreshold(Double.valueOf(configurationService.getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY))); 287 memoryMonitor = new MemoryMonitor("OLE Memory Monitor: Over " + configurationService.getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY) + "% Memory Used"); 288 memoryMonitor.addListener(new MemoryMonitor.Listener() { 289 org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MemoryMonitor.class); 290 291 @Override 292 public void memoryUsageLow(String springContextId, Map<String, String> memoryUsageStatistics, String deadlockedThreadIds) { 293 StringBuilder logStatement = new StringBuilder(springContextId).append("\n\tMemory Usage"); 294 for (String memoryType : memoryUsageStatistics.keySet()) { 295 logStatement.append("\n\t\t").append(memoryType.toUpperCase()).append(": ").append(memoryUsageStatistics.get(memoryType)); 296 } 297 logStatement.append("\n\tLocked Thread Ids: ").append(deadlockedThreadIds).append("\n\tThread Stacks"); 298 for (Map.Entry<Thread, StackTraceElement[]> threadStackTrace : Thread.getAllStackTraces().entrySet()) { 299 logStatement.append("\n\t\tThread: name=").append(threadStackTrace.getKey().getName()).append(", id=").append(threadStackTrace.getKey().getId()).append(", priority=").append(threadStackTrace.getKey().getPriority()).append(", state=").append(threadStackTrace.getKey().getState()); 300 for (StackTraceElement stackTraceElement : threadStackTrace.getValue()) { 301 logStatement.append("\n\t\t\t").append(stackTraceElement); 302 } 303 } 304 LOG.warn(logStatement); 305 MemoryMonitor.setPercentageUsageThreshold(0.95); 306 } 307 }); 308 } 309 } else { 310 LOG.warn( MEMORY_MONITOR_THRESHOLD_KEY + " is not a number: " + configurationService.getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY) ); 311 } 312 } 313 314 static void initMonitoringThread() { 315 ConfigurationService configurationService = GlobalResourceLoader.getService("kualiConfigurationService"); 316 if ( configurationService.getPropertyValueAsBoolean("periodic.thread.dump") ) { 317 final long sleepPeriod = Long.parseLong(configurationService.getPropertyValueAsString("periodic.thread.dump.seconds") ) * 1000; 318 final File logDir = new File(configurationService.getPropertyValueAsString("logs.directory") ); 319 final File monitoringLogDir = new File( logDir, "monitoring" ); 320 if ( !monitoringLogDir.exists() ) { 321 monitoringLogDir.mkdir(); 322 } 323 if ( LOG.isInfoEnabled() ) { 324 LOG.info( "Starting the Periodic Thread Dump thread - dumping every " + (sleepPeriod/1000) + " seconds"); 325 LOG.info( "Periodic Thread Dump Logs: " + monitoringLogDir.getAbsolutePath() ); 326 } 327 final DateFormat df = new SimpleDateFormat( "yyyyMMdd" ); 328 final DateFormat tf = new SimpleDateFormat( "HH-mm-ss" ); 329 Runnable processWatch = new Runnable() { 330 @Override 331 public void run() { 332 while ( true ) { 333 Date now = new Date(); 334 File todaysLogDir = new File( monitoringLogDir, df.format(now) ); 335 if ( !todaysLogDir.exists() ) { 336 todaysLogDir.mkdir(); 337 } 338 File logFile = new File( todaysLogDir, "process-"+tf.format(now)+".log" ); 339 try { 340 StringBuilder logStatement = new StringBuilder(10240); 341 logStatement.append("Threads Running at: " ).append( now ).append( "\n\n\n" ); 342 Map<Thread,StackTraceElement[]> threads = Thread.getAllStackTraces(); 343 List<Thread> sortedThreads = new ArrayList<Thread>( threads.keySet() ); 344 Collections.sort( sortedThreads, new Comparator<Thread>() { 345 @Override 346 public int compare(Thread o1, Thread o2) { 347 return o1.getName().compareTo( o2.getName() ); 348 } 349 }); 350 for ( Thread t : sortedThreads ) { 351 logStatement.append("\tThread: name=").append(t.getName()).append(", id=").append(t.getId()).append(", priority=").append(t.getPriority()).append(", state=").append(t.getState()); 352 logStatement.append('\n'); 353 for (StackTraceElement stackTraceElement : threads.get(t) ) { 354 logStatement.append("\t\t" + stackTraceElement).append( '\n' ); 355 } 356 logStatement.append('\n'); 357 } 358 FileUtils.writeStringToFile(logFile, logStatement.toString(), "UTF-8"); 359 } 360 catch (IOException ex) { 361 LOG.error( "Unable to write the ProcessWatch output file: " + logFile.getAbsolutePath(), ex ); 362 } 363 try { 364 Thread.sleep( sleepPeriod ); 365 } 366 catch (InterruptedException ex) { 367 LOG.error( "woken up during sleep of the ProcessWatch thread", ex ); 368 } 369 } 370 } 371 }; 372 processWatchThread = new Thread( processWatch, "ProcessWatch thread" ); 373 processWatchThread.setDaemon(true); 374 processWatchThread.start(); 375 } 376 } 377 378 static void initScheduler() { 379 ConfigurationService configurationService = GlobalResourceLoader.getService("kualiConfigurationService"); 380 if (configurationService.getPropertyValueAsBoolean(USE_QUARTZ_SCHEDULING_KEY)) { 381 try { 382 LOG.info("Attempting to initialize the SchedulerService"); 383 getBean(SchedulerService.class).initialize(); 384 getBean(OLEBatchSchedulerService.class).initialize(); 385 LOG.info("Starting the Quartz scheduler"); 386 getBean(Scheduler.class).start(); 387 } catch (NoSuchBeanDefinitionException e) { 388 LOG.warn("Not initializing the scheduler because there is no scheduler bean"); 389 } catch ( Exception ex ) { 390 LOG.error("Caught Exception while starting the scheduler", ex); 391 } 392 } 393 } 394 395 public static void registerSingletonBean(String beanId, Object bean) { 396 applicationContext.getBeanFactory().registerSingleton(beanId, bean); 397 //Cleaning caches 398 SINGLETON_BEANS_BY_NAME_CACHE.clear(); 399 SINGLETON_BEANS_BY_TYPE_CACHE.clear(); 400 SINGLETON_BEANS_OF_TYPE_CACHE.clear(); 401 } 402 403 public static void finishInitializationAfterRiceStartup() { 404 if ( LOG.isTraceEnabled() ) { 405 GlobalResourceLoader.logAllContents(); 406 } 407 DataDictionaryComponentPublisherService dataDictionaryComponentPublisherService= GlobalResourceLoader.getService("dataDictionaryComponentPublisherService"); 408 SpringResourceLoader mainKfsSpringResourceLoader = (SpringResourceLoader)GlobalResourceLoader.getResourceLoader( new QName("OLE", "OLE_RICE_SPRING_RESOURCE_LOADER_NAME") ); 409 SpringContext.applicationContext = mainKfsSpringResourceLoader.getContext(); 410 411 // KFS addition - republish all components now - until this point, the KFS DD has not been loaded 412 dataDictionaryComponentPublisherService.publishAllComponents(); 413 414 // KFS addition - we also publish all our Step classes as components - and these are not in the 415 // DD so are not published by the command above 416 publishBatchStepComponents(); 417 } 418 419 public static void publishBatchStepComponents() { 420 Map<String,Step> steps = SpringContext.getBeansOfType(Step.class); 421 List<Component> stepComponents = new ArrayList<Component>( steps.size() ); 422 for ( Step step : steps.values() ) { 423 Step unproxiedStep = (Step) ProxyUtils.getTargetIfProxied(step); 424 String namespaceCode = OLEConstants.CoreModuleNamespaces.OLE; 425 if ( LOG.isDebugEnabled() ) { 426 LOG.debug( "Building component for step: " + unproxiedStep.getName() + "(" + unproxiedStep.getClass() + ")" ); 427 } 428 ModuleService moduleService = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(unproxiedStep.getClass()); 429 if ( moduleService != null ) { 430 namespaceCode = moduleService.getModuleConfiguration().getNamespaceCode(); 431 } 432 Component.Builder component = Component.Builder.create(namespaceCode, unproxiedStep.getClass().getSimpleName(), unproxiedStep.getClass().getSimpleName()); 433 component.setComponentSetId(OLE_BATCH_STEP_COMPONENT_SET_ID); 434 component.setActive(true); 435 stepComponents.add(component.build()); 436 } 437 438 CoreServiceApiServiceLocator.getComponentService().publishDerivedComponents(OLE_BATCH_STEP_COMPONENT_SET_ID, stepComponents); 439 } 440 441 public static ConfigurableApplicationContext getApplicationContext() { 442 return applicationContext; 443 } 444 445 public static void setApplicationContext(ConfigurableApplicationContext applicationContext) { 446 SpringContext.applicationContext = applicationContext; 447 } 448}