/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sys.context; import static com.google.common.io.Files.createParentDirs; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import javax.xml.namespace.QName; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.log4j.Logger; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.MemoryMonitor; import org.kuali.kfs.sys.batch.Step; import org.kuali.kfs.sys.batch.service.SchedulerService; import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; import org.kuali.rice.core.framework.resourceloader.SpringResourceLoader; import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator; import org.kuali.rice.coreservice.api.component.Component; import org.kuali.rice.krad.service.KRADServiceLocator; import org.kuali.rice.krad.service.KRADServiceLocatorInternal; import org.kuali.rice.krad.service.KualiModuleService; import org.kuali.rice.krad.service.ModuleService; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.Resource; @SuppressWarnings("deprecation") public class SpringContext { private static final Logger LOG = Logger.getLogger(SpringContext.class); protected static final String MEMORY_MONITOR_THRESHOLD_KEY = "memory.monitor.threshold"; protected static final String USE_QUARTZ_SCHEDULING_KEY = "use.quartz.scheduling"; protected static final String KFS_BATCH_STEP_COMPONENT_SET_ID = "STEP:KFS"; protected static final String DIRECTORIES_TO_CREATE_PATH = "directoriesToCreateOnStartup"; protected static ConfigurableApplicationContext applicationContext; protected static Set<Class<? extends Object>> SINGLETON_TYPES = new HashSet<Class<? extends Object>>(); protected static Map<Class<? extends Object>, Object> SINGLETON_BEANS_BY_TYPE_CACHE = new HashMap<Class<? extends Object>, Object>(); protected static Map<String, Object> SINGLETON_BEANS_BY_NAME_CACHE = new HashMap<String, Object>(); @SuppressWarnings("rawtypes") protected static Map<Class<? extends Object>, Map> SINGLETON_BEANS_OF_TYPE_CACHE = new HashMap<Class<? extends Object>, Map>(); protected static Thread processWatchThread = null; protected static MemoryMonitor memoryMonitor; /** * Use this method to retrieve a service which may or may not be implemented locally. (That is, * defined in the main Spring ApplicationContext created by Rice. */ public static Object getService(String serviceName) { return GlobalResourceLoader.getService(serviceName); } /** * Use this method to retrieve a spring bean when one of the following is the case. Pass in the type of the service interface, * NOT the service implementation. 1. there is only one bean of the specified type in our spring context 2. there is only one * bean of the specified type in our spring context, but you want the one whose bean id is the same as type.getSimpleName() with * the exception of the first letter being lower case in the former and upper case in the latter, For example, there are two * beans of type DateTimeService in our context dateTimeService and testDateTimeService. To retrieve the former, you should * specific DateTimeService.class as the type. To retrieve the latter, you should specify ConfigurableDateService.class as the * 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 * this method. * * @param <T> * @param type * @return an object that has been defined as a bean in our spring context and is of the specified type */ public static <T> T getBean(Class<T> type) { verifyProperInitialization(); T bean = null; if (SINGLETON_BEANS_BY_TYPE_CACHE.containsKey(type)) { bean = (T) SINGLETON_BEANS_BY_TYPE_CACHE.get(type); } else { if ( LOG.isDebugEnabled() ) { LOG.debug("Bean not already in cache: " + type + " - calling getBeansOfType() "); } Collection<T> beansOfType = getBeansOfType(type).values(); if ( !beansOfType.isEmpty() ) { if (beansOfType.size() > 1) { bean = getBean(type, StringUtils.uncapitalize(type.getSimpleName()) ); } else { bean = beansOfType.iterator().next(); } } else { try { bean = getBean(type, StringUtils.uncapitalize(type.getSimpleName()) ); } catch ( Exception ex ) { // do nothing, let fall through } if ( bean == null ) { // unable to find bean - check GRL // this is needed in case no beans of the given type exist locally if ( LOG.isDebugEnabled() ) { LOG.debug("Bean not found in local context: " + type.getName() + " - calling GRL"); } Object remoteServiceBean = getService( StringUtils.uncapitalize(type.getSimpleName()) ); if ( remoteServiceBean != null ) { if ( type.isAssignableFrom( remoteServiceBean.getClass() ) ) { bean = (T)remoteServiceBean; } } } } if ( bean != null ) { synchronized( SINGLETON_TYPES ) { if (SINGLETON_TYPES.contains(type) || hasSingletonSuperType(type,SINGLETON_TYPES)) { SINGLETON_TYPES.add(type); SINGLETON_BEANS_BY_TYPE_CACHE.put(type, bean); } } } else { throw new RuntimeException( "Request for non-existent bean. Unable to find in local context or on the GRL: " + type.getName() ); } } return bean; } /** * Use this method to retrieve all beans of a give type in our spring context. Pass in the type of the service interface, NOT * the service implementation. * * @param <T> * @param type * @return a map of the spring bean ids / beans that are of the specified type */ public static <T> Map<String, T> getBeansOfType(Class<T> type) { verifyProperInitialization(); Map<String, T> beansOfType = null; if (SINGLETON_BEANS_OF_TYPE_CACHE.containsKey(type)) { beansOfType = SINGLETON_BEANS_OF_TYPE_CACHE.get(type); } else { if ( LOG.isDebugEnabled() ) { LOG.debug("Bean not already in \"OF_TYPE\" cache: " + type + " - calling getBeansOfType() on Spring context"); } boolean allOfTypeAreSingleton = true; beansOfType = applicationContext.getBeansOfType(type); for ( String key : beansOfType.keySet() ) { if ( !applicationContext.isSingleton(key) ) { allOfTypeAreSingleton = false; } } if ( allOfTypeAreSingleton ) { synchronized( SINGLETON_TYPES ) { SINGLETON_TYPES.add(type); SINGLETON_BEANS_OF_TYPE_CACHE.put(type, beansOfType); } } } return beansOfType; } public static <T> T getBean(Class<T> type, String name) { T bean = null; if (SINGLETON_BEANS_BY_NAME_CACHE.containsKey(name)) { bean = (T) SINGLETON_BEANS_BY_NAME_CACHE.get(name); } else { try { bean = (T) applicationContext.getBean(name); if ( applicationContext.isSingleton(name) ) { synchronized( SINGLETON_BEANS_BY_NAME_CACHE ) { SINGLETON_BEANS_BY_NAME_CACHE.put(name, bean); } } } catch (NoSuchBeanDefinitionException nsbde) { if ( LOG.isDebugEnabled() ) { LOG.debug("Bean with name and type not found in local context: " + name + "/" + type.getName() + " - calling GRL"); } Object remoteServiceBean = getService( name ); if ( remoteServiceBean != null ) { if ( type.isAssignableFrom( AopUtils.getTargetClass(remoteServiceBean) ) ) { bean = (T)remoteServiceBean; // assume remote beans are services and thus singletons synchronized( SINGLETON_BEANS_BY_NAME_CACHE ) { SINGLETON_BEANS_BY_NAME_CACHE.put(name, bean); } } } if (bean == null) { throw new RuntimeException("No bean of this type and name exist in the application context or from the GRL: " + type.getName() + ", " + name); } } } return bean; } private static boolean hasSingletonSuperType(Class<? extends Object> type, Set<Class<? extends Object>> knownSingletonTypes ) { for (Class<? extends Object> singletonType : knownSingletonTypes) { if (singletonType.isAssignableFrom(type)) { return true; } } return false; } protected static Object getBean(String beanName) { return getBean(Object.class, beanName); } protected static String[] getBeanNames() { verifyProperInitialization(); return applicationContext.getBeanDefinitionNames(); } public static Resource getResource(String uri) { return applicationContext.getResource(uri); } protected static void close() { if ( processWatchThread != null ) { LOG.info("Shutting down the ProcessWatch thread" ); if ( processWatchThread.isAlive() ) { processWatchThread.stop(); } processWatchThread = null; } try { if ( isInitialized() && getBean(Scheduler.class) != null ) { if ( getBean(Scheduler.class).isStarted() ) { LOG.info( "Shutting Down scheduler" ); getBean(Scheduler.class).shutdown(); } } } catch (SchedulerException ex) { LOG.error( "Exception while shutting down the scheduler", ex ); } LOG.info( "Stopping the MemoryMonitor thread" ); if ( memoryMonitor != null ) { memoryMonitor.stop(); } } public static boolean isInitialized() { return applicationContext != null; } private static void verifyProperInitialization() { if (!isInitialized()) { LOG.fatal( "*****************************************************************" ); LOG.fatal( "*****************************************************************" ); LOG.fatal( "*****************************************************************" ); LOG.fatal( "*****************************************************************" ); LOG.fatal( "*****************************************************************" ); 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() ); LOG.fatal( "*****************************************************************" ); LOG.fatal( "*****************************************************************" ); LOG.fatal( "*****************************************************************" ); LOG.fatal( "*****************************************************************" ); LOG.fatal( "*****************************************************************" ); 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."); } } static void initMemoryMonitor() { if ( NumberUtils.isNumber(KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY))) { if (Double.valueOf(KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY)) > 0) { LOG.info( "Starting up MemoryMonitor thread" ); MemoryMonitor.setPercentageUsageThreshold(Double.valueOf(KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY))); memoryMonitor = new MemoryMonitor("KFS Memory Monitor: Over " + KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY) + "% Memory Used"); memoryMonitor.addListener(new MemoryMonitor.Listener() { org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MemoryMonitor.class); @Override public void memoryUsageLow(String springContextId, Map<String, String> memoryUsageStatistics, String deadlockedThreadIds) { StringBuilder logStatement = new StringBuilder(springContextId).append("\n\tMemory Usage"); for (String memoryType : memoryUsageStatistics.keySet()) { logStatement.append("\n\t\t").append(memoryType.toUpperCase()).append(": ").append(memoryUsageStatistics.get(memoryType)); } logStatement.append("\n\tLocked Thread Ids: ").append(deadlockedThreadIds).append("\n\tThread Stacks"); for (Map.Entry<Thread, StackTraceElement[]> threadStackTrace : Thread.getAllStackTraces().entrySet()) { 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()); for (StackTraceElement stackTraceElement : threadStackTrace.getValue()) { logStatement.append("\n\t\t\t").append(stackTraceElement); } } LOG.warn(logStatement); MemoryMonitor.setPercentageUsageThreshold(0.95); } }); } } else { LOG.warn( MEMORY_MONITOR_THRESHOLD_KEY + " is not a number: " + KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(MEMORY_MONITOR_THRESHOLD_KEY) ); } } static void initMonitoringThread() { if ( KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsBoolean( "periodic.thread.dump" ) ) { final long sleepPeriod = Long.parseLong( KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString("periodic.thread.dump.seconds") ) * 1000; final File logDir = new File( KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString( "logs.directory" ) ); final File monitoringLogDir = new File( logDir, "monitoring" ); if ( !monitoringLogDir.exists() ) { monitoringLogDir.mkdir(); } if ( LOG.isInfoEnabled() ) { LOG.info( "Starting the Periodic Thread Dump thread - dumping every " + (sleepPeriod/1000) + " seconds"); LOG.info( "Periodic Thread Dump Logs: " + monitoringLogDir.getAbsolutePath() ); } final DateFormat df = new SimpleDateFormat( "yyyyMMdd" ); final DateFormat tf = new SimpleDateFormat( "HH-mm-ss" ); Runnable processWatch = new Runnable() { @Override public void run() { while ( true ) { Date now = new Date(); File todaysLogDir = new File( monitoringLogDir, df.format(now) ); if ( !todaysLogDir.exists() ) { todaysLogDir.mkdir(); } File logFile = new File( todaysLogDir, "process-"+tf.format(now)+".log" ); try { createParentDirs(logFile); BufferedWriter w = new BufferedWriter( new FileWriter( logFile ) ); StringBuilder logStatement = new StringBuilder(10240); logStatement.append("Threads Running at: " ).append( now ).append( "\n\n\n" ); Map<Thread,StackTraceElement[]> threads = Thread.getAllStackTraces(); List<Thread> sortedThreads = new ArrayList<Thread>( threads.keySet() ); Collections.sort( sortedThreads, new Comparator<Thread>() { @Override public int compare(Thread o1, Thread o2) { return o1.getName().compareTo( o2.getName() ); } }); for ( Thread t : sortedThreads ) { logStatement.append("\tThread: name=").append(t.getName()) .append(", id=").append(t.getId()) .append(", priority=").append(t.getPriority()) .append(", state=").append(t.getState()); logStatement.append('\n'); for (StackTraceElement stackTraceElement : threads.get(t) ) { logStatement.append("\t\t" + stackTraceElement).append( '\n' ); } logStatement.append('\n'); } w.write(logStatement.toString()); w.close(); } catch ( IOException ex ) { LOG.error( "Unable to write the ProcessWatch output file: " + logFile.getAbsolutePath(), ex ); } try { Thread.sleep( sleepPeriod ); } catch ( InterruptedException ex ) { LOG.error( "woken up during sleep of the ProcessWatch thread", ex ); } } } }; processWatchThread = new Thread( processWatch, "ProcessWatch thread" ); processWatchThread.setDaemon(true); processWatchThread.start(); } } static void initScheduler() { if (KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsBoolean(USE_QUARTZ_SCHEDULING_KEY)) { try { LOG.info("Attempting to initialize the SchedulerService"); getBean(SchedulerService.class).initialize(); LOG.info("Starting the Quartz scheduler"); getBean(Scheduler.class).start(); } catch (NoSuchBeanDefinitionException e) { LOG.warn("Not initializing the scheduler because there is no scheduler bean"); } catch ( Exception ex ) { LOG.error("Caught Exception while starting the scheduler", ex); } } } static void initDirectories() { String dirPaths = KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(DIRECTORIES_TO_CREATE_PATH); for (String file : Arrays.asList(dirPaths.split(","))) { String trimmedFile = file.trim(); if (!trimmedFile.isEmpty()) { File directory = new File(trimmedFile); if (!directory.exists()) { if (!directory.mkdirs()) { throw new RuntimeException(trimmedFile + " does not exist and the server was unable to create it."); } else { if (LOG.isInfoEnabled()) { LOG.info("Created directory: "+ directory); } } } else { if (!directory.isDirectory()) { throw new RuntimeException(trimmedFile + " exists but is not a directory."); } } } } } public static void registerSingletonBean(String beanId, Object bean) { applicationContext.getBeanFactory().registerSingleton(beanId, bean); //Cleaning caches SINGLETON_BEANS_BY_NAME_CACHE.clear(); SINGLETON_BEANS_BY_TYPE_CACHE.clear(); SINGLETON_BEANS_OF_TYPE_CACHE.clear(); } public static void finishInitializationAfterRiceStartup() { SpringResourceLoader mainKfsSpringResourceLoader = (SpringResourceLoader)GlobalResourceLoader.getResourceLoader( new QName("KFS", "KFS_RICE_SPRING_RESOURCE_LOADER_NAME") ); SpringContext.applicationContext = mainKfsSpringResourceLoader.getContext(); if ( LOG.isTraceEnabled() ) { GlobalResourceLoader.logAllContents(); } // KFS addition - republish all components now - until this point, the KFS DD has not been loaded KRADServiceLocatorInternal.getDataDictionaryComponentPublisherService().publishAllComponents(); // KFS addition - we also publish all our Step classes as components - and these are not in the // DD so are not published by the command above publishBatchStepComponents(); initDirectories(); } public static void publishBatchStepComponents() { Map<String,Step> steps = SpringContext.getBeansOfType(Step.class); List<Component> stepComponents = new ArrayList<Component>( steps.size() ); for ( Step step : steps.values() ) { Step unproxiedStep = (Step) ProxyUtils.getTargetIfProxied(step); String namespaceCode = KFSConstants.CoreModuleNamespaces.KFS; if ( LOG.isDebugEnabled() ) { LOG.debug( "Building component for step: " + unproxiedStep.getName() + "(" + unproxiedStep.getClass() + ")" ); } ModuleService moduleService = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(unproxiedStep.getClass()); if ( moduleService != null ) { namespaceCode = moduleService.getModuleConfiguration().getNamespaceCode(); } Component.Builder component = Component.Builder.create(namespaceCode, unproxiedStep.getClass().getSimpleName(), unproxiedStep.getClass().getSimpleName()); component.setComponentSetId(KFS_BATCH_STEP_COMPONENT_SET_ID); component.setActive(true); stepComponents.add(component.build()); } CoreServiceApiServiceLocator.getComponentService().publishDerivedComponents(KFS_BATCH_STEP_COMPONENT_SET_ID, stepComponents); } }