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.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 }