View Javadoc

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 }