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.viewController.spring;
    21
    22  import org.apache.myfaces.orchestra.conversation.Conversation;
    23  import org.apache.myfaces.orchestra.conversation.ConversationContext;
    24  import org.apache.myfaces.orchestra.conversation.spring.AbstractSpringOrchestraScope;
    25  import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
    26  import org.apache.myfaces.orchestra.lib.OrchestraException;
    27  import org.apache.myfaces.orchestra.viewController.DefaultViewControllerManager;
    28  import org.apache.myfaces.orchestra.viewController.ViewControllerManager;
    29  import org.springframework.aop.scope.ScopedProxyFactoryBean;
    30  import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    31  import org.springframework.beans.factory.config.BeanDefinition;
    32  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    33  import org.springframework.beans.factory.config.Scope;
    34  import org.springframework.context.ConfigurableApplicationContext;
    35
    36  /**
    37   * This hooks into the Spring2.x scope-handling mechanism to provide a dummy scope type
    38   * which will place a bean configured for it into the same conversation that the current
    39   * viewController lives in.
    40   * <p>
    41   * To use this, in the spring config file add an element with id=N that defines a spring
    42   * scope with this class as its controller. Then define managed beans with scope="N". When
    43   * code references such a bean, then the current "view controller" bean is located
    44   * (ie the bean handling lifecycle events for the current view), and the instance of
    45   * the target bean from the same conversation is returned. If no such instance currently
    46   * exists, then one is created and added to that conversation (even when an instance
    47   * already exists in a different scope).
    48   * <p>
    49   * Note that this means a bean configured with a scope of this type will actually
    50   * have a separate instance per conversation.
    51   * <p>
    52   * In particular, this works well with spring aop-proxy, where the proxy looks up the
    53   * bean on each method call, and so always returns the instance in the conversation
    54   * associated with the current view.
    55   * <p>
    56   * One use for this is implementing custom JSF converters or validators that access
    57   * persistent objects. When accessing the database they need to use the same
    58   * PersistenceContext that the beans handing this view use. Defining the converter
    59   * using this scope type ensures that this happens.
    60   * <p>
    61   * It is an error (ie an exception is thrown) if a bean of this scope is referenced
    62   * but there is no "view controller" bean associated with the current view.
    63   */
    64  public class SpringViewControllerScope extends AbstractSpringOrchestraScope
    65  {
    66      // should this really be static? Are all the classes it references really static?
    67      private final static ViewControllerManager DEFAULT_VCM = new DefaultViewControllerManager();
    68
    69      public SpringViewControllerScope()
    70      {
    71      }
    72
    73      public Conversation createConversation(ConversationContext context, String conversationName)
    74      {
    75          throw new IllegalStateException(
    76              "The viewController scope is not supposed to start a conversation on its own. " +
    77              "Conversation to start: " + conversationName);
    78      }
    79
    80      protected void assertSameScope(String beanName, Conversation conversation)
    81      {
    82          // since we do not start any conversation, there is no need to check
    83          // if this scope uses the same conversationFactory
    84      }
    85
    86      /**
    87       * Find the conversation-controller bean for the current view, then return the conversation that
    88       * is configured for that controller bean.
    89       * <p>
    90       * The parameter is completely ignored; the conversation-name returned is that associated with the
    91       * controller bean, not the specified bean at all.
    92       */
    93      public String getConversationNameForBean(String beanName)
    94      {
    95          ViewControllerManager viewControllerManager = getViewControllerManager();
    96          String viewId = FrameworkAdapter.getCurrentInstance().getCurrentViewId();
    97          String viewControllerName = viewControllerManager.getViewControllerName(viewId);
    98          if (viewControllerName == null)
    99          {
    100             // The current view does not have any bean that is its "view controller", ie
    101             // which handles the lifecycle events for that view. Therefore we cannot
    102             // do anything more here...
    103             throw new OrchestraException(
    104                 "Error while processing bean " + beanName
    105                 + ": no view controller name found for view " + viewId);
    106         }
    107
    108         // Look up the definition with the specified name.
    109         ConfigurableApplicationContext appContext = getApplicationContext();
    110         ConfigurableListableBeanFactory beanFactory = appContext.getBeanFactory();
    111         BeanDefinition beanDefinition = beanFactory.getBeanDefinition(viewControllerName);
    112
    113         if (beanDefinition.getBeanClassName().equals(ScopedProxyFactoryBean.class.getName()))
    114         {
    115             // The BeanDefinition we found is one that contains a nested aop:scopedProxy tag.
    116             // In this case, Spring has actually renamed the original BeanDefinition which
    117             // contains the data we need. So here we need to look up the renamed definition.
    118             //
    119             // It would be nice if the fake definition that Spring creates had some way of
    120             // fetching the definition it wraps, but instead it appears that we need to
    121             // rely on the magic string prefix...
    122             beanDefinition = beanFactory.getBeanDefinition("scopedTarget." + viewControllerName);
    123         }
    124
    125         String scopeName = beanDefinition.getScope();
    126
    127         if (scopeName == null)
    128         {
    129             // should never happen
    130             throw new OrchestraException(
    131                 "Error while processing bean " + beanName
    132                 + ": view controller " + viewControllerName + " has no scope.");
    133         }
    134
    135         Scope registeredScope = beanFactory.getRegisteredScope(scopeName);
    136         if (registeredScope == null)
    137         {
    138             throw new OrchestraException(
    139                 "Error while processing bean " + beanName
    140                 + ": view controller " + viewControllerName
    141                 + " has unknown scope " + scopeName);
    142         }
    143
    144         if (registeredScope instanceof AbstractSpringOrchestraScope)
    145         {
    146             return ((AbstractSpringOrchestraScope) registeredScope).getConversationNameForBean(viewControllerName);
    147         }
    148
    149         throw new OrchestraException(
    150             "Error while processing bean " + beanName
    151             + ": the scope " + scopeName
    152             + " should be of type AbstractSpringOrchestraScope"
    153             + ", but is type " + registeredScope.getClass().getName());
    154     }
    155
    156     /**
    157      * Look for a Spring bean definition that defines a custom ViewControllerManager;
    158      * if not found then return the default instance.
    159      * <p>
    160      * Never returns null.
    161      */
    162     private ViewControllerManager getViewControllerManager()
    163     {
    164         try
    165         {
    166             return (ViewControllerManager)
    167                 getApplicationContext().getBean(
    168                     ViewControllerManager.VIEW_CONTROLLER_MANAGER_NAME,
    169                     ViewControllerManager.class);
    170         }
    171         catch(NoSuchBeanDefinitionException e)
    172         {
    173             return DEFAULT_VCM;
    174         }
    175     }
    176 }