Package org.apache.myfaces.portlet.faces.application

Source Code of org.apache.myfaces.portlet.faces.application.PortletViewHandlerImpl$StringBuilderWriter

/* 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.portlet.faces.application;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;



import javax.faces.application.Application;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.application.ViewHandlerWrapper;
import javax.faces.component.UIComponent;
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.portlet.MimeResponse;
import javax.portlet.PortletContext;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceResponse;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.BridgeUtil;
import javax.portlet.faces.BridgeWriteBehindResponse;
import javax.portlet.faces.annotation.PortletNamingContainer;
import javax.portlet.faces.component.PortletNamingContainerUIViewRoot;

import org.apache.myfaces.portlet.faces.application.BridgeViewHandlerRenderResponseWrapper;
import org.apache.myfaces.portlet.faces.application.BridgeViewHandlerResourceResponseWrapper;
import org.apache.myfaces.portlet.faces.bridge.BridgeImpl;
import org.apache.myfaces.portlet.faces.util.QueryString;

/**
* View handler implementation for JSF portlet bridge.
*
* The only method we override here is getActionURL().
*
* TODO JSF 1.2 note: JSF 1.2 RI implements ViewHandler.renderView() differently in order to handle
* emitting non-JSF markup that follows the JSF tags after the JSF renders correctly. Unfortunately,
* the RI handles this by introducing several servlet dependencies. Currently, the bridge handles
* this by overriding the renderView() and ignoring (not interleafing) the non-JSF markup - see HACK
* below
*/
public class PortletViewHandlerImpl extends ViewHandlerWrapper
{

  // the ViewHandler to delegate to
  private ViewHandler mDelegate;
  private Bridge.BridgeRenderPolicy mRenderPolicy = null;

  public PortletViewHandlerImpl(ViewHandler handler)
  {
    mDelegate = handler;
  }
 
  protected ViewHandler getWrapped()
  {
    return mDelegate;
  }

  @Override
  public String getActionURL(FacesContext context, String viewId)
  {
    // action URLs are processed by the bridge in encodeActionURL
    // however the bridge extends Faces navigation rule support in that it
    // allows a to-view-id element to contain an EL expression.
    // We recognize this EL expresion here and evaluate to a viewid
    // before delegating// Do nothing when not running in portlet request
   
    if (!BridgeUtil.isPortletRequest())
    {
      return super.getActionURL(context, viewId);
    }
   
   
    // else its a portlet request so processing accoring to bridge rules
    /* Note: later versions of glassfish jsf append a / if not in the nav rule
    * -- remove it if blocking the expression.
    */
    if (viewId.startsWith("/#"))
    {
      viewId = viewId.substring(1);
    }
   
    if (viewId.startsWith("#"))
    {
      // evaluate this as an EL expression
      viewId = (String) context.getApplication().evaluateExpressionGet(context, viewId, String.class);
      if (viewId == null)
      {
        //TODO:  at least log an error.
      }
    }
   
    // Faces can't do suffix mapping (extension mapping) properly if there is a query string
    int qsLoc = viewId.indexOf('?');
    if (qsLoc < 0) qsLoc = viewId.length();
   
    String actionURL = super.getActionURL(context, viewId.substring(0, qsLoc));

    // Now add the parameters back on (if needed)
    if (qsLoc < viewId.length())
    {
      int aLoc = actionURL.indexOf('?');
      if (aLoc < 0)
      {
        actionURL = actionURL.concat(viewId.substring(qsLoc));
      }
      else if (viewId.length() > (qsLoc + 1))
      {
        actionURL = actionURL + "&" + viewId.substring(qsLoc + 1);
      }
    }
   
    return actionURL;
   
  }

