/*
* 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);
}
}