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.aopalliance.aop.Advice; 23 import org.apache.commons.logging.Log; 24 import org.apache.commons.logging.LogFactory; 25 import org.apache.myfaces.orchestra.conversation.Conversation; 26 import org.apache.myfaces.orchestra.conversation.CurrentConversationAdvice; 27 import org.springframework.aop.Advisor; 28 import org.springframework.aop.Pointcut; 29 import org.springframework.aop.PointcutAdvisor; 30 import org.springframework.aop.TargetSource; 31 import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator; 32 import org.springframework.beans.BeansException; 33 import org.springframework.beans.factory.NoSuchBeanDefinitionException; 34 import org.springframework.beans.factory.config.BeanDefinition; 35 import org.springframework.context.ConfigurableApplicationContext; 36 37 import java.io.Serializable; 38 39 /** 40 * Define a BeanPostProcessor that is run when any bean is created by Spring. 41 * <p> 42 * This checks whether the scope of the bean is an Orchestra scope. If not, it has 43 * no effect. 44 * <p> 45 * For orchestra-scoped beans, this ensures that a CGLIB-generated proxy object is 46 * returned that wraps the real bean requested (ie holds an internal reference to 47 * an instance of the real bean). The proxy has the CurrentConversationAdvice 48 * attached to it always, plus any other advices that are configured for the scope 49 * that the bean is associated with. 50 * <p> 51 * These advices then run on each invocation of any method on the proxy. 52 * <p> 53 * Note that the proxy may have other advices attached to it to, as specified by 54 * any other BeanPostProcessor objects that happen to be registered and relevant 55 * to the created bean (eg an advice to handle declarative transactions). 56 */ 57 class OrchestraAdvisorBeanPostProcessor extends AbstractAutoProxyCreator 58 { 59 private static final long serialVersionUID = 1; 60 private final Log log = LogFactory.getLog(OrchestraAdvisorBeanPostProcessor.class); 61 private ConfigurableApplicationContext appContext; 62 63 /** 64 * Define a trivial Advisor object that always applies to the class passed to it. 65 * <p> 66 * Spring requires Advisor objects to be returned rather than Advices. Advisors 67 * can implement various interfaces to control which classes or methods they 68 * get applied to, but here the OrchestraAdvisorBeanPostProcessor only returns an 69 * instance of this for classes that it really *should* apply to, and the advices 70 * always apply to all methods, so there is really nothing for this Advisor to do. 71 * <p> 72 * TODO: maybe it would be nice to allow an orchestra scope object to hold Advisors 73 * as well as just Advices, so that users can configure specific code to run only 74 * for specific methods of orchestra beans. 75 * <p> 76 * NB: In Spring2.5, this class can simply implement Advisor, and it will be applied to 77 * all methods. However in Spring2.0, class DefaultAdvisorChainFactory only accepts 78 * PointcutAdvisor or IntroductionAdvisor, and silently ignores Advisor classes that 79 * are not of those types. So here for Spring2.x compatibility we need to make this 80 * class a PointcutAdvisor with a dummy pointcut that matches all methods... 81 */ 82 private static class SimpleAdvisor implements PointcutAdvisor, Serializable 83 { 84 private Advice advice; 85 86 public SimpleAdvisor(Advice advice) 87 { 88 this.advice = advice; 89 } 90 91 public Advice getAdvice() 92 { 93 return advice; 94 } 95 96 public boolean isPerInstance() 97 { 98 return false; 99 } 100 101 public Pointcut getPointcut() 102 { 103 return Pointcut.TRUE; 104 } 105 } 106 107 public OrchestraAdvisorBeanPostProcessor(ConfigurableApplicationContext appContext) 108 { 109 this.appContext = appContext; 110 111 // Always force CGLIB to be used to generate proxies, rather than java.lang.reflect.Proxy. 112 // 113 // Without this, the Orchestra scoped-proxy instance will not work; it requires 114 // the target to fully implement the same class it is proxying, not just the 115 // interfaces on the target class. 116 // 117 // 118 // Alas, this is not sufficient to solve all the problems. If a BeanPostProcessor runs 119 // before this processor, and it creates a CGLIB based proxy, then this class creates 120 // a new proxy that *replaces* that one by peeking into the preceding proxy to find 121 // its real target class/interfaces and its advices and merging that data with the 122 // settings here (see method Cglib2AopProxy.getProxy for details). However if an 123 // earlier BeanPostProcessor has created a java.lang.reflect.Proxy proxy instance 124 // then this merging does not occur; instead this class just tries to wrap that proxy 125 // in another cglib proxy, but that fails because java.lang.reflect.Proxy creates 126 // final (unsubclassable) classes. So in short either this BeanPostProcessor needs to 127 // be the *first* processor, or some trick is needed to force all BeanPostProcessors 128 // to use cglib. This can be done by setting a special attribute in the BeanDefinition 129 // for a bean, and AbstractSpringOrchestraScope does this. 130 // 131 // Note that forging cglib to be used for proxies is also necessary when creating a 132 // "scoped proxy" for an object. The aop:scoped-proxy class also has the same needs 133 // as the Orchestra scoped-proxy, and also forces CGLIB usage, using the same method 134 // (setting AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE in the attributes of the 135 // BeanDefinition of the target bean). 136 setProxyTargetClass(true); 137 } 138 139 protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, 140 TargetSource customTargetSource) throws BeansException 141 { 142 BeanDefinition bd; 143 try 144 { 145 bd = appContext.getBeanFactory().getBeanDefinition(beanName); 146 } 147 catch(NoSuchBeanDefinitionException e) 148 { 149 // odd. However it appears that there are some "special" beans that Spring 150 // creates that cause the BeanPostProcessor to be run even though they do 151 // not have a corresponding definition. The name "(inner bean)" is one of them, 152 // but there are also names of form "example.classname#xyz123" passed to here. 153 if (log.isDebugEnabled()) 154 { 155 log.debug("Bean has no definition:" + beanName); 156 } 157 return null; 158 } 159 160 String scopeName = bd.getScope(); 161 if (scopeName == null) 162 { 163 // does this ever happen? 164 if (log.isDebugEnabled()) 165 { 166 log.debug("no scope associated with bean " + beanName); 167 } 168 return null; 169 } 170 171 if (log.isDebugEnabled()) 172 { 173 log.debug("Processing scope [" + scopeName + "]"); 174 } 175 176 Object scopeObj = appContext.getBeanFactory().getRegisteredScope(scopeName); 177 if (scopeObj == null) 178 { 179 // Ok, this is not an orchestra-scoped bean. This happens for standard scopes 180 // like Singleton. 181 if (log.isDebugEnabled()) 182 { 183 log.debug("No scope object for scope [" + scopeName + "]"); 184 } 185 return null; 186 } 187 else if (scopeObj instanceof AbstractSpringOrchestraScope == false) 188 { 189 // ok, this is not an orchestra-scoped bean 190 if (log.isDebugEnabled()) 191 { 192 log.debug( 193 "scope associated with bean " + beanName + 194 " is not orchestra:" + scopeObj.getClass().getName()); 195 } 196 return null; 197 } 198 199 AbstractSpringOrchestraScope scopeForThisBean = (AbstractSpringOrchestraScope) scopeObj; 200 Conversation conversation = scopeForThisBean.getConversationForBean(beanName); 201 202 if (conversation == null) 203 { 204 // In general, getConversationForBean is allowed to return null. However in this case 205 // that is really not expected. Calling getBean for a bean in a scope only ever calls 206 // the get method on the scope object. The only way an instance can *really* be 207 // created is by invoking the ObjectFactory that is passed to the scope object. And 208 // the AbstractSpringOrchestraScope type only ever does that after ensuring that the 209 // conversation object has been created. 210 // 211 // Therefore, this is theoretically impossible.. 212 throw new IllegalStateException("Internal error: null conversation for bean " + beanName); 213 } 214 215 Advice[] advices = scopeForThisBean.getAdvices(); 216 if ((advices == null) || (advices.length == 0)) 217 { 218 advices = new Advice[0]; 219 } 220 221 // wrap every Advice in an Advisor instance that returns it in all cases 222 int len = advices.length + 1; 223 Advisor[] advisors = new Advisor[len]; 224 225 // always add the standard CurrentConversationAdvice, and do it FIRST, so it runs first 226 advisors[0] = new SimpleAdvisor(new CurrentConversationAdvice(conversation, beanName)); 227 228 for(int i=0; i<advices.length; ++i) 229 { 230 advisors[i+1] = new SimpleAdvisor(advices[i]); 231 } 232 233 return advisors; 234 } 235 }