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 }