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.conversation.spring;
21
22 import org.apache.myfaces.orchestra.conversation.Conversation;
23 import org.apache.myfaces.orchestra.conversation.ConversationAccessLifetimeAspect;
24 import org.apache.myfaces.orchestra.conversation.ConversationAspect;
25 import org.apache.myfaces.orchestra.conversation.ConversationContext;
26 import org.apache.myfaces.orchestra.conversation.ConversationTimeoutableAspect;
27
28 /**
29 * Handles creation and lookup of any bean whose bean-definition specifies a scope
30 * that maps to an instance of this type.
31 * <p>
32 * A scope bean handles Spring-specific callbacks in order to locate or create any beans whose definition
33 * specifies that scope. A scope can also be thought of as a "conversation template", as this object
34 * is responsible for creating a conversation when one is needed but does not yet exist.
35 * <p>
36 * <h2>Example</h2>
37 * A sample configuration for a conversation scope with persistence:
38 * <pre>
39 * <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
40 * <property name="scopes">
41 * <map>
42 * <entry key="conversation.manual">
43 * <bean class="org.apache.myfaces.orchestra.conversation.spring.SpringConversationScope">
44 * <property name="advices">
45 * <list>
46 * <ref bean="persistentContextConversationInterceptor" />
47 * </list>
48 * </property>
49 * </bean>
50 * </entry>
51 * </map>
52 * </property>
53 * </bean>
54 *
55 *
56 * <bean id="persistentContextConversationInterceptor"
57 * class="org.apache.myfaces.orchestra.conversation.spring.PersistenceContextConversationInterceptor">
58 * <property name="persistenceContextFactory" ref="yourPersistentContextFactory" />
59 * </bean>
60 * </pre>
61 * <p>
62 *
63 * <h2>Conversation properties</h2>
64 * The following properties can be defined on a scope and then apply to any conversation that is created
65 * to hold a bean of this scope:
66 * <ul>
67 * <li>lifetime: may be "manual" or "access". If not specified, then defaults to "manual".</li>
68 * <li>timeout: a time period (in minutes) after which inactive conversations are terminated.
69 * If not specified, then inactive conversations are never automatically terminated.</li>
70 * </ul>
71 *
72 * <h2>Other Notes</h2>
73 * If the bean definition does not specify a conversation name, then the bean name is used
74 * as the conversation name.
75 * <p>
76 * As shown above, a list of AOP Advices can be specified for the scope, in which case each of the
77 * advices gets configured for any bean declared with this scope.
78 */
79 public class SpringConversationScope extends AbstractSpringOrchestraScope
80 {
81 /** @deprecated Use LIFETIME_ACCESS instead. */
82 public static final String LIFETIME_FLASH = "flash";
83
84 public static final String LIFETIME_ACCESS = "access";
85 public static final String LIFETIME_MANUAL = "manual";
86
87 private Integer timeout;
88 private String lifetime = LIFETIME_MANUAL;
89
90 /**
91 * See {@link #setTimeout}.
92 */
93 public Integer getTimeout()
94 {
95 return timeout;
96 }
97
98 /**
99 * The timeout in minutes when the conversation will end.
100 * See {@link ConversationTimeoutableAspect#timeout} for the default timeout.
101 */
102 public void setTimeout(Integer timeout)
103 {
104 this.timeout = timeout;
105 }
106
107 /**
108 * See {@link #setLifetime}.
109 */
110 public String getLifetime()
111 {
112 return lifetime;
113 }
114
115 /**
116 * Must be one of "manual" or "access".
117 * <p>
118 * Defaults to "manual".
119 * <p>
120 * Note that "flash" is also supported as an alias for "access", for
121 * reasons of backwards compatibility with release 1.0.
122 */
123 public void setLifetime(String lifetime)
124 {
125 // Check for validity here so that an exception gets thrown on startup
126 // rather than when the first bean in this scope is created.
127 if (LIFETIME_FLASH.equals(lifetime))
128 {
129 this.lifetime = LIFETIME_ACCESS;
130 }
131 else if (LIFETIME_ACCESS.equals(lifetime))
132 {
133 this.lifetime = LIFETIME_ACCESS;
134 }
135 else if (LIFETIME_MANUAL.equals(lifetime))
136 {
137 this.lifetime = LIFETIME_MANUAL;
138 }
139 else
140 {
141 throw new IllegalArgumentException("Invalid lifetime:" + lifetime);
142 }
143 }
144
145 /**
146 * Implementation of ConversationFactory interface.
147 */
148 public Conversation createConversation(ConversationContext context, String name)
149 {
150 Conversation conversation = new Conversation(context, name, this);
151 conversation.setBinder(new SpringConversationBinder(this, conversation));
152
153 // invoke child scope classes so they can add any aspects they desire.
154 initAspects(conversation);
155
156 return conversation;
157 }
158
159 /**
160 * Add aspects to a newly-created conversation.
161 * <p>
162 * Subclasses are expected to call super.initAspects, then make
163 * zero or more calls to conversation.addAspect.
164 */
165 protected void initAspects(Conversation conversation)
166 {
167 // conversation timeout
168 if (timeout != null)
169 {
170 long timeoutMsecs = timeout.longValue() * 60L * 1000L;
171 ConversationTimeoutableAspect aspect = new ConversationTimeoutableAspect(conversation);
172 aspect.setTimeout(timeoutMsecs);
173 conversation.addAspect(aspect);
174 }
175
176 // conversation lifetime
177 if (LIFETIME_ACCESS.equals(lifetime))
178 {
179 ConversationAspect aspect = new ConversationAccessLifetimeAspect(conversation);
180 conversation.addAspect(aspect);
181 }
182 }
183
184 /**
185 * Mark the specified conversation as having been accessed.
186 * <p>
187 * This affects conversation timeouts, and the removal of access-scoped conversations.
188 */
189 protected void notifyAccessConversation(Conversation conversation)
190 {
191 super.notifyAccessConversation(conversation);
192
193 ConversationAccessLifetimeAspect aspect = (ConversationAccessLifetimeAspect)
194 conversation.getAspect(ConversationAccessLifetimeAspect.class);
195 if (aspect != null)
196 {
197 aspect.markAsAccessed();
198 }
199 }
200 }