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 }