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.flow.components;
21
22 import java.io.IOException;
23 import java.util.Map;
24
25 import javax.el.ELContext;
26 import javax.faces.FacesException;
27 import javax.faces.component.UIComponentBase;
28 import javax.faces.context.FacesContext;
29 import javax.faces.render.Renderer;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.apache.myfaces.orchestra.flow.config.FlowCall;
34 import org.apache.myfaces.orchestra.flow.config.FlowConfig;
35 import org.apache.myfaces.orchestra.flow.digest.FlowDigester;
36 import org.apache.myfaces.orchestra.flow.utils._ExpressionFactory;
37 import org.apache.myfaces.orchestra.lib.OrchestraException;
38
39 /**
40 * A component which allows the configuration for a flow-call to be defined in the page
41 * rather than in a separate flow.xml file.
42 * <p>
43 * A page can contain zero or more of these tags. It is recommended that all flowCall tags be
44 * placed at the top of the view definition file (eg immediately after the f:view) so that
45 * the flows can clearly be seen at a glance. However placing tags next to the command
46 * components that can be used to trigger the associated outcome is also reasonable.
47 * <p>
48 * Defining flow-calls via tags is particularly useful when the component that triggers the
49 * call is in a "view fragment" that is included into other pages via jsp:include or facelets
50 * templating. In that case, defining the flow call configuration in a separate file that is
51 * associated with the *including* view is ugly, and an in-page tag is preferable.
52 * <p>
53 * This must be a component (rather than just being implemented in a JSP tag) because the
54 * flowcall information must be available at the end of the postback phase when navigation
55 * occurs. The render phase has not run, so no JSF tags have executed; the view tree
56 * consists just of objects recreated from the saved state. Therefore the flowcall info
57 * must be held by a component that is recreated during RESTORE_VIEW.
58 * <p>
59 * It would not be good to re-parse the nested text on each postback; that would be very
60 * inefficient. Therefore we parse it just once and store the (serializable) FlowCall
61 * object as a member. This does mean that if the nested text changes then this will be
62 * ignored until the view is recreated, but it is not expected that the flow config data
63 * will be dynamic. In fact, that would be quite insane.
64 */
65
66 public class FlowCallComponent extends UIComponentBase
67 {
68 public static final String COMPONENT_FAMILY = "javax.faces.Component";
69 public static final String COMPONENT_TYPE = "org.apache.myfaces.orchestra.flow.components.FlowCall";
70
71 // This must not be transient; it is needed during postback
72 private FlowCall flowCall;
73
74 private transient String outcome;
75 private transient String viewId;
76 private transient String service;
77
78 @Override
79 public String getFamily()
80 {
81 return COMPONENT_FAMILY;
82 }
83
84 public boolean isInitialized()
85 {
86 return flowCall != null;
87 }
88
89 public void setOutcome(String outcome)
90 {
91 this.outcome = outcome;
92 }
93
94 public void setViewId(String viewId)
95 {
96 this.viewId = viewId;
97 }
98
99 public void setService(String service)
100 {
101 this.service = service;
102 }
103
104 public void setBody(ELContext elContext, String content)
105 {
106 if (flowCall != null)
107 {
108 // already parsed once, just ignore
109 return;
110 }
111
112 if (content == null)
113 {
114 throw new FacesException("Empty flowCall tag");
115 }
116
117 try
118 {
119 // Parse the plain xml that is nested within this component.
120 //
121 // Note that it is important that any EL expressions within this text content be converted into
122 // real EL-expression objects immediately, rather than simply being stored as strings and the
123 // EL expression objects being created later when needed. The reason is that during the building
124 // of the component tree, the "current expression context" may be modified. Creating an EL
125 // expression captures the current state of the "expression context" so that when the EL expression
126 // is later executed it runs with the appropriate environment. In particular, the Facelets
127 // ui:include tag can include ui:param tags that expose data to the nested components, effectively
128 // mapping a name to another EL expression during the build-tree phase (note: the param-expression
129 // is not *evaluated*; the name maps to the expression itself, not its value). When the included page
130 // creates EL objects, they internally remember the current mappings. During rendering, the ui:param
131 // settings are *not* restored; rendering simply walks the tree of components and a ui:include is not
132 // a component. However the EL expression objects have enough info to recreate the ui:param settings
133 // that existed when the EL expression was created, so setValue and getValue work ok.
134 //
135 // Of course, the above only applies when running in an environment that supports javax.el (ie JSF1.2
136 // or JSF1.1 with Facelets). For JSF1.1 with JSP, this is not relevant; there is no standard support
137 // for passing "parameters" to included files. There is one non-standard way: the Tomahawk t:aliasBean
138 // tag. I don't know how that works with respect to orchestra flow....
139 flowCall = new FlowCall();
140 flowCall.setOutcome(outcome);
141 flowCall.setViewId(viewId);
142 flowCall.setService(service);
143
144 _ExpressionFactory.pushContext(elContext);
145 try
146 {
147 FlowDigester.digestFlowCall(flowCall, content);
148 }
149 finally
150 {
151 _ExpressionFactory.popContext(elContext);
152 }
153
154 flowCall.validate();
155 }
156 catch(OrchestraException e)
157 {
158 String msg = "Invalid flowCall declaration for component " + this.getId() + ":" + content;
159 Log log = LogFactory.getLog(FlowCallComponent.class);
160 log.warn(msg, e);
161 throw new FacesException(msg, e);
162 }
163 }
164
165 // ================ State Methods =================
166
167 @Override
168 public void restoreState(FacesContext context, Object state)
169 throws FacesException
170 {
171 Object[] states = (Object[]) state;
172 super.restoreState(context, states[0]);
173 flowCall = (FlowCall) states[1];
174
175 getFlowConfigForRequest(context).addFlowCall(flowCall);
176 }
177
178 @Override
179 public Object saveState(FacesContext context)
180 {
181 return new Object[]
182 {
183 super.saveState(context),
184 flowCall
185 };
186 }
187
188 // ================ Renderer Methods =================
189
190 @Override
191 protected Renderer getRenderer(FacesContext context)
192 {
193 return null;
194 }
195
196 /**
197 * Return true so that method encodeChildren gets called.
198 */
199 @Override
200 public boolean getRendersChildren()
201 {
202 return true;
203 }
204
205 /**
206 * Suppress rendering of children.
207 * <p>
208 * We don't expect there to ever be children here. However it's best to be sure...
209 */
210 @Override
211 public void encodeChildren(FacesContext context)
212 throws IOException
213 {
214 int nChildren = getChildCount();
215 if (nChildren > 0)
216 {
217 Log log = LogFactory.getLog(FlowCallComponent.class);
218 log.warn("encodeChildren: child count is " + nChildren);
219 }
220 // ignore all children
221 }
222
223 // ================ Static Methods =================
224
225 private static FlowConfig getFlowConfigForRequest(FacesContext facesContext)
226 {
227 Map<String, Object> reqMap = facesContext.getExternalContext().getRequestMap();
228 FlowConfig flowConfig = (FlowConfig) reqMap.get(FlowConfig.CONFIG_KEY);
229 if (flowConfig == null)
230 {
231 flowConfig = new FlowConfig();
232 reqMap.put(FlowConfig.CONFIG_KEY, flowConfig);
233 }
234 return flowConfig;
235 }
236 }