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

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

/* 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.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Locale;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
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.PortletContext;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.BridgeUtil;
import javax.portlet.faces.annotation.PortletNamingContainer;
import javax.portlet.faces.component.PortletNamingContainerUIViewRoot;

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())
    {
      /* 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));
    if (qsLoc < viewId.length())
      actionURL = actionURL.concat(viewId.substring(qsLoc));
   
    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())
    {
      /* 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())
    {
      viewRoot.setViewId(viewRoot.getViewId().concat(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;
    }

    // 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
      }
    }

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

    ExternalContext extContext = context.getExternalContext();
    RenderResponse renderResponse = (RenderResponse) extContext.getResponse();

    try
    {

      // 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);
      // TODO JSF 1.2 - executePageToBuildView() creates
      // ViewHandlerResponseWrapper
      // to handle error page and text that exists after the <f:view> tag
      // among other things which have lots of servlet dependencies -
      // we're skipping this for now for portlet
     
     
      // Bridge has had to set this attribute so  Faces RI will skip servlet dependent
      // code when mapping from request paths to viewIds -- however we need to remove it
      // as it screws up the dispatch
      extContext.getRequestMap().remove("javax.servlet.include.servlet_path");
      extContext.dispatch(viewToRender.getViewId());
      /*
       * if (executePageToBuildView(context, viewToRender)) { response.flushBuffer(); return; }
       */
    }
    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))
    {
      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,
                                                 renderResponse.getCharacterEncoding());
    }
    context.setResponseWriter(newWriter);

    newWriter.startDocument();

    doRenderView(context, viewToRender);

    newWriter.endDocument();

    // 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 = renderResponse.getWriter();
    }
    catch (IllegalStateException ise) {    
      // got this exception because we've called getOutputStream() previously
      renderResponseWriter = new BufferedWriter(
                          new OutputStreamWriter(
                               renderResponse.getPortletOutputStream(),
                               renderResponse.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)
    {
      if (content instanceof char[])
      {
        renderResponse.getWriter().write(new String((char[]) content));
      }
      else if (content instanceof byte[])
      {
        renderResponse.getWriter().write(new String((byte[]) content));
      }
      else
      {
        throw new IOException("PortletViewHandlerImpl: invalid" + "AFTER_VIEW_CONTENT buffer type");
      }
    }
    renderResponse.flushBuffer();
  }

  /**
   * <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

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.