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.EnumSet;
25  import java.util.List;
26  import java.util.RandomAccess;
27  import java.util.Set;
28  
29  import javax.faces.FacesException;
30  import javax.faces.component.ActionSource;
31  import javax.faces.component.EditableValueHolder;
32  import javax.faces.component.UIComponent;
33  import javax.faces.component.behavior.AjaxBehavior;
34  import javax.faces.component.behavior.ClientBehavior;
35  import javax.faces.component.behavior.ClientBehaviorContext;
36  import javax.faces.component.search.SearchExpressionContext;
37  import javax.faces.component.search.SearchExpressionHandler;
38  import javax.faces.component.search.SearchExpressionHint;
39  import javax.faces.context.FacesContext;
40  import javax.faces.event.AjaxBehaviorEvent;
41  import javax.faces.event.PhaseId;
42  import javax.faces.render.ClientBehaviorRenderer;
43  import org.apache.myfaces.shared.renderkit.html.util.SharedStringBuilder;
44  
45  /**
46   * @author Werner Punz  (latest modification by $Author$)
47   * @version $Revision$ $Date$
48   */
49  public class HtmlAjaxBehaviorRenderer extends ClientBehaviorRenderer
50  {
51  
52      private static final String QUOTE = "'";
53      private static final String BLANK = " ";
54  
55      private static final String AJAX_KEY_ONERROR = "onerror";
56      private static final String AJAX_KEY_ONEVENT = "onevent";
57      private static final String AJAX_KEY_EXECUTE = "execute";
58      private static final String AJAX_KEY_RENDER = "render";
59      private static final String AJAX_KEY_DELAY = "delay";
60      private static final String AJAX_KEY_RESETVALUES = "resetValues";
61  
62      private static final String AJAX_VAL_THIS = "this";
63      private static final String AJAX_VAL_EVENT = "event";
64      private static final String JS_AJAX_REQUEST = "jsf.ajax.request";
65  
66      private static final String COLON = ":";
67      private static final String EMPTY = "";
68      private static final String COMMA = ",";
69  
70      private static final String ERR_NO_AJAX_BEHAVIOR = "The behavior must be an instance of AjaxBehavior";
71      private static final String L_PAREN = "(";
72      private static final String R_PAREN = ")";
73  
74      private static final String AJAX_SB = "oam.renderkit.AJAX_SB";
75      private static final String AJAX_PARAM_SB = "oam.renderkit.AJAX_PARAM_SB";
76  
77      @Override
78      public void decode(FacesContext context, UIComponent component, ClientBehavior behavior)
79      {
80          assertBehavior(behavior);
81          AjaxBehavior ajaxBehavior = (AjaxBehavior) behavior;
82          if (ajaxBehavior.isDisabled() || !component.isRendered())
83          {
84              return;
85          }
86  
87          dispatchBehaviorEvent(component, ajaxBehavior);
88      }
89  
90      @Override
91      public String getScript(ClientBehaviorContext behaviorContext, 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     private void dispatchBehaviorEvent(UIComponent component, AjaxBehavior ajaxBehavior)
105     {
106         AjaxBehaviorEvent event = new AjaxBehaviorEvent(component, ajaxBehavior);
107 
108         boolean isImmediate = false;
109         if (ajaxBehavior.isImmediateSet())
110         {
111             isImmediate = ajaxBehavior.isImmediate();
112         }
113         else
114         {
115             isImmediate = isComponentImmediate(component);
116         }
117 
118         PhaseId phaseId = isImmediate ? PhaseId.APPLY_REQUEST_VALUES : PhaseId.INVOKE_APPLICATION;
119 
120         event.setPhaseId(phaseId);
121 
122         component.queueEvent(event);
123     }
124 
125 
126     private boolean isComponentImmediate(UIComponent component)
127     {
128         boolean isImmediate = false;
129         if (component instanceof EditableValueHolder)
130         {
131             isImmediate = ((EditableValueHolder)component).isImmediate();
132         }
133         else if (component instanceof ActionSource)
134         {
135             isImmediate = ((ActionSource)component).isImmediate();
136         }
137         return isImmediate;
138     }
139 
140 
141     /**
142      * builds the generic ajax call depending upon
143      * the ajax behavior parameters
144      *
145      * @param context  the Client behavior context
146      * @param behavior the behavior
147      * @return a fully working javascript with calls into jsf.js
148      */
149     private StringBuilder makeAjax(ClientBehaviorContext context, AjaxBehavior behavior)
150     {
151         StringBuilder retVal = SharedStringBuilder.get(context.getFacesContext(), AJAX_SB, 60);
152         StringBuilder paramBuffer = SharedStringBuilder.get(context.getFacesContext(), AJAX_PARAM_SB, 20);
153 
154         String executes = mapToString(context, paramBuffer, AJAX_KEY_EXECUTE, behavior.getExecute());
155         String render = mapToString(context, paramBuffer, AJAX_KEY_RENDER, behavior.getRender());
156 
157         String onError = behavior.getOnerror();
158         if (onError != null && !onError.trim().equals(EMPTY))
159         {
160             //onError = AJAX_KEY_ONERROR + COLON + onError;
161             paramBuffer.setLength(0);
162             paramBuffer.append(AJAX_KEY_ONERROR);
163             paramBuffer.append(COLON);
164             paramBuffer.append(onError);
165             onError = paramBuffer.toString();
166         }
167         else
168         {
169             onError = null;
170         }
171         String onEvent = behavior.getOnevent();
172         if (onEvent != null && !onEvent.trim().equals(EMPTY))
173         {
174             paramBuffer.setLength(0);
175             paramBuffer.append(AJAX_KEY_ONEVENT);
176             paramBuffer.append(COLON);
177             paramBuffer.append(onEvent);
178             onEvent = paramBuffer.toString();
179         }
180         else
181         {
182             onEvent = null;
183         }
184         /*
185          * since version 2.2
186          */
187         String delay = behavior.getDelay();
188         if (delay != null && !delay.trim().equals(EMPTY))
189         {
190             paramBuffer.setLength(0);
191             paramBuffer.append(AJAX_KEY_DELAY);
192             paramBuffer.append(COLON);
193             if ("none".equals(delay))
194             {
195                 paramBuffer.append('\'');
196                 paramBuffer.append(delay);
197                 paramBuffer.append('\'');
198             }
199             else
200             {
201                 paramBuffer.append(delay);
202             }
203             delay = paramBuffer.toString();
204         }
205         else
206         {
207             delay = null;
208         }
209         /*
210          * since version 2.2
211          */
212         String resetValues = Boolean.toString(behavior.isResetValues());
213         if (resetValues.equals("true"))
214         {
215             paramBuffer.setLength(0);
216             paramBuffer.append(AJAX_KEY_RESETVALUES);
217             paramBuffer.append(COLON);
218             paramBuffer.append(resetValues);
219             resetValues = paramBuffer.toString();
220         }
221         else
222         {
223             resetValues = null;
224         }
225 
226         String sourceId = null;
227         if (context.getSourceId() == null)
228         {
229             sourceId = AJAX_VAL_THIS;
230         }
231         else
232         {
233             paramBuffer.setLength(0);
234             paramBuffer.append('\'');
235             paramBuffer.append(context.getSourceId());
236             paramBuffer.append('\'');
237             sourceId = paramBuffer.toString();
238 
239             if (!context.getSourceId().trim().equals(
240                 context.getComponent().getClientId(context.getFacesContext())))
241             {
242                 // Check if sourceId is not a clientId and there is no execute set
243                 UIComponent ref = context.getComponent();
244                 ref = (ref.getParent() == null) ? ref : ref.getParent();
245                 UIComponent instance = null;
246                 try
247                 {
248                     instance = ref.findComponent(context.getSourceId());
249                 }
250                 catch (IllegalArgumentException e)
251                 {
252                     // No Op
253                 }
254                 if (instance == null && executes == null)
255                 {
256                     // set the clientId of the component so the behavior can be decoded later,
257                     // otherwise the behavior will fail
258                     List<String> list = new ArrayList<String>();
259                     list.add(context.getComponent().getClientId(context.getFacesContext()));
260                     executes = mapToString(context, paramBuffer, AJAX_KEY_EXECUTE, list);
261                 }
262             }
263         }
264 
265 
266         String event = context.getEventName();
267 
268         retVal.append(JS_AJAX_REQUEST);
269         retVal.append(L_PAREN);
270         retVal.append(sourceId);
271         retVal.append(COMMA);
272         retVal.append(AJAX_VAL_EVENT);
273         retVal.append(COMMA);
274 
275         Collection<ClientBehaviorContext.Parameter> params = context.getParameters();
276         int paramSize = (params != null) ? params.size() : 0;
277 
278         List<String> parameterList = new ArrayList<>(paramSize + 2);
279         if (executes != null)
280         {
281             parameterList.add(executes);
282         }
283         if (render != null)
284         {
285             parameterList.add(render);
286         }
287         if (onError != null)
288         {
289             parameterList.add(onError);
290         }
291         if (onEvent != null)
292         {
293             parameterList.add(onEvent);
294         }
295         /*
296          * since version 2.2
297          */
298         if (delay != null)
299         {
300             parameterList.add(delay);
301         }
302         /*
303          * since version 2.2
304          */
305         if (resetValues != null)
306         {
307             parameterList.add(resetValues);
308         }
309         if (paramSize > 0)
310         {
311             /**
312              * see ClientBehaviorContext.html of the spec
313              * the param list has to be added in the post back
314              */
315             // params are in 99% RamdonAccess instace created in
316             // HtmlRendererUtils.getClientBehaviorContextParameters(Map<String, String>)
317             if (params instanceof RandomAccess)
318             {
319                 List<ClientBehaviorContext.Parameter> list = (List<ClientBehaviorContext.Parameter>) params;
320                 for (int i = 0, size = list.size(); i < size; i++)
321                 {
322                     ClientBehaviorContext.Parameter param = list.get(i);
323                     append(paramBuffer, parameterList, param);
324                 }
325             }
326             else
327             {
328                 for (ClientBehaviorContext.Parameter param : params)
329                 {
330                     append(paramBuffer, parameterList, param);
331                 }
332             }
333         }
334 
335         //parameterList.add(QUOTE + BEHAVIOR_EVENT + QUOTE + COLON + QUOTE + event + QUOTE);
336         paramBuffer.setLength(0);
337         paramBuffer.append(QUOTE);
338         paramBuffer.append(ClientBehaviorContext.BEHAVIOR_EVENT_PARAM_NAME);
339         paramBuffer.append(QUOTE);
340         paramBuffer.append(COLON);
341         paramBuffer.append(QUOTE);
342         paramBuffer.append(event);
343         paramBuffer.append(QUOTE);
344         parameterList.add(paramBuffer.toString());
345 
346         /**
347          * I assume here for now that the options are the same which also
348          * can be sent via the options attribute to javax.faces.ajax
349          * this still needs further clarifications but I assume so for now
350          */
351         retVal.append(buildOptions(paramBuffer, parameterList));
352 
353         retVal.append(R_PAREN);
354 
355         return retVal;
356     }
357 
358     private void append(StringBuilder paramBuffer, List<String> parameterList, ClientBehaviorContext.Parameter param)
359     {
360         //TODO we may need a proper type handling in this part
361         //lets leave it for now as it is
362         //quotes etc.. should be transferred directly
363         //and the rest is up to the toString properly implemented
364         //ANS: Both name and value should be quoted
365         paramBuffer.setLength(0);
366         paramBuffer.append(QUOTE);
367         paramBuffer.append(param.getName());
368         paramBuffer.append(QUOTE);
369         paramBuffer.append(COLON);
370         paramBuffer.append(QUOTE);
371         paramBuffer.append(param.getValue().toString());
372         paramBuffer.append(QUOTE);
373         parameterList.add(paramBuffer.toString());
374     }
375 
376 
377     private StringBuilder buildOptions(StringBuilder retVal, List<String> options)
378     {
379         retVal.setLength(0);
380 
381         retVal.append("{");
382 
383         boolean first = true;
384 
385         for (int i = 0, size = options.size(); i < size; i++)
386         {
387             String option = options.get(i);
388             if (option != null && !option.trim().equals(EMPTY))
389             {
390                 if (!first)
391                 {
392                     retVal.append(COMMA);
393                 }
394                 else
395                 {
396                     first = false;
397                 }
398                 retVal.append(option);
399             }
400         }
401         retVal.append("}");
402         return retVal;
403     }
404 
405     private String mapToString(ClientBehaviorContext context, StringBuilder retVal,
406             String target, Collection<String> dataHolder)
407     {
408         //Clear buffer
409         retVal.setLength(0);
410 
411         if (dataHolder == null)
412         {
413             dataHolder = Collections.emptyList();
414         }
415         int executeSize = dataHolder.size();
416         if (executeSize > 0)
417         {
418 
419             retVal.append(target);
420             retVal.append(COLON);
421             retVal.append(QUOTE);
422 
423             int cnt = 0;
424 
425             SearchExpressionContext searchExpressionContext = null;
426             
427             // perf: dataHolder is a Collection : ajaxBehaviour.getExecute()
428             // and ajaxBehaviour.getRender() API
429             // In most cases comes here a ArrayList, because
430             // javax.faces.component.behavior.AjaxBehavior.getCollectionFromSpaceSplitString
431             // creates it.
432             if (dataHolder instanceof RandomAccess)
433             {
434                 List<String> list = (List<String>) dataHolder;
435                 for (; cnt  < executeSize; cnt++)
436                 {
437                     if (searchExpressionContext == null)
438                     {
439                         searchExpressionContext = SearchExpressionContext.createSearchExpressionContext(
440                                 context.getFacesContext(), context.getComponent(), EXPRESSION_HINTS, null);
441                     }
442                     
443                     String strVal = list.get(cnt);
444                     build(context, executeSize, retVal, cnt, strVal, searchExpressionContext);
445                 }
446             }
447             else
448             {
449                 for (String strVal : dataHolder)
450                 {
451                     if (searchExpressionContext == null)
452                     {
453                         searchExpressionContext = SearchExpressionContext.createSearchExpressionContext(
454                                 context.getFacesContext(), context.getComponent(), EXPRESSION_HINTS, null);
455                     }
456                     
457                     cnt++;
458                     build(context, executeSize, retVal, cnt, strVal, searchExpressionContext);
459                 }
460             }
461 
462             retVal.append(QUOTE);
463             return retVal.toString();
464         }
465         return null;
466 
467     }
468 
469     private static final Set<SearchExpressionHint> EXPRESSION_HINTS =
470             EnumSet.of(SearchExpressionHint.RESOLVE_CLIENT_SIDE, SearchExpressionHint.RESOLVE_SINGLE_COMPONENT);
471     
472     public void build(ClientBehaviorContext context,
473             int size, StringBuilder retVal, int cnt,
474             String strVal, SearchExpressionContext searchExpressionContext)
475     {
476         strVal = strVal.trim();
477         if (!EMPTY.equals(strVal))
478         {
479             /*
480             if (!strVal.startsWith(IDENTIFYER_MARKER))
481             {
482                 String componentId = getComponentId(context, strVal);
483                 retVal.append(componentId);
484             }
485             else
486             {
487                 retVal.append(strVal);
488             }*/
489             SearchExpressionHandler handler = context.getFacesContext().getApplication().getSearchExpressionHandler();
490             String clientId = handler.resolveClientId(searchExpressionContext, strVal);
491             retVal.append(clientId);
492             if (cnt < size)
493             {
494                 retVal.append(BLANK);
495             }
496         }
497     }
498 
499     /*
500     private String getComponentId(ClientBehaviorContext context, String id)
501     {
502 
503         UIComponent contextComponent = context.getComponent();
504         UIComponent target = contextComponent.findComponent(id);
505         if (target == null)
506         {
507             target = contextComponent.findComponent(
508                 context.getFacesContext().getNamingContainerSeparatorChar() + id);
509         }
510         if (target != null)
511         {
512             return target.getClientId(context.getFacesContext());
513         }
514         throw new FacesException("Component with id:" + id + " not found");
515     }
516     */
517 
518     private void assertBehavior(ClientBehavior behavior)
519     {
520         if (!(behavior instanceof AjaxBehavior))
521         {
522             throw new FacesException(ERR_NO_AJAX_BEHAVIOR);
523         }
524     }
525 
526 }