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