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