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.custom.ppr;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.myfaces.custom.dojo.DojoConfig;
25  import org.apache.myfaces.custom.dojo.DojoUtils;
26  import org.apache.myfaces.custom.subform.SubForm;
27  import org.apache.myfaces.renderkit.html.util.AddResource;
28  import org.apache.myfaces.renderkit.html.util.AddResourceFactory;
29  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
30  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
31  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
32  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.FormInfo;
33  
34  import javax.faces.FacesException;
35  import javax.faces.component.UIComponent;
36  import javax.faces.context.ExternalContext;
37  import javax.faces.context.FacesContext;
38  import javax.faces.context.ResponseWriter;
39  import java.io.IOException;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.ArrayList;
43  
44  /**
45   * @author Ernst Fastl
46   * @author Thomas Spiegl
47   */
48  public class PPRSupport
49  {
50      private static Log log = LogFactory.getLog(PPRSupport.class);
51  
52      private static final String PPR_INITIALIZED = "org.apache.myfaces.ppr.INITIALIZED";
53  
54      private static final String PPR_JS_FILE = "ppr.js";
55  
56      private static final String ADD_PARTIAL_TRIGGER_FUNCTION = "addPartialTrigger";
57  
58      private static final String ADD_PERIODICAL_TRIGGER_FUNCTION = "addPeriodicalTrigger";
59  
60      private static final String ADD_PARTIAL_TRIGGER_PATTERN_FUNCTION = "addPartialTriggerPattern";
61  
62      private static final String SET_SUBFORM_ID_FUNCTION = "setSubFormId";
63  
64      private static final String SET_COMPONENT_UPDATE_FUNCTION = "setComponentUpdateFunction";
65  
66      private static final String ADD_INLINE_LOADING_MESSAGE_FUNCTION = "addInlineLoadingMessage";
67  
68      private static final String MY_FACES_PPR_INIT_CODE = "new org.apache.myfaces.PPRCtrl";
69  
70  
71      public final static String COMMAND_CONFIGURED_MARK = PPRSupport.class.getName() + "_CONFIGURED";
72  
73      public final static String PROCESS_COMPONENTS = PPRSupport.class.getName() + "PROCESS_COMPONENTS";
74  
75  
76      public static boolean isPartialRequest(FacesContext facesContext)
77      {
78          return PPRPhaseListener.isPartialRequest(facesContext);
79      }
80  
81  
82      /**
83       * Renders inline JavaScript registering an onLoad function for:
84       * <ul>
85       * <li>Initializing the PPRCtrl for the current Form</li>
86       * </ul>
87       *
88       * @param facesContext the current {@link javax.faces.context.FacesContext}
89       * @param uiComponent  the currently rendered {@link PPRPanelGroup}
90       * @throws java.io.IOException if the underlying Layer throws an {@link java.io.IOException}
91       *                             it is passed through
92       */
93      public static void initPPR(FacesContext facesContext, PPRPanelGroup uiComponent) throws IOException
94      {
95          //if(isPartialRequest(facesContext)) {
96          //    return;
97          //}
98          final ExternalContext externalContext = facesContext.getExternalContext();
99          final Map requestMap = externalContext.getRequestMap();
100 
101         // Do not render the JavaScript if answering to a PPR response
102 
103         //Initialize the client side PPR engine
104         if (!requestMap.containsKey(PPR_INITIALIZED)) {
105             requestMap.put(PPR_INITIALIZED, Boolean.TRUE);
106 
107             String encoding = "UTF-8"; // Hardcoded default
108             if (facesContext.getResponseWriter().getCharacterEncoding() != null) {
109                 encoding = facesContext.getResponseWriter().getCharacterEncoding();
110             }
111 
112             DojoConfig currentConfig = DojoUtils.getDjConfigInstance(facesContext);
113             currentConfig.setBindEncoding(encoding);
114 
115             String javascriptLocation = (String) uiComponent.getAttributes().get(JSFAttr.JAVASCRIPT_LOCATION);
116             AddResource addResource = AddResourceFactory.getInstance(facesContext);
117             DojoUtils.addMainInclude(facesContext, uiComponent, javascriptLocation, currentConfig);
118             DojoUtils.addRequire(facesContext, uiComponent, "dojo.io.*");
119             DojoUtils.addRequire(facesContext, uiComponent, "dojo.event.*");
120             DojoUtils.addRequire(facesContext, uiComponent, "dojo.xml.*");
121             addResource.addJavaScriptAtPosition(facesContext, AddResource.HEADER_BEGIN, PPRPanelGroup.class, PPR_JS_FILE);
122         }
123     }
124 
125     /**
126      * Renders inline JavaScript registering an onLoad function for:
127      * <ul>
128      * <li>Initializing the PPRCtrl for the current Form</li>
129      * <li>Registering partialTriggers</li>
130      * <li>Registering partialTriggerPatterns</li>
131      * <li>Starting periodical updates</li>
132      * <li>Registering inline Loading messages</li>
133      * </ul>
134      *
135      * @param facesContext the current {@link FacesContext}
136      * @param pprGroup     the currently rendered {@link PPRPanelGroup}
137      * @throws IOException if the underlying Layer throws an {@link IOException}
138      *                     it is passed through
139      */
140     public static void encodeJavaScript(FacesContext facesContext, PPRPanelGroup pprGroup) throws IOException
141     {
142         StringBuffer script = new StringBuffer();
143 
144         // all JS is put inside a function passed to dojoOnLoad
145         // this is necessary in order to be able to replace all button onClick
146         // handlers
147 
148         script.append("dojo.addOnLoad( function(){ ");
149 
150         String pprCtrlReference = initPPRFormControl(facesContext, pprGroup, script);
151 
152         encodePeriodicalUpdates(facesContext, pprGroup, script, pprCtrlReference);
153 
154         encodePartialTriggerPattern(facesContext, pprGroup, script, pprCtrlReference);
155 
156         encodeSubFormFunction(facesContext, pprGroup, script, pprCtrlReference);
157 
158         encodeDomUpdateConfig(facesContext, pprGroup, script, pprCtrlReference);
159 
160         encodeInlineLoadMsg(facesContext, pprGroup, script, pprCtrlReference);
161 
162         encodePartialTriggers(facesContext, pprGroup, script, pprCtrlReference);
163 
164         // closing the dojo.addOnLoad call
165         script.append("});");
166 
167         //Really render the script
168         renderInlineScript(facesContext, pprGroup, script.toString());
169     }
170 
171     public static void encodeJavaScriptTriggerOnly(FacesContext context, UIComponent uiComponent, PPRPanelGroup pprGroup,
172                                         PartialTriggerParser.PartialTrigger trigger) throws IOException
173     {
174         StringBuffer script = new StringBuffer();
175         script.append("dojo.addOnLoad( function(){ ");
176         String pprCtrlReference = initPPRFormControl(context, pprGroup, script);
177         String clientId = pprGroup.getClientId(context);
178         encodePartialTrigger(context, script, pprCtrlReference, clientId, uiComponent, trigger);
179         // closing the dojo.addOnLoad call
180         script.append("});");
181 
182         //Really render the script
183         renderInlineScript(context, pprGroup, script.toString());
184     }
185 
186     private static void encodePartialTrigger(FacesContext facesContext,
187                                              StringBuffer script,
188                                              String pprCtrlReference,
189                                              String clientId,
190                                              UIComponent partialTriggerComponent,
191                                              PartialTriggerParser.PartialTrigger trigger)
192     {
193         String partialTriggerClientId;
194         String partialTriggerId = trigger.getPartialTriggerId();
195         if (partialTriggerComponent == null) {
196             partialTriggerComponent = facesContext.getViewRoot().findComponent(partialTriggerId);
197         }
198         if (partialTriggerComponent != null) {
199             partialTriggerClientId = partialTriggerComponent.getClientId(facesContext);
200             script.append(pprCtrlReference + "." + ADD_PARTIAL_TRIGGER_FUNCTION + "('" + partialTriggerClientId + "'," + encodeArray(trigger.getEventHooks()) + ",'" + clientId + "');");
201         }
202         else {
203             if (log.isDebugEnabled()) {
204                 log.debug("PPRPanelGroupRenderer Component with id " + partialTriggerId + " not found!");
205             }
206         }
207     }
208 
209     private static void encodePartialTriggers(FacesContext context, PPRPanelGroup pprGroup, StringBuffer script, String pprCtrlReference)
210     {
211         String clientId = pprGroup.getClientId(context);
212         UIComponent partialTriggerComponent;
213 
214         List partialTriggerIds = pprGroup.parsePartialTriggers();
215         for (int i = 0; i < partialTriggerIds.size(); i++) {
216             PartialTriggerParser.PartialTrigger trigger = (PartialTriggerParser.PartialTrigger) partialTriggerIds
217                 .get(i);
218             partialTriggerComponent = pprGroup.findComponent(trigger.getPartialTriggerId());
219             encodePartialTrigger(context, script, pprCtrlReference, clientId, partialTriggerComponent, trigger);
220         }
221     }
222 
223     private static void encodePartialTriggerPattern(FacesContext context, PPRPanelGroup pprGroup, StringBuffer script, String pprCtrlReference)
224     {
225         String clientId = pprGroup.getClientId(context);
226 
227         String partialTriggerPattern = pprGroup.getPartialTriggerPattern();
228 
229         //handle partial trigger patterns
230         if (partialTriggerPattern != null && partialTriggerPattern.trim().length() > 0) {
231             script.append(pprCtrlReference + "." + ADD_PARTIAL_TRIGGER_PATTERN_FUNCTION + "('" + partialTriggerPattern + "','" + clientId + "');");
232         }
233 
234     }
235 
236     private static void encodeInlineLoadMsg(FacesContext context, PPRPanelGroup pprGroup, StringBuffer script, String pprCtrlReference)
237     {
238         String clientId = pprGroup.getClientId(context);
239         String inlineLoadingMessage = pprGroup.getInlineLoadingMessage();
240 
241         //handle inline loading messages
242         if (inlineLoadingMessage != null && inlineLoadingMessage.trim().length() > 0) {
243             script.append(pprCtrlReference + "." + ADD_INLINE_LOADING_MESSAGE_FUNCTION + "('" + inlineLoadingMessage + "','" + clientId + "');");
244         }
245     }
246 
247     private static void encodeSubFormFunction(FacesContext context, PPRPanelGroup pprGroup, StringBuffer script, String pprCtrlReference)
248     {
249         String clientId = pprGroup.getClientId(context);
250         SubForm subFormParent = findParentSubForm(pprGroup);
251         if (subFormParent != null) {
252             script.append(pprCtrlReference + "." + SET_SUBFORM_ID_FUNCTION + "('" + subFormParent.getId() + "','" + clientId + "');");
253         }
254     }
255 
256     private static void encodeDomUpdateConfig(FacesContext context, PPRPanelGroup pprGroup, StringBuffer script, String pprCtrlReference)
257     {
258         String componentUpdateFunction = pprGroup.getComponentUpdateFunction();
259         if (!StringUtils.isEmpty(componentUpdateFunction)) {
260             script.append(pprCtrlReference + "." + SET_COMPONENT_UPDATE_FUNCTION+ "('" + componentUpdateFunction + "');");
261         }
262     }
263 
264     private static void encodePeriodicalUpdates(FacesContext facesContext, PPRPanelGroup pprGroup, StringBuffer script, String pprCtrlReference)
265     {
266         String clientId = pprGroup.getClientId(facesContext);
267 
268         //Handle periodical updates
269         if (pprGroup.getPeriodicalUpdate() != null) {
270             List partialTriggers = pprGroup.parsePeriodicalTriggers();
271             if (partialTriggers.size() == 0) {
272                 Integer wait = null;
273                 if (pprGroup.getExcludeFromStoppingPeriodicalUpdate() != null) {
274                     wait = pprGroup.getWaitBeforePeriodicalUpdate();
275                 }
276                 script.append(pprCtrlReference + ".startPeriodicalUpdate(" + pprGroup.getPeriodicalUpdate() + ",'" + clientId + "', " + wait + ");");
277             }
278             else {
279                 String periodicalTriggerId;
280                 String periodicalTriggerClientId;
281                 UIComponent periodicalTriggerComponent;
282                 for (int i = 0; i < partialTriggers.size(); i++) {
283                     PartialTriggerParser.PartialTrigger trigger = (PartialTriggerParser.PartialTrigger) partialTriggers
284                         .get(i);
285                     periodicalTriggerId = trigger.getPartialTriggerId();
286                     periodicalTriggerComponent = pprGroup.findComponent(periodicalTriggerId);
287                     if (periodicalTriggerComponent == null) {
288                         periodicalTriggerComponent = facesContext.getViewRoot().findComponent(periodicalTriggerId);
289                     }
290 
291                     // Component found
292                     if (periodicalTriggerComponent != null) {
293                         periodicalTriggerClientId = periodicalTriggerComponent.getClientId(facesContext);
294                         script.append(pprCtrlReference + "." + ADD_PERIODICAL_TRIGGER_FUNCTION +
295                             "('" + periodicalTriggerClientId + "'," +
296                             encodeArray(trigger.getEventHooks()) + ",'" + clientId + "', " +
297                             pprGroup.getPeriodicalUpdate() + ");");
298 
299                         // Component missing
300                     }
301                     else {
302                         if (log.isDebugEnabled()) {
303                             log.debug("PPRPanelGroupRenderer Component with id " + periodicalTriggerId + " not found!");
304                         }
305                     }
306                 }
307             }
308 
309             String idRegex = pprGroup.getExcludeFromStoppingPeriodicalUpdate();
310 
311             if (idRegex != null) {
312                 script.append(pprCtrlReference + ".excludeFromStoppingPeriodicalUpdate('" + idRegex + "');");
313             }
314         }
315     }
316 
317     private static String initPPRFormControl(FacesContext facesContext, PPRPanelGroup pprGroup, StringBuffer script)
318     {
319         FormInfo fi = RendererUtils.findNestingForm(pprGroup, facesContext);
320         if (fi == null) {
321             throw new FacesException("PPRPanelGroup must be embedded in a form.");
322         }
323         final String formName = fi.getFormName();
324         Map requestMap = facesContext.getExternalContext().getRequestMap();
325 
326         String pprCtrlReference = "dojo.byId('" + formName + "').myFacesPPRCtrl";
327 
328         //Each form containing PPRPanelGroups has its own PPRCtrl
329 
330         // the following complicated stuff should deal with the following use-cases:
331         // 1) normal create ppr on non-ppr-response
332         // 2) create ppr on ppr-response
333         // 3) add triggers to ppr on ppr-response (e.g to commands within uidata)
334         //
335         // get state of the ppr component ...
336         boolean pprInited = pprGroup.getInitializationSent();
337         if (!PPRSupport.isPartialRequest(facesContext))
338         {
339             // ... but override with current request state if we are not within an
340             // ppr request.
341             pprInited = Boolean.TRUE.equals(requestMap.get(PPR_INITIALIZED + "." + formName));
342         }
343 
344         if (!pprInited) {
345             pprGroup.setInitializationSent(true);
346             requestMap.put(PPR_INITIALIZED + "." + formName, Boolean.TRUE);
347 
348             script.append(pprCtrlReference + "=" + MY_FACES_PPR_INIT_CODE + "('" + formName + "'," + pprGroup.getShowDebugMessages().booleanValue() + "," + pprGroup.getStateUpdate().booleanValue() + ");\n");
349 
350             if (pprGroup.getPeriodicalUpdate() != null) {
351                 script.append(pprCtrlReference + ".registerOnSubmitInterceptor();");
352             }
353         }
354         return pprCtrlReference;
355     }
356 
357     public static SubForm findParentSubForm(UIComponent base)
358     {
359         if (base == null) {
360             return null;
361         }
362         if (base instanceof SubForm) {
363             return (SubForm) base;
364         }
365         return findParentSubForm(base.getParent());
366     }
367 
368     private static String encodeArray(List eventHooks)
369     {
370         if (eventHooks == null || eventHooks.size() == 0) {
371             return "null";
372         }
373         else {
374             StringBuffer buf = new StringBuffer();
375             buf.append("[");
376 
377             for (int i = 0; i < eventHooks.size(); i++) {
378                 if (i > 0) {
379                     buf.append(",");
380                 }
381                 String eventHook = (String) eventHooks.get(i);
382                 buf.append("'");
383                 buf.append(eventHook);
384                 buf.append("'");
385             }
386             buf.append("]");
387 
388             return buf.toString();
389         }
390     }
391 
392     /**
393      * Helper to write an inline javascript at the exact resource location
394      * of the call.
395      *
396      * @param facesContext The current faces-context.
397      * @param component    The component for which the script is written.
398      * @param script       The script to be written.
399      * @throws IOException A forwarded exception from the underlying renderer.
400      */
401     private static void renderInlineScript(FacesContext facesContext, UIComponent component, String script) throws IOException
402     {
403         ResponseWriter writer = facesContext.getResponseWriter();
404         writer.startElement(HTML.SCRIPT_ELEM, component);
405         writer.writeAttribute(HTML.TYPE_ATTR, HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
406         writer.write(script);
407         writer.endElement(HTML.SCRIPT_ELEM);
408     }
409 
410     /**
411      * get all components by given id-string-list ("id1,id2,id3") and appropriate type
412      *
413      * @param context
414      * @param comp
415      * @param idList
416      * @param desiredType
417      * @return
418      */
419     public static List getComponentsByCommaSeparatedIdList(FacesContext context, UIComponent comp, String idList, Class desiredType)
420     {
421         List retval = new ArrayList();
422         UIComponent currentComponent = null;
423         String[] ids = StringUtils.split(idList, ',');
424         for (int i = 0; i < ids.length; i++) {
425             String id = StringUtils.trim(ids[i]);
426             currentComponent = comp.findComponent(id);
427             if (nullSafeCheckComponentType(desiredType, currentComponent)) {
428                 retval.add(currentComponent);
429             }
430             else {
431                 currentComponent = context.getViewRoot().findComponent(id);
432                 if (nullSafeCheckComponentType(desiredType, currentComponent)) {
433                     retval.add(currentComponent);
434                 }
435             }
436         }
437         return retval;
438     }
439 
440     private static boolean nullSafeCheckComponentType(Class desiredType, UIComponent currentComponent)
441     {
442         return currentComponent != null && (desiredType == null || desiredType.isAssignableFrom(currentComponent.getClass()));
443     }
444 }