All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.application.jsp.JspViewHandlerImpl Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.myfaces.application.jsp;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.ResponseStateManager;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.jstl.core.Config;

import org.apache.myfaces.shared_impl.application.DefaultViewHandlerSupport;
import org.apache.myfaces.shared_impl.application.InvalidViewIdException;
import org.apache.myfaces.shared_impl.application.ViewHandlerSupport;
import org.apache.myfaces.shared_impl.config.MyfacesConfig;
import org.apache.myfaces.shared_impl.renderkit.html.util.JavascriptUtils;

/**
 * Implementation of the ViewHandler interface that knows how to use JSP pages
 * as the view templating mechanism.
 * 

* This implementation works tightly together with the various JSP TagHandler classes * to implement the behaviour mandated by the ViewHandler specification. *

* Rendering of a view is done in two parts: first a jsp-generated servlet is invoked * to create or refresh a jsf component tree, then the component tree is walked to generate * the output to send to the user. *

* The invoked servlet is the one generated from the jsp file which corresponds to the * viewId of the view being rendered. As is normal for jsp, this servlet alternates between * writing literal text to the response output stream and invoking "tag handler" classes * representing the jsp tags that were present in the page. This servlet is not aware of * JSF at all. *

* On the first visit to a view, when each JSF taghandler is invoked, the corresponding * JSF component will not yet exist so it is created and added to the current view tree. * Each JSF taghandler also marks itself as having "buffered body content", which means that * after the start-tag is executed a temporary output stream is installed for the response. * Any output generated by the jsp-servlet therefore gets written into a memory buffer * rather than sent via the network socket to the sender of the request. When the end * of the JSF tag is encountered, the JSF tag checks whether any such body text did exist, * and if so it creates a transient f:verbatim component and inserts it into the component * tree. The final result is that after this "first pass" a component tree exists which has * all the JSF components in it, plus a bunch of auto-generated f:verbatim components that * hold all plain text, or output generated by non-jsf jsp tags. Absolutely NO output has * yet been sent to the real response stream. *

* On later visits to the same view, the component tree already exists (has been restored). * However the "verbatim" components holding static text are not present as they were * marked "transient" (not keeping them reduces the amount of memory required to "save state"). * Note that these components are not needed for any phase prior to RENDER because they * are not "input" components. When the jsp-generated servlet is executed, JSF taghandlers * that are invoked will simply verify that a corresponding component already exists in the * view-tree rather than creating a new one. However the "body buffering" occurs again, so * that the appropriate transient verbatim components are once again created and inserted into * the tree. *

* Regardless of whether the view is new or restored, the rendered output can now be generated * simply by walking the component tree and invoking the encodeBegin/encodeChildren/encodeEnd * methods on each component. The static components simply output their contained text. *

* Notes for JSF1.1 users: the new two-phase approach that uses "output buffering" to capture * non-JSF output is rather like wrapping all non-jsf components in an invisible f:verbatim tag. * Although that doesn't sound like a big change, it allows processing to occur in two passes * rather than one. And that means that before any component is rendered the entire component * tree already exists. This solves a number of JSF1.1 problems, including output-ordering * problems between text and jsf components, and errors when using the "for" attribute of a * label to reference a component later in the page. It does introduce a performance penalty; * non-JSF-generated output now gets buffered rather than being streamed directly to the * user. *

* @author Thomas Spiegl (latest modification by $Author: lu4242 $) * @author Bruno Aranda * @version $Revision: 966305 $ $Date: 2010-07-21 11:39:15 -0500 (Wed, 21 Jul 2010) $ */ public class JspViewHandlerImpl extends ViewHandler { //private static final Log log = LogFactory.getLog(JspViewHandlerImpl.class); private static final Logger log = Logger.getLogger(JspViewHandlerImpl.class.getName()); public static final String FORM_STATE_MARKER = ""; public static final int FORM_STATE_MARKER_LEN = FORM_STATE_MARKER.length(); private static final String AFTER_VIEW_TAG_CONTENT_PARAM = JspViewHandlerImpl.class + ".AFTER_VIEW_TAG_CONTENT"; private ViewHandlerSupport _viewHandlerSupport; public JspViewHandlerImpl() { if (log.isLoggable(Level.FINEST)) log.finest("New ViewHandler instance created"); } /** * @param viewHandlerSupport * the viewHandlerSupport to set */ public void setViewHandlerSupport(ViewHandlerSupport viewHandlerSupport) { _viewHandlerSupport = viewHandlerSupport; } /** * @return the viewHandlerSupport */ protected ViewHandlerSupport getViewHandlerSupport() { if (_viewHandlerSupport == null) { _viewHandlerSupport = new DefaultViewHandlerSupport(); } return _viewHandlerSupport; } /** * Get the locales specified as acceptable by the original request, compare them to the * locales supported by this Application and return the best match. */ @Override public Locale calculateLocale(FacesContext facesContext) { Application application = facesContext.getApplication(); for (Iterator requestLocales = facesContext.getExternalContext().getRequestLocales(); requestLocales .hasNext();) { Locale requestLocale = requestLocales.next(); for (Iterator supportedLocales = application.getSupportedLocales(); supportedLocales.hasNext();) { Locale supportedLocale = supportedLocales.next(); // higher priority to a language match over an exact match // that occures further down (see Jstl Reference 1.0 8.3.1) if (requestLocale.getLanguage().equals(supportedLocale.getLanguage()) && (supportedLocale.getCountry() == null || supportedLocale.getCountry().length() == 0)) { return supportedLocale; } else if (supportedLocale.equals(requestLocale)) { return supportedLocale; } } } Locale defaultLocale = application.getDefaultLocale(); return defaultLocale != null ? defaultLocale : Locale.getDefault(); } @Override public String calculateRenderKitId(FacesContext facesContext) { Object renderKitId = facesContext.getExternalContext().getRequestMap().get( ResponseStateManager.RENDER_KIT_ID_PARAM); if (renderKitId == null) { renderKitId = facesContext.getApplication().getDefaultRenderKitId(); } if (renderKitId == null) { renderKitId = RenderKitFactory.HTML_BASIC_RENDER_KIT; } return renderKitId.toString(); } /** * Create a UIViewRoot object and return it; the returned object has no children. *

* As required by the spec, the returned object inherits locale and renderkit settings from * the viewRoot currently configured for the facesContext (if any). This means that on navigation * from one view to another these settings are "inherited". */ @Override public UIViewRoot createView(FacesContext facesContext, String viewId) { String calculatedViewId = viewId; try { calculatedViewId = getViewHandlerSupport().calculateViewId(facesContext, viewId); } catch (InvalidViewIdException e) { sendSourceNotFound(facesContext, e.getMessage()); } Application application = facesContext.getApplication(); ViewHandler applicationViewHandler = application.getViewHandler(); Locale currentLocale = null; String currentRenderKitId = null; UIViewRoot uiViewRoot = facesContext.getViewRoot(); if (uiViewRoot != null) { // Remember current locale and renderKitId currentLocale = uiViewRoot.getLocale(); currentRenderKitId = uiViewRoot.getRenderKitId(); } uiViewRoot = (UIViewRoot) application.createComponent(UIViewRoot.COMPONENT_TYPE); uiViewRoot.setViewId(calculatedViewId); if (currentLocale != null) { // set old locale uiViewRoot.setLocale(currentLocale); } else { // calculate locale uiViewRoot.setLocale(applicationViewHandler.calculateLocale(facesContext)); } if (currentRenderKitId != null) { // set old renderKit uiViewRoot.setRenderKitId(currentRenderKitId); } else { // calculate renderKit uiViewRoot.setRenderKitId(applicationViewHandler.calculateRenderKitId(facesContext)); } if (log.isLoggable(Level.FINEST)) log.finest("Created view " + viewId); return uiViewRoot; } private void sendSourceNotFound(FacesContext context, String message) { HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); try { context.responseComplete(); response.sendError(HttpServletResponse.SC_NOT_FOUND, message); } catch (IOException ioe) { throw new FacesException(ioe); } } /** * Return a string containing a webapp-relative URL that the user can invoke * to render the specified view. *

* URLs and ViewIds are not quite the same; for example a url of "/foo.jsf" * or "/faces/foo.jsp" may be needed to access the view "/foo.jsp". *

* This method simply delegates to ViewHandlerSupport.calculateActionURL. */ @Override public String getActionURL(FacesContext facesContext, String viewId) { return getViewHandlerSupport().calculateActionURL(facesContext, viewId); } @Override public String getResourceURL(FacesContext facesContext, String path) { if (path.length() > 0 && path.charAt(0) == '/') { return facesContext.getExternalContext().getRequestContextPath() + path; } return path; } /** * Generate output to the user by combining the data in the jsp-page specified by viewToRender * with the existing JSF component tree (if any). *

* As described in the class documentation, this first runs the jsp-generated servlet to * create or enhance the JSF component tree - including verbatim nodes for any non-jsf * data in that page. *

* The component tree is then walked to generate the appropriate output for each component. */ @Override public void renderView(FacesContext facesContext, UIViewRoot viewToRender) throws IOException, FacesException { if (viewToRender == null) { log.severe("viewToRender must not be null"); throw new NullPointerException("viewToRender must not be null"); } // do not render the view if the rendered attribute for the view is false if (!viewToRender.isRendered()) { if (log.isLoggable(Level.FINEST)) log.finest("View is not rendered"); return; } ExternalContext externalContext = facesContext.getExternalContext(); String viewId = facesContext.getViewRoot().getViewId(); if (log.isLoggable(Level.FINEST)) log.finest("Rendering JSP view: " + viewId); ServletResponse response = (ServletResponse) externalContext.getResponse(); ServletRequest request = (ServletRequest) externalContext.getRequest(); Locale locale = viewToRender.getLocale(); response.setLocale(locale); Config.set(request, Config.FMT_LOCALE, facesContext.getViewRoot().getLocale()); if(!buildView(response, externalContext, viewId)) { //building the view was unsuccessful - an exception occurred during rendering //we need to jump out return; } // handle character encoding as of section 2.5.2.2 of JSF 1.1 if (externalContext.getRequest() instanceof HttpServletRequest) { HttpServletRequest httpServletRequest = (HttpServletRequest) externalContext.getRequest(); HttpSession session = httpServletRequest.getSession(false); if (session != null) { session.setAttribute(ViewHandler.CHARACTER_ENCODING_KEY, response.getCharacterEncoding()); } } // render the view in this method (since JSF 1.2) RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY); RenderKit renderKit = renderFactory.getRenderKit(facesContext, viewToRender.getRenderKitId()); ResponseWriter responseWriter = facesContext.getResponseWriter(); if (responseWriter == null) { responseWriter = renderKit.createResponseWriter(response.getWriter(), null, ((HttpServletRequest) externalContext.getRequest()).getCharacterEncoding()); facesContext.setResponseWriter(responseWriter); } ResponseWriter oldResponseWriter = responseWriter; StateMarkerAwareWriter stateAwareWriter = null; StateManager stateManager = facesContext.getApplication().getStateManager(); if (stateManager.isSavingStateInClient(facesContext)) { stateAwareWriter = new StateMarkerAwareWriter(); // Create a new response-writer using as an underlying writer the stateAwareWriter // Effectively, all output will be buffered in the stateAwareWriter so that later // this writer can replace the state-markers with the actual state. responseWriter = hookInStateAwareWriter( oldResponseWriter, stateAwareWriter, renderKit, externalContext); facesContext.setResponseWriter(responseWriter); } actuallyRenderView(facesContext, viewToRender); facesContext.setResponseWriter(oldResponseWriter); //We're done with the document - now we can write all content //to the response, properly replacing the state-markers on the way out //by using the stateAwareWriter if (stateManager.isSavingStateInClient(facesContext)) { stateAwareWriter.flushToWriter(response.getWriter()); } else { stateManager.saveView(facesContext); } // Final step - we output any content in the wrappedResponse response from above to the response, // removing the wrappedResponse response from the request, we don't need it anymore ServletViewResponseWrapper afterViewTagResponse = (ServletViewResponseWrapper) externalContext.getRequestMap().get( AFTER_VIEW_TAG_CONTENT_PARAM); externalContext.getRequestMap().remove(AFTER_VIEW_TAG_CONTENT_PARAM); if (afterViewTagResponse != null) { afterViewTagResponse.flushToWriter(response.getWriter(), facesContext.getExternalContext().getResponseCharacterEncoding()); } response.flushBuffer(); } /** * Render the view now - properly setting and resetting the response writer */ private void actuallyRenderView(FacesContext facesContext, UIViewRoot viewToRender) throws IOException { // Set the new ResponseWriter into the FacesContext, saving the old one aside. ResponseWriter responseWriter = facesContext.getResponseWriter(); //Now we actually render the document // Call startDocument() on the ResponseWriter. responseWriter.startDocument(); // Call encodeAll() on the UIViewRoot viewToRender.encodeAll(facesContext); // Call endDocument() on the ResponseWriter responseWriter.endDocument(); responseWriter.flush(); } /**Create a new response-writer using as an underlying writer the stateAwareWriter * Effectively, all output will be buffered in the stateAwareWriter so that later * this writer can replace the state-markers with the actual state. * * If the FacesContext has a non-null ResponseWriter create a new writer using its * cloneWithWriter() method, passing the response's Writer as the argument. * Otherwise, use the current RenderKit to create a new ResponseWriter. * * @param oldResponseWriter * @param stateAwareWriter * @param renderKit * @param externalContext * @return */ private ResponseWriter hookInStateAwareWriter(ResponseWriter oldResponseWriter, StateMarkerAwareWriter stateAwareWriter, RenderKit renderKit, ExternalContext externalContext) { return oldResponseWriter.cloneWithWriter(stateAwareWriter); /* ResponseWriter newResponseWriter; if (oldResponseWriter != null) { newResponseWriter = oldResponseWriter.cloneWithWriter(stateAwareWriter); } else { if (log.isTraceEnabled()) log.trace("Creating new ResponseWriter"); newResponseWriter = renderKit.createResponseWriter(stateAwareWriter, null, ((HttpServletRequest) externalContext.getRequest()).getCharacterEncoding()); } return newResponseWriter; */ } /**Build the view-tree before rendering. * This is done by dispatching to the underlying JSP-page, effectively processing it, creating * components out of any text in between JSF components (not rendering the text to the output of course, this * will happen later while rendering), attaching these components * to the component tree, and buffering any content after the view-root. * * @param response The current response - it will be replaced while the view-building happens (we want the text in the component tree, not on the actual servlet output stream) * @param externalContext The external context where the response will be replaced while building * @param viewId The view-id to dispatch to * @return true if successfull, false if an error occurred during rendering * @throws IOException */ private boolean buildView(ServletResponse response, ExternalContext externalContext, String viewId) throws IOException { ServletViewResponseWrapper wrappedResponse = new ServletViewResponseWrapper((HttpServletResponse) response); externalContext.setResponse(wrappedResponse); try { externalContext.dispatch(viewId); } finally { externalContext.setResponse(response); } boolean errorResponse = wrappedResponse.getStatus() < 200 || wrappedResponse.getStatus() > 299; if (errorResponse) { wrappedResponse.flushToWrappedResponse(); return false; } // store the wrapped response in the request, so it is thread-safe externalContext.getRequestMap().put(AFTER_VIEW_TAG_CONTENT_PARAM, wrappedResponse); return true; } /** * Just invoke StateManager.restoreView. */ @Override public UIViewRoot restoreView(FacesContext facesContext, String viewId) { Application application = facesContext.getApplication(); ViewHandler applicationViewHandler = application.getViewHandler(); String renderKitId = applicationViewHandler.calculateRenderKitId(facesContext); String calculatedViewId = getViewHandlerSupport().calculateViewId(facesContext, viewId); UIViewRoot viewRoot = application.getStateManager().restoreView(facesContext, calculatedViewId, renderKitId); return viewRoot; } /** * Writes a state marker that is replaced later by one or more hidden form * inputs. *

* The problem with html is that the only place to encode client-side state is * in a hidden html input field. However when a form is submitted, only the fields * within a particular form are sent; fields in other forms are not sent. Therefore * the view tree state must be written into every form in the page. This method * is therefore invoked at the end of every form. *

* Theoretically the state of a component tree will not change after rendering * starts. Therefore it is possible to create a serialized representation of that * state at the start of the rendering phase (or when first needed) and output it * whenever needed as described above. However this is not currently implemented; * instead the entire page being generated is buffered, and a "marker" string is * output instead of the tree state. After the rendering pass is complete the component * final tree state is computed and the buffer is then post-processed to replace the * "marker" strings with the real data. *

* This method also supports "javascript viewstate". TODO: document this. * * @param facesContext * @throws IOException */ @Override public void writeState(FacesContext facesContext) throws IOException { StateManager stateManager = facesContext.getApplication().getStateManager(); if (stateManager.isSavingStateInClient(facesContext)) { // Only write state marker if javascript view state is disabled ExternalContext extContext = facesContext.getExternalContext(); if (!(JavascriptUtils.isJavascriptAllowed(extContext) && MyfacesConfig.getCurrentInstance(extContext).isViewStateJavascript())) { facesContext.getResponseWriter().write(FORM_STATE_MARKER); } } else { stateManager.writeState(facesContext, new Object[2]); } } /** * Writes the response and replaces the state marker tags with the state information for the current context */ private static class StateMarkerAwareWriter extends Writer { private StringBuilder buf; public StateMarkerAwareWriter() { this.buf = new StringBuilder(); } @Override public void close() throws IOException { } @Override public void flush() throws IOException { } @Override public void write(char[] cbuf, int off, int len) throws IOException { if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } buf.append(cbuf, off, len); } public StringBuilder getStringBuilder() { return buf; } public void flushToWriter(Writer writer) throws IOException { FacesContext facesContext = FacesContext.getCurrentInstance(); StateManager stateManager = facesContext.getApplication().getStateManager(); StringWriter stateWriter = new StringWriter(); ResponseWriter realWriter = facesContext.getResponseWriter(); facesContext.setResponseWriter(realWriter.cloneWithWriter(stateWriter)); Object serializedView = stateManager.saveView(facesContext); stateManager.writeState(facesContext, serializedView); facesContext.setResponseWriter(realWriter); StringBuilder contentBuffer = getStringBuilder(); String state = stateWriter.getBuffer().toString(); ExternalContext extContext = facesContext.getExternalContext(); if (JavascriptUtils.isJavascriptAllowed(extContext) && MyfacesConfig.getCurrentInstance(extContext).isViewStateJavascript()) { // If javascript viewstate is enabled no state markers were written write(contentBuffer, 0, contentBuffer.length(), writer); writer.write(state); } else { // If javascript viewstate is disabled state markers must be replaced int lastFormMarkerPos = 0; int formMarkerPos = 0; // Find all state markers and write out actual state instead while ((formMarkerPos = contentBuffer.indexOf(FORM_STATE_MARKER, formMarkerPos)) > -1) { // Write content before state marker write(contentBuffer, lastFormMarkerPos, formMarkerPos, writer); // Write state and move position in buffer after marker writer.write(state); formMarkerPos += FORM_STATE_MARKER_LEN; lastFormMarkerPos = formMarkerPos; } // Write content after last state marker if (lastFormMarkerPos < contentBuffer.length()) { write(contentBuffer, lastFormMarkerPos, contentBuffer.length(), writer); } } } /** * Writes the content of the specified StringBuffer from index * beginIndex to index endIndex - 1. * * @param contentBuffer the StringBuffer to copy content from * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive * @param writer the Writer to write to * @throws IOException if an error occurs writing to specified Writer */ private void write(StringBuilder contentBuffer, int beginIndex, int endIndex, Writer writer) throws IOException { int index = beginIndex; int bufferSize = 2048; char[] bufToWrite = new char[bufferSize]; while (index < endIndex) { int maxSize = Math.min(bufferSize, endIndex - index); contentBuffer.getChars(index, index + maxSize, bufToWrite, 0); writer.write(bufToWrite, 0, maxSize); index += bufferSize; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy