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