  @Override
  public UIViewRoot createView(FacesContext facesContext, String viewId)
  {
    // The Bridge extends Faces navigation rule support in that it
    // allows a to-view-id element to contain an EL expression.
    // We recognize this EL expresion here and evaluate to a viewid
    // before delegating
    // Do nothing when not running in portlet request
   
    if (!BridgeUtil.isPortletRequest())
    {
      return super.createView(facesContext, viewId);
    }
   
   
    // else its a portlet request so processing accoring to bridge rules

    /* Note: later versions of glassfish jsf append a / if not in the nav rule
    * -- remove it if blocking the expression.
    */
    if (viewId.startsWith("/#"))
    {
      viewId = viewId.substring(1);
    }
   
    if (viewId.startsWith("#"))
    {
      // evaluate this as an EL expression
      viewId = (String) facesContext.getApplication().evaluateExpressionGet(facesContext, viewId, String.class);
      if (viewId == null)
      {
        //TODO:  at least log an error.
      }
    }
   
    UIViewRoot viewRoot = null;
   
    // The bridge allows viewIds to carry querystrings -- e.g. a qs that has the
    // mode parameter.  Unfortunately, underlying Faces impls don't expect any query
    // string when mapping from the viewId to the view -- in particular they don't
    // account for the query string when the Faces servlet is suffix mapped (extension
    // mapped).  So strip the query string here -- create the view and add back
    int qsLoc = viewId.indexOf('?');
    if (qsLoc < 0)
      qsLoc = viewId.length();

    viewRoot = super.createView(facesContext, viewId.substring(0, qsLoc));
   
    // As we have provided an ApplicationImpl to override createComponent
    // to detect when to swap in our NamingContainer the returned viewRoot
    // from above should already be the one we want.  Because its possible
    // its still the native UIViewRoot -- i.e. createComponent wasn't called
    // the following code checks/and does the right thing to satisfy the spec.
   
    if (viewRoot.getClass() == UIViewRoot.class &&
        UIViewRoot.class.getAnnotation(PortletNamingContainer.class) == null)
    {
      String prior = setUIViewRootComponent(facesContext);
      try
      {
        viewRoot = super.createView(facesContext, viewId.substring(0, qsLoc));
      }
      finally
      {
        if (prior != null)
          resetUIViewRootComponent(facesContext, prior);
      }
    }
   
    if (qsLoc < viewId.length())
    {
      // Add the QueryString as an additional attribute to the UIViewRoot.
      // Note:  only expected situation is when a NavigationHandler returns a viewId with a QS
      // as we support this in the faces-config.xml to introduce modes, etc.
      // The Bridge (ActionHandler) will pull this when it constructs the target
      // of the actionResponse based on this current ViewId.
      viewRoot.getAttributes().put(BridgeImpl.VIEWID_QUERYSTRING_ATTRIBUTE, viewId.substring(qsLoc));
    }
      
    return viewRoot;

  }
 
  private String setUIViewRootComponent(FacesContext context)
  {
    Application app = context.getApplication();
    UIComponent root = app.createComponent(UIViewRoot.COMPONENT_TYPE);
    if (root == null || (root.getClass() == UIViewRoot.class &&
        root.getClass().getAnnotation(PortletNamingContainer.class) == null))
    {
      app.addComponent(UIViewRoot.COMPONENT_TYPE, PortletNamingContainerUIViewRoot.class.getName());
      return UIViewRoot.class.getName();
    }
   
    return null;
  }
 
