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