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