  private void resetUIViewRootComponent(FacesContext context, String componentClassName)
  {
    if (componentClassName != null)
    {
      context.getApplication().addComponent(UIViewRoot.COMPONENT_TYPE, componentClassName);
    }
  }
 
 
  @Override
  public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException,
                                                                       FacesException
  {
    // Do nothing when not running in portlet request
    if (!BridgeUtil.isPortletRequest())
    {
      super.renderView(context, viewToRender);
      return;
    }
   
    ExternalContext extContext = context.getExternalContext();
    MimeResponse mimeResponse = (MimeResponse) extContext.getResponse();

    // If first time -- Get the renderPolicy from the context init parameter
    if (mRenderPolicy == null)
    {
      PortletContext pCtx = (PortletContext) context.getExternalContext().getContext();
      String policy = pCtx.getInitParameter(Bridge.RENDER_POLICY);
      if (policy != null)
      {
        mRenderPolicy = Bridge.BridgeRenderPolicy.valueOf(policy);
      }
      else
      {
        mRenderPolicy = Bridge.BridgeRenderPolicy.DEFAULT;
      }
    }

    if (mRenderPolicy == Bridge.BridgeRenderPolicy.ALWAYS_DELEGATE)
    {
      super.renderView(context, viewToRender);
      return;
    }
    else if (mRenderPolicy == Bridge.BridgeRenderPolicy.DEFAULT)
    {
      try
      {
        super.renderView(context, viewToRender);
        return;
      }
      catch (Throwable t)
      {
        // catch all throws and swallow -- falling through to our own

        // render -- Note because delegate may have set the Response object and
        // not cleaned it up on the exception -- reset to ensure a good object
        if (!mimeResponse.equals(extContext.getResponse()))
        {
          extContext.setResponse(mimeResponse);
        }
      }
    }

    // suppress rendering if "rendered" property on the component is
    // false
    if (!viewToRender.isRendered())
    {
      return;
    }

    try
    {
      extContext.getRequestMap().remove("javax.servlet.include.servlet_path");
      dispatchPageToBuildView(context, extContext, viewToRender);
    }
    catch (IOException e)
    {
      throw new FacesException(e);
    }
   
    // If a redirect occurred -- merely return
    // check here to see if a redirect occurred -- if so rerun doFacesRequest
    // for this new view
    QueryString redirectParams = (QueryString) context.getExternalContext()
                      .getRequestMap().get(BridgeImpl.REDIRECT_VIEWPARAMS);
    if ((redirectParams != null))
    {
      // redirect must have occurred during dispatch
     
      // set a flag indicating we are in a renderRedirect so the ViewHandler will know to dispatch.include instead of forward
      // mark it as such so the next dispatch will use an include
      extContext.getRequestMap().put(BridgeImpl.HAS_RENDER_REDIRECTED_AFTER_FORWARD, Boolean.TRUE);
      return;
    }

    // set up the ResponseWriter
    RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder
                                                                     .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
    RenderKit renderKit = renderFactory.getRenderKit(context, viewToRender.getRenderKitId());

    ResponseWriter oldWriter = context.getResponseWriter();
    StringBuilderWriter strWriter = new StringBuilderWriter(context, 4096);
    ResponseWriter newWriter;
    if (null != oldWriter)
    {
      newWriter = oldWriter.cloneWithWriter(strWriter);
    }
    else
    {
      newWriter = renderKit.createResponseWriter(strWriter, null,
                                                 mimeResponse.getCharacterEncoding());
    }
    context.setResponseWriter(newWriter);

    newWriter.startDocument();

    doRenderView(context, viewToRender);

    newWriter.endDocument();
   
    // Check again to see if the processing of the Faces view triggered a redirect
    redirectParams = (QueryString) context.getExternalContext()
                      .getRequestMap().get(BridgeImpl.REDIRECT_VIEWPARAMS);
    if ((redirectParams != null))
    {
      // set a flag indicating we are in a renderRedirect so the ViewHandler will know to dispatch.include instead of forward
      // mark it as such so the next dispatch will use an include
      extContext.getRequestMap().put(BridgeImpl.HAS_RENDER_REDIRECTED_AFTER_FORWARD, Boolean.TRUE);
      return;
    }   

    // replace markers in the body content and write it to response.

    ResponseWriter responseWriter;

    // Dispatch may have output to an OutputStream instead of a Writer
    Writer renderResponseWriter = null;
    try {
      renderResponseWriter = mimeResponse.getWriter();
    }
    catch (IllegalStateException ise) {    
      // got this exception because we've called getOutputStream() previously
      renderResponseWriter = new BufferedWriter(
                             new OutputStreamWriter(
                               mimeResponse.getPortletOutputStream(),
                               mimeResponse.getCharacterEncoding()));
    }
    if (null != oldWriter)
    {
      responseWriter = oldWriter.cloneWithWriter(renderResponseWriter);
    }
    else
    {
      responseWriter = newWriter.cloneWithWriter(renderResponseWriter);
    }
    context.setResponseWriter(responseWriter);

    strWriter.write(responseWriter);
    renderResponseWriter.flush();

    if (null != oldWriter)
    {
      context.setResponseWriter(oldWriter);
    }

    Object content = extContext.getRequestMap().get(Bridge.AFTER_VIEW_CONTENT);
    if (content != null)
    {
      try
      {
        if (content instanceof char[])
        {
          mimeResponse.getWriter().write(new String((char[]) content));
        }
        else if (content instanceof byte[])
        {
          mimeResponse.getWriter().write(new String((byte[]) content));
        }
        else
        {
          throw new IOException("PortletViewHandlerImpl: invalid" + "AFTER_VIEW_CONTENT buffer type");
        }
      }
      finally
      {
        // So it doesn't geet reused accidentaly
        extContext.getRequestMap().remove(Bridge.AFTER_VIEW_CONTENT);
      }
    }
    mimeResponse.flushBuffer();
  }
 

