Package org.jboss.portletbridge.application

Source Code of org.jboss.portletbridge.application.PortletViewHandler$StringBuilderWriter

/******************************************************************************
* JBoss, a division of Red Hat                                               *
* Copyright 2006, Red Hat Middleware, LLC, and individual                    *
* contributors as indicated by the @authors tag. See the                     *
* copyright.txt in the distribution for a full listing of                    *
* individual contributors.                                                   *
*                                                                            *
* This is free software; you can redistribute it and/or modify it            *
* under the terms of the GNU Lesser General Public License as                *
* published by the Free Software Foundation; either version 2.1 of           *
* the License, or (at your option) any later version.                        *
*                                                                            *
* This software is distributed in the hope that it will be useful,           *
* but WITHOUT ANY WARRANTY; without even the implied warranty of             *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU           *
* Lesser General Public License for more details.                            *
*                                                                            *
* You should have received a copy of the GNU Lesser General Public           *
* License along with this software; if not, write to the Free                *
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA         *
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.                   *
******************************************************************************/
package org.jboss.portletbridge.application;

import java.io.IOException;
import java.io.Writer;
import java.net.MalformedURLException;
import java.util.Locale;
import java.util.Map;

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.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.RenderResponse;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.annotation.PortletNamingContainer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.portletbridge.AjaxPortletBridge;
import org.jboss.portletbridge.component.UIPortletViewRoot;
import org.jboss.portletbridge.context.PortalActionURL;
import org.jboss.portletbridge.richfaces.RichFacesHelper;
import org.jboss.portletbridge.util.FacesUtil;

/**
* @author asmirnov
*/
public class PortletViewHandler extends ViewHandlerWrapper {

  private static final String FACELETS_VIEW_HANDLER_CLASS = "org.jboss.portletbridge.application.FaceletPortletViewHandler";

  private static final Log _log = LogFactory.getLog(PortletViewHandler.class);

  // 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 SAVESTATE_FIELD_MARKER = "~com.sun.faces.saveStateFieldMarker~";

  ViewHandler parent;

  private volatile Class<? extends UIViewRoot> viewRootClass;

  /**
   * @param parent
   */
  public PortletViewHandler(ViewHandler parent) {
    super();
    try {
      ClassLoader classLoader = Thread.currentThread()
          .getContextClassLoader();
      if (null == classLoader) {
        classLoader = PortletViewHandler.class.getClassLoader();
      }
      Class<? extends ViewHandler> faceletsViewRootClass = classLoader
          .loadClass(FACELETS_VIEW_HANDLER_CLASS).asSubclass(
              ViewHandler.class);
      parent = faceletsViewRootClass.getConstructor(ViewHandler.class)
          .newInstance(parent);
    } catch (Throwable e) {
      _log.info("No Facelets library is presented");
    }
    this.parent = parent;
    getViewRootClass();
  }

  private Class<? extends UIViewRoot> getViewRootClass() {
    if (null == viewRootClass) {
      // JDK-5 Double Checked Locking
      synchronized (this) {
        if (null == viewRootClass) {
          try {
            RichFacesHelper richFacesHelper = new RichFacesHelper();
            viewRootClass = richFacesHelper.getViewRootClass();
          } catch (NoClassDefFoundError e) {
            viewRootClass = UIPortletViewRoot.class;
          }
        }
      }
    }
    return viewRootClass;
  }

  @Override
  public Locale calculateLocale(FacesContext context) {
    Locale locale;
    if (FacesUtil.isPortletRequest(context)) {
      locale = context.getExternalContext().getRequestLocale();
      if (null == locale) {
        locale = super.calculateLocale(context);
      }
    } else {
      locale = super.calculateLocale(context);
    }
    return locale;
  }

