View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.sys.context;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.text.DateFormat;
21  import java.text.SimpleDateFormat;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.Date;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import javax.xml.namespace.QName;
34  
35  import org.apache.commons.io.FileUtils;
36  import org.apache.commons.lang.StringUtils;
37  import org.apache.commons.lang.math.NumberUtils;
38  import org.apache.log4j.Logger;
39  import org.kuali.ole.batch.service.OLEBatchSchedulerService;
40  import org.kuali.ole.sys.MemoryMonitor;
41  import org.kuali.ole.sys.OLEConstants;
42  import org.kuali.ole.sys.batch.Step;
43  import org.kuali.ole.sys.batch.service.SchedulerService;
44  import org.kuali.rice.core.api.config.property.ConfigurationService;
45  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
46  import org.kuali.rice.core.framework.resourceloader.SpringResourceLoader;
47  import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
48  import org.kuali.rice.coreservice.api.component.Component;
49  import org.kuali.rice.krad.service.*;
50  import org.quartz.Scheduler;
51  import org.quartz.SchedulerException;
52  import org.springframework.aop.support.AopUtils;
53  import org.springframework.beans.factory.NoSuchBeanDefinitionException;
54  import org.springframework.context.ConfigurableApplicationContext;
55  
56  @SuppressWarnings("deprecation")
57  public class SpringContext {
58      private static final Logger LOG = Logger.getLogger(SpringContext.class);
59      protected static final String MEMORY_MONITOR_THRESHOLD_KEY = "memory.monitor.threshold";
60      protected static final String USE_QUARTZ_SCHEDULING_KEY = "use.quartz.scheduling";
61      protected static final String OLE_BATCH_STEP_COMPONENT_SET_ID = "STEP:OLE";
62      protected static ConfigurableApplicationContext applicationContext;
63      protected static Set<Class<? extends Object>> SINGLETON_TYPES = new HashSet<Class<? extends Object>>();
64      protected static Map<Class<? extends Object>, Object> SINGLETON_BEANS_BY_TYPE_CACHE = new HashMap<Class<? extends Object>, Object>();
65      protected static Map<String, Object> SINGLETON_BEANS_BY_NAME_CACHE = new HashMap<String, Object>();
66      @SuppressWarnings("rawtypes")
67      protected static Map<Class<? extends Object>, Map> SINGLETON_BEANS_OF_TYPE_CACHE = new HashMap<Class<? extends Object>, Map>();
68      protected static Thread processWatchThread = null;
69      protected static MemoryMonitor memoryMonitor;
70      /**
71       * Use this method to retrieve a service which may or may not be implemented locally. (That is, defined in the main Spring
72       * ApplicationContext created by Rice.
73       */
74      public static Object getService(String serviceName) {
75          return GlobalResourceLoader.getService(serviceName);
76      }
77  
78      /**
79       * Use this method to retrieve a spring bean when one of the following is the case. Pass in the type of the service interface,
80       * NOT the service implementation. 1. there is only one bean of the specified type in our spring context 2. there is only one
81       * bean of the specified type in our spring context, but you want the one whose bean id is the same as type.getSimpleName() with
82       * the exception of the first letter being lower case in the former and upper case in the latter, For example, there are two
83       * beans of type DateTimeService in our context dateTimeService and testDateTimeService. To retrieve the former, you should
84       * specific DateTimeService.class as the type. To retrieve the latter, you should specify ConfigurableDateService.class as the
85       * 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
86       * this method.
87       *
88       * @param <T>
89       * @param type
90       * @return an object that has been defined as a bean in our spring context and is of the specified type
91       */
92      public static <T> T getBean(Class<T> type) {
93          verifyProperInitialization();
94          T bean = null;
95          if (SINGLETON_BEANS_BY_TYPE_CACHE.containsKey(type)) {
96              bean = (T) SINGLETON_BEANS_BY_TYPE_CACHE.get(type);
97          }
98          else {
99              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 }