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.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 }