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.jsf.lib;
21  
22  import java.util.Collection;
23  
24  import javax.faces.FacesException;
25  import javax.faces.component.StateHolder;
26  import javax.faces.component.UIComponentBase;
27  import javax.faces.context.FacesContext;
28  import javax.faces.el.EvaluationException;
29  import javax.faces.el.MethodBinding;
30  import javax.faces.el.MethodNotFoundException;
31  
32  import org.apache.myfaces.orchestra.conversation.ConversationManager;
33  import org.apache.myfaces.orchestra.conversation.ConversationUtils;
34  
35  /**
36   * A facade for the original method binding to deal with end conversation conditions.
37   * <p>
38   * This class implements MethodBinding, ie represents an EL expression string that specifies
39   * a method to call. It is expected to be used when invoking action methods when the current
40   * conversation should be closed upon certain results of the action.
41   * <p>
42   * This facade also enhances error-handling for action methods. If the invoked method throws
43   * an exception of any kind, and an errorOutcome value has been specified then the errorOutcome
44   * is returned instead of allowing the exception to propagate. The exception that occurred is
45   * reported to the ConversationMessager object associated with the conversation, so it can
46   * choose whether and how to present the error to the user.
47   */
48  public class _EndConversationMethodBindingFacade extends MethodBinding implements StateHolder
49  {
50      private MethodBinding original;
51      private String conversationName;
52      private Collection onOutcomes;
53      private String errorOutcome;
54  
55      private boolean _transient = false;
56  
57      public _EndConversationMethodBindingFacade()
58      {
59      }
60  
61      /**
62       * Constructor.
63       *
64       * @param conversation is the name of the conversation to conditionally be closed.
65       *
66       * @param onOutcomes is a collection of navigation strings that may be returned from the
67       * invoked method. One of the following rules is then used to determine whether the conversation
68       * is ended or not:
69       * <ul>
70       * <li>If there was no action to invoke, end the conversation, else</li>
71       * <li>If the action returned null, do not end the conversation, else</li>
72       * <li>If the onOutcomes list is null or empty then end the conversation, else</li>
73       * <li>If the returned value is in the onOutcomes list, then end the conversation, else</li>
74       * <li>do not end the conversation.</li>
75       * </ul>
76       *
77       * @param original is the EL expression to be invoked.
78       *
79       * @param errorOutcome is a JSF navigation string to be returned if the action method
80       * throws an exception of any kind. This navigation value is checked against the onOutcomes
81       * values just as if the action method had actually returned this value. When not specified,
82       * then on exception the current conversation is not ended.
83       */
84      public _EndConversationMethodBindingFacade(
85          String conversation,
86          Collection onOutcomes,
87          MethodBinding original,
88          String errorOutcome)
89      {
90          this.original = original;
91          this.conversationName = conversation;
92          this.onOutcomes = onOutcomes;
93          this.errorOutcome = errorOutcome;
94      }
95  
96      public String getConversationName()
97      {
98          return conversationName;
99      }
100 
101     public String getExpressionString()
102     {
103         if (original == null)
104         {
105             return null;
106         }
107         return original.getExpressionString();
108     }
109 
110     public Class getType(FacesContext context) throws MethodNotFoundException
111     {
112         if (original == null)
113         {
114             return null;
115         }
116         return original.getType(context);
117     }
118 
119     public Object invoke(FacesContext context, Object[] values) throws EvaluationException, MethodNotFoundException
120     {
121         Object returnValue = null;
122         //noinspection CatchGenericClass
123         try
124         {
125             if (original != null)
126             {
127                 returnValue = original.invoke(context, values);
128             }
129         }
130         catch (Throwable t)
131         {
132             ConversationManager conversationManager = ConversationManager.getInstance();
133 
134             if (errorOutcome != null)
135             {
136                 // Suppress the exception, and act as if errorOutcome had been returned. 
137 
138                 conversationManager.getMessager().setConversationException(t);
139                 returnValue = errorOutcome;
140             }
141             else
142             {
143                 // When no errorOutcomes are specified, then there is nothing to check against
144                 // the onOutcomes list. 
145                 //
146                 // Note that in this case the conversation is NEVER ended. It is debatable what
147                 // the correct behaviour should be here. If this action wrapper was not present
148                 // then the conversation would not be terminated, so an instance with no
149                 // errorOutcomes specified is consistent with that. In any case, the user can
150                 // easily get the alternate behaviour by simply specifying an errorOutcome and
151                 // adding that to the onOutcomes list.
152 
153                 returnValue = null; // do not end conversation
154                 throw new FacesException(t);
155             }
156         }
157         finally
158         {
159             boolean endConversation;
160             if (original == null)
161             {
162                 endConversation = true;
163             }
164             else if (returnValue == null)
165             {
166                 endConversation = false;
167             }
168             else if (onOutcomes == null || onOutcomes.isEmpty())
169             {
170                 endConversation = true;
171             }
172             else
173             {
174                 endConversation = onOutcomes.contains(returnValue);
175             }
176 
177             if (endConversation)
178             {
179                 ConversationUtils.invalidateIfExists(conversationName);
180             }
181         }
182         return returnValue;
183     }
184 
185     /** Required by StateHolder interface. */
186     public void setTransient(boolean newTransientValue)
187     {
188         _transient = newTransientValue;
189     }
190 
191     /** Required by StateHolder interface. */
192     public boolean isTransient()
193     {
194         return _transient;
195     }
196 
197     public void restoreState(FacesContext context, Object states)
198     {
199         Object[] state = (Object[]) states;
200 
201         original = (MethodBinding) UIComponentBase.restoreAttachedState(context, state[0]);
202         conversationName = (String) state[1];
203         onOutcomes = (Collection) state[2];
204         errorOutcome = (String) state[3];
205     }
206 
207     public Object saveState(FacesContext context)
208     {
209         return new Object[]
210             {
211                 UIComponentBase.saveAttachedState(context, original),
212                 conversationName,
213                 onOutcomes,
214                 errorOutcome
215             };
216     }
217 }