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