  public UIViewRoot createView(FacesContext facesContext, String viewId) {
    boolean portletRequest = FacesUtil.isPortletRequest(facesContext);
    if (portletRequest) {
      viewId = evaluateUrl(facesContext, viewId);
      try {
        PortalActionURL viewIdUrl = new PortalActionURL(viewId);
        viewId = viewIdUrl.getPath();
        Map<String, String[]> viewIdParameters = viewIdUrl
            .getParameters();
        facesContext.getExternalContext().getRequestMap().put(
            AjaxPortletBridge.VIEW_ID_PARAMETERS, viewIdParameters);
      } catch (MalformedURLException e) {
        // Do nothing, it is ordinary view Id
        _log.warn("Mailformed ViewId url", e);
      }
    }
    UIViewRoot root = super.createView(facesContext, viewId);
    Class<? extends UIViewRoot> rootClass = root.getClass();

    if (portletRequest
        && rootClass.getAnnotation(PortletNamingContainer.class) == null) {
      try {
        UIViewRoot portletRoot = getViewRootClass().newInstance();
        portletRoot.setViewId(root.getViewId());
        portletRoot.setLocale(root.getLocale());
        portletRoot.setRenderKitId(root.getRenderKitId());
        root = portletRoot;
      } catch (Exception e) {
        throw new FacesException(e);
      }
    }

    return root;
  }

  @Override
  public void writeState(FacesContext context) throws IOException {
    StringBuilderWriter stringBuilderWriter = StringBuilderWriter
        .getInstance();
    if (null != stringBuilderWriter) {
      stringBuilderWriter.stateWrited();
      context.getResponseWriter().write(SAVESTATE_FIELD_MARKER);
    } else {
      super.writeState(context);
    }
  }

  public String getActionURL(FacesContext context, String url) {
    // 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. Only executed during portlet request or AJAX
    // request
    // from portlet page.

    url = evaluateUrl(context, url);

    return super.getActionURL(context, url);

  }

  protected String evaluateUrl(FacesContext context, String url) {
    if (url.startsWith("#")) {
      // evaluate this as an EL expression
      url = (String) context.getApplication().evaluateExpressionGet(
          context, url, String.class);
      if (url == null) {
        throw new FacesException("Evaluated view ID is null " + url);
      }
    }
    return url;
  }

  /**
   * @deprecated Use {@link FacesUtil#isPortletRequest(FacesContext)} instead
   */
  public static boolean isPortletRequest(FacesContext context) {
    return FacesUtil.isPortletRequest(context);
  }

