1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.myfaces.orchestra.conversation.spring; 21 22 import org.apache.commons.logging.Log; 23 import org.apache.commons.logging.LogFactory; 24 import org.apache.myfaces.orchestra.conversation.Conversation; 25 import org.springframework.aop.Advisor; 26 import org.springframework.aop.TargetSource; 27 import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator; 28 import org.springframework.beans.BeansException; 29 import org.springframework.beans.factory.NoSuchBeanDefinitionException; 30 import org.springframework.beans.factory.config.BeanDefinition; 31 import org.springframework.context.ConfigurableApplicationContext; 32 33 /** 34 * Define a BeanPostProcessor that is run when any bean is created by Spring. 35 * <p> 36 * This checks whether the scope of the bean is an Orchestra scope. If not, it has 37 * no effect. 38 * <p> 39 * For orchestra-scoped beans, this ensures that a CGLIB-generated proxy object is 40 * returned that wraps the real bean requested (ie holds an internal reference to 41 * an instance of the real bean). The proxy has the CurrentConversationAdvice 42 * attached to it always, plus any other advices that are configured for the scope 43 * that the bean is associated with. 44 * <p> 45 * These advices then run on each invocation of any method on the proxy. 46 * <p> 47 * Note that the proxy may have other advices attached to it to, as specified by 48 * any other BeanPostProcessor objects that happen to be registered and relevant 49 * to the created bean (eg an advice to handle declarative transactions). 50 */ 51 class OrchestraAdvisorBeanPostProcessor extends AbstractAutoProxyCreator 52 { 53 private static final long serialVersionUID = 1; 54 private final Log log = LogFactory.getLog(OrchestraAdvisorBeanPostProcessor.class); 55 private ConfigurableApplicationContext appContext; 56 57 58 public OrchestraAdvisorBeanPostProcessor(ConfigurableApplicationContext appContext) 59 { 60 this.appContext = appContext; 61 62 // Always force CGLIB to be used to generate proxies, rather than java.lang.reflect.Proxy. 63 // 64 // Without this, the Orchestra scoped-proxy instance will not work; it requires 65 // the target to fully implement the same class it is proxying, not just the 66 // interfaces on the target class. 67 // 68 // 69 // Alas, this is not sufficient to solve all the problems. If a BeanPostProcessor runs 70 // before this processor, and it creates a CGLIB based proxy, then this class creates 71 // a new proxy that *replaces* that one by peeking into the preceding proxy to find 72 // its real target class/interfaces and its advices and merging that data with the 73 // settings here (see method Cglib2AopProxy.getProxy for details). However if an 74 // earlier BeanPostProcessor has created a java.lang.reflect.Proxy proxy instance 75 // then this merging does not occur; instead this class just tries to wrap that proxy 76 // in another cglib proxy, but that fails because java.lang.reflect.Proxy creates 77 // final (unsubclassable) classes. So in short either this BeanPostProcessor needs to 78 // be the *first* processor, or some trick is needed to force all BeanPostProcessors 79 // to use cglib. This can be done by setting a special attribute in the BeanDefinition 80 // for a bean, and AbstractSpringOrchestraScope does this. 81 // 82 // Note that forging cglib to be used for proxies is also necessary when creating a 83 // "scoped proxy" for an object. The aop:scoped-proxy class also has the same needs 84 // as the Orchestra scoped-proxy, and also forces CGLIB usage, using the same method 85 // (setting AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE in the attributes of the 86 // BeanDefinition of the target bean). 87 setProxyTargetClass(true); 88 } 89 90 protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, 91 TargetSource customTargetSource) throws BeansException 92 { 93 BeanDefinition bd; 94 try 95 { 96 bd = appContext.getBeanFactory().getBeanDefinition(beanName); 97 } 98 catch(NoSuchBeanDefinitionException e) 99 { 100 // odd. However it appears that there are some "special" beans that Spring 101 // creates that cause the BeanPostProcessor to be run even though they do 102 // not have a corresponding definition. The name "(inner bean)" is one of them, 103 // but there are also names of form "example.classname#xyz123" passed to here. 104 if (log.isDebugEnabled()) 105 { 106 log.debug("Bean has no definition:" + beanName); 107 } 108 return null; 109 } 110 111 String scopeName = bd.getScope(); 112 if (scopeName == null) 113 { 114 // does this ever happen? 115 if (log.isDebugEnabled()) 116 { 117 log.debug("no scope associated with bean " + beanName); 118 } 119 return null; 120 } 121 122 if (log.isDebugEnabled()) 123 { 124 log.debug("Processing scope [" + scopeName + "]"); 125 } 126 127 Object scopeObj = appContext.getBeanFactory().getRegisteredScope(scopeName); 128 if (scopeObj == null) 129 { 130 // Ok, this is not an orchestra-scoped bean. This happens for standard scopes 131 // like Singleton. 132 if (log.isDebugEnabled()) 133 { 134 log.debug("No scope object for scope [" + scopeName + "]"); 135 } 136 return null; 137 } 138 else if (scopeObj instanceof AbstractSpringOrchestraScope == false) 139 { 140 // ok, this is not an orchestra-scoped bean 141 if (log.isDebugEnabled()) 142 { 143 log.debug( 144 "scope associated with bean " + beanName + 145 " is not orchestra:" + scopeObj.getClass().getName()); 146 } 147 return null; 148 } 149 150 AbstractSpringOrchestraScope scopeForThisBean = (AbstractSpringOrchestraScope) scopeObj; 151 Conversation conversation = scopeForThisBean.getConversationForBean(beanName); 152 153 if (conversation == null) 154 { 155 // In general, getConversationForBean is allowed to return null. However in this case 156 // that is really not expected. Calling getBean for a bean in a scope only ever calls 157 // the get method on the scope object. The only way an instance can *really* be 158 // created is by invoking the ObjectFactory that is passed to the scope object. And 159 // the AbstractSpringOrchestraScope type only ever does that after ensuring that the 160 // conversation object has been created. 161 // 162 // Therefore, this is theoretically impossible.. 163 throw new IllegalStateException("Internal error: null conversation for bean " + beanName); 164 } 165 166 Advisor[] advisors = scopeForThisBean.getAdvisors(conversation, beanName); 167 return advisors; 168 } 169 }