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