  // TODO - create own implementation. Now, this code are copied from MyFaces
  // implementation.
  @Override
  public void renderView(FacesContext context, UIViewRoot viewToRender)
      throws IOException, FacesException {
    // Get the renderPolicy from the init parameters
    ExternalContext externalContext = context.getExternalContext();
    String renderPolicyParam = externalContext
        .getInitParameter(Bridge.BRIDGE_PACKAGE_PREFIX
            + Bridge.RENDER_POLICY);

    Bridge.BridgeRenderPolicy renderPolicy;
    if (renderPolicyParam == null) {
      renderPolicy = Bridge.BridgeRenderPolicy.DEFAULT;
    } else {
      renderPolicy = Bridge.BridgeRenderPolicy.valueOf(renderPolicyParam);
    }

    if (null == externalContext.getRequestMap().get(
        Bridge.PORTLET_LIFECYCLE_PHASE)
        || renderPolicy == Bridge.BridgeRenderPolicy.ALWAYS_DELEGATE) {
      super.renderView(context, viewToRender);
      return;
    } else if (renderPolicy == Bridge.BridgeRenderPolicy.DEFAULT) {
      try {
        super.renderView(context, viewToRender);
        return;
      } catch (Throwable t) {
        if (_log.isDebugEnabled()) {
          _log
              .debug(
                  "Error rendering view by parent ViewHandler, try to render as portletbridge JSP page",
                  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 = externalContext;
    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 portletbridge
      // extContext.dispatch(viewToRender.getViewId());

      if (executePageToBuildView(context, viewToRender)) {
        renderResponse.flushBuffer();
        return;
      }

    } catch (IOException e) {
      throw new FacesException(e);
    }

    // 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,
        renderResponse.getWriter(), 4096);
    try {
      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.

      strWriter.flushToWriter();

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

    renderResponse.flushBuffer();
  }

  /**
   * <p>
   * This is a separate method to account for handling the content after the
   * view tag.
   * </p>
   * <p/>
   * <p>
   * Create a new ResponseWriter around this response's Writer. Set it into
   * the FacesContext, saving the old one aside.
   * </p>
   * <p/>
   * <p>
   * call encodeBegin(), encodeChildren(), encodeEnd() on the argument
   * <code>UIViewRoot</code>.
   * </p>
   * <p/>
   * <p>
   * Restore the old ResponseWriter into the FacesContext.
   * </p>
   * <p/>
   * <p>
   * Write out the after view content to the response's writer.
   * </p>
   * <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 static final ThreadLocal<StringBuilderWriter> instance = new ThreadLocal<StringBuilderWriter>();
    private final StringBuilder mBuilder;
    private final FacesContext context;
    private final Writer responseWriter;
    private boolean stateWrited = false;
    private static final int SAVESTATE_MARK_LEN = SAVESTATE_FIELD_MARKER
        .length();

    public StringBuilderWriter(FacesContext context, Writer responseWriter,
        int initialCapacity) {
      if (initialCapacity < 0) {
        throw new IllegalArgumentException();
      }
      mBuilder = new StringBuilder(initialCapacity);
      this.context = context;
      this.responseWriter = responseWriter;
      instance.set(this);
    }

    public void release() {
      instance.remove();
    }

    public void stateWrited() {
      this.stateWrited = true;

    }

    public static StringBuilderWriter getInstance() {
      return instance.get();
    }

    @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;
      }
      if (stateWrited) {
        mBuilder.append(cbuf, off, len);
      } else {
        responseWriter.write(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) throws IOException {
      if (stateWrited) {
        mBuilder.append(str);
      } else {
        responseWriter.write(str);
      }
    }

    @Override
    public void write(String str, int off, int len) throws IOException {
      if (stateWrited) {
        mBuilder.append(str, off, off + len);
      } else {
        responseWriter.write(str, off, len);
      }
    }

    public StringBuilder getBuffer() {
      return mBuilder;
    }

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

    public void flushToWriter() throws IOException {
      // TODO: Buffer?
      if (stateWrited) {
        StateManager stateManager = context.getApplication()
            .getStateManager();
        ResponseWriter oldResponseWriter = context.getResponseWriter();
        context.setResponseWriter(oldResponseWriter
            .cloneWithWriter(responseWriter));
        Object stateToWrite = stateManager.saveView(context);
        int pos = 0;
        int tildeIdx = mBuilder.indexOf(SAVESTATE_FIELD_MARKER);
        while (tildeIdx >= 0) {
          responseWriter.write(mBuilder.substring(pos, tildeIdx));
          stateManager.writeState(context, stateToWrite);
          pos = tildeIdx + SAVESTATE_MARK_LEN;
          tildeIdx = mBuilder.indexOf(SAVESTATE_FIELD_MARKER, pos);
        }
        responseWriter.write(mBuilder.substring(pos));
        context.setResponseWriter(oldResponseWriter);
      }
    }
  }

  /**
   * Execute the target view. If the HTTP status code range is not 2xx, then
   * return true to indicate the response should be immediately flushed by the
   * caller so that conditions such as 404 are properly handled.
   *
   * @param context
   *            the <code>FacesContext</code> for the current request
   * @param viewToExecute
   *            the view to build
   * @return <code>true</code> if the response should be immediately flushed
   *         to the client, otherwise <code>false</code>
   * @throws IOException
   *             if an error occurs executing the page
   */
  private boolean executePageToBuildView(FacesContext context,
      UIViewRoot viewToExecute) throws IOException {
    String requestURI = viewToExecute.getViewId();

    ExternalContext extContext = context.getExternalContext();

    extContext.dispatch(requestURI);
    return false;
  }

  @Override
  protected ViewHandler getWrapped() {
    return parent;
  }

}
TOP

Related Classes of org.jboss.portletbridge.application.PortletViewHandler$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.