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.components;
21
22 import java.io.IOException;
23 import java.util.Arrays;
24 import java.util.Collection;
25
26 import javax.faces.component.UICommand;
27 import javax.faces.context.FacesContext;
28 import javax.faces.el.MethodBinding;
29 import javax.faces.el.ValueBinding;
30
31 import org.apache.myfaces.orchestra.conversation.ConversationUtils;
32 import org.apache.myfaces.orchestra.conversation.jsf._JsfConversationUtils;
33 import org.apache.myfaces.orchestra.conversation.jsf.lib._EndConversationMethodBindingFacade;
34 import org.apache.myfaces.shared_orchestra.util.StringUtils;
35
36 /**
37 * Can be used to end a manual-scope conversation, and optionally handles exceptions thrown
38 * by action methods.
39 * <p>
40 * When nested within a UICommand component (eg a commandLink or commandButton) the specified
41 * conversation will be ended after the method invoked by the parent component is executed.
42 * <pre>
43 * <h:commandLink action="#{backing.saveAction}">
44 * <orchestra:endConversation name="conversation1" onOutcome="success" />
45 * </h:commandLink>
46 * </pre>
47 * <p>
48 * The "name" attribute is mandatory, and specifies which conversation is to be ended.
49 * The optional attributes are:
50 * <ul>
51 * <li>onOutcome</li>
52 * <li>errorOutcome</li>
53 * </ul>
54 *
55 * <h2>onOutcome</h2>
56 *
57 * This is a string or comma-separated list of strings. After invoking the action
58 * associated with the nearest ancestor UICommand component, the following rules
59 * are executed:
60 * <ul>
61 * <li>If there is no ancestor UICommand component then end the conversation, else</li>
62 * <li>If the action returned null, then do not end the conversation, else</li>
63 * <li>If the onOutcomes list is null or empty then end the conversation, else</li>
64 * <li>If the returned value is in the onOutcomes list then end the conversation, else</li>
65 * <li>do not end the conversation.</li>
66 * </ul>
67 *
68 * Note in particular that when this component has no enclosing UICommand component, then
69 * the specified conversation is always terminated. This is often useful on the "confirmation"
70 * page of a wizard-style page sequence.
71 *
72 * <h2>errorOutcome</h2>
73 *
74 * In case of an exception being thrown by the action method, use the given outcome as the
75 * new outcome so normal navigation to a specified page can occur instead of showing the
76 * default errorPage. This value is checked against the onOutcome list to determine whether
77 * the specified conversation should be terminated when an exception occurs. If an exception
78 * occurs, but no errorOutcome is specified then the conversation is never terminated.
79 */
80 public class UIEndConversation extends AbstractConversationComponent
81 {
82 public static final String COMPONENT_TYPE = "org.apache.myfaces.orchestra.EndConversation";
83
84 private String onOutcome;
85 private String errorOutcome;
86
87 private boolean inited = false;
88
89 public void encodeBegin(FacesContext context) throws IOException
90 {
91 super.encodeBegin(context);
92
93 UICommand command = _JsfConversationUtils.findParentCommand(this);
94 if (command != null)
95 {
96 // This component has a UICommand ancestor. Replace its "action" MethodBinding
97 // with a proxy.
98 if (!inited)
99 {
100 MethodBinding original = command.getAction();
101 command.setAction(new _EndConversationMethodBindingFacade(
102 getName(),
103 getOnOutcomes(),
104 original,
105 getErrorOutcome()));
106 inited = true;
107 }
108 }
109 else
110 {
111 // This component has no UICommand ancestor. Always end the conversation.
112 ConversationUtils.invalidateIfExists(getName());
113 }
114 }
115
116 private Collection getOnOutcomes()
117 {
118 String onOutcome = getOnOutcome();
119 if (onOutcome == null || onOutcome.trim().length() < 1)
120 {
121 return null;
122 }
123
124 return Arrays.asList(StringUtils.trim(StringUtils.splitShortString(onOutcome, ',')));
125 }
126
127 public void restoreState(FacesContext context, Object state)
128 {
129 Object[] states = (Object[]) state;
130 super.restoreState(context, states[0]);
131 inited = ((Boolean) states[1]).booleanValue();
132 onOutcome = (String) states[2];
133 errorOutcome = (String) states[3];
134 }
135
136 public Object saveState(FacesContext context)
137 {
138 return new Object[]
139 {
140 super.saveState(context),
141 inited ? Boolean.TRUE : Boolean.FALSE,
142 onOutcome,
143 errorOutcome
144 };
145 }
146
147 public String getOnOutcome()
148 {
149 if (onOutcome != null)
150 {
151 return onOutcome;
152 }
153 ValueBinding vb = getValueBinding("onOutcome");
154 if (vb == null)
155 {
156 return null;
157 }
158 return (String) vb.getValue(getFacesContext());
159 }
160
161 public void setOnOutcome(String onOutcome)
162 {
163 this.onOutcome = onOutcome;
164 }
165
166 public String getErrorOutcome()
167 {
168 if (errorOutcome != null)
169 {
170 return errorOutcome;
171 }
172 ValueBinding vb = getValueBinding("errorOutcome");
173 if (vb == null)
174 {
175 return null;
176 }
177 return (String) vb.getValue(getFacesContext());
178 }
179
180 public void setErrorOutcome(String errorOutcome)
181 {
182 this.errorOutcome = errorOutcome;
183 }
184 }