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    }