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  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       *  &lt;bean name="foo" class="example.Foo" orchestra:foo="foo"&gt;
53       *   &lt;....&gt;
54       *   &lt;aop:scopedProxy/&gt;
55       *  &lt;/bean&gt;
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         if (log.isDebugEnabled())
147         {
148             log.debug("newProxy invoked for " + targetBeanName);
149         }
150 
151         if (!(beanFactory instanceof ConfigurableBeanFactory))
152         {
153             throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
154         }
155         ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
156 
157         ScopedBeanTargetSource scopedTargetSource = new ScopedBeanTargetSource(
158                 scope, conversationName, targetBeanName, objectFactory, beanFactory);
159 
160         ProxyFactory pf = new ProxyFactory();
161         pf.setProxyTargetClass(true);
162         pf.setTargetSource(scopedTargetSource);
163 
164         Class beanType = beanFactory.getType(targetBeanName);
165         if (beanType == null)
166         {
167             throw new IllegalStateException("Cannot create scoped proxy for bean '" + targetBeanName +
168                     "': Target type could not be determined at the time of proxy creation.");
169         }
170 
171         // Add an introduction that implements only the methods on ScopedObject.
172         // Not sure if this is useful...
173         ScopedObject scopedObject = new DefaultScopedObject(cbf, scopedTargetSource.getTargetBeanName());
174         pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
175 
176         // Add the AopInfrastructureBean marker to indicate that the scoped proxy
177         // itself is not subject to auto-proxying! Only its target bean is.
178         // Not sure if this is needed....
179         pf.addInterface(AopInfrastructureBean.class);
180 
181         return pf.getProxy(cbf.getBeanClassLoader());
182     }
183 
184     /**
185      * Given a proxy object, return the underlying object that it currently refers to.
186      * <p>
187      * This method is currently experimental; it works for the current Spring implementation
188      * of Orchestra but at the current time it is not known whether this functionality can
189      * be implemented for all dependency-injection frameworks. If it does, then it might
190      * later make sense to promote this up to the public Orchestra API.
191      * <p>
192      * Note that invoking this method will create the "target object" if it does not yet exist.
193      * There is currently no way of testing whether this will happen, ie null will never
194      * be returned from this method.
195      * 
196      * @since 1.3
197      */
198     public static Object getTargetObject(Object proxy) throws Exception
199     {
200         Advised advised = (Advised) proxy;
201         TargetSource targetSource = advised.getTargetSource();
202         Object target = targetSource.getTarget();
203         
204         // Possibly we could add a method on the ScopedBeanTargetSource class to test
205         // whether the target bean exists. Then here we could cast TargetSource to
206         // ScopedBeanTargetSource and return null if the target does not exist. This
207         // might be useful, but let's leave that until someone actually has a use-case
208         // for that functionality.
209         return target;
210     }
211 }