/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.orchestra.viewController.spring;
import org.apache.myfaces.orchestra.conversation.Conversation;
import org.apache.myfaces.orchestra.conversation.ConversationContext;
import org.apache.myfaces.orchestra.conversation.spring.AbstractSpringOrchestraScope;
import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
import org.apache.myfaces.orchestra.lib.OrchestraException;
import org.apache.myfaces.orchestra.viewController.DefaultViewControllerManager;
import org.apache.myfaces.orchestra.viewController.ViewControllerManager;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.ConfigurableApplicationContext;
/**
* This hooks into the Spring2.x scope-handling mechanism to provide a dummy scope type
* which will place a bean configured for it into the same conversation that the current
* viewController lives in.
* <p>
* To use this, in the spring config file add an element with id=N that defines a spring
* scope with this class as its controller. Then define managed beans with scope="N". When
* code references such a bean, then the current "view controller" bean is located
* (ie the bean handling lifecycle events for the current view), and the instance of
* the target bean from the same conversation is returned. If no such instance currently
* exists, then one is created and added to that conversation (even when an instance
* already exists in a different scope).
* <p>
* Note that this means a bean configured with a scope of this type will actually
* have a separate instance per conversation.
* <p>
* In particular, this works well with spring aop-proxy, where the proxy looks up the
* bean on each method call, and so always returns the instance in the conversation
* associated with the current view.
* <p>
* One use for this is implementing custom JSF converters or validators that access
* persistent objects. When accessing the database they need to use the same
* PersistenceContext that the beans handing this view use. Defining the converter
* using this scope type ensures that this happens.
* <p>
* It is an error (ie an exception is thrown) if a bean of this scope is referenced
* but there is no "view controller" bean associated with the current view.
*/
public class SpringViewControllerScope extends AbstractSpringOrchestraScope
{
private final static ViewControllerManager DEFAULT_VCM = new DefaultViewControllerManager();
public SpringViewControllerScope()
{
}
public Conversation createConversation(ConversationContext context, String conversationName)
{
throw new IllegalStateException(
"The viewController scope is not supposed to start a conversation on its own. " +
"Conversation to start: " + conversationName);
}
protected void assertSameScope(String beanName, Conversation conversation)
{
// since we do not start any conversation, there is no need to check
// if this scope uses the same conversationFactory
}
/**
* Find the conversation-controller bean for the current view, then return the conversation that
* is configured for that controller bean.
* <p>
* The parameter is completely ignored; the conversation-name returned is that associated with the
* controller bean, not the specified bean at all.
*/
public String getConversationNameForBean(String beanName)
{
ViewControllerManager viewControllerManager = getViewControllerManager();
String viewId = FrameworkAdapter.getCurrentInstance().getCurrentViewId();
String viewControllerName = viewControllerManager.getViewControllerName(viewId);
if (viewControllerName == null)
{
// The current view does not have any bean that is its "view controller", ie
// which handles the lifecycle events for that view. Therefore we cannot
// do anything more here...
throw new OrchestraException(
"Error while processing bean " + beanName
+ ": no view controller name found for view " + viewId);
}
// Look up the definition with the specified name.
ConfigurableApplicationContext appContext = getApplicationContext();
ConfigurableListableBeanFactory beanFactory = appContext.getBeanFactory();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(viewControllerName);
if (beanDefinition.getBeanClassName().equals(ScopedProxyFactoryBean.class.getName()))
{
// The BeanDefinition we found is one that contains a nested aop:scopedProxy tag.
// In this case, Spring has actually renamed the original BeanDefinition which
// contains the data we need. So here we need to look up the renamed definition.
//
// It would be nice if the fake definition that Spring creates had some way of
// fetching the definition it wraps, but instead it appears that we need to
// rely on the magic string prefix...
beanDefinition = beanFactory.getBeanDefinition("scopedTarget." + viewControllerName);
}
String scopeName = beanDefinition.getScope();
if (scopeName == null)
{
// should never happen
throw new OrchestraException(
"Error while processing bean " + beanName
+ ": view controller " + viewControllerName + " has no scope.");
}
Scope registeredScope = beanFactory.getRegisteredScope(scopeName);
if (registeredScope == null)
{
throw new OrchestraException(
"Error while processing bean " + beanName
+ ": view controller " + viewControllerName
+ " has unknown scope " + scopeName);
}
if (registeredScope instanceof AbstractSpringOrchestraScope)
{
return ((AbstractSpringOrchestraScope) registeredScope).getConversationNameForBean(viewControllerName);
}
throw new OrchestraException(
"Error while processing bean " + beanName
+ ": the scope " + scopeName
+ " should be of type AbstractSpringOrchestraScope"
+ ", but is type " + registeredScope.getClass().getName());
}
/**
* Look for a Spring bean definition that defines a custom ViewControllerManager;
* if not found then return the default instance.
* <p>
* Never returns null.
*/
private ViewControllerManager getViewControllerManager()
{
try
{
return (ViewControllerManager)
getApplicationContext().getBean(
ViewControllerManager.VIEW_CONTROLLER_MANAGER_NAME,
ViewControllerManager.class);
}
catch(NoSuchBeanDefinitionException e)
{
return DEFAULT_VCM;
}
}
}