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