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}