/**
* Copyright (C) 2015 Valkyrie RCP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.valkyriercp.application.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.*;
import org.springframework.context.MessageSource;
import org.springframework.util.Assert;
import org.valkyriercp.progress.ProgressMonitor;
/**
* A {@code BeanFactoryPostProcessor} that notifies a specified
* {@link ProgressMonitor} of progress made while loading a bean factory.
*
* <p>
* The messages sent to the progress monitor can be internationalized by
* providing a {@link org.springframework.context.MessageSource} to the constructor of this class. Note that
* if a {@link org.springframework.context.MessageSource} is provided it must already be initialized in
* order for it to successfully retrieve messages.
* </p>
*
* <p>
* The progress monitor will be notified once prior to initializing the beans in
* the bean factory and once for each singleton bean before it is initialized.
* The message keys used to find these messages are
* {@value #LOADING_APP_CONTEXT_KEY} and {@value #LOADING_BEAN_KEY}. If the
* message source is unable to find any messages under these keys, or if no
* message source is provided, default messages (in English) will be used
* instead.
* </p>
*
* @author Kevin Stembridge
* @since 0.3.0
*
* @see org.valkyriercp.progress.ProgressMonitor
*
*/
public class ProgressMonitoringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
/**
* The message key used to retrieve the message to be sent to the progress
* monitor when the application context begins loading.
*/
public static final String LOADING_APP_CONTEXT_KEY = "progress.loading.applicationContext";
/**
* The message key used to retrieve the message to be sent to the progress
* monitor for each bean that is loaded.
*/
public static final String LOADING_BEAN_KEY = "progress.loading.bean";
private static final Log logger = LogFactory.getLog(ProgressMonitoringBeanFactoryPostProcessor.class);
private final ProgressMonitor progressMonitor;
@Autowired(required = false)
private MessageSource messageSource;
private final String loadingAppContextMessage;
/**
* Creates a new {@code ProgressMonitoringBeanFactoryPostProcessor} that
* will report the progress of loading the beans in a bean factory to the
* given progress monitor, optionally providing internationalized messages.
*
* @param progressMonitor The progress monitor that will be notified of
* progress while processing the bean factory.
*
* @throws IllegalArgumentException if {@code progressMonitor} is null.
*/
public ProgressMonitoringBeanFactoryPostProcessor(ProgressMonitor progressMonitor) {
Assert.notNull(progressMonitor, "The ProgressMonitor cannot be null");
this.progressMonitor = progressMonitor;
this.loadingAppContextMessage = getLoadingAppContextMessage();
}
/**
* Notifies this instance's associated progress monitor of progress made
* while processing the given bean factory.
*
* @param beanFactory the bean factory that is to be processed.
*/
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (beanFactory == null) {
return;
}
String[] beanNames = beanFactory.getBeanDefinitionNames();
int singletonBeanCount = 0;
for (int i = 0; i < beanNames.length; i++) {
// using beanDefinition to check singleton property because when
// accessing through
// context (applicationContext.isSingleton(beanName)), bean will be
// created already,
// possibly bypassing other BeanFactoryPostProcessors
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanNames[i]);
if (beanDefinition.isSingleton()) {
singletonBeanCount++;
}
}
this.progressMonitor.taskStarted(this.loadingAppContextMessage, singletonBeanCount);
beanFactory.addBeanPostProcessor(new ProgressMonitoringBeanPostProcessor(beanFactory));
}
private String getLoadingAppContextMessage() {
String defaultMessage = "Loading Application Context ...";
if (this.messageSource == null) {
return defaultMessage;
}
return this.messageSource.getMessage(LOADING_APP_CONTEXT_KEY, null, defaultMessage, null);
}
private class ProgressMonitoringBeanPostProcessor implements BeanPostProcessor {
private final ConfigurableBeanFactory beanFactory;
private ProgressMonitoringBeanPostProcessor(ConfigurableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "The bean factory cannot be null");
this.beanFactory = beanFactory;
}
/**
* A default implementation that performs no operation on the bean.
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* {@inheritDoc}
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("BEGIN: postProcessBeforeInitialization(" + beanName + ")");
}
if (this.beanFactory.containsLocalBean(beanName)) {
String loadingBeanMessage = getLoadingBeanMessage(beanName);
progressMonitor.subTaskStarted(loadingBeanMessage);
progressMonitor.worked(1);
}
logger.trace("END: postProcessBeforeInitialization()");
return bean;
}
private String getLoadingBeanMessage(String beanName) {
String defaultMessage = "Loading " + beanName + " ...";
if (messageSource == null) {
return defaultMessage;
}
Object[] args = { beanName };
return messageSource.getMessage(LOADING_BEAN_KEY, args, defaultMessage, null);
}
}
}