  private void dispatchPageToBuildView(FacesContext context,
                                          ExternalContext extContext,
                                          UIViewRoot viewToRender)
      throws IOException
  {   
      String viewURI = viewToRender.getViewId();

      // update the JSTL locale attribute in request scope so that JSTL
      // picks up the locale from viewRoot. This attribute must be updated
      // before the JSTL setBundle tag is called because that is when the
      // new LocalizationContext object is created based on the locale.
      extContext.getRequestMap().put("javax.servlet.jsp.jstl.fmt.locale", context.getViewRoot().getLocale());

      // save the original response
      MimeResponse originalResponse = (MimeResponse) extContext.getResponse();

      // see whether the portlet wants to use a writeBehindResponse(Wrapper)
      Class<? extends BridgeWriteBehindResponse> writeBehindResponseWrapperClass = (Class<? extends BridgeWriteBehindResponse>) extContext.getRequestMap().get(BridgeImpl.WRITE_BEHIND_RESPONSE);
      BridgeWriteBehindResponse wrapped = null;
      if (writeBehindResponseWrapperClass != null)
      {
        try
       
          wrapped = writeBehindResponseWrapperClass.newInstance();
          if ((BridgeUtil.getPortletRequestPhase() == Bridge.PortletPhase.RENDER_PHASE && wrapped instanceof RenderResponse) ||
              (BridgeUtil.getPortletRequestPhase() == Bridge.PortletPhase.RESOURCE_PHASE && wrapped instanceof ResourceResponse))                                                                                                                     
          {

            extContext.setResponse(wrapped);
          }
          else
          {
            // warn and don't use
            wrapped = null;
            extContext.log("Unable to use configured BridgeWriteBehindResponse instance because its not the appropriate PortletResponseWrapper for the associated phase.");
          }
         
        } catch (Exception e)
        {
          // Just warn and don't use;
          extContext.log("Unable to instantiate configured BridgeWriteBehindResponse instance: ", e);
        }
      }
      else
      {
        // Use the generic (non-writeBehindResponse) wrapper
        if (BridgeUtil.getPortletRequestPhase() == Bridge.PortletPhase.RENDER_PHASE)
        {
          wrapped = new BridgeViewHandlerRenderResponseWrapper();
        }
        else
        {
          wrapped = new BridgeViewHandlerResourceResponseWrapper();
        }
        extContext.setResponse(wrapped);
        // don't set write behind as this one doesn't support the Faces specific implementation dependent interfaces
      }
      // set request attribute indicating we can deal with content
      // that is supposed to be delayed until after JSF tree is ouput.
      extContext.getRequestMap().put(Bridge.RENDER_CONTENT_AFTER_VIEW, Boolean.TRUE);


      // build the view by executing the page
      extContext.dispatch(viewURI);

