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