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