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 }