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  package org.apache.myfaces.renderkit.html;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.RandomAccess;
26  
27  import javax.faces.FacesException;
28  import javax.faces.component.ActionSource;
29  import javax.faces.component.EditableValueHolder;
30  import javax.faces.component.UIComponent;
31  import javax.faces.component.UINamingContainer;
32  import javax.faces.component.behavior.AjaxBehavior;
33  import javax.faces.component.behavior.ClientBehavior;
34  import javax.faces.component.behavior.ClientBehaviorContext;
35  import javax.faces.context.FacesContext;
36  import javax.faces.event.AjaxBehaviorEvent;
37  import javax.faces.event.PhaseId;
38  import javax.faces.render.ClientBehaviorRenderer;
39  import org.apache.myfaces.shared.renderkit.html.util.SharedStringBuilder;
40  
41  /**
42   * @author Werner Punz  (latest modification by $Author: lu4242 $)
43   * @version $Revision: 1298419 $ $Date: 2012-03-08 09:54:46 -0500 (Thu, 08 Mar 2012) $
44   */
45  public class HtmlAjaxBehaviorRenderer extends ClientBehaviorRenderer
46  {
47  
48      private static final String QUOTE = "'";
49      private static final String BLANK = " ";
50  
51      private static final String AJAX_KEY_ONERROR = "onerror";
52      private static final String AJAX_KEY_ONEVENT = "onevent";
53      private static final String AJAX_KEY_EXECUTE = "execute";
54      private static final String AJAX_KEY_RENDER = "render";
55  
56      private static final String AJAX_VAL_THIS = "this";
57      private static final String AJAX_VAL_EVENT = "event";
58      private static final String JS_AJAX_REQUEST = "jsf.ajax.request";
59  
60      private static final String COLON = ":";
61      private static final String EMPTY = "";
62      private static final String COMMA = ",";
63  
64      private static final String ERR_NO_AJAX_BEHAVIOR = "The behavior must be an instance of AjaxBehavior";
65      private static final String L_PAREN = "(";
66      private static final String R_PAREN = ")";
67  
68      /*if this marker is present in the request we have to dispatch a behavior event*/
69      /*if an attached behavior triggers an ajax request this request param must be added*/
70      private static final String BEHAVIOR_EVENT = "javax.faces.behavior.event";
71      private static final String IDENTIFYER_MARKER = "@";
72      
73      private static final String AJAX_SB = "oam.renderkit.AJAX_SB";
74      private static final String AJAX_PARAM_SB = "oam.renderkit.AJAX_PARAM_SB";
75  
76      public void decode(FacesContext context, UIComponent component,
77                         ClientBehavior behavior)
78      {
79          assertBehavior(behavior);
80          AjaxBehavior ajaxBehavior = (AjaxBehavior) behavior;
81          if (ajaxBehavior.isDisabled() || !component.isRendered())
82          {
83              return;
84          }
85  
86          dispatchBehaviorEvent(component, ajaxBehavior);
87      }
88  
89  
90      public String getScript(ClientBehaviorContext behaviorContext,
91                              ClientBehavior behavior)
92      {
93          assertBehavior(behavior);
94          AjaxBehavior ajaxBehavior = (AjaxBehavior) behavior;
95  
96          if (ajaxBehavior.isDisabled())
97          {
98              return null;
99          }
100 
101         return makeAjax(behaviorContext, ajaxBehavior).toString();
102     }
103 
104 
105     private final void dispatchBehaviorEvent(UIComponent component, AjaxBehavior ajaxBehavior)
106     {
107         AjaxBehaviorEvent event = new AjaxBehaviorEvent(component, ajaxBehavior);
108         
109         boolean isImmediate = false;
110         if (ajaxBehavior.isImmediateSet())
111         {
112             isImmediate = ajaxBehavior.isImmediate();
113         }            
114         else
115         {
116             isImmediate = isComponentImmediate(component);
117         }
118         PhaseId phaseId = isImmediate ?
119                 PhaseId.APPLY_REQUEST_VALUES :
120                 PhaseId.INVOKE_APPLICATION;
121 
122         event.setPhaseId(phaseId);
123 
124         component.queueEvent(event);
125     }
126 
127 
128     private final boolean isComponentImmediate(UIComponent component)
129     {
130         boolean isImmediate = false;
131         if (component instanceof EditableValueHolder)
132         {
133             isImmediate = ((EditableValueHolder)component).isImmediate();
134         }
135         else if (component instanceof ActionSource)
136         {
137             isImmediate = ((ActionSource)component).isImmediate();
138         }
139         return isImmediate;
140     }
141 
142 
143     /**
144      * builds the generic ajax call depending upon
145      * the ajax behavior parameters
146      *
147      * @param context  the Client behavior context
148      * @param behavior the behavior
149      * @return a fully working javascript with calls into jsf.js
150      */
151     private final StringBuilder makeAjax(ClientBehaviorContext context, AjaxBehavior behavior)
152     {
153         StringBuilder retVal = SharedStringBuilder.get(context.getFacesContext(), AJAX_SB, 60);
154         StringBuilder paramBuffer = SharedStringBuilder.get(context.getFacesContext(), AJAX_PARAM_SB, 20);
155 
156         String executes = mapToString(context, paramBuffer, AJAX_KEY_EXECUTE, behavior.getExecute());
157         String render = mapToString(context, paramBuffer, AJAX_KEY_RENDER, behavior.getRender());
158 
159         String onError = behavior.getOnerror();
160         if (onError != null && !onError.trim().equals(EMPTY))
161         {
162             //onError = AJAX_KEY_ONERROR + COLON + onError;
163             paramBuffer.setLength(0);
164             paramBuffer.append(AJAX_KEY_ONERROR);
165             paramBuffer.append(COLON);
166             paramBuffer.append(onError);
167             onError = paramBuffer.toString();
168         }
169         else
170         {
171             onError = null;
172         }
173         String onEvent = behavior.getOnevent();
174         if (onEvent != null && !onEvent.trim().equals(EMPTY))
175         {
176             paramBuffer.setLength(0);
177             paramBuffer.append(AJAX_KEY_ONEVENT);
178             paramBuffer.append(COLON);
179             paramBuffer.append(onEvent);
180             onEvent = paramBuffer.toString();
181         }
182         else
183         {
184             onEvent = null;
185         }
186 
187         String sourceId = null;
188         if (context.getSourceId() == null) 
189         {
190             sourceId = AJAX_VAL_THIS;
191         }
192         else
193         {
194             paramBuffer.setLength(0);
195             paramBuffer.append('\'');
196             paramBuffer.append(context.getSourceId());
197             paramBuffer.append('\'');
198             sourceId = paramBuffer.toString();
199         }
200         String event = context.getEventName();
201 
202         retVal.append(JS_AJAX_REQUEST);
203         retVal.append(L_PAREN);
204         retVal.append(sourceId);
205         retVal.append(COMMA);
206         retVal.append(AJAX_VAL_EVENT);
207         retVal.append(COMMA);
208 
209         Collection<ClientBehaviorContext.Parameter> params = context.getParameters();
210         int paramSize = (params != null) ? params.size() : 0;
211 
212         List<String> parameterList = new ArrayList<String>(paramSize + 2);
213         if (executes != null)
214         {
215             parameterList.add(executes.toString());
216         }
217         if (render != null)
218         {
219             parameterList.add(render.toString());
220         }
221         if (onError != null)
222         {
223             parameterList.add(onError);
224         }
225         if (onEvent != null)
226         {
227             parameterList.add(onEvent);
228         }
229         if (paramSize > 0)
230         {
231             /**
232              * see ClientBehaviorContext.html of the spec
233              * the param list has to be added in the post back
234              */
235             // params are in 99% RamdonAccess instace created in 
236             // HtmlRendererUtils.getClientBehaviorContextParameters(Map<String, String>)
237             if (params instanceof RandomAccess)
238             {
239                 List<ClientBehaviorContext.Parameter> list = (List<ClientBehaviorContext.Parameter>) params;
240                 for (int i = 0, size = list.size(); i < size; i++)
241                 {
242                     ClientBehaviorContext.Parameter param = list.get(i);
243                     append(paramBuffer, parameterList, param);
244                 }
245             }
246             else
247             {
248                 for (ClientBehaviorContext.Parameter param : params)
249                 {
250                     append(paramBuffer, parameterList, param);
251                 }
252             }
253         }
254 
255         //parameterList.add(QUOTE + BEHAVIOR_EVENT + QUOTE + COLON + QUOTE + event + QUOTE);
256         paramBuffer.setLength(0);
257         paramBuffer.append(QUOTE);
258         paramBuffer.append(BEHAVIOR_EVENT);
259         paramBuffer.append(QUOTE);
260         paramBuffer.append(COLON);
261         paramBuffer.append(QUOTE);
262         paramBuffer.append(event);
263         paramBuffer.append(QUOTE);
264         parameterList.add(paramBuffer.toString());
265         
266         /**
267          * I assume here for now that the options are the same which also
268          * can be sent via the options attribute to javax.faces.ajax
269          * this still needs further clarifications but I assume so for now
270          */
271         retVal.append(buildOptions(context.getFacesContext(), paramBuffer, parameterList));
272 
273         retVal.append(R_PAREN);
274 
275         return retVal;
276     }
277 
278     private void append(StringBuilder paramBuffer, List<String> parameterList, ClientBehaviorContext.Parameter param)
279     {
280         //TODO we may need a proper type handling in this part
281         //lets leave it for now as it is
282         //quotes etc.. should be transferred directly
283         //and the rest is up to the toString properly implemented
284         //ANS: Both name and value should be quoted
285         paramBuffer.setLength(0);
286         paramBuffer.append(QUOTE);
287         paramBuffer.append(param.getName());
288         paramBuffer.append(QUOTE);
289         paramBuffer.append(COLON);
290         paramBuffer.append(QUOTE);
291         paramBuffer.append(param.getValue().toString());
292         paramBuffer.append(QUOTE);
293         parameterList.add(paramBuffer.toString());
294     }
295 
296 
297     private StringBuilder buildOptions(FacesContext facesContext, StringBuilder retVal, List<String> options)
298     {
299         retVal.setLength(0);
300 
301         retVal.append("{");
302 
303         boolean first = true;
304 
305         for (int i = 0, size = options.size(); i < size; i++)
306         {
307             String option = options.get(i);
308             if (option != null && !option.trim().equals(EMPTY))
309             {
310                 if (!first)
311                 {
312                     retVal.append(COMMA);
313                 }
314                 else
315                 {
316                     first = false;
317                 }
318                 retVal.append(option);
319             }
320         }
321         retVal.append("}");
322         return retVal;
323     }
324 
325     private final String mapToString(ClientBehaviorContext context, StringBuilder retVal, 
326             String target, Collection<String> dataHolder)
327     {
328         //Clear buffer
329         retVal.setLength(0);
330 
331         if (dataHolder == null)
332         {
333             dataHolder = Collections.emptyList();
334         }
335         int executeSize = dataHolder.size();
336         if (executeSize > 0)
337         {
338 
339             retVal.append(target);
340             retVal.append(COLON);
341             retVal.append(QUOTE);
342 
343             int cnt = 0;
344             
345             // perf: dataHolder is a Collection : ajaxBehaviour.getExecute() 
346             // and ajaxBehaviour.getRender() API
347             // In most cases comes here a ArrayList, because 
348             // javax.faces.component.behavior.AjaxBehavior.getCollectionFromSpaceSplitString
349             // creates it.
350             if (dataHolder instanceof RandomAccess)
351             {
352                 List<String> list = (List<String>) dataHolder;
353                 for (; cnt  < executeSize; cnt++)
354                 {
355                     String strVal = list.get(cnt);
356                     build(context, executeSize, retVal, cnt, strVal);
357                 }
358             }
359             else
360             {
361                 for (String strVal : dataHolder)
362                 {
363                     cnt++;
364                     build(context, executeSize, retVal, cnt, strVal);
365                 }
366             }
367 
368             retVal.append(QUOTE);
369             return retVal.toString();
370         }
371         return null;
372 
373     }
374 
375     public void build(ClientBehaviorContext context,
376             int size, StringBuilder retVal, int cnt,
377             String strVal)
378     {
379         strVal = strVal.trim();
380         if (!EMPTY.equals(strVal))
381         {
382             if (!strVal.startsWith(IDENTIFYER_MARKER))
383             {
384                 String componentId = getComponentId(context, strVal);
385                 retVal.append(componentId);
386             }
387             else
388             {
389                 retVal.append(strVal);
390             }
391             if (cnt < size)
392             {
393                 retVal.append(BLANK);
394             }
395         }
396     }
397 
398     private final String getComponentId(ClientBehaviorContext context, String id)
399     {
400 
401         UIComponent contextComponent = context.getComponent();
402         UIComponent target = contextComponent.findComponent(id);
403         if (target == null)
404         {
405             target = contextComponent.findComponent(UINamingContainer.getSeparatorChar(context.getFacesContext()) + id);
406         }
407         if (target != null)
408         {
409             return target.getClientId(context.getFacesContext());
410         }
411         throw new FacesException("Component with id:" + id + " not found");
412     }
413 
414     private final void assertBehavior(ClientBehavior behavior)
415     {
416         if (!(behavior instanceof AjaxBehavior))
417         {
418             throw new FacesException(ERR_NO_AJAX_BEHAVIOR);
419         }
420     }
421 
422 }