/*
* 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.conversation.spring;
import org.apache.myfaces.orchestra.conversation.Conversation;
import org.apache.myfaces.orchestra.conversation.ConversationAccessLifetimeAspect;
import org.apache.myfaces.orchestra.conversation.ConversationAspect;
import org.apache.myfaces.orchestra.conversation.ConversationContext;
import org.apache.myfaces.orchestra.conversation.ConversationTimeoutableAspect;
/**
* Handles creation and lookup of any bean whose bean-definition specifies a scope
* that maps to an instance of this type.
* <p>
* A scope bean handles Spring-specific callbacks in order to locate or create any beans whose definition
* specifies that scope. A scope can also be thought of as a "conversation template", as this object
* is responsible for creating a conversation when one is needed but does not yet exist.
* <p>
* <h2>Example</h2>
* A sample configuration for a conversation scope with persistence:
* <pre>
* <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
* <property name="scopes">
* <map>
* <entry key="conversation.manual">
* <bean class="org.apache.myfaces.orchestra.conversation.spring.SpringConversationScope">
* <property name="advices">
* <list>
* <ref bean="persistentContextConversationInterceptor" />
* </list>
* </property>
* </bean>
* </entry>
* </map>
* </property>
* </bean>
*
*
* <bean id="persistentContextConversationInterceptor"
* class="org.apache.myfaces.orchestra.conversation.spring.PersistenceContextConversationInterceptor">
* <property name="persistenceContextFactory" ref="yourPersistentContextFactory" />
* </bean>
* </pre>
* <p>
*
* <h2>Conversation properties</h2>
* The following properties can be defined on a scope and then apply to any conversation that is created
* to hold a bean of this scope:
* <ul>
* <li>lifetime: may be "manual" or "access". If not specified, then defaults to "manual".</li>
* <li>timeout: a time period (in minutes) after which inactive conversations are terminated.
* If not specified, then inactive conversations are never automatically terminated.</li>
* </ul>
*
* <h2>Other Notes</h2>
* If the bean definition does not specify a conversation name, then the bean name is used
* as the conversation name.
* <p>
* As shown above, a list of AOP Advices can be specified for the scope, in which case each of the
* advices gets configured for any bean declared with this scope.
*/
public class SpringConversationScope extends AbstractSpringOrchestraScope
{
/** @deprecated Use LIFETIME_ACCESS instead. */
public static final String LIFETIME_FLASH = "flash";
public static final String LIFETIME_ACCESS = "access";
public static final String LIFETIME_MANUAL = "manual";
private Integer timeout;
private String lifetime = LIFETIME_MANUAL;
/**
* See {@link #setTimeout}.
*/
public Integer getTimeout()
{
return timeout;
}
/**
* The timeout in minutes when the conversation will end.
* See {@link ConversationTimeoutableAspect#timeout} for the default timeout.
*/
public void setTimeout(Integer timeout)
{
this.timeout = timeout;
}
/**
* See {@link #setLifetime}.
*/
public String getLifetime()
{
return lifetime;
}
/**
* Must be one of "manual" or "access".
* <p>
* Defaults to "manual".
* <p>
* Note that "flash" is also supported as an alias for "access", for
* reasons of backwards compatibility with release 1.0.
*/
public void setLifetime(String lifetime)
{
// Check for validity here so that an exception gets thrown on startup
// rather than when the first bean in this scope is created.
if (LIFETIME_FLASH.equals(lifetime))
{
this.lifetime = LIFETIME_ACCESS;
}
else if (LIFETIME_ACCESS.equals(lifetime))
{
this.lifetime = LIFETIME_ACCESS;
}
else if (LIFETIME_MANUAL.equals(lifetime))
{
this.lifetime = LIFETIME_MANUAL;
}
else
{
throw new IllegalArgumentException("Invalid lifetime:" + lifetime);
}
}
/**
* Implementation of ConversationFactory interface.
*/
public Conversation createConversation(ConversationContext context, String name)
{
Conversation conversation = new Conversation(context, name, this);
conversation.setBinder(new SpringConversationBinder(this, conversation));
// invoke child scope classes so they can add any aspects they desire.
initAspects(conversation);
return conversation;
}
/**
* Add aspects to a newly-created conversation.
* <p>
* Subclasses are expected to call super.initAspects, then make
* zero or more calls to conversation.addAspect.
*/
protected void initAspects(Conversation conversation)
{
// conversation timeout
if (timeout != null)
{
long timeoutMsecs = timeout.longValue() * 60L * 1000L;
ConversationTimeoutableAspect aspect = new ConversationTimeoutableAspect(conversation);
aspect.setTimeout(timeoutMsecs);
conversation.addAspect(aspect);
}
// conversation lifetime
if (LIFETIME_ACCESS.equals(lifetime))
{
ConversationAspect aspect = new ConversationAccessLifetimeAspect(conversation);
conversation.addAspect(aspect);
}
}
/**
* Mark the specified conversation as having been accessed.
* <p>
* This affects conversation timeouts, and the removal of access-scoped conversations.
*/
protected void notifyAccessConversation(Conversation conversation)
{
super.notifyAccessConversation(conversation);
ConversationAccessLifetimeAspect aspect = (ConversationAccessLifetimeAspect)
conversation.getAspect(ConversationAccessLifetimeAspect.class);
if (aspect != null)
{
aspect.markAsAccessed();
}
}
}