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}