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 package org.apache.myfaces.orchestra.conversation.spring;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.springframework.aop.TargetSource;
24 import org.springframework.aop.framework.Advised;
25 import org.springframework.aop.framework.AopInfrastructureBean;
26 import org.springframework.aop.framework.ProxyFactory;
27 import org.springframework.aop.scope.DefaultScopedObject;
28 import org.springframework.aop.scope.ScopedObject;
29 import org.springframework.aop.support.DelegatingIntroductionInterceptor;
30 import org.springframework.beans.factory.BeanFactory;
31 import org.springframework.beans.factory.ObjectFactory;
32 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
33
34 /**
35 * <p>Various Spring utilities used by the conversation framework</p>
36 * <p>Notice: this class is not meant to be used outside of this library</p>
37 */
38 public final class _SpringUtils
39 {
40 // Equal to ScopedProxyUtils.TARGET_NAME_PREFIX, but that is private.
41 private final static String SCOPED_TARGET_PREFIX = "scopedTarget.";
42
43 private _SpringUtils()
44 {
45 }
46
47 /**
48 * Detect the case where a class includes a nested aop:scoped-target tag.
49 * <p>
50 * When a definition is present in the config file like this:
51 * <pre>
52 * <bean name="foo" class="example.Foo" orchestra:foo="foo">
53 * <....>
54 * <aop:scopedProxy/>
55 * </bean>
56 * </pre>
57 * then what Spring actually does is create two BeanDefinition objects, one
58 * with name "foo" that creates a proxy object, and one with name "scopedTarget.foo"
59 * that actually defines the bean of type example.Foo (see Spring class ScopedProxyUtils
60 * for details.
61 * <p>
62 * Using this pattern is very common with Orchestra, so we need to detect it and
63 * look for orchestra settings on the renamed bean definition rather than the
64 * one with the original name.
65 *
66 * @since 1.1
67 */
68 public static boolean isModifiedBeanName(String beanName)
69 {
70 return beanName.startsWith(SCOPED_TARGET_PREFIX);
71 }
72
73 /**
74 * Given the name of a BeanDefinition, if this is a fake name created
75 * by spring because an aop:scoped-proxy is now wrapping this object,
76 * then return the name of that scoped-proxy bean (ie the name that the
77 * user accesses this bean by).
78 *
79 * @since 1.1
80 */
81 public static String getOriginalBeanName(String beanName)
82 {
83 if (beanName != null && isModifiedBeanName(beanName))
84 {
85 return beanName.substring(SCOPED_TARGET_PREFIX.length());
86 }
87
88 return beanName;
89 }
90
91 /**
92 * Given a bean name "foo", if it refers to a scoped proxy then the bean
93 * definition that holds the original settings is now under another name,
94 * so return that other name so the original BeanDefinition can be found.
95 *
96 * @since 1.1
97 */
98 public static String getModifiedBeanName(String beanName)
99 {
100 if (beanName != null && !isModifiedBeanName(beanName))
101 {
102 return SCOPED_TARGET_PREFIX + beanName;
103 }
104
105 return beanName;
106 }
107
108 /** @deprecated use isModifiedBeanName instead. */
109 public static boolean isAlternateBeanName(String beanName)
110 {
111 return isModifiedBeanName(beanName);
112 }
113
114 /** @deprecated use getModifiedBeanName instead. */
115 public static String getAlternateBeanName(String beanName)
116 {
117 return getModifiedBeanName(beanName);
118 }
119
120 /** @deprecated use getOriginalBeanName instead. */
121 public static String getRealBeanName(String beanName)
122 {
123 return getOriginalBeanName(beanName);
124 }
125
126 /**
127 * Create an object that subclasses the concrete class of the BeanDefinition
128 * for the specified targetBeanName, and when invoked delegates to an instance
129 * of that type fetched from a scope object.
130 * <p>
131 * The proxy returned currently also currently implements the standard Spring
132 * ScopedObject interface; this is experimental and may be removed if not useful.
133 *
134 * @since 1.1
135 */
136 public static Object newProxy(
137 AbstractSpringOrchestraScope scope,
138 String conversationName,
139 String targetBeanName,
140 ObjectFactory objectFactory,
141 BeanFactory beanFactory)
142 {
143 // based on code from Spring ScopedProxyFactoryBean (APL2.0)
144 final Log log = LogFactory.getLog(_SpringUtils.class);
145
146 log.debug("newProxy invoked for " + targetBeanName);
147
148 if (!(beanFactory instanceof ConfigurableBeanFactory))
149 {
150 throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
151 }
152 ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
153
154 ScopedBeanTargetSource scopedTargetSource = new ScopedBeanTargetSource(
155 scope, conversationName, targetBeanName, objectFactory, beanFactory);
156
157 ProxyFactory pf = new ProxyFactory();
158 pf.setProxyTargetClass(true);
159 pf.setTargetSource(scopedTargetSource);
160
161 Class beanType = beanFactory.getType(targetBeanName);
162 if (beanType == null)
163 {
164 throw new IllegalStateException("Cannot create scoped proxy for bean '" + targetBeanName +
165 "': Target type could not be determined at the time of proxy creation.");
166 }
167
168 // Add an introduction that implements only the methods on ScopedObject.
169 // Not sure if this is useful...
170 ScopedObject scopedObject = new DefaultScopedObject(cbf, scopedTargetSource.getTargetBeanName());
171 pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
172
173 // Add the AopInfrastructureBean marker to indicate that the scoped proxy
174 // itself is not subject to auto-proxying! Only its target bean is.
175 // Not sure if this is needed....
176 pf.addInterface(AopInfrastructureBean.class);
177
178 return pf.getProxy(cbf.getBeanClassLoader());
179 }
180
181 /**
182 * Given a proxy object, return the underlying object that it currently refers to.
183 * <p>
184 * This method is currently experimental; it works for the current Spring implementation
185 * of Orchestra but at the current time it is not known whether this functionality can
186 * be implemented for all dependency-injection frameworks. If it does, then it might
187 * later make sense to promote this up to the public Orchestra API.
188 * <p>
189 * Note that invoking this method will create the "target object" if it does not yet exist.
190 * There is currently no way of testing whether this will happen, ie null will never
191 * be returned from this method.
192 *
193 * @since 1.3
194 */
195 public static Object getTargetObject(Object proxy) throws Exception
196 {
197 Advised advised = (Advised) proxy;
198 TargetSource targetSource = advised.getTargetSource();
199 Object target = targetSource.getTarget();
200
201 // Possibly we could add a method on the ScopedBeanTargetSource class to test
202 // whether the target bean exists. Then here we could cast TargetSource to
203 // ScopedBeanTargetSource and return null if the target does not exist. This
204 // might be useful, but let's leave that until someone actually has a use-case
205 // for that functionality.
206 return target;
207 }
208 }