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;
21
22 import org.aopalliance.intercept.MethodInterceptor;
23 import org.aopalliance.intercept.MethodInvocation;
24
25 import java.io.Serializable;
26
27 /**
28 * An advice which is added to all conversation scoped beans.
29 *
30 * <p>It does the following:
31 * <ul>
32 * <li>Maintain the {@link Conversation#getCurrentInstance()}</li>
33 * <li>End the conversation if it has been invalidated</li>
34 * </ul>
35 * </p>
36 *
37 * <p>A bean that is declared as belonging to a particular conversation is
38 * always wrapped by the IOC system in a proxy object. When any method is
39 * called on that proxy, the call is forwarded to the "invoke" method here.
40 * This class then ensures that the "current conversation" is set to the
41 * conversation configured for that target bean. The result is that the real
42 * bean can call Conversation.getCurrentInstance() and always receives a
43 * reference to the conversation object that has been configured for it.</p>
44 *
45 * <p>In addition, on return from the target bean method, this object
46 * checks whether the conversation has been marked as invalid. If so (and
47 * the conversation is not in use by something further up the call stack)
48 * then destroy the conversation object.</p>
49 *
50 * <p>Attempting to invoke a methods on a bean that is associated with a
51 * conversation that has been destroyed will cause an IllegalStateException
52 * to be thrown. This is a safety-net to detect cases where a reference to a
53 * proxy object bound to a specific conversation has been kept for longer than the
54 * conversation lifetime. This is bad, as it (a) attempts to access a "stale"
55 * conversation, and (b) prevents the destroyed conversation from being
56 * garbage-collected (although all the beans in it will already have been
57 * removed).</p>
58 */
59 public class CurrentConversationAdvice implements MethodInterceptor, Serializable
60 {
61 private final CurrentConversationInfo conversationInfo;
62
63 public CurrentConversationAdvice(Conversation conversation, String beanName)
64 {
65 conversationInfo = new CurrentConversationInfo(conversation, beanName);
66 }
67
68 public Object invoke(MethodInvocation methodInvocation) throws Throwable
69 {
70 // Save the current conversation so it can be restored later.
71 // Note that for "top-level" calls, the saved value is null.
72 CurrentConversationInfo previous = Conversation.getCurrentInstanceInfo();
73
74 // Set the appropriate conversation for the target object.
75 Conversation.setCurrentInstance(conversationInfo);
76
77 try
78 {
79 // Note: Conversation.enterConversation throws IllegalStateException
80 // when the conversation has been destroyed (invalidated).
81 Conversation conversation = conversationInfo.getConversation();
82 conversation.enterConversation();
83
84 return methodInvocation.proceed();
85 }
86 finally
87 {
88 // Always restore the previous conversation (which may be null).
89 // Do this before anything else in case other methods throw exceptions.
90 Conversation.setCurrentInstance(previous);
91
92 conversationInfo.getConversation().leaveConversation();
93
94 if (conversationInfo.getConversation().isQueueInvalid())
95 {
96 conversationInfo.getConversation().invalidate();
97 }
98 }
99 }
100 }