View Javadoc

    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 }