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 }