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      private final static ViewControllerManager DEFAULT_VCM = new DefaultViewControllerManager();
67  
68      public SpringViewControllerScope()
69      {
70      }
71  
72      public Conversation createConversation(ConversationContext context, String conversationName)
73      {
74          throw new IllegalStateException(
75              "The viewController scope is not supposed to start a conversation on its own. " +
76              "Conversation to start: " + conversationName);
77      }
78  
79      protected void assertSameScope(String beanName, Conversation conversation)
80      {
81          // since we do not start any conversation, there is no need to check
82          // if this scope uses the same conversationFactory
83      }
84  
85      /**
86       * Find the conversation-controller bean for the current view, then return the conversation that
87       * is configured for that controller bean.
88       * <p>
89       * The parameter is completely ignored; the conversation-name returned is that associated with the
90       * controller bean, not the specified bean at all.
91       */
92      public String getConversationNameForBean(String beanName)
93      {
94          ViewControllerManager viewControllerManager = getViewControllerManager();
95          String viewId = FrameworkAdapter.getCurrentInstance().getCurrentViewId();
96          String viewControllerName = viewControllerManager.getViewControllerName(viewId);
97          if (viewControllerName == null)
98          {
99              // The current view does not have any bean that is its "view controller", ie
100             // which handles the lifecycle events for that view. Therefore we cannot 
101             // do anything more here...
102             throw new OrchestraException(
103                 "Error while processing bean " + beanName
104                 + ": no view controller name found for view " + viewId);
105         }
106 
107         // Look up the definition with the specified name.
108         ConfigurableApplicationContext appContext = getApplicationContext();
109         ConfigurableListableBeanFactory beanFactory = appContext.getBeanFactory();
110         BeanDefinition beanDefinition = beanFactory.getBeanDefinition(viewControllerName);
111 
112         if (beanDefinition.getBeanClassName().equals(ScopedProxyFactoryBean.class.getName()))
113         {
114             // The BeanDefinition we found is one that contains a nested aop:scopedProxy tag.
115             // In this case, Spring has actually renamed the original BeanDefinition which
116             // contains the data we need. So here we need to look up the renamed definition.
117             //
118             // It would be nice if the fake definition that Spring creates had some way of
119             // fetching the definition it wraps, but instead it appears that we need to
120             // rely on the magic string prefix...
121             beanDefinition = beanFactory.getBeanDefinition("scopedTarget." + viewControllerName);
122         }
123 
124         String scopeName = beanDefinition.getScope();
125         
126         if (scopeName == null)
127         {
128             // should never happen
129             throw new OrchestraException(
130                 "Error while processing bean " + beanName
131                 + ": view controller " + viewControllerName + " has no scope."); 
132         }
133 
134         Scope registeredScope = beanFactory.getRegisteredScope(scopeName);
135         if (registeredScope == null)
136         {
137             throw new OrchestraException(
138                 "Error while processing bean " + beanName
139                 + ": view controller " + viewControllerName 
140                 + " has unknown scope " + scopeName); 
141         }
142 
143         if (registeredScope instanceof AbstractSpringOrchestraScope)
144         {
145             return ((AbstractSpringOrchestraScope) registeredScope).getConversationNameForBean(viewControllerName);
146         }
147 
148         throw new OrchestraException(
149             "Error while processing bean " + beanName
150             + ": the scope " + scopeName
151             + " should be of type AbstractSpringOrchestraScope"
152             + ", but is type " + registeredScope.getClass().getName());
153     }
154 
155     /**
156      * Look for a Spring bean definition that defines a custom ViewControllerManager;
157      * if not found then return the default instance.
158      * <p>
159      * Never returns null.
160      */
161     private ViewControllerManager getViewControllerManager()
162     {
163         try
164         {
165             return (ViewControllerManager)
166                 getApplicationContext().getBean(
167                     ViewControllerManager.VIEW_CONTROLLER_MANAGER_NAME,
168                     ViewControllerManager.class);
169         }
170         catch(NoSuchBeanDefinitionException e)
171         {
172             return DEFAULT_VCM;
173         }
174     }
175 }