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 }