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.custom.ajax.api;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.myfaces.component.html.ext.UIComponentPerspective;
25  import org.apache.myfaces.custom.ajax.util.AjaxRendererUtils;
26  import org.apache.myfaces.custom.inputAjax.HtmlCommandButtonAjax;
27  import org.apache.myfaces.custom.util.URIComponentUtils;
28  import org.apache.myfaces.shared_tomahawk.component.ExecuteOnCallback;
29  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
30  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlResponseWriterImpl;
31  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.FormInfo;
32  
33  import javax.faces.application.StateManager;
34  import javax.faces.component.UIComponent;
35  import javax.faces.component.UIViewRoot;
36  import javax.faces.context.FacesContext;
37  import javax.faces.event.PhaseEvent;
38  import javax.faces.event.PhaseId;
39  import javax.faces.event.PhaseListener;
40  import javax.servlet.ServletRequest;
41  import javax.servlet.ServletResponse;
42  import java.io.IOException;
43  import java.io.PrintWriter;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Set;
48  
49  /**
50   * This short circuits the life cycle and applies updates to affected components only
51   * <p/>
52   * User: treeder
53   * Date: Oct 26, 2005
54   * Time: 6:03:21 PM
55   */
56  public class AjaxDecodePhaseListener
57          implements PhaseListener
58  {
59      private static Log log = LogFactory.getLog(AjaxDecodePhaseListener.class);
60  
61  
62      /**
63       * Some Ajax components might do special decoding at apply request values phase
64       *
65       * @return PhaseId The AJAX decode phase listener will be invoked before apply request values phase.
66       */
67      public PhaseId getPhaseId()
68      {
69          return PhaseId.APPLY_REQUEST_VALUES;
70      }
71  
72      public void beforePhase(PhaseEvent event)
73      {
74          log.debug("In AjaxDecodePhaseListener beforePhase");
75          FacesContext context = event.getFacesContext();
76          Map externalRequestMap = context.getExternalContext().getRequestParameterMap();
77  
78          if (externalRequestMap.containsKey("affectedAjaxComponent"))
79          {
80              UIViewRoot root = context.getViewRoot();
81              String affectedAjaxComponent = (String) context.getExternalContext()
82                                                          .getRequestParameterMap().get("affectedAjaxComponent");
83              UIComponent ajaxComponent = root.findComponent(affectedAjaxComponent);
84              //checking if ajaxComp is inside a dataTable - necessary for non JSF 1.2 MyFaces implementation
85              if (ajaxComponent instanceof UIComponentPerspective)
86              {
87                  UIComponentPerspective componentPerspective = (UIComponentPerspective) ajaxComponent;
88                  ajaxComponent = (UIComponent) componentPerspective.executeOn(context, new ExecuteOnCallback()
89                  {
90                      public Object execute(FacesContext facesContext, UIComponent ajaxComponent)
91                      {
92                          handleAjaxRequest(ajaxComponent, facesContext);
93  
94                          return ajaxComponent;
95                      }
96                  });
97              }
98              else
99              {
100                 handleAjaxRequest(ajaxComponent, context);
101             }
102 
103             StateManager stateManager = context.getApplication().getStateManager();
104             if (!stateManager.isSavingStateInClient(context))
105             {
106                 stateManager.saveSerializedView(context);
107             }
108             context.responseComplete();
109         }
110     }
111 
112 
113     private void handleAjaxRequest(UIComponent ajaxComponent, FacesContext facesContext)
114     {
115         String updateOnly = (String) facesContext.getExternalContext().getRequestParameterMap().get("updateOnly");
116         if(updateOnly == null) {
117             // then decode
118             decodeAjax(ajaxComponent, facesContext);
119             facesContext.getViewRoot().processApplication(facesContext);
120         }
121         // else only update
122         encodeAjax(ajaxComponent, facesContext);
123     }
124 
125     private void decodeAjax(UIComponent ajaxComponent, FacesContext context)
126     {
127 
128         String affectedAjaxComponent = (String) context.getExternalContext()
129                                                     .getRequestParameterMap().get("affectedAjaxComponent");
130         if (ajaxComponent == null)
131         {
132             String msg = "Component with id [" + affectedAjaxComponent + "] not found in view tree.";
133             log.error(msg);
134             throw new ComponentNotFoundException(msg);
135         }
136         log.debug("affectedAjaxComponent: " + ajaxComponent + " - " + ajaxComponent.getId());
137         //todo: refactor this completely - should be somewhere else, but definitely not
138         //in the general phase listener
139         if (ajaxComponent instanceof HtmlCommandButtonAjax)
140         {
141             // special treatment for this one, it will try to update the entire form
142             // 1. get surrounding form
143             //String elname = (String) requestMap.get("elname");
144             FormInfo fi = RendererUtils.findNestingForm(ajaxComponent, context);
145             UIComponent form = fi.getForm();
146             //System.out.println("FOUND FORM: " + form);
147             if (form != null)
148             {
149                 form.processDecodes(context);
150                 //if(context.getRenderResponse())
151                     form.processValidators(context);
152                 //else log.debug("Decode failed on Ajax Decode");
153                 //if(context.getRenderResponse())
154                     form.processUpdates(context);
155                 //else log.debug("Validation failed on Ajax Decode");
156                 //System.out.println("DONE!");
157             }
158         }
159         else if (ajaxComponent instanceof AjaxComponent)
160         {
161             try
162             {
163                 // Now let the component decode this request
164                 ((AjaxComponent) ajaxComponent).decodeAjax(context);
165             }
166             catch (Exception e)
167             {
168                 log.error("Exception while decoding ajax-request", e);
169             }
170         }
171         else
172         {
173             log.error("Found component is no ajaxComponent : " + RendererUtils.getPathToComponent(ajaxComponent));
174         }
175     }
176 
177 
178     private void encodeAjax(UIComponent component, FacesContext context)
179     {
180         ServletResponse response = 
181             (ServletResponse) context.getExternalContext().getResponse();
182         ServletRequest request = 
183             (ServletRequest) context.getExternalContext().getRequest();
184         UIViewRoot viewRoot = context.getViewRoot();
185         Map requestMap = context.getExternalContext().getRequestParameterMap();
186         
187         String charset = (String) requestMap.get("charset");
188 
189        /* Handle character encoding as of section 2.5.2.2 of JSF 1.1:
190         * At the beginning of the render-response phase, the ViewHandler must ensure
191         * that the response Locale is set to that of the UIViewRoot, for exampe by
192         * calling ServletResponse.setLocale() when running in the servlet environment.
193         * Setting the response Locale may affect the response character encoding.
194         *
195         * Since there is no 'Render Response' phase for AJAX requests, we have to handle
196         * this manually.
197         */
198         response.setLocale(viewRoot.getLocale());
199 
200 
201         if(component instanceof DeprecatedAjaxComponent)
202         {
203             try
204             {
205                 String contentType = getContentType("text/xml", charset);
206                 response.setContentType(contentType);
207 
208                 StringBuffer buff = new StringBuffer();
209                 buff.append("<?xml version=\"1.0\"?>\n");
210                 buff.append("<response>\n");
211 
212                 PrintWriter out = response.getWriter();
213                 out.print(buff);
214 
215                 // imario@apache.org: setup response writer, otherwise the component will fail with an NPE. I dont know why this worked before.
216                 context.setResponseWriter(new HtmlResponseWriterImpl(out,
217                                           contentType,
218                                           request.getCharacterEncoding()));
219 
220                 if (component instanceof HtmlCommandButtonAjax)
221                 {
222                     buff = new StringBuffer();
223                     buff.append("<triggerComponent id=\"");
224                     buff.append(component.getClientId(context));
225                     buff.append("\" />\n");
226                     out.print(buff);                    
227 
228                     // special treatment for this one, it will try to update the entire form
229                     // 1. get surrounding form
230                     //String elname = (String) requestMap.get("elname");
231                     FormInfo fi = RendererUtils.findNestingForm(component, context);
232                     UIComponent form = fi.getForm();
233                     //System.out.println("FOUND FORM: " + form);
234                     if (form != null)
235                     {
236                         // special case, add responses from all components in form
237                         encodeChildren(form, context, requestMap);
238                     }
239                 }
240                 else if (component instanceof AjaxComponent)
241                 {
242                     // let component render xml response
243                     // NOTE: probably don't need an encodeAjax in each component, but leaving it in until that's for sure
244                     ((AjaxComponent) component).encodeAjax(context);
245                 } else {
246                     // just get latest value
247                     AjaxRendererUtils.encodeAjax(context, component);
248                 }
249                 // end response
250                 out.print("</response>");
251                 out.flush();
252             }
253             catch (IOException e)
254             {
255                 log.error("Exception while rendering ajax-response", e);
256             }
257         }
258         else if (component instanceof AjaxComponent)
259         {
260             try
261             {
262                 if (context.getResponseWriter() == null)
263                 {
264                     String contentType = getContentType("text/html", charset);
265                     response.setContentType(contentType);
266                     PrintWriter writer = response.getWriter();
267                     context.setResponseWriter(new HtmlResponseWriterImpl(writer,
268                                               contentType, response.getCharacterEncoding()));
269                 }
270 
271                 ((AjaxComponent) component).encodeAjax(context);
272             }
273             catch (IOException e)
274             {
275                 log.error("Exception while rendering ajax-response", e);
276             }
277         }
278 
279     }
280 
281     private String getContentType(String contentType, String charset)
282     {
283         if (charset == null || charset.trim().length() == 0)
284             return contentType;
285         else
286             return contentType + ";charset=" + charset;
287     }
288 
289     private void encodeChildren(UIComponent form, FacesContext context, Map requestMap)
290             throws IOException
291     {                                     
292         List formChildren = form.getChildren();
293         for (int i = 0; i < formChildren.size(); i++)
294         {
295             UIComponent uiComponent = (UIComponent) formChildren.get(i);
296             //System.out.println("component id: " + uiComponent.getClientId(context));
297             // only if it has a matching id in the request list
298             if (requestMap.containsKey(uiComponent.getClientId(context)))
299             {
300                 //System.out.println("FOUND COMPONENT SO ENCODING AJAX");
301                 AjaxRendererUtils.encodeAjax(context, uiComponent);
302             }
303             // recurse
304             encodeChildren(uiComponent, context, requestMap);
305         }
306     }
307 
308     /**
309      * spit out each name/value pair
310      * THIS IS IN HASHMAPUTILS, BUT FOR SOME REASON, ISN'T GETTING INTO THE JARS
311      */
312     public static String mapToString(Map map)
313     {
314         Set entries = map.entrySet();
315         Iterator iter = entries.iterator();
316         StringBuffer buff = new StringBuffer();
317         while (iter.hasNext())
318         {
319             Map.Entry entry = (Map.Entry) iter.next();
320             buff.append("[" + entry.getKey() + "," + entry.getValue() + "]\n");
321         }
322         return buff.toString();
323     }
324 
325     public static Object getValueForComponent(FacesContext context, UIComponent component)
326     {
327         String possibleClientId = component.getClientId(context);
328 
329         if (!context.getExternalContext().getRequestParameterMap().containsKey(possibleClientId))
330         {
331             possibleClientId = (String) context.getExternalContext()
332                                             .getRequestParameterMap().get("affectedAjaxComponent");
333 
334             log.debug("affectedAjaxComponent: " + possibleClientId);
335             UIViewRoot root = context.getViewRoot();
336             UIComponent ajaxComponent = root.findComponent(possibleClientId);
337             if (ajaxComponent instanceof UIComponentPerspective)
338             {
339                 UIComponentPerspective componentPerspective = (UIComponentPerspective) ajaxComponent;
340 
341                 ajaxComponent = (UIComponent)componentPerspective.executeOn(context, new ExecuteOnCallback()
342                 {
343                     public Object execute(FacesContext facesContext, UIComponent uiComponent)
344                     {
345                         return uiComponent;
346                     }
347                 });
348             }
349 
350             if (ajaxComponent != component)
351             {
352                 log.error("No value found for this component : " + possibleClientId);
353                 return null;
354             }
355         }
356 
357         Object encodedValue = context.getExternalContext().getRequestParameterMap().get(possibleClientId);
358 
359         if (encodedValue instanceof String)
360         {
361             return URIComponentUtils.decodeURIComponent((String) encodedValue);
362         }
363         else return encodedValue;
364     }
365 
366     public void afterPhase(PhaseEvent event)
367     {
368 
369     }
370 
371 
372 }