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.shared.view;
20  
21  import java.beans.BeanInfo;
22  import java.io.IOException;
23  import java.io.StringWriter;
24  import java.io.Writer;
25  import java.util.logging.Level;
26  import java.util.logging.Logger;
27  
28  import javax.faces.FactoryFinder;
29  import javax.faces.application.Resource;
30  import javax.faces.application.StateManager;
31  import javax.faces.application.ViewHandler;
32  import javax.faces.component.UIViewRoot;
33  import javax.faces.context.ExternalContext;
34  import javax.faces.context.FacesContext;
35  import javax.faces.context.ResponseWriter;
36  import javax.faces.render.RenderKit;
37  import javax.faces.render.RenderKitFactory;
38  import javax.faces.view.StateManagementStrategy;
39  import javax.faces.view.ViewMetadata;
40  import javax.servlet.ServletResponse;
41  import javax.servlet.ServletResponseWrapper;
42  import javax.servlet.http.HttpServletResponse;
43  
44  import org.apache.myfaces.shared.application.DefaultViewHandlerSupport;
45  import org.apache.myfaces.shared.application.ViewHandlerSupport;
46  import org.apache.myfaces.shared.config.MyfacesConfig;
47  import org.apache.myfaces.shared.renderkit.html.util.JavascriptUtils;
48  import org.apache.myfaces.shared.util.ExternalContextUtils;
49  
50  
51  public abstract class JspViewDeclarationLanguageBase extends ViewDeclarationLanguageBase
52  {
53    private static final Logger log = Logger.getLogger(JspViewDeclarationLanguageBase.class.getName());
54    
55    private static final String FORM_STATE_MARKER = "<!--@@JSF_FORM_STATE_MARKER@@-->";
56    private static final String AFTER_VIEW_TAG_CONTENT_PARAM = JspViewDeclarationLanguageBase.class
57                + ".AFTER_VIEW_TAG_CONTENT";
58    private static final int FORM_STATE_MARKER_LEN = FORM_STATE_MARKER.length();
59  
60    private ViewHandlerSupport _cachedViewHandlerSupport;
61    
62    @Override
63    public void buildView(FacesContext context, UIViewRoot view) throws IOException
64    {
65        // memorize that buildView() has been called for this view
66        setViewBuilt(context, view);
67        
68        if (context.getPartialViewContext().isPartialRequest())
69        {
70            // try to get (or create) a ResponseSwitch and turn off the output
71            Object origResponse = context.getExternalContext().getResponse();
72            ResponseSwitch responseSwitch = getResponseSwitch(origResponse);
73            if (responseSwitch == null)
74            {
75                // no ResponseSwitch installed yet - create one 
76                responseSwitch = createResponseSwitch(origResponse);
77                if (responseSwitch != null)
78                {
79                    // install the ResponseSwitch
80                    context.getExternalContext().setResponse(responseSwitch);
81                }
82            }
83            if (responseSwitch != null)
84            {
85                // turn the output off
86                responseSwitch.setEnabled(false);
87            }
88        }
89    }
90    
91    /**
92     * {@inheritDoc}
93     */
94    @Override
95    public BeanInfo getComponentMetadata(FacesContext context, Resource componentResource)
96    {
97        throw new UnsupportedOperationException();
98    }
99    /**
100    * {@inheritDoc}
101    */
102   @Override
103   public Resource getScriptComponentResource(FacesContext context, Resource componentResource)
104   {
105       throw new UnsupportedOperationException();
106   }
107   /**
108    * {@inheritDoc}
109    */
110   @Override
111   public void renderView(FacesContext context, UIViewRoot view) throws IOException
112   {
113       //Try not to use native objects in this class.  Both MyFaces and the bridge
114       //provide implementations of buildView but they do not override this class.
115       checkNull(context, "context");
116       checkNull(view, "view");
117       
118       // do not render the view if the rendered attribute for the view is false
119       if (!view.isRendered())
120       {
121           if (log.isLoggable(Level.FINEST))
122               log.finest("View is not rendered");
123           return;
124       }
125       
126       // Check if the current view has already been built via VDL.buildView()
127       // and if not, build it from here. This is necessary because legacy ViewHandler
128       // implementations return null on getViewDeclarationLanguage() and thus
129       // VDL.buildView() is never called. Furthermore, before JSF 2.0 introduced 
130       // the VDLs, the code that built the view was in ViewHandler.renderView().
131       if (!isViewBuilt(context, view))
132       {
133           buildView(context, view);
134       }
135   
136       ExternalContext externalContext = context.getExternalContext();
137   
138       String viewId = context.getViewRoot().getViewId();
139   
140       if (log.isLoggable(Level.FINEST))
141           log.finest("Rendering JSP view: " + viewId);
142   
143   
144       // handle character encoding as of section 2.5.2.2 of JSF 1.1
145       if(null != externalContext.getSession(false))
146       {
147         externalContext.getSessionMap().put(ViewHandler.CHARACTER_ENCODING_KEY, externalContext.getResponseCharacterEncoding());
148       }
149   
150       // render the view in this method (since JSF 1.2)
151       RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
152       RenderKit renderKit = renderFactory.getRenderKit(context, view.getRenderKitId());
153   
154       ResponseWriter responseWriter = context.getResponseWriter();
155       if (responseWriter == null)
156       {
157           responseWriter = renderKit.createResponseWriter(externalContext.getResponseOutputWriter(), null, externalContext.getRequestCharacterEncoding());
158           context.setResponseWriter(responseWriter);
159       }
160       
161       // try to enable the ResponseSwitch again (disabled in buildView())
162       Object response = context.getExternalContext().getResponse();
163       ResponseSwitch responseSwitch = getResponseSwitch(response);
164       if (responseSwitch != null)
165       {
166           responseSwitch.setEnabled(true);
167       }
168   
169       ResponseWriter oldResponseWriter = responseWriter;
170       StringWriter stateAwareWriter = null;
171       
172       StateManager stateManager = context.getApplication().getStateManager();
173       boolean viewStateAlreadyEncoded = isViewStateAlreadyEncoded(context);
174       
175       if (!viewStateAlreadyEncoded)
176       {
177         // we will need to parse the reponse and replace the view_state token with the actual state
178         stateAwareWriter = new StringWriter();
179   
180         // Create a new response-writer using as an underlying writer the stateAwareWriter
181         // Effectively, all output will be buffered in the stateAwareWriter so that later
182         // this writer can replace the state-markers with the actual state.
183         responseWriter = oldResponseWriter.cloneWithWriter(stateAwareWriter);
184         context.setResponseWriter(responseWriter);
185       }
186   
187       try
188       {
189         if (!actuallyRenderView(context, view))
190         {
191           return;
192         }
193       }
194       finally
195       {
196         if(oldResponseWriter != null)
197         {
198             context.setResponseWriter(oldResponseWriter);    
199         }
200       }
201   
202       if (!viewStateAlreadyEncoded)
203       {
204         // parse the response and replace the token wit the state
205         flushBufferToWriter(stateAwareWriter.getBuffer(), externalContext.getResponseOutputWriter());
206       }
207       else
208       {
209         stateManager.saveView(context);
210       }
211       
212       // now disable the ResponseSwitch again
213       if (responseSwitch != null)
214       {
215           responseSwitch.setEnabled(false);
216       }
217   
218       // Final step - we output any content in the wrappedResponse response from above to the response,
219       // removing the wrappedResponse response from the request, we don't need it anymore
220       ViewResponseWrapper afterViewTagResponse = (ViewResponseWrapper) externalContext.getRequestMap()
221               .get(AFTER_VIEW_TAG_CONTENT_PARAM);
222       externalContext.getRequestMap().remove(AFTER_VIEW_TAG_CONTENT_PARAM);
223       
224       // afterViewTagResponse is null if the current request is a partial request
225       if (afterViewTagResponse != null)
226       {
227           afterViewTagResponse.flushToWriter(externalContext.getResponseOutputWriter(), externalContext.getResponseCharacterEncoding());
228       }
229   
230       //TODO sobryan: Is this right?
231       context.getResponseWriter().flush();
232   }
233   /**
234    * {@inheritDoc}
235    */
236   @Override
237   public ViewMetadata getViewMetadata(FacesContext context, String viewId)
238   {
239       // Not necessary given that this method always returns null, but staying true to
240       // the spec.
241   
242       checkNull(context, "context");
243       //checkNull(viewId, "viewId");
244   
245       // JSP impl must return null.
246   
247       return null;
248   }
249   
250   protected boolean isViewStateAlreadyEncoded(FacesContext context)
251   {
252     if (MyfacesConfig.getCurrentInstance(context.getExternalContext()).isMyfacesImplAvailable())
253     {
254       // In MyFaces the viewState key is already encoded is server side state saving is being used
255       StateManager stateManager = context.getApplication().getStateManager();
256       return !context.getApplication().getStateManager().isSavingStateInClient(context);
257     }
258     else
259     {
260       return false;
261     }
262   }
263   
264   protected void setAfterViewTagResponseWrapper(ExternalContext ec, ViewResponseWrapper wrapper)
265   {
266     ec.getRequestMap().put(AFTER_VIEW_TAG_CONTENT_PARAM, wrapper);
267   }
268   
269   protected void flushBufferToWriter(StringBuffer buff, Writer writer) throws IOException
270   {
271     FacesContext facesContext = FacesContext.getCurrentInstance();
272     StateManager stateManager = facesContext.getApplication().getStateManager();
273 
274     StringWriter stateWriter = new StringWriter();
275     ResponseWriter realWriter = facesContext.getResponseWriter();
276     facesContext.setResponseWriter(realWriter.cloneWithWriter(stateWriter));
277 
278     Object serializedView = stateManager.saveView(facesContext);
279 
280     stateManager.writeState(facesContext, serializedView);
281     facesContext.setResponseWriter(realWriter);
282 
283     String state = stateWriter.getBuffer().toString();
284 
285     ExternalContext extContext = facesContext.getExternalContext();
286     if (JavascriptUtils.isJavascriptAllowed(extContext)
287             && MyfacesConfig.getCurrentInstance(extContext).isViewStateJavascript())
288     {
289       // If javascript viewstate is enabled no state markers were written
290       writePartialBuffer(buff, 0, buff.length(), writer);
291       writer.write(state);
292     }
293     else
294     {
295       // If javascript viewstate is disabled state markers must be replaced
296       int lastFormMarkerPos = 0;
297       int formMarkerPos = 0;
298       // Find all state markers and write out actual state instead
299       while ((formMarkerPos = buff.indexOf(JspViewDeclarationLanguageBase.FORM_STATE_MARKER, formMarkerPos)) > -1)
300       {
301         // Write content before state marker
302         writePartialBuffer(buff, lastFormMarkerPos, formMarkerPos, writer);
303         // Write state and move position in buffer after marker
304         writer.write(state);
305         formMarkerPos += JspViewDeclarationLanguageBase.FORM_STATE_MARKER_LEN;
306         lastFormMarkerPos = formMarkerPos;
307       }
308       
309       // Write content after last state marker
310       if (lastFormMarkerPos < buff.length())
311       {
312         writePartialBuffer(buff, lastFormMarkerPos, buff.length(), writer);
313       }
314     }
315   }
316   
317   protected void writePartialBuffer(StringBuffer contentBuffer, int beginIndex, int endIndex, Writer writer) throws IOException
318   {
319     int index = beginIndex;
320     int bufferSize = 2048;
321     char[] bufToWrite = new char[bufferSize];
322 
323     while (index < endIndex)
324     {
325       int maxSize = Math.min(bufferSize, endIndex - index);
326 
327       contentBuffer.getChars(index, index + maxSize, bufToWrite, 0);
328       writer.write(bufToWrite, 0, maxSize);
329 
330       index += bufferSize;
331     }
332   }
333 
334   /**
335    * Render the view now - properly setting and resetting the response writer
336    * [MF] Modified to return a boolean so subclass that delegates can determine
337    * whether the rendering succeeded or not. TRUE means success.
338    */
339   protected boolean actuallyRenderView(FacesContext facesContext, UIViewRoot viewToRender)
340       throws IOException
341   {
342       // Set the new ResponseWriter into the FacesContext, saving the old one aside.
343       ResponseWriter responseWriter = facesContext.getResponseWriter();
344   
345       // Now we actually render the document
346       // Call startDocument() on the ResponseWriter.
347       responseWriter.startDocument();
348   
349       // Call encodeAll() on the UIViewRoot
350       viewToRender.encodeAll(facesContext);
351   
352       // Call endDocument() on the ResponseWriter
353       responseWriter.endDocument();
354   
355       responseWriter.flush();
356       
357       // rendered successfully -- forge ahead
358       return true;
359   }
360   
361   @Override
362   public StateManagementStrategy getStateManagementStrategy(FacesContext context, String viewId)
363   {
364       return null;
365   }
366 
367   @Override
368   protected String calculateViewId(FacesContext context, String viewId)
369   {
370       if (_cachedViewHandlerSupport == null)
371       {
372           _cachedViewHandlerSupport = new DefaultViewHandlerSupport();
373       }
374   
375       return _cachedViewHandlerSupport.calculateViewId(context, viewId);
376   }
377   
378   /**
379    * Returns true if the given UIViewRoot has already been built via VDL.buildView().
380    * This is necessary because legacy ViewHandler implementations return null on 
381    * getViewDeclarationLanguage() and thus VDL.buildView() is never called. 
382    * So we have to check this in renderView() and, if it is false, we have to
383    * call buildView() manually before the rendering.
384    *  
385    * @param facesContext
386    * @param view
387    * @return
388    */
389   protected boolean isViewBuilt(FacesContext facesContext, UIViewRoot view)
390   {
391       return Boolean.TRUE.equals(facesContext.getAttributes().get(view));
392   }
393   
394   /**
395    * Saves a flag in the attribute map of the FacesContext to indicate
396    * that the given UIViewRoot was already built with VDL.buildView().
397    * 
398    * @param facesContext
399    * @param view
400    */
401   protected void setViewBuilt(FacesContext facesContext, UIViewRoot view)
402   {
403       facesContext.getAttributes().put(view, Boolean.TRUE);
404   }
405 
406   /**
407    * Trys to obtain a ResponseSwitch from the Response.
408    * @param response
409    * @return if found, the ResponseSwitch, null otherwise
410    */
411   private static ResponseSwitch getResponseSwitch(Object response)
412   {
413       // unwrap the response until we find a ResponseSwitch
414       while (response != null)
415       {
416           if (response instanceof ResponseSwitch)
417           {
418               // found
419               return (ResponseSwitch) response;
420           }
421           if (response instanceof ServletResponseWrapper)
422           {
423               // unwrap
424               response = ((ServletResponseWrapper) response).getResponse();
425           }
426           // no more possibilities to find a ResponseSwitch
427           break; 
428       }
429       return null; // not found
430   }
431   
432   /**
433    * Try to create a ResponseSwitch for this response.
434    * @param response
435    * @return the created ResponseSwitch, if there is a ResponseSwitch 
436    *         implementation for the given response, null otherwise
437    */
438   private static ResponseSwitch createResponseSwitch(Object response)
439   {
440       if (response instanceof HttpServletResponse)
441       {
442           return new HttpServletResponseSwitch((HttpServletResponse) response);
443       }
444       else if (response instanceof ServletResponse)
445       {
446           return new ServletResponseSwitch((ServletResponse) response);
447       }
448       return null;
449   }
450 
451   /**
452    * Writes the response and replaces the state marker tags with the state information for the current context
453    */
454 /*  private static class StateMarkerAwareWriter extends Writer
455   {
456     private StringBuilder buf;
457 
458     public StateMarkerAwareWriter()
459     {
460         this.buf = new StringBuilder();
461     }
462 
463     @Override
464     public void close() throws IOException
465     {
466     }
467 
468     @Override
469     public void flush() throws IOException
470     {
471     }
472 
473     @Override
474     public void write(char[] cbuf, int off, int len) throws IOException
475     {
476       if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0))
477       {
478         throw new IndexOutOfBoundsException();
479       }
480       else if (len == 0)
481       {
482         return;
483       }
484       buf.append(cbuf, off, len);
485     }
486 
487     public StringBuilder getStringBuilder()
488     {
489       return buf;
490     }
491   }*/
492 }