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