001 /* 002 * Copyright 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 package org.kuali.kfs.sys.context; 017 018 import java.io.BufferedWriter; 019 import java.io.File; 020 import java.io.FileWriter; 021 import java.io.IOException; 022 import java.text.DateFormat; 023 import java.text.SimpleDateFormat; 024 import java.util.ArrayList; 025 import java.util.Collection; 026 import java.util.Collections; 027 import java.util.Comparator; 028 import java.util.Date; 029 import java.util.HashMap; 030 import java.util.HashSet; 031 import java.util.List; 032 import java.util.Map; 033 import java.util.Set; 034 035 import org.apache.commons.lang.StringUtils; 036 import org.apache.log4j.Logger; 037 import org.kuali.kfs.sys.MemoryMonitor; 038 import org.kuali.kfs.sys.batch.service.SchedulerService; 039 import org.kuali.rice.core.config.ConfigContext; 040 import org.kuali.rice.core.config.RiceConfigurer; 041 import org.kuali.rice.core.resourceloader.GlobalResourceLoader; 042 import org.kuali.rice.core.resourceloader.RiceResourceLoaderFactory; 043 import org.kuali.rice.core.util.RiceConstants; 044 import org.kuali.rice.kns.service.KualiConfigurationService; 045 import org.kuali.rice.kns.util.cache.MethodCacheInterceptor; 046 import org.kuali.rice.kns.util.spring.ClassPathXmlApplicationContext; 047 import org.quartz.Scheduler; 048 import org.quartz.SchedulerException; 049 import org.springframework.aop.support.AopUtils; 050 import org.springframework.beans.factory.NoSuchBeanDefinitionException; 051 import org.springframework.context.ConfigurableApplicationContext; 052 053 import uk.ltd.getahead.dwr.create.SpringCreator; 054 055 @SuppressWarnings("deprecation") 056 public class SpringContext { 057 protected static final Logger LOG = Logger.getLogger(SpringContext.class); 058 protected static final String APPLICATION_CONTEXT_DEFINITION = "spring-rice-startup.xml"; 059 protected static final String TEST_CONTEXT_DEFINITION = "spring-rice-startup-test.xml"; 060 protected static final String MEMORY_MONITOR_THRESHOLD_KEY = "memory.monitor.threshold"; 061 protected static final String USE_QUARTZ_SCHEDULING_KEY = "use.quartz.scheduling"; 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 protected static Map<Class<? extends Object>, Map> SINGLETON_BEANS_OF_TYPE_CACHE = new HashMap<Class<? extends Object>, Map>(); 067 protected static Thread processWatchThread = null; 068 /** 069 * Use this method to retrieve a service which may or may not be implemented locally. (That is, 070 * defined in the main Spring ApplicationContext created by Rice. 071 */ 072 public static Object getService(String serviceName) { 073 return GlobalResourceLoader.getService(serviceName); 074 } 075 076 /** 077 * Use this method to retrieve a spring bean when one of the following is the case. Pass in the type of the service interface, 078 * NOT the service implementation. 1. there is only one bean of the specified type in our spring context 2. there is only one 079 * bean of the specified type in our spring context, but you want the one whose bean id is the same as type.getSimpleName() with 080 * the exception of the first letter being lower case in the former and upper case in the latter, For example, there are two 081 * beans of type DateTimeService in our context � dateTimeService and testDateTimeService. To retrieve the former, you should 082 * specific DateTimeService.class as the type. To retrieve the latter, you should specify ConfigurableDateService.class as the 083 * 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 084 * this method. 085 * 086 * @param <T> 087 * @param type 088 * @return an object that has been defined as a bean in our spring context and is of the specified type 089 */ 090 public static <T> T getBean(Class<T> type) { 091 verifyProperInitialization(); 092 T bean = null; 093 if (SINGLETON_BEANS_BY_TYPE_CACHE.containsKey(type)) { 094 bean = (T) SINGLETON_BEANS_BY_TYPE_CACHE.get(type); 095 } else { 096 if ( LOG.isDebugEnabled() ) { 097 LOG.debug("Bean not already in cache: " + type + " - calling getBeansOfType() "); 098 } 099 Collection<T> beansOfType = getBeansOfType(type).values(); 100 if ( !beansOfType.isEmpty() ) { 101 if (beansOfType.size() > 1) { 102 bean = getBean(type, StringUtils.uncapitalize(type.getSimpleName()) ); 103 } else { 104 bean = beansOfType.iterator().next(); 105 } 106 } else { 107 try { 108 bean = getBean(type, StringUtils.uncapitalize(type.getSimpleName()) ); 109 } catch ( Exception ex ) { 110 // do nothing, let fall through 111 } 112 if ( bean == null ) { // unable to find bean - check GRL 113 // this is needed in case no beans of the given type exist locally 114 if ( LOG.isDebugEnabled() ) { 115 LOG.debug("Bean not found in local context: " + type.getName() + " - calling GRL"); 116 } 117 Object remoteServiceBean = getService( StringUtils.uncapitalize(type.getSimpleName()) ); 118 if ( remoteServiceBean != null ) { 119 if ( type.isAssignableFrom( remoteServiceBean.getClass() ) ) { 120 bean = (T)remoteServiceBean; 121 } 122 } 123 } 124 } 125 if ( bean != null ) { 126 synchronized( SINGLETON_TYPES ) { 127 if (SINGLETON_TYPES.contains(type) || hasSingletonSuperType(type,SINGLETON_TYPES)) { 128 SINGLETON_TYPES.add(type); 129 SINGLETON_BEANS_BY_TYPE_CACHE.put(type, bean); 130 } 131 } 132 } else { 133 throw new RuntimeException( "Request for non-existent bean. Unable to find in local context or on the GRL: " + type.getName() ); 134 } 135 } 136 return bean; 137 } 138 139 /** 140 * Use this method to retrieve all beans of a give type in our spring context. Pass in the type of the service interface, NOT 141 * the service implementation. 142 * 143 * @param <T> 144 * @param type 145 * @return a map of the spring bean ids / beans that are of the specified type 146 */ 147 public static <T> Map<String, T> getBeansOfType(Class<T> type) { 148 verifyProperInitialization(); 149 Map<String, T> beansOfType = null; 150 if (SINGLETON_BEANS_OF_TYPE_CACHE.containsKey(type)) { 151 beansOfType = SINGLETON_BEANS_OF_TYPE_CACHE.get(type); 152 } 153 else { 154 if ( LOG.isDebugEnabled() ) { 155 LOG.debug("Bean not already in \"OF_TYPE\" cache: " + type + " - calling getBeansOfType() on Spring context"); 156 } 157 boolean allOfTypeAreSingleton = true; 158 beansOfType = applicationContext.getBeansOfType(type); 159 for ( String key : beansOfType.keySet() ) { 160 if ( !applicationContext.isSingleton(key) ) { 161 allOfTypeAreSingleton = false; 162 } 163 } 164 if ( allOfTypeAreSingleton ) { 165 synchronized( SINGLETON_TYPES ) { 166 SINGLETON_TYPES.add(type); 167 SINGLETON_BEANS_OF_TYPE_CACHE.put(type, beansOfType); 168 } 169 } 170 } 171 return beansOfType; 172 } 173 174 public static <T> T getBean(Class<T> type, String name) { 175 T bean = null; 176 if (SINGLETON_BEANS_BY_NAME_CACHE.containsKey(name)) { 177 bean = (T) SINGLETON_BEANS_BY_NAME_CACHE.get(name); 178 } else { 179 try { 180 bean = (T) applicationContext.getBean(name); 181 if ( applicationContext.isSingleton(name) ) { 182 synchronized( SINGLETON_BEANS_BY_NAME_CACHE ) { 183 SINGLETON_BEANS_BY_NAME_CACHE.put(name, bean); 184 } 185 } 186 } 187 catch (NoSuchBeanDefinitionException nsbde) { 188 if ( LOG.isDebugEnabled() ) { 189 LOG.debug("Bean with name and type not found in local context: " + name + "/" + type.getName() + " - calling GRL"); 190 } 191 Object remoteServiceBean = getService( name ); 192 if ( remoteServiceBean != null ) { 193 if ( type.isAssignableFrom( AopUtils.getTargetClass(remoteServiceBean) ) ) { 194 bean = (T)remoteServiceBean; 195 // assume remote beans are services and thus singletons 196 synchronized( SINGLETON_BEANS_BY_NAME_CACHE ) { 197 SINGLETON_BEANS_BY_NAME_CACHE.put(name, bean); 198 } 199 } 200 } 201 throw new RuntimeException("No bean of this type and name exist in the application context or from the GRL: " + type.getName() + ", " + name); 202 } 203 } 204 return bean; 205 } 206 207 private static boolean hasSingletonSuperType(Class<? extends Object> type, Set<Class<? extends Object>> knownSingletonTypes ) { 208 for (Class<? extends Object> singletonType : knownSingletonTypes) { 209 if (singletonType.isAssignableFrom(type)) { 210 return true; 211 } 212 } 213 return false; 214 } 215 216 public static List<MethodCacheInterceptor> getMethodCacheInterceptors() { 217 List<MethodCacheInterceptor> methodCacheInterceptors = new ArrayList<MethodCacheInterceptor>(); 218 methodCacheInterceptors.add(getBean(MethodCacheInterceptor.class)); 219 return methodCacheInterceptors; 220 } 221 222 protected static Object getBean(String beanName) { 223 return getBean(Object.class, beanName); 224 } 225 226 protected static String[] getBeanNames() { 227 verifyProperInitialization(); 228 return applicationContext.getBeanDefinitionNames(); 229 } 230 231 protected static void initializeApplicationContext() { 232 initializeApplicationContext(APPLICATION_CONTEXT_DEFINITION, true); 233 } 234 235 protected static void initializeApplicationContextWithoutSchedule() { 236 initializeApplicationContext(APPLICATION_CONTEXT_DEFINITION, false); 237 } 238 239 protected static void initializeBatchApplicationContext() { 240 initializeApplicationContext(APPLICATION_CONTEXT_DEFINITION, true); 241 } 242 243 protected static void initializeTestApplicationContext() { 244 initializeApplicationContext(TEST_CONTEXT_DEFINITION, false); 245 } 246 247 protected static void close() throws Exception { 248 if ( processWatchThread != null ) { 249 if ( processWatchThread.isAlive() ) { 250 processWatchThread.stop(); 251 } 252 processWatchThread = null; 253 } 254 if ( applicationContext == null ) { 255 applicationContext = RiceResourceLoaderFactory.getSpringResourceLoader().getContext(); 256 } 257 RiceConfigurer riceConfigurer = null; 258 try { 259 riceConfigurer = (RiceConfigurer) applicationContext.getBean( "rice" ); 260 } catch ( Exception ex ) { 261 LOG.debug( "Unable to get 'rice' bean - attempting to get from the Rice ConfigContext", ex ); 262 riceConfigurer = (RiceConfigurer)ConfigContext.getObjectFromConfigHierarchy(RiceConstants.RICE_CONFIGURER_CONFIG_NAME); 263 } 264 applicationContext = null; 265 if ( riceConfigurer != null ) { 266 riceConfigurer.destroy(); 267 } else { 268 LOG.error( "Unable to close SpringContext - unable to get a handle to a RiceConfigurer object." ); 269 } 270 ConfigContext.destroy(); 271 PropertyLoadingFactoryBean.clear(); 272 } 273 274 private static void verifyProperInitialization() { 275 if (applicationContext == null) { 276 throw new RuntimeException("Spring not initialized properly. Initialization has begun and the application context is null. Probably spring loaded bean is trying to use KNSServiceLocator before the application context is initialized."); 277 } 278 } 279 280 private static void initializeApplicationContext( String riceInitializationSpringFile, boolean initializeSchedule ) { 281 LOG.info( "Starting Spring context initialization" ); 282 // use the base config file to bootstrap the real application context started by Rice 283 new ClassPathXmlApplicationContext(riceInitializationSpringFile); 284 // pull the Rice application context into here for further use and efficiency 285 applicationContext = RiceResourceLoaderFactory.getSpringResourceLoader().getContext(); 286 LOG.info( "Completed Spring context initialization" ); 287 288 SpringCreator.setOverrideBeanFactory(applicationContext.getBeanFactory()); 289 290 if (Double.valueOf((getBean(KualiConfigurationService.class)).getPropertyString(MEMORY_MONITOR_THRESHOLD_KEY)) > 0) { 291 MemoryMonitor.setPercentageUsageThreshold(Double.valueOf((getBean(KualiConfigurationService.class)).getPropertyString(MEMORY_MONITOR_THRESHOLD_KEY))); 292 MemoryMonitor memoryMonitor = new MemoryMonitor(APPLICATION_CONTEXT_DEFINITION); 293 memoryMonitor.addListener(new MemoryMonitor.Listener() { 294 org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MemoryMonitor.class); 295 296 public void memoryUsageLow(String springContextId, Map<String, String> memoryUsageStatistics, String deadlockedThreadIds) { 297 if ( LOG.isInfoEnabled() ) { 298 StringBuffer logStatement = new StringBuffer(springContextId).append("\n\tMemory Usage"); 299 for (String memoryType : memoryUsageStatistics.keySet()) { 300 logStatement.append("\n\t\t").append(memoryType.toUpperCase()).append(": ").append(memoryUsageStatistics.get(memoryType)); 301 } 302 logStatement.append("\n\tLocked Thread Ids: ").append(deadlockedThreadIds).append("\n\tThread Stacks"); 303 for (Map.Entry<Thread, StackTraceElement[]> threadStackTrace : Thread.getAllStackTraces().entrySet()) { 304 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()); 305 for (StackTraceElement stackTraceElement : threadStackTrace.getValue()) { 306 logStatement.append("\n\t\t\t" + stackTraceElement); 307 } 308 } 309 LOG.info(logStatement); 310 } 311 MemoryMonitor.setPercentageUsageThreshold(0.95); 312 } 313 }); 314 } 315 if (getBean(KualiConfigurationService.class).getPropertyAsBoolean(USE_QUARTZ_SCHEDULING_KEY)) { 316 try { 317 if (initializeSchedule) { 318 LOG.info("Attempting to initialize the scheduler"); 319 (getBean(SchedulerService.class)).initialize(); 320 } 321 LOG.info("Starting the scheduler"); 322 try { 323 (getBean(Scheduler.class)).start(); 324 } catch ( NullPointerException ex ) { 325 LOG.error("Caught NPE while starting the scheduler", ex); 326 } 327 } 328 catch (NoSuchBeanDefinitionException e) { 329 LOG.info("Not initializing the scheduler because there is no scheduler bean"); 330 } 331 catch (SchedulerException e) { 332 LOG.error("Caught exception while starting the scheduler", e); 333 } 334 } 335 336 if ( getBean(KualiConfigurationService.class).getPropertyAsBoolean( "periodic.thread.dump" ) ) { 337 if ( LOG.isInfoEnabled() ) { 338 LOG.info( "Starting the Periodic Thread Dump thread - dumping every " + getBean(KualiConfigurationService.class).getPropertyString("periodic.thread.dump.seconds") + " seconds"); 339 } 340 Runnable processWatch = new Runnable() { 341 DateFormat df = new SimpleDateFormat( "yyyyMMdd" ); 342 DateFormat tf = new SimpleDateFormat( "HH-mm-ss" ); 343 long sleepPeriod = Long.parseLong( getBean(KualiConfigurationService.class).getPropertyString("periodic.thread.dump.seconds") ) * 1000; 344 public void run() { 345 File logDir = new File( getBean(KualiConfigurationService.class).getPropertyString( "logs.directory" ) ); 346 File monitoringLogDir = new File( logDir, "monitoring" ); 347 if ( !monitoringLogDir.exists() ) { 348 monitoringLogDir.mkdir(); 349 } 350 while ( true ) { 351 File todaysLogDir = new File( monitoringLogDir, df.format(new Date()) ); 352 if ( !todaysLogDir.exists() ) { 353 todaysLogDir.mkdir(); 354 } 355 File logFile = new File( todaysLogDir, "process-"+tf.format(new Date())+".log" ); 356 try { 357 BufferedWriter w = new BufferedWriter( new FileWriter( logFile ) ); 358 StringBuffer logStatement = new StringBuffer(10240); 359 logStatement.append("Threads Running at: " ).append( new Date() ).append( "\n\n\n" ); 360 Map<Thread,StackTraceElement[]> threads = Thread.getAllStackTraces(); 361 List<Thread> sortedThreads = new ArrayList<Thread>( threads.keySet() ); 362 Collections.sort( sortedThreads, new Comparator<Thread>() { 363 public int compare(Thread o1, Thread o2) { 364 return o1.getName().compareTo( o2.getName() ); 365 } 366 }); 367 for ( Thread t : sortedThreads ) { 368 logStatement.append("\tThread: name=").append(t.getName()) 369 .append(", id=").append(t.getId()) 370 .append(", priority=").append(t.getPriority()) 371 .append(", state=").append(t.getState()); 372 logStatement.append('\n'); 373 for (StackTraceElement stackTraceElement : threads.get(t) ) { 374 logStatement.append("\t\t" + stackTraceElement).append( '\n' ); 375 } 376 logStatement.append('\n'); 377 } 378 /* 379 Object ds = getBean("dataSource"); 380 if ( ds != null && ds instanceof XAPoolDataSource ) { 381 logStatement.append( "-----------------------------------------------\n" ); 382 logStatement.append( "Datasource Information:\n" ); 383 logStatement.append( ((XAPoolDataSource)ds).getDataSource().toString() ).append( '\n' ); 384 try { 385 logStatement.append( "-----------------------------------------------\n" ); 386 logStatement.append( "Active Connection SQL Dump:\n" ); 387 if ( ((XAPoolDataSource)ds).getDriverClassName().contains("Oracle") ) { 388 String sql = " SELECT " + 389 " sess.USERNAME \r\n" + 390 // " , process.SPID \r\n" + 391 // " , sess.SID \r\n" + 392 // " , sess.SERIAL## AS serial \r\n" + 393 " , sess.STATUS \r\n" + 394 " , TO_DATE( sql.FIRST_LOAD_TIME, 'YYYY-MM-DD/HH24:MI:SS' ) AS first_load_time\r\n" + 395 // " , sql.OPTIMIZER_MODE \r\n" + 396 " , sql.EXECUTIONS \r\n" + 397 " , sql.DISK_READS \r\n" + 398 " , sql.BUFFER_GETS \r\n" + 399 " , sql.ROWS_PROCESSED \r\n" + 400 " , sql.OPTIMIZER_COST \r\n" + 401 " , sql.SQL_TEXT \r\n" + 402 // " , sql.MODULE, \r\n" + 403 // " , sql.loads,\r\n" + 404 // " , sql.invalidations,\r\n" + 405 // " , sess.MACHINE, \r\n" + 406 // " , sess.TERMINAL, \r\n" + 407 // " , sess.PROGRAM, \r\n" + 408 // " , sess.OSUSER,\r\n" + 409 // " , RAWTOHEX( sess.SQL_ADDRESS ) SQL_ADDRESS\r\n" + 410 " FROM SYS.V_$SESSION sess, \r\n" + 411 " SYS.V_$PROCESS process, \r\n" + 412 " SYS.V_$SQL sql\r\n" + 413 " WHERE sess.USERNAME IS NOT NULL\r\n" + 414 " AND sql.SQL_TEXT NOT LIKE '%SYS.V_$%'\r\n" + 415 " AND sess.STATUS<>'KILLED'\r\n" + 416 " AND sess.SQL_ADDRESS=sql.ADDRESS\r\n" + 417 " AND sess.PADDR=process.ADDR\r\n" + 418 " AND sess.status = 'ACTIVE'\r\n" + 419 " ORDER BY sess.STATUS ASC,\r\n" + 420 " sess.USERNAME ASC,\r\n" + 421 " sql.sql_text ASC\r\n"; 422 java.sql.Connection con = ((XAPoolDataSource)ds).getConnection(); 423 try { 424 Statement stmt = con.createStatement(); 425 ResultSet rs = stmt.executeQuery(sql); 426 ResultSetMetaData md = rs.getMetaData(); 427 logStatement.append( "Columns: " ); 428 for ( int i = 1; i < md.getColumnCount(); i++ ) { 429 logStatement.append( md.getColumnName(i) ).append( "|" ); 430 } 431 while ( rs.next() ) { 432 logStatement.append( "Statement Info: " ); 433 for ( int i = 1; i < md.getColumnCount(); i++ ) { 434 logStatement.append( rs.getString(i) ).append( "|" ); 435 } 436 logStatement.append( "\nSQL Text: " + rs.getString(md.getColumnCount()) ); 437 logStatement.append( "\n\n" ); 438 } 439 rs.close(); 440 stmt.close(); 441 } finally { 442 if ( con != null ) { 443 con.close(); 444 } 445 } 446 } else { 447 logStatement.append( "Not Running - don't have MySQL specific code.\n" ); 448 } 449 } 450 catch (SQLException e) { 451 LOG.warn( "Unable to pull current connection SQL: " + e.getMessage() ); 452 } 453 } 454 */ 455 w.write(logStatement.toString()); 456 w.close(); 457 } catch ( IOException ex ) { 458 LOG.error( "Unable to write the ProcessWatch output file: " + logFile.getAbsolutePath(), ex ); 459 } 460 try { 461 Thread.sleep( sleepPeriod ); 462 } catch ( InterruptedException ex ) { 463 LOG.error( "woken up during sleep of the ProcessWatch thread", ex ); 464 } 465 } 466 } 467 }; 468 processWatchThread = new Thread( processWatch, "ProcessWatch thread" ); 469 processWatchThread.setDaemon(true); 470 processWatchThread.start(); 471 } 472 } 473 }