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