      if (wrapped != null)
      {
        // replace the original response
        extContext.setResponse(originalResponse);
       
        // Check whether our wrapper participated in the the writeBehind mechanism and/or
        // something else already did (servlet filter)
        if (wrapped.hasFacesWriteBehindMarkup() && extContext.getRequestMap().get(Bridge.AFTER_VIEW_CONTENT) == null)
        {
          // Put the AFTER_VIEW_CONTENT into request scope
          // temporarily   
          Object o = (wrapped.isChars()) ? (Object) wrapped.getChars() : (Object) wrapped.getBytes();
       
          if (o != null)
          {
            extContext.getRequestMap().put(Bridge.AFTER_VIEW_CONTENT, o);
          }
        }
        else
        {
          // write ahead -- just write this to the restored response
          wrapped.flushMarkupToWrappedResponse();
        }
      }
  }


  /**
   * <p>
   * This is a separate method to account for handling the content after the view tag.
   * </p>
   *
   * <p>
   * Create a new ResponseWriter around this response's Writer. Set it into the FacesContext, saving
   * the old one aside.
   * </p>
   *
   * <p>
   * call encodeBegin(), encodeChildren(), encodeEnd() on the argument <code>UIViewRoot</code>.
   * </p>
   *
   * <p>
   * Restore the old ResponseWriter into the FacesContext.
   * </p>
   *
   * <p>
   * Write out the after view content to the response's writer.
   * </p>
   *
   * <p>
   * Flush the response buffer, and remove the after view content from the request scope.
   * </p>
   *
   * @param context
   *          the <code>FacesContext</code> for the current request
   * @param viewToRender
   *          the view to render
   * @throws IOException
   *           if an error occurs rendering the view to the client
   */
  private void doRenderView(FacesContext context, UIViewRoot viewToRender) throws IOException,
                                                                          FacesException
  {
    viewToRender.encodeAll(context);
  }

  private static final class StringBuilderWriter extends Writer
  {
    private StringBuilder       mBuilder;
    private FacesContext        mContext;

    // TODO: These bridge needs to use it's own constants here. This will
    // confine
    // us to only work with the R.I.
    private static final String RI_SAVESTATE_FIELD_MARKER = "~com.sun.faces.saveStateFieldMarker~";
    private static final String MYFACES_SAVESTATE_FIELD_MARKER = "<!--@@JSF_FORM_STATE_MARKER@@-->";
    private static String sSaveStateFieldMarker = null;

    public StringBuilderWriter(FacesContext context, int initialCapacity)
    {
      if (initialCapacity < 0)
      {
        throw new IllegalArgumentException();
      }
      mBuilder = new StringBuilder(initialCapacity);
      mContext = context;
    }

    @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;
      }
      mBuilder.append(cbuf, off, len);
    }

    @Override
    public void flush() throws IOException
    {
    }

    @Override
    public void close() throws IOException
    {
    }

    /**
     * Write a string.
     *
     * @param str
     *          String to be written
     */
    @Override
    public void write(String str)
    {
      mBuilder.append(str);
    }

    @Override
    public void write(String str, int off, int len)
    {
      write(str.substring(off, off + len));
    }

    public StringBuilder getBuffer()
    {
      return mBuilder;
    }

    @Override
    public String toString()
    {
      return mBuilder.toString();
    }

    public void write(Writer writer) throws IOException
    {

      // See if we already have determined the SAVESTATE_FIELD_MARKER in use
      // If not then determine it and set for future use
      if (sSaveStateFieldMarker == null)
      {
        sSaveStateFieldMarker = determineSaveStateFieldMarker();
      }
     
      // TODO: Buffer?
      int pos = 0;
     
      // First we need to make sure we save the view
      StateManager stateManager = mContext.getApplication().getStateManager();
      Object stateToWrite = stateManager.saveView(mContext);
     
      // If we didn't find a savestate_field_marker don't search to replace for one.
      if (sSaveStateFieldMarker != null)
      {
        int markLen = sSaveStateFieldMarker.length();
        int tildeIdx = mBuilder.indexOf(sSaveStateFieldMarker);
        while (tildeIdx > 0)
        {
          writer.write(mBuilder.substring(pos, tildeIdx));
          stateManager.writeState(mContext, stateToWrite);
          pos = tildeIdx + markLen;
          tildeIdx = mBuilder.indexOf(sSaveStateFieldMarker, pos);
        }
      }
     
      writer.write(mBuilder.substring(pos));
    }
   
    private String determineSaveStateFieldMarker() throws IOException
    {
      // First check to see if there is one set in the configuration - if so test it first
      String marker = ((PortletContext)FacesContext.getCurrentInstance().
                    getExternalContext().getContext()).getInitParameter(Bridge.SAVESTATE_FIELD_MARKER);

      if (isMarker(marker))
      {
        return marker;
      }
      // wasn't that one so test the Faces RI marker
      else if (isMarker(RI_SAVESTATE_FIELD_MARKER))
      {
        return RI_SAVESTATE_FIELD_MARKER;
      }
      // wasn't that one so test the MyFaces marker
      else if (isMarker(MYFACES_SAVESTATE_FIELD_MARKER))
      {
        return MYFACES_SAVESTATE_FIELD_MARKER;
      }
     
      // log that we didn't find a marker
      // However ignore this "exceptional" situation because its not so exceptional
      // MyFaces actually directly writes the state into the response more commonly
      // than it writes the Marker. 
      mContext.getExternalContext().log("Unable to locate a SAVESTATE_FIELD_MARKER in response.  This could be because your Faces environment doesn't write such a marker or because the bridge doesn't know the marker in use.  If the later, configure the appropriate application init parameter javax.portlet.faces.SAVESTATE_FIELD_MARKER.");
      return null;
    }
   
    private boolean isMarker(String marker)
    {
      return marker != null && mBuilder.indexOf(marker) >= 0;
    }
  }

  // END TODO HACK JSF 1.2
}
TOP

Related Classes of org.apache.myfaces.portlet.faces.application.PortletViewHandlerImpl$StringBuilderWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.