001    /*
002     * Copyright 2002-2006 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
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, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.application;
017    
018    import org.apache.commons.logging.Log;
019    import org.apache.commons.logging.LogFactory;
020    import org.springframework.beans.BeansException;
021    import org.springframework.beans.factory.config.BeanDefinition;
022    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
023    import org.springframework.beans.factory.config.BeanPostProcessor;
024    import org.springframework.beans.factory.config.ConfigurableBeanFactory;
025    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
026    import org.springframework.context.MessageSource;
027    import org.springframework.richclient.progress.ProgressMonitor;
028    import org.springframework.util.Assert;
029    
030    /**
031     * A {@code BeanFactoryPostProcessor} that notifies a specified
032     * {@link ProgressMonitor} of progress made while loading a bean factory.
033     * 
034     * <p>
035     * The messages sent to the progress monitor can be internationalized by
036     * providing a {@link MessageSource} to the constructor of this class. Note that
037     * if a {@link MessageSource} is provided it must already be initialized in
038     * order for it to successfully retrieve messages.
039     * </p>
040     * 
041     * <p>
042     * The progress monitor will be notified once prior to initializing the beans in
043     * the bean factory and once for each singleton bean before it is initialized.
044     * The message keys used to find these messages are
045     * {@value #LOADING_APP_CONTEXT_KEY} and {@value #LOADING_BEAN_KEY}. If the
046     * message source is unable to find any messages under these keys, or if no
047     * message source is provided, default messages (in English) will be used
048     * instead.
049     * </p>
050     * 
051     * @author Kevin Stembridge
052     * @since 0.3.0
053     * 
054     * @see ProgressMonitor
055     * 
056     */
057    public class ProgressMonitoringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
058    
059            /**
060             * The message key used to retrieve the message to be sent to the progress
061             * monitor when the application context begins loading.
062             */
063            public static final String LOADING_APP_CONTEXT_KEY = "progress.loading.applicationContext";
064    
065            /**
066             * The message key used to retrieve the message to be sent to the progress
067             * monitor for each bean that is loaded.
068             */
069            public static final String LOADING_BEAN_KEY = "progress.loading.bean";
070    
071            private static final Log logger = LogFactory.getLog(ProgressMonitoringBeanFactoryPostProcessor.class);
072    
073            private final ProgressMonitor progressMonitor;
074    
075            private final MessageSource messageSource;
076    
077            private final String loadingAppContextMessage;
078    
079            /**
080             * Creates a new {@code ProgressMonitoringBeanFactoryPostProcessor} that
081             * will report the progress of loading the beans in a bean factory to the
082             * given progress monitor, optionally providing internationalized messages.
083             * 
084             * @param progressMonitor The progress monitor that will be notified of
085             * progress while processing the bean factory.
086             * @param messageSource The message source that will be used to resolve
087             * messages to be displayed by the progress monitor. May be null.
088             * 
089             * @throws IllegalArgumentException if {@code progressMonitor} is null.
090             */
091            public ProgressMonitoringBeanFactoryPostProcessor(ProgressMonitor progressMonitor, MessageSource messageSource) {
092    
093                    Assert.notNull(progressMonitor, "The ProgressMonitor cannot be null");
094    
095                    this.progressMonitor = progressMonitor;
096                    this.messageSource = messageSource;
097                    this.loadingAppContextMessage = getLoadingAppContextMessage();
098    
099            }
100    
101            /**
102             * Notifies this instance's associated progress monitor of progress made
103             * while processing the given bean factory.
104             * 
105             * @param beanFactory the bean factory that is to be processed.
106             */
107            public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
108    
109                    if (beanFactory == null) {
110                            return;
111                    }
112    
113                    String[] beanNames = beanFactory.getBeanDefinitionNames();
114                    int singletonBeanCount = 0;
115    
116                    for (int i = 0; i < beanNames.length; i++) {
117                            // using beanDefinition to check singleton property because when
118                            // accessing through
119                            // context (applicationContext.isSingleton(beanName)), bean will be
120                            // created already,
121                            // possibly bypassing other BeanFactoryPostProcessors
122                            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanNames[i]);
123    
124                            if (beanDefinition.isSingleton()) {
125                                    singletonBeanCount++;
126                            }
127    
128                    }
129    
130                    this.progressMonitor.taskStarted(this.loadingAppContextMessage, singletonBeanCount);
131    
132                    beanFactory.addBeanPostProcessor(new ProgressMonitoringBeanPostProcessor(beanFactory));
133    
134            }
135    
136            private String getLoadingAppContextMessage() {
137    
138                    String defaultMessage = "Loading Application Context ...";
139    
140                    if (this.messageSource == null) {
141                            return defaultMessage;
142                    }
143    
144                    return this.messageSource.getMessage(LOADING_APP_CONTEXT_KEY, null, defaultMessage, null);
145    
146            }
147    
148            private class ProgressMonitoringBeanPostProcessor implements BeanPostProcessor {
149    
150                    private final ConfigurableBeanFactory beanFactory;
151    
152                    private ProgressMonitoringBeanPostProcessor(ConfigurableBeanFactory beanFactory) {
153                            Assert.notNull(beanFactory, "The bean factory cannot be null");
154                            this.beanFactory = beanFactory;
155                    }
156    
157                    /**
158                     * A default implementation that performs no operation on the bean.
159                     */
160                    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
161                            return bean;
162                    }
163    
164                    /**
165                     * {@inheritDoc}
166                     */
167                    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
168    
169                            if (logger.isTraceEnabled()) {
170                                    logger.trace("BEGIN: postProcessBeforeInitialization(" + beanName + ")");
171                            }
172    
173                            if (this.beanFactory.containsLocalBean(beanName)) {
174                                    String loadingBeanMessage = getLoadingBeanMessage(beanName);
175                                    progressMonitor.subTaskStarted(loadingBeanMessage);
176                                    progressMonitor.worked(1);
177                            }
178    
179                            logger.trace("END: postProcessBeforeInitialization()");
180                            return bean;
181    
182                    }
183    
184                    private String getLoadingBeanMessage(String beanName) {
185    
186                            String defaultMessage = "Loading " + beanName + " ...";
187    
188                            if (messageSource == null) {
189                                    return defaultMessage;
190                            }
191    
192                            Object[] args = { beanName };
193    
194                            return messageSource.getMessage(LOADING_BEAN_KEY, args, defaultMessage, null);
195    
196                    }
197    
198            }
199    
200    }