/*
* Copyright 2002-2011 the original author or authors.
*
* 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.springframework.flex.core;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.flex.config.FlexConfigurationManager;
import org.springframework.flex.config.MessageBrokerConfigProcessor;
import org.springframework.flex.config.RuntimeEnvironment;
import org.springframework.flex.servlet.MessageBrokerHandlerAdapter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.DispatcherServlet;
import flex.management.MBeanLifecycleManager;
import flex.management.MBeanServerLocatorFactory;
import flex.messaging.FlexContext;
import flex.messaging.HttpFlexSession;
import flex.messaging.HttpFlexSessionProvider;
import flex.messaging.MessageBroker;
import flex.messaging.VersionInfo;
import flex.messaging.config.ConfigurationManager;
import flex.messaging.config.MessagingConfiguration;
import flex.messaging.io.PropertyProxyRegistry;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.TypeMarshallingContext;
/**
* {@link FactoryBean} that creates a local {@link MessageBroker} instance within a Spring web application context. The
* resulting Spring-managed MessageBroker can be used to export Spring beans for direct remoting from a Flex client.
*
* <p>
* By default, this FactoryBean will look for a BlazeDS config file at /WEB-INF/flex/services-config.xml. This location
* may be overridden using the servicesConfigPath property. Spring's {@link ResourceLoader} abstraction is used to load
* the config resources, so the location may be specified using ant-style paths.
*
* <p>
* The initialization of the MessageBroker logically consists of two phases:
* <ol>
* <li>
* Parsing the BlazeDS XML configuration files and applying their settings to a newly created MessageBroker</li>
* <li>
* Starting the MessageBroker and its services</li>
* </ol>
* Further custom configuration of the MessageBroker can be achieved through registering custom
* {@link MessageBrokerConfigProcessor} instances with this FactoryBean via the <code>configProcessors</code> property.
*
* <p>
* Http-based messages should be routed to the MessageBroker using the {@link DispatcherServlet} in combination with the
* {@link MessageBrokerHandlerAdapter}.
* </p>
*
* @see MessageBrokerHandlerAdapter
* @see MessageBrokerConfigProcessor
*
* @author Jeremy Grelle
*/
public class MessageBrokerFactoryBean implements FactoryBean<MessageBroker>, BeanClassLoaderAware, BeanNameAware, ResourceLoaderAware,
InitializingBean, DisposableBean, ServletContextAware {
private static String FLEXDIR = "/WEB-INF/flex/";
private static final Log logger = LogFactory.getLog(MessageBrokerFactoryBean.class);
private MessageBroker messageBroker;
private String name;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private ConfigurationManager configurationManager;
private ResourceLoader resourceLoader;
private String servicesConfigPath;
private ServletContext servletContext;
private Set<MessageBrokerConfigProcessor> configProcessors = new HashSet<MessageBrokerConfigProcessor>();
/**
*
* {@inheritDoc}
*/
@SuppressWarnings("rawtypes")
public void afterPropertiesSet() throws Exception {
try {
ServletConfig servletConfig = new DelegatingServletConfig();
// allocate thread local variables
initThreadLocals();
// Set the servlet config as thread local
FlexContext.setThreadLocalObjects(null, null, null, null, null, servletConfig);
// Get the configuration manager
if (this.configurationManager == null) {
this.configurationManager = new FlexConfigurationManager(this.resourceLoader, this.servicesConfigPath);
}
// Load configuration
MessagingConfiguration messagingConfig = this.configurationManager.getMessagingConfiguration(servletConfig);
// Set up logging system ahead of everything else.
messagingConfig.createLogAndTargets();
// Create broker.
this.messageBroker = messagingConfig.createBroker(this.name, this.beanClassLoader);
// Set the servlet config as thread local
FlexContext.setThreadLocalObjects(null, null, this.messageBroker, null, null, servletConfig);
setupPathResolvers();
setInitServletContext();
if (logger.isInfoEnabled()) {
logger.info(VersionInfo.buildMessage());
}
// Create endpoints, services, security, and log on the broker based
// on configuration
messagingConfig.configureBroker(this.messageBroker);
long timeBeforeStartup = 0;
if (logger.isInfoEnabled()) {
timeBeforeStartup = System.currentTimeMillis();
logger.info("MessageBroker with id '" + this.messageBroker.getId() + "' is starting.");
}
// initialize the httpSessionToFlexSessionMap
synchronized (HttpFlexSession.mapLock) {
if (servletConfig.getServletContext().getAttribute(HttpFlexSession.SESSION_MAP) == null) {
servletConfig.getServletContext().setAttribute(HttpFlexSession.SESSION_MAP, new ConcurrentHashMap());
}
}
this.messageBroker = processBeforeStart(this.messageBroker);
this.messageBroker.start();
this.messageBroker = processAfterStart(this.messageBroker);
if (logger.isInfoEnabled()) {
long timeAfterStartup = System.currentTimeMillis();
Long diffMillis = new Long(timeAfterStartup - timeBeforeStartup);
logger.info("MessageBroker with id '" + this.messageBroker.getId() + "' is ready (startup time: '" + diffMillis + "' ms)");
}
// Report replaced tokens
this.configurationManager.reportTokens();
// Report any unused properties.
messagingConfig.reportUnusedProperties();
// Setup provider for FlexSessions that wrap underlying J2EE HttpSessions.
this.messageBroker.getFlexSessionManager().registerFlexSessionProvider(HttpFlexSession.class, new HttpFlexSessionProvider());
// clear the broker and servlet config as this thread is done
clearThreadLocals();
} catch (Throwable error) {
// Ensure the broker gets cleaned up properly, then re-throw
if (logger.isErrorEnabled()) {
logger.error("Error thrown during MessageBroker initialization", error);
}
destroy();
throw new BeanInitializationException("MessageBroker initialization failed", error);
}
}
/**
*
* {@inheritDoc}
*/
public void destroy() throws Exception {
if (this.messageBroker != null) {
if (this.messageBroker.isStarted()) {
this.messageBroker.stop();
}
if (this.messageBroker.isManaged()) {
MBeanLifecycleManager.unregisterRuntimeMBeans(this.messageBroker);
}
}
//release static thread locals
destroyThreadLocals();
}
/**
* Return the set of configuration processors that can customize the created {@link MessageBroker}
*
* @return the config processors
*/
public Set<MessageBrokerConfigProcessor> getConfigProcessors() {
return this.configProcessors;
}
/**
*
* {@inheritDoc}
*/
public MessageBroker getObject() throws Exception {
return this.messageBroker;
}
/**
*
* {@inheritDoc}
*/
public Class<? extends MessageBroker> getObjectType() {
return this.messageBroker != null ? this.messageBroker.getClass() : MessageBroker.class;
}
/**
*
* {@inheritDoc}
*/
public boolean isSingleton() {
return true;
}
/**
*
* {@inheritDoc}
*/
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
/**
*
* {@inheritDoc}
*/
public void setBeanName(String name) {
this.name = name;
}
/**
*
* @param startupProcessors
*/
public void setConfigProcessors(Set<MessageBrokerConfigProcessor> startupProcessors) {
this.configProcessors = startupProcessors;
}
/**
*
* @param configurationManager
*/
public void setConfigurationManager(ConfigurationManager configurationManager) {
this.configurationManager = configurationManager;
}
/**
*
* {@inheritDoc}
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
*
* @param servicesConfigPath
*/
public void setServicesConfigPath(String servicesConfigPath) {
this.servicesConfigPath = servicesConfigPath;
}
/**
*
* {@inheritDoc}
*/
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
private MessageBroker processAfterStart(MessageBroker broker) {
for (MessageBrokerConfigProcessor processor : this.configProcessors) {
broker = processor.processAfterStartup(broker);
}
return broker;
}
private MessageBroker processBeforeStart(MessageBroker broker) {
for (MessageBrokerConfigProcessor processor : this.configProcessors) {
broker = processor.processBeforeStartup(broker);
}
return broker;
}
private void setInitServletContext() {
// This is undesirable but necessary at the moment for LCDS to be able to load its license configuration.
// Hopefully we can get the BlazeDS/LCDS team to give us a better option in the future.
Method initMethod = ReflectionUtils.findMethod(MessageBroker.class, "setServletContext", new Class[] { ServletContext.class });
if (initMethod == null) {
initMethod = ReflectionUtils.findMethod(MessageBroker.class, "setInitServletContext", new Class[] { ServletContext.class });
}
ReflectionUtils.makeAccessible(initMethod);
ReflectionUtils.invokeMethod(initMethod, this.messageBroker, new Object[] { this.servletContext });
}
private void setupPathResolvers() {
setupInternalPathResolver();
if (RuntimeEnvironment.isBlazeDS46()) {
setupExternalPathResolver();
}
}
private void setupInternalPathResolver() {
this.messageBroker.setInternalPathResolver(new MessageBroker.InternalPathResolver() {
public InputStream resolve(String filename) {
try {
Resource resource = MessageBrokerFactoryBean.this.resourceLoader.getResource(FLEXDIR + filename);
if (resource.exists()) {
return resource.getInputStream();
} else {
return null;
}
} catch (IOException e) {
throw new IllegalStateException("Could not resolve Flex internal resource at: " + FLEXDIR + filename);
}
}
});
}
private void setupExternalPathResolver() {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(this.messageBroker);
wrapper.setPropertyValue("externalPathResolver", new MessageBroker.InternalPathResolver() {
public InputStream resolve(String filename) throws IOException {
try {
Resource resource = MessageBrokerFactoryBean.this.resourceLoader.getResource(filename);
if (resource.exists()) {
return resource.getInputStream();
} else {
return null;
}
} catch (IOException e) {
throw new IllegalStateException("Could not resolve Flex internal resource at: " + filename);
}
}
});
}
private void initThreadLocals() {
// allocate static thread local objects
// If available, invoke the MessageBroker.createThreadLocalObjects() method:
Method createThreadLocalObjMethod = ReflectionUtils.findMethod(MessageBroker.class, "createThreadLocalObjects");
if (createThreadLocalObjMethod != null) {
ReflectionUtils.invokeMethod(createThreadLocalObjMethod, null);
}
FlexContext.createThreadLocalObjects();
SerializationContext.createThreadLocalObjects();
TypeMarshallingContext.createThreadLocalObjects();
PropertyProxyRegistry.getRegistry();
}
private void clearThreadLocals() {
// clear thread local objects after startup
FlexContext.clearThreadLocalObjects();
SerializationContext.clearThreadLocalObjects();
}
private void destroyThreadLocals() {
// clear static member variables for shutdown
MBeanServerLocatorFactory.clear();
// If available, invoke the MessageBroker.releaseThreadLocalObjects() method:
Method releaseThreadLocalObjMethod = ReflectionUtils.findMethod(MessageBroker.class, "releaseThreadLocalObjects");
if (releaseThreadLocalObjMethod != null) {
ReflectionUtils.invokeMethod(releaseThreadLocalObjMethod, null);
}
FlexContext.releaseThreadLocalObjects();
SerializationContext.releaseThreadLocalObjects();
TypeMarshallingContext.releaseThreadLocalObjects();
}
/**
* Internal implementation of the {@link ServletConfig} interface, to be passed to BlazeDS.
*/
private class DelegatingServletConfig implements ServletConfig {
public String getInitParameter(String paramName) {
return null;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Enumeration getInitParameterNames() {
return Collections.enumeration(Collections.EMPTY_SET);
}
public ServletContext getServletContext() {
return MessageBrokerFactoryBean.this.servletContext;
}
public String getServletName() {
return MessageBrokerFactoryBean.this.name;
}
}
}