001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.spring;
018    
019    import java.util.LinkedHashSet;
020    import java.util.Map;
021    import java.util.Set;
022    import javax.xml.bind.annotation.XmlAccessType;
023    import javax.xml.bind.annotation.XmlAccessorType;
024    import javax.xml.bind.annotation.XmlRootElement;
025    import javax.xml.bind.annotation.XmlTransient;
026    
027    import org.apache.camel.CamelContext;
028    import org.apache.camel.Endpoint;
029    import org.apache.camel.Service;
030    import org.apache.camel.core.xml.CamelJMXAgentDefinition;
031    import org.apache.camel.impl.CamelPostProcessorHelper;
032    import org.apache.camel.impl.DefaultCamelBeanPostProcessor;
033    import org.apache.camel.util.ServiceHelper;
034    import org.slf4j.Logger;
035    import org.slf4j.LoggerFactory;
036    import org.springframework.beans.BeanInstantiationException;
037    import org.springframework.beans.BeansException;
038    import org.springframework.beans.factory.config.BeanPostProcessor;
039    import org.springframework.context.ApplicationContext;
040    import org.springframework.context.ApplicationContextAware;
041    
042    /**
043     * Spring specific {@link DefaultCamelBeanPostProcessor} which uses Spring {@link BeanPostProcessor} to post process beans.
044     *
045     * @see DefaultCamelBeanPostProcessor
046     */
047    @XmlRootElement(name = "beanPostProcessor")
048    @XmlAccessorType(XmlAccessType.FIELD)
049    public class CamelBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
050        private static final Logger LOG = LoggerFactory.getLogger(CamelBeanPostProcessor.class);
051        @XmlTransient
052        Set<String> prototypeBeans = new LinkedHashSet<String>();
053        @XmlTransient
054        private CamelContext camelContext;
055        @XmlTransient
056        private ApplicationContext applicationContext;
057        @XmlTransient
058        private String camelId;
059    
060        // must use a delegate, as we cannot extend DefaultCamelBeanPostProcessor, as this will cause the
061        // XSD schema generator to include the DefaultCamelBeanPostProcessor as a type, which we do not want to
062        @XmlTransient
063        private final DefaultCamelBeanPostProcessor delegate = new DefaultCamelBeanPostProcessor() {
064            @Override
065            public CamelContext getOrLookupCamelContext() {
066                if (camelContext == null) {
067                    if (camelId != null) {
068                        LOG.trace("Looking up CamelContext by id: {} from Spring ApplicationContext: {}", camelId, applicationContext);
069                        camelContext = applicationContext.getBean(camelId, CamelContext.class);
070                    } else {
071                        // lookup by type and grab the single CamelContext if exists
072                        LOG.trace("Looking up CamelContext by type from Spring ApplicationContext: {}", applicationContext);
073                        Map<String, CamelContext> contexts = applicationContext.getBeansOfType(CamelContext.class);
074                        if (contexts != null && contexts.size() == 1) {
075                            camelContext = contexts.values().iterator().next();
076                        }
077                    }
078                }
079                return camelContext;
080            }
081    
082            @Override
083            public boolean canPostProcessBean(Object bean, String beanName) {
084                // the JMXAgent is a bit strange and causes Spring issues if we let it being
085                // post processed by this one. It does not need it anyway so we are good to go.
086                // We should also avoid to process the null object bean (in Spring 2.5.x)
087                if (bean == null || bean instanceof CamelJMXAgentDefinition) {
088                    return false;
089                }
090    
091                return super.canPostProcessBean(bean, beanName);
092            }
093    
094            @Override
095            public CamelPostProcessorHelper getPostProcessorHelper() {
096                // lets lazily create the post processor
097                if (camelPostProcessorHelper == null) {
098                    camelPostProcessorHelper = new CamelPostProcessorHelper() {
099    
100                        @Override
101                        public CamelContext getCamelContext() {
102                            // lets lazily lookup the camel context here
103                            // as doing this will cause this context to be started immediately
104                            // breaking the lifecycle ordering of different camel contexts
105                            // so we only want to do this on demand
106                            return delegate.getOrLookupCamelContext();
107                        }
108    
109                        @Override
110                        protected RuntimeException createProxyInstantiationRuntimeException(Class<?> type, Endpoint endpoint, Exception e) {
111                            return new BeanInstantiationException(type, "Could not instantiate proxy of type " + type.getName() + " on endpoint " + endpoint, e);
112                        }
113    
114                        protected boolean isSingleton(Object bean, String beanName) {
115                            // no application context has been injected which means the bean
116                            // has not been enlisted in Spring application context
117                            if (applicationContext == null || beanName == null) {
118                                return super.isSingleton(bean, beanName);
119                            } else {
120                                return applicationContext.isSingleton(beanName);
121                            }
122                        }
123    
124                        protected void startService(Service service, Object bean, String beanName) throws Exception {
125                            if (isSingleton(bean, beanName)) {
126                                getCamelContext().addService(service);
127                            } else {
128                                // only start service and do not add it to CamelContext
129                                ServiceHelper.startService(service);
130                                if (prototypeBeans.add(beanName)) {
131                                    // do not spam the log with WARN so do this only once per bean name
132                                    CamelBeanPostProcessor.LOG.warn("The bean with id [" + beanName + "] is prototype scoped and cannot stop the injected service when bean is destroyed: "
133                                            + service + ". You may want to stop the service manually from the bean.");
134                                }
135                            }
136                        }
137                    };
138                }
139                return camelPostProcessorHelper;
140            }
141        };
142    
143        public CamelBeanPostProcessor() {
144        }
145    
146        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
147            try {
148                return delegate.postProcessBeforeInitialization(bean, beanName);
149            } catch (Exception e) {
150                // do not wrap already beans exceptions
151                if (e instanceof BeansException) {
152                    throw (BeansException) e;
153                }
154                throw new GenericBeansException("Error post processing bean: " + beanName, e);
155            }
156        }
157    
158        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
159            try {
160                return delegate.postProcessAfterInitialization(bean, beanName);
161            } catch (Exception e) {
162                // do not wrap already beans exceptions
163                if (e instanceof BeansException) {
164                    throw (BeansException) e;
165                }
166                throw new GenericBeansException("Error post processing bean: " + beanName, e);
167            }
168        }
169    
170        // Properties
171        // -------------------------------------------------------------------------
172    
173        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
174            this.applicationContext = applicationContext;
175        }
176    
177        public CamelContext getCamelContext() {
178            return camelContext;
179        }
180    
181        public void setCamelContext(CamelContext camelContext) {
182            this.camelContext = camelContext;
183        }
184    
185        public String getCamelId() {
186            return camelId;
187        }
188    
189        public void setCamelId(String camelId) {
190            this.camelId = camelId;
191        }
192    
193    }