Ejemplo n.º 1
0
/** This {@link ViewHandler} implementation handles both JSP-based and Facelets/PDL-based views. */
public class MultiViewHandler extends ViewHandler {

  // Log instance for this class
  private static final Logger logger = FacesLogger.APPLICATION.getLogger();

  private String[] configuredExtensions;

  private PageDeclarationLanguageFactory pdlFactory;

  // ------------------------------------------------------------ Constructors

  public MultiViewHandler() {

    WebConfiguration config = WebConfiguration.getInstance();
    String defaultSuffixConfig =
        config.getOptionValue(WebConfiguration.WebContextInitParameter.DefaultSuffix);
    configuredExtensions = Util.split(defaultSuffixConfig, " ");
    pdlFactory =
        (PageDeclarationLanguageFactory)
            FactoryFinder.getFactory(FactoryFinder.PAGE_DECLARATION_LANGUAGE_FACTORY);
  }

  // ------------------------------------------------ Methods from ViewHandler

  /**
   * Do not call the default implementation of {@link
   * javax.faces.application.ViewHandler#initView(javax.faces.context.FacesContext)} if the {@link
   * javax.faces.context.ExternalContext#getRequestCharacterEncoding()} returns a <code>non-null
   * </code> result.
   *
   * @see javax.faces.application.ViewHandler#initView(javax.faces.context.FacesContext)
   */
  @Override
  public void initView(FacesContext context) throws FacesException {

    if (context.getExternalContext().getRequestCharacterEncoding() == null) {
      super.initView(context);
    }
  }

  /**
   * Call {@link PageDeclarationLanguage#renderView(javax.faces.context.FacesContext,
   * javax.faces.component.UIViewRoot)} if the view can be rendered.
   *
   * @see ViewHandler#renderView(javax.faces.context.FacesContext, javax.faces.component.UIViewRoot)
   */
  public void renderView(FacesContext context, UIViewRoot viewToRender)
      throws IOException, FacesException {

    Util.notNull("context", context);
    Util.notNull("viewToRender", viewToRender);

    pdlFactory
        .getPageDeclarationLanguage(viewToRender.getViewId())
        .renderView(context, viewToRender);
  }

  /**
   * Call {@link PageDeclarationLanguage#restoreView(javax.faces.context.FacesContext, String)}.
   *
   * @see ViewHandler#restoreView(javax.faces.context.FacesContext, String)
   */
  public UIViewRoot restoreView(FacesContext context, String viewId) {

    Util.notNull("context", context);
    String actualViewId = derivePhysicalViewId(context, viewId);
    return pdlFactory.getPageDeclarationLanguage(actualViewId).restoreView(context, actualViewId);
  }

  /**
   * @see ViewHandler#retargetAttachedObjects(javax.faces.context.FacesContext,
   *     javax.faces.component.UIComponent, java.util.List)
   */
  @Override
  public void retargetAttachedObjects(
      FacesContext context, UIComponent topLevelComponent, List<AttachedObjectHandler> handlers) {

    BeanInfo componentBeanInfo =
        (BeanInfo) topLevelComponent.getAttributes().get(UIComponent.BEANINFO_KEY);
    // PENDING(edburns): log error message if componentBeanInfo is null;
    if (null == componentBeanInfo) {
      return;
    }
    BeanDescriptor componentDescriptor = componentBeanInfo.getBeanDescriptor();
    // There is an entry in targetList for each attached object in the
    // <composite:interface> section of the composite component.
    List<AttachedObjectTarget> targetList =
        (List<AttachedObjectTarget>)
            componentDescriptor.getValue(AttachedObjectTarget.ATTACHED_OBJECT_TARGETS_KEY);
    // Each entry in targetList will vend one or more UIComponent instances
    // that is to serve as the target of an attached object in the consuming
    // page.
    List<UIComponent> targetComponents = null;
    String forAttributeValue, curTargetName, handlerTagId, componentTagId;
    boolean foundMatch = false;

    // For each of the attached object handlers...
    for (AttachedObjectHandler curHandler : handlers) {
      // Get the name given to this attached object by the page author
      // in the consuming page.
      forAttributeValue = curHandler.getFor();
      // For each of the attached objects in the <composite:interface> section
      // of this composite component...
      foundMatch = false;
      for (AttachedObjectTarget curTarget : targetList) {
        if (foundMatch) {
          break;
        }
        // Get the name given to this attached object target by the
        // composite component author
        curTargetName = curTarget.getName();
        targetComponents = curTarget.getTargets(topLevelComponent);

        if (curHandler instanceof ActionSource2AttachedObjectHandler
            && curTarget instanceof ActionSource2AttachedObjectTarget) {
          if (forAttributeValue.equals(curTargetName)) {
            for (UIComponent curTargetComponent : targetComponents) {
              curHandler.applyAttachedObject(context, curTargetComponent);
              foundMatch = true;
            }
          }
        } else if (curHandler instanceof EditableValueHolderAttachedObjectHandler
            && curTarget instanceof EditableValueHolderAttachedObjectTarget) {
          if (forAttributeValue.equals(curTargetName)) {
            for (UIComponent curTargetComponent : targetComponents) {
              curHandler.applyAttachedObject(context, curTargetComponent);
              foundMatch = true;
            }
          }
        } else if (curHandler instanceof ValueHolderAttachedObjectHandler
            && curTarget instanceof ValueHolderAttachedObjectTarget) {
          if (forAttributeValue.equals(curTargetName)) {
            for (UIComponent curTargetComponent : targetComponents) {
              curHandler.applyAttachedObject(context, curTargetComponent);
              foundMatch = true;
            }
          }
        }
      }
    }
  }

  /**
   * @see ViewHandler#retargetMethodExpressions(javax.faces.context.FacesContext,
   *     javax.faces.component.UIComponent)
   */
  @Override
  public void retargetMethodExpressions(FacesContext context, UIComponent topLevelComponent) {
    BeanInfo componentBeanInfo =
        (BeanInfo) topLevelComponent.getAttributes().get(UIComponent.BEANINFO_KEY);
    // PENDING(edburns): log error message if componentBeanInfo is null;
    if (null == componentBeanInfo) {
      return;
    }
    PropertyDescriptor attributes[] = componentBeanInfo.getPropertyDescriptors();
    String targets = null, attrName = null, strValue = null, methodSignature = null;
    UIComponent target = null;
    ExpressionFactory expressionFactory = null;
    ValueExpression valueExpression = null;
    MethodExpression toApply = null;
    Class expectedReturnType = null;
    Class expectedParameters[] = null;

    for (PropertyDescriptor cur : attributes) {
      // If the current attribute represents a ValueExpression
      if (null != (valueExpression = (ValueExpression) cur.getValue("type"))) {
        // take no action on this attribute.
        continue;
      }
      // If the current attribute representes a MethodExpression
      if (null != (valueExpression = (ValueExpression) cur.getValue("method-signature"))) {
        methodSignature = (String) valueExpression.getValue(context.getELContext());
        if (null != methodSignature) {

          // This is the name of the attribute on the top level component,
          // and on the inner component.
          if (null != (valueExpression = (ValueExpression) cur.getValue("targets"))) {
            targets = (String) valueExpression.getValue(context.getELContext());
          }

          if (null == targets) {
            targets = cur.getName();
          }

          if (null == targets || 0 == targets.length()) {
            // PENDING error message in page?
            logger.severe("Unable to retarget MethodExpression: " + methodSignature);
            continue;
          }

          String[] targetIds = targets.split(" ");

          for (String curTarget : targetIds) {

            attrName = cur.getName();

            // Find the attribute on the top level component
            valueExpression = (ValueExpression) topLevelComponent.getAttributes().get(attrName);
            if (null == valueExpression) {
              // PENDING error message in page?
              logger.severe(
                  "Unable to find attribute with name \""
                      + attrName
                      + "\" in top level component in consuming page.  "
                      + "Page author error.");
              continue;
            }

            // lazily initialize this local variable
            if (null == expressionFactory) {
              expressionFactory = context.getApplication().getExpressionFactory();
            }

            // If the attribute is one of the pre-defined
            // MethodExpression attributes
            boolean isAction = false,
                isActionListener = false,
                isValidator = false,
                isValueChangeListener = false;
            if ((isAction = attrName.equals("action"))
                || (isActionListener = attrName.equals("actionListener"))
                || (isValidator = attrName.equals("validator"))
                || (isValueChangeListener = attrName.equals("valueChangeListener"))) {
              // This is the inner component to which the attribute should
              // be applied
              target = topLevelComponent.findComponent(curTarget);
              if (null == targets) {
                // PENDING error message in page?
                logger.severe(
                    "Unable to retarget MethodExpression.  "
                        + "Unable to find inner component with id "
                        + targets
                        + ".");
                continue;
              }

              if (isAction) {
                expectedReturnType = Object.class;
                expectedParameters = new Class[] {};
                toApply =
                    expressionFactory.createMethodExpression(
                        context.getELContext(),
                        valueExpression.getExpressionString(),
                        expectedReturnType,
                        expectedParameters);
                ((ActionSource2) target).setActionExpression(toApply);
              } else if (isActionListener) {
                expectedReturnType = Void.TYPE;
                expectedParameters = new Class[] {ActionEvent.class};
                toApply =
                    expressionFactory.createMethodExpression(
                        context.getELContext(),
                        valueExpression.getExpressionString(),
                        expectedReturnType,
                        expectedParameters);
                ((ActionSource2) target)
                    .addActionListener(new MethodExpressionActionListener(toApply));
              } else if (isValidator) {
                expectedReturnType = Void.TYPE;
                expectedParameters =
                    new Class[] {FacesContext.class, UIComponent.class, Object.class};
                toApply =
                    expressionFactory.createMethodExpression(
                        context.getELContext(),
                        valueExpression.getExpressionString(),
                        expectedReturnType,
                        expectedParameters);
                ((EditableValueHolder) target).addValidator(new MethodExpressionValidator(toApply));
              } else if (isValueChangeListener) {
                expectedReturnType = Void.TYPE;
                expectedParameters = new Class[] {ValueChangeEvent.class};
                toApply =
                    expressionFactory.createMethodExpression(
                        context.getELContext(),
                        valueExpression.getExpressionString(),
                        expectedReturnType,
                        expectedParameters);
                ((EditableValueHolder) target)
                    .addValueChangeListener(new MethodExpressionValueChangeListener(toApply));
              }
            } else {
              // There is no explicit methodExpression property on
              // an inner component to which this MethodExpression
              // should be retargeted.  In this case, replace the
              // ValueExpression with a method expresson.

              // Pull apart the methodSignature to derive the
              // expectedReturnType and expectedParameters

              // PENDING(rlubke,jimdriscoll) bulletproof this

              assert (null != methodSignature);
              methodSignature = methodSignature.trim();

              // Get expectedReturnType
              int j, i = methodSignature.indexOf(" ");
              if (-1 != i) {
                strValue = methodSignature.substring(0, i);
                try {
                  expectedReturnType = Util.getTypeFromString(strValue);
                } catch (ClassNotFoundException cnfe) {
                  logger.log(
                      Level.SEVERE,
                      "Unable to determine expected return type for " + methodSignature,
                      cnfe);
                  continue;
                }
              } else {
                logger.severe("Unable to determine expected return type for " + methodSignature);
                continue;
              }

              // derive the arguments
              i = methodSignature.indexOf("(");
              if (-1 != i) {
                j = methodSignature.indexOf(")", i + 1);
                if (-1 != j) {
                  strValue = methodSignature.substring(i + 1, j);
                  if (0 < strValue.length()) {
                    String[] params = strValue.split(",");
                    expectedParameters = new Class[params.length];
                    boolean exceptionThrown = false;
                    for (i = 0; i < params.length; i++) {
                      try {
                        expectedParameters[i] = Util.getTypeFromString(params[i]);
                      } catch (ClassNotFoundException cnfe) {
                        logger.log(
                            Level.SEVERE,
                            "Unable to determine expected return type for " + methodSignature,
                            cnfe);
                        exceptionThrown = true;
                        break;
                      }
                    }
                    if (exceptionThrown) {
                      continue;
                    }

                  } else {
                    expectedParameters = new Class[] {};
                  }
                }
              }

              assert (null != expectedReturnType);
              assert (null != expectedParameters);

              toApply =
                  expressionFactory.createMethodExpression(
                      context.getELContext(),
                      valueExpression.getExpressionString(),
                      expectedReturnType,
                      expectedParameters);
              topLevelComponent.getAttributes().put(attrName, toApply);
            }
          }
        }
      }
    }
  }

  /**
   * Derive the actual view ID (i.e. the physical resource) and call call {@link
   * PageDeclarationLanguage#createView(javax.faces.context.FacesContext, String)}.
   *
   * @see ViewHandler#restoreView(javax.faces.context.FacesContext, String)
   */
  public UIViewRoot createView(FacesContext context, String viewId) {

    Util.notNull("context", context);
    String actualViewId = derivePhysicalViewId(context, viewId);
    return pdlFactory.getPageDeclarationLanguage(actualViewId).createView(context, actualViewId);
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#calculateLocale(javax.faces.context.FacesContext)
   */
  public Locale calculateLocale(FacesContext context) {

    Util.notNull("context", context);

    Locale result = null;
    // determine the locales that are acceptable to the client based on the
    // Accept-Language header and the find the best match among the
    // supported locales specified by the client.
    Iterator<Locale> locales = context.getExternalContext().getRequestLocales();
    while (locales.hasNext()) {
      Locale perf = locales.next();
      result = findMatch(context, perf);
      if (result != null) {
        break;
      }
    }
    // no match is found.
    if (result == null) {
      if (context.getApplication().getDefaultLocale() == null) {
        result = Locale.getDefault();
      } else {
        result = context.getApplication().getDefaultLocale();
      }
    }
    return result;
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#calculateRenderKitId(javax.faces.context.FacesContext)
   */
  public String calculateRenderKitId(FacesContext context) {

    Util.notNull("context", context);

    Map<String, String> requestParamMap = context.getExternalContext().getRequestParameterMap();
    String result = requestParamMap.get(ResponseStateManager.RENDER_KIT_ID_PARAM);

    if (result == null) {
      if (null == (result = context.getApplication().getDefaultRenderKitId())) {
        result = RenderKitFactory.HTML_BASIC_RENDER_KIT;
      }
    }
    return result;
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#writeState(javax.faces.context.FacesContext)
   */
  public void writeState(FacesContext context) throws IOException {

    Util.notNull("context", context);
    if (!context.getPartialViewContext().isAjaxRequest()) {
      if (logger.isLoggable(Level.FINE)) {
        logger.fine("Begin writing marker for viewId " + context.getViewRoot().getViewId());
      }

      WriteBehindStateWriter writer = WriteBehindStateWriter.getCurrentInstance();
      if (writer != null) {
        writer.writingState();
      }
      context.getResponseWriter().write(RIConstants.SAVESTATE_FIELD_MARKER);
      if (logger.isLoggable(Level.FINE)) {
        logger.fine("End writing marker for viewId " + context.getViewRoot().getViewId());
      }
    }
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#getActionURL(javax.faces.context.FacesContext, String)
   */
  public String getActionURL(FacesContext context, String viewId) {

    Util.notNull("context", context);
    Util.notNull("viewId", viewId);

    if (viewId.charAt(0) != '/') {
      String message =
          MessageUtils.getExceptionMessageString(MessageUtils.ILLEGAL_VIEW_ID_ID, viewId);
      if (logger.isLoggable(Level.SEVERE)) {
        logger.log(Level.SEVERE, "jsf.illegal_view_id_error", viewId);
      }
      throw new IllegalArgumentException(message);
    }

    // Acquire the context path, which we will prefix on all results
    ExternalContext extContext = context.getExternalContext();
    String contextPath = extContext.getRequestContextPath();

    // Acquire the mapping used to execute this request (if any)
    String mapping = Util.getFacesMapping(context);

    // If no mapping can be identified, just return a server-relative path
    if (mapping == null) {
      return (contextPath + viewId);
    }

    // Deal with prefix mapping
    if (Util.isPrefixMapped(mapping)) {
      if (mapping.equals("/*")) {
        return (contextPath + viewId);
      } else {
        return (contextPath + mapping + viewId);
      }
    }

    // Deal with extension mapping
    int period = viewId.lastIndexOf('.');
    if (period < 0) {
      return (contextPath + viewId + mapping);
    } else if (!viewId.endsWith(mapping)) {
      return (contextPath + viewId.substring(0, period) + mapping);
    } else {
      return (contextPath + viewId);
    }
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#getResourceURL(javax.faces.context.FacesContext, String)
   */
  public String getResourceURL(FacesContext context, String path) {

    ExternalContext extContext = context.getExternalContext();
    if (path.charAt(0) == '/') {
      return (extContext.getRequestContextPath() + path);
    } else {
      return path;
    }
  }

  /** @see ViewHandler#getPageDeclarationLanguage(javax.faces.context.FacesContext, String) */
  @Override
  public PageDeclarationLanguage getPageDeclarationLanguage(FacesContext context, String viewId) {

    String actualViewId = derivePhysicalViewId(context, viewId);
    return pdlFactory.getPageDeclarationLanguage(actualViewId);
  }

  // ------------------------------------------------------- Protected Methods

  /**
   * Attempts to find a matching locale based on <code>pref</code> and list of supported locales,
   * using the matching algorithm as described in JSTL 8.3.2.
   *
   * @param context the <code>FacesContext</code> for the current request
   * @param pref the preferred locale
   * @return the Locale based on pref and the matching alogritm specified in JSTL 8.3.2
   */
  protected Locale findMatch(FacesContext context, Locale pref) {

    Locale result = null;
    Iterator<Locale> it = context.getApplication().getSupportedLocales();
    while (it.hasNext()) {
      Locale supportedLocale = it.next();

      if (pref.equals(supportedLocale)) {
        // exact match
        result = supportedLocale;
        break;
      } else {
        // Make sure the preferred locale doesn't have country
        // set, when doing a language match, For ex., if the
        // preferred locale is "en-US", if one of supported
        // locales is "en-UK", even though its language matches
        // that of the preferred locale, we must ignore it.
        if (pref.getLanguage().equals(supportedLocale.getLanguage())
            && supportedLocale.getCountry().length() == 0) {
          result = supportedLocale;
        }
      }
    }
    // if it's not in the supported locales,
    if (null == result) {
      Locale defaultLocale = context.getApplication().getDefaultLocale();
      if (defaultLocale != null) {
        if (pref.equals(defaultLocale)) {
          // exact match
          result = defaultLocale;
        } else {
          // Make sure the preferred locale doesn't have country
          // set, when doing a language match, For ex., if the
          // preferred locale is "en-US", if one of supported
          // locales is "en-UK", even though its language matches
          // that of the preferred locale, we must ignore it.
          if (pref.getLanguage().equals(defaultLocale.getLanguage())
              && defaultLocale.getCountry().length() == 0) {
            result = defaultLocale;
          }
        }
      }
    }

    return result;
  }

  /**
   * if the specified mapping is a prefix mapping, and the provided request URI (usually the value
   * from <code>ExternalContext.getRequestServletPath()</code>) starts with <code>mapping + '/'
   * </code>, prune the mapping from the URI and return it, otherwise, return the original URI.
   *
   * @param uri the servlet request path
   * @param mapping the FacesServlet mapping used for this request
   * @return the URI without additional FacesServlet mappings
   * @since 1.2
   */
  protected String normalizeRequestURI(String uri, String mapping) {

    if (mapping == null || !Util.isPrefixMapped(mapping)) {
      return uri;
    } else {
      int length = mapping.length() + 1;
      StringBuilder builder = new StringBuilder(length);
      builder.append(mapping).append('/');
      String mappingMod = builder.toString();
      boolean logged = false;
      while (uri.startsWith(mappingMod)) {
        if (!logged && logger.isLoggable(Level.WARNING)) {
          logged = true;
          logger.log(
              Level.WARNING, "jsf.viewhandler.requestpath.recursion", new Object[] {uri, mapping});
        }
        uri = uri.substring(length - 1);
      }
      return uri;
    }
  }

  /**
   * Send {@link HttpServletResponse#SC_NOT_FOUND} (404) to the client.
   *
   * @param context the {@link FacesContext} for the current request
   */
  protected void send404Error(FacesContext context) {

    HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
    try {
      context.responseComplete();
      response.sendError(HttpServletResponse.SC_NOT_FOUND);
    } catch (IOException ioe) {
      throw new FacesException(ioe);
    }
  }

  /**
   * Adjust the viewID per the requirements of {@link #renderView}.
   *
   * @param context current {@link javax.faces.context.FacesContext}
   * @param viewId incoming view ID
   * @return the view ID with an altered suffix mapping (if necessary)
   */
  protected String convertViewId(FacesContext context, String viewId) {

    // if the viewId doesn't already use the above suffix,
    // replace or append.
    StringBuilder buffer = new StringBuilder(viewId);
    for (String ext : configuredExtensions) {
      if (viewId.endsWith(ext)) {
        return viewId;
      }
      int extIdx = viewId.lastIndexOf('.');
      if (extIdx != -1) {
        buffer.replace(extIdx, viewId.length(), ext);
      } else {
        // no extension in the provided viewId, append the suffix
        buffer.append(ext);
      }
      String convertedViewId = buffer.toString();
      try {
        if (context.getExternalContext().getResource(convertedViewId) != null) {
          // RELEASE_PENDING (rlubke,driscoll) cache the lookup
          return convertedViewId;
        } else {
          // reset the buffer to check for the next extension
          buffer.setLength(0);
          buffer.append(viewId);
        }
      } catch (MalformedURLException e) {
        if (logger.isLoggable(Level.SEVERE)) {
          logger.log(Level.SEVERE, e.toString(), e);
        }
      }
    }

    // unable to find any resource match that the default ViewHandler
    // can deal with.  Return the viewId as it was passed.  There is
    // probably another ViewHandler in the stack that will handle this.
    return viewId;
  }

  protected String derivePhysicalViewId(FacesContext ctx, String viewId) {
    if (viewId != null) {
      String mapping = Util.getFacesMapping(ctx);

      if (mapping != null) {
        if (!Util.isPrefixMapped(mapping)) {
          viewId = convertViewId(ctx, viewId);
        } else {
          viewId = normalizeRequestURI(viewId, mapping);
          if (viewId.equals(mapping)) {
            // The request was to the FacesServlet only - no
            // path info
            // on some containers this causes a recursion in the
            // RequestDispatcher and the request appears to hang.
            // If this is detected, return status 404
            send404Error(ctx);
          }
        }
      }
    }
    return viewId;
  }
}
Ejemplo n.º 2
0
/**
 * <strong>NavigationHandlerImpl</strong> is the class that implements default navigation handling.
 * Refer to section 7.4.2 of the specification for more details. PENDING: Make independent of
 * ApplicationAssociate.
 */
public class NavigationHandlerImpl extends ConfigurableNavigationHandler {

  //
  // Protected Constants
  //

  // Log instance for this class
  private static final Logger logger = FacesLogger.APPLICATION.getLogger();

  //
  // Class Variables
  //

  // Instance Variables

  /** <code>Map</code> containing configured navigation cases. */
  private Map<String, List<NavigationCase>> caseListMap;

  /** <code>Set</code> containing wildcard navigation cases. */
  private Set<String> wildCardSet;

  /** Flag indicating navigation cases properly consumed and available. */
  private boolean navigationConfigured;

  /** Flag indicated the current mode. */
  private boolean development;

  /**
   * This constructor uses the current <code>Application</code> instance to obtain the navigation
   * mappings used to make navigational decisions.
   */
  public NavigationHandlerImpl() {
    super();
    if (logger.isLoggable(Level.FINE)) {
      logger.log(Level.FINE, "Created NavigationHandler instance ");
    }
    // if the user is using the decorator pattern, this would cause
    // our ApplicationAssociate to be created, if it isn't already
    // created.
    ApplicationFactory aFactory =
        (ApplicationFactory) FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
    aFactory.getApplication();
    ApplicationAssociate associate =
        ApplicationAssociate.getInstance(FacesContext.getCurrentInstance().getExternalContext());
    if (associate != null) {
      caseListMap = associate.getNavigationCaseListMappings();
      wildCardSet = associate.getNavigationWildCardList();
      navigationConfigured = (wildCardSet != null && caseListMap != null);
      development = associate.isDevModeEnabled();
    }
  }

  NavigationHandlerImpl(ApplicationAssociate associate) {
    if (associate == null) {
      throw new NullPointerException();
    } else {
      caseListMap = associate.getNavigationCaseListMappings();
      wildCardSet = associate.getNavigationWildCardList();
      navigationConfigured = (wildCardSet != null && caseListMap != null);
    }
  }

  @Override
  public NavigationCase getNavigationCase(FacesContext context, String fromAction, String outcome) {
    NavigationCase result = null;
    CaseStruct caseStruct = getViewId(context, fromAction, outcome);
    if (null != caseStruct) {
      result = caseStruct.navCase;
    }

    return result;
  }

  @Override
  public Map<String, List<NavigationCase>> getNavigationCases() {
    return caseListMap;
  }

  /**
   * Determine the next view based on the current view (<code>from-view-id</code> stored in <code>
   * FacesContext</code>), <code>fromAction</code> and <code>outcome</code>.
   *
   * @param context The <code>FacesContext</code>
   * @param fromAction the action reference string
   * @param outcome the outcome string
   */
  public void handleNavigation(FacesContext context, String fromAction, String outcome) {
    if (context == null) {
      String message =
          MessageUtils.getExceptionMessageString(
              MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
      throw new NullPointerException(message);
    }
    if (outcome == null) {
      return; // Explicitly remain on the current view
    }
    CaseStruct caseStruct = getViewId(context, fromAction, outcome);
    ExternalContext extContext = context.getExternalContext();
    if (caseStruct != null) {
      ViewHandler viewHandler = Util.getViewHandler(context);
      assert (null != viewHandler);

      if (caseStruct.navCase.isRedirect()) {
        // perform a 302 redirect.
        String newPath = viewHandler.getActionURL(context, caseStruct.viewId);
        try {
          if (logger.isLoggable(Level.FINE)) {
            logger.fine(
                "Redirecting to path "
                    + newPath
                    + " for outcome "
                    + outcome
                    + "and viewId "
                    + caseStruct.viewId);
          }
          // encode the redirect to ensure session state
          // is maintained
          extContext.redirect(extContext.encodeActionURL(newPath));
        } catch (java.io.IOException ioe) {
          if (logger.isLoggable(Level.SEVERE)) {
            logger.log(Level.SEVERE, "jsf.redirect_failed_error", newPath);
          }
          throw new FacesException(ioe.getMessage(), ioe);
        }
        context.responseComplete();
        if (logger.isLoggable(Level.FINE)) {
          logger.fine("Response complete for " + caseStruct.viewId);
        }
      } else {
        UIViewRoot newRoot = viewHandler.createView(context, caseStruct.viewId);
        context.setViewRoot(newRoot);
        if (logger.isLoggable(Level.FINE)) {
          logger.fine("Set new view in FacesContext for " + caseStruct.viewId);
        }
      }
    }
  }

  /**
   * This method uses helper methods to determine the new <code>view</code> identifier. Refer to
   * section 7.4.2 of the specification for more details.
   *
   * @param context The Faces Context
   * @param fromAction The action reference string
   * @param outcome The outcome string
   * @return The <code>view</code> identifier.
   */
  private CaseStruct getViewId(FacesContext context, String fromAction, String outcome) {

    UIViewRoot root = context.getViewRoot();
    String viewId = (root != null ? root.getViewId() : null);

    // if viewId is not null, use its value to find
    // a navigation match, otherwise look for a match
    // based soley on the fromAction and outcome
    CaseStruct caseStruct = null;
    if (viewId != null) {
      caseStruct = findExactMatch(viewId, fromAction, outcome);

      if (caseStruct == null) {
        caseStruct = findWildCardMatch(viewId, fromAction, outcome);
      }
    }

    if (caseStruct == null) {
      caseStruct = findDefaultMatch(fromAction, outcome);
    }

    if (caseStruct == null && development) {
      String key;
      Object[] params;
      if (fromAction == null) {
        key = MessageUtils.NAVIGATION_NO_MATCHING_OUTCOME_ID;
        params = new Object[] {viewId, outcome};
      } else {
        key = MessageUtils.NAVIGATION_NO_MATCHING_OUTCOME_ACTION_ID;
        params = new Object[] {viewId, fromAction, outcome};
      }
      FacesMessage m = MessageUtils.getExceptionMessage(key, params);
      m.setSeverity(FacesMessage.SEVERITY_WARN);
      context.addMessage(null, m);
    }
    return caseStruct;
  }

  /**
   * This method finds the List of cases for the current <code>view</code> identifier. After the
   * cases are found, the <code>from-action</code> and <code>from-outcome</code> values are
   * evaluated to determine the new <code>view</code> identifier. Refer to section 7.4.2 of the
   * specification for more details.
   *
   * @param viewId The current <code>view</code> identifier.
   * @param fromAction The action reference string.
   * @param outcome The outcome string.
   * @return The <code>view</code> identifier.
   */
  private CaseStruct findExactMatch(String viewId, String fromAction, String outcome) {

    // if the user has elected to replace the Application instance
    // entirely
    if (!navigationConfigured) {
      return null;
    }

    List<NavigationCase> caseList = caseListMap.get(viewId);

    if (caseList == null) {
      return null;
    }

    // We've found an exact match for the viewId.  Now we need to evaluate
    // from-action/outcome in the following order:
    // 1) elements specifying both from-action and from-outcome
    // 2) elements specifying only from-outcome
    // 3) elements specifying only from-action
    // 4) elements where both from-action and from-outcome are null

    return determineViewFromActionOutcome(caseList, fromAction, outcome);
  }

  /**
   * This method traverses the wild card match List (containing <code>from-view-id</code> strings
   * and finds the List of cases for each <code>from-view-id</code> string. Refer to section 7.4.2
   * of the specification for more details.
   *
   * @param viewId The current <code>view</code> identifier.
   * @param fromAction The action reference string.
   * @param outcome The outcome string.
   * @return The <code>view</code> identifier.
   */
  private CaseStruct findWildCardMatch(String viewId, String fromAction, String outcome) {
    CaseStruct result = null;

    // if the user has elected to replace the Application instance
    // entirely
    if (!navigationConfigured) {
      return null;
    }

    for (String fromViewId : wildCardSet) {
      // See if the entire wildcard string (without the trailing "*" is
      // contained in the incoming viewId.
      // Ex: /foobar is contained with /foobarbaz
      // If so, then we have found our largest pattern match..
      // If not, then continue on to the next case;

      if (!viewId.startsWith(fromViewId)) {
        continue;
      }

      // Append the trailing "*" so we can do our map lookup;

      String wcFromViewId = new StringBuilder(32).append(fromViewId).append('*').toString();
      List<NavigationCase> caseList = caseListMap.get(wcFromViewId);

      if (caseList == null) {
        return null;
      }

      // If we've found a match, then we need to evaluate
      // from-action/outcome in the following order:
      // 1) elements specifying both from-action and from-outcome
      // 2) elements specifying only from-outcome
      // 3) elements specifying only from-action
      // 4) elements where both from-action and from-outcome are null

      result = determineViewFromActionOutcome(caseList, fromAction, outcome);
      if (result != null) {
        break;
      }
    }
    return result;
  }

  /**
   * This method will extract the cases for which a <code>from-view-id</code> is an asterisk "*".
   * Refer to section 7.4.2 of the specification for more details.
   *
   * @param fromAction The action reference string.
   * @param outcome The outcome string.
   * @return The <code>view</code> identifier.
   */
  private CaseStruct findDefaultMatch(String fromAction, String outcome) {
    // if the user has elected to replace the Application instance
    // entirely
    if (!navigationConfigured) {
      return null;
    }

    List<NavigationCase> caseList = caseListMap.get("*");

    if (caseList == null) {
      return null;
    }

    // We need to evaluate from-action/outcome in the follow
    // order:  1)elements specifying both from-action and from-outcome
    // 2) elements specifying only from-outcome
    // 3) elements specifying only from-action
    // 4) elements where both from-action and from-outcome are null

    return determineViewFromActionOutcome(caseList, fromAction, outcome);
  }

  /**
   * This method will attempt to find the <code>view</code> identifier based on action reference and
   * outcome. Refer to section 7.4.2 of the specification for more details.
   *
   * @param caseList The list of navigation cases.
   * @param fromAction The action reference string.
   * @param outcome The outcome string.
   * @return The <code>view</code> identifier.
   */
  private CaseStruct determineViewFromActionOutcome(
      List<NavigationCase> caseList, String fromAction, String outcome) {

    CaseStruct result = new CaseStruct();
    for (NavigationCase cnc : caseList) {
      String cncFromAction = cnc.getFromAction();
      String fromOutcome = cnc.getFromOutcome();
      String toViewId = cnc.getToViewId();
      if ((cncFromAction != null) && (fromOutcome != null)) {
        if ((cncFromAction.equals(fromAction)) && (fromOutcome.equals(outcome))) {
          result.viewId = toViewId;
          result.navCase = cnc;
          return result;
        }
      }

      if ((cncFromAction == null) && (fromOutcome != null)) {
        if (fromOutcome.equals(outcome)) {
          result.viewId = toViewId;
          result.navCase = cnc;
          return result;
        }
      }

      if ((cncFromAction != null) && (fromOutcome == null)) {
        if (cncFromAction.equals(fromAction)) {
          result.viewId = toViewId;
          result.navCase = cnc;
          return result;
        }
      }

      if ((cncFromAction == null) && (fromOutcome == null)) {
        result.viewId = toViewId;
        result.navCase = cnc;
        return result;
      }
    }

    return null;
  }

  private static class CaseStruct {
    String viewId;
    NavigationCase navCase;
  }
}
Ejemplo n.º 3
0
/**
 * This <code>StateHelper</code> provides the functionality associated with server-side state
 * saving, though in actuallity, it is a hybrid between client and server.
 */
public class ServerSideStateHelper extends StateHelper {

  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

  /** Key to store the <code>AtomicInteger</code> used to generate unique state map keys. */
  public static final String STATEMANAGED_SERIAL_ID_KEY =
      ServerSideStateHelper.class.getName() + ".SerialId";

  /** The top level attribute name for storing the state structures within the session. */
  public static final String LOGICAL_VIEW_MAP =
      ServerSideStateHelper.class.getName() + ".LogicalViewMap";

  /** The number of logical views as configured by the user. */
  protected final Integer numberOfLogicalViews;

  /** The number of views as configured by the user. */
  protected final Integer numberOfViews;

  /** Flag determining how server state IDs are generated. */
  protected boolean generateUniqueStateIds;

  /** Flag determining whether or not javax.faces.ViewState should be namespaced. */
  protected boolean namespaceParameters;

  /** Used to generate unique server state IDs. */
  protected final Random random;

  // ------------------------------------------------------------ Constructors

  /** Construct a new <code>ServerSideStateHelper</code> instance. */
  public ServerSideStateHelper() {

    numberOfLogicalViews = getIntegerConfigValue(NumberOfLogicalViews);
    numberOfViews = getIntegerConfigValue(NumberOfViews);
    WebConfiguration webConfig = WebConfiguration.getInstance();
    generateUniqueStateIds = webConfig.isOptionEnabled(GenerateUniqueServerStateIds);
    if (generateUniqueStateIds) {
      random = new Random(System.nanoTime() + webConfig.getServletContext().hashCode());
    } else {
      random = null;
    }
    namespaceParameters = webConfig.isOptionEnabled(NamespaceParameters);
  }

  // ------------------------------------------------ Methods from StateHelper

  /**
   * <p>
   * Stores the provided state within the session obtained from the provided
   * <code>FacesContext</code>
   * </p>
   *
   * <p>If <code>stateCapture</code> is <code>null</code>, the composite
   * key used to look up the actual and logical views will be written to
   * the client as a hidden field using the <code>ResponseWriter</code>
   * from the provided <code>FacesContext</code>.</p>
   *
   * <p>If <code>stateCapture</code> is not <code>null</code>, the composite
   * key will be appended to the <code>StringBuilder<code> without any markup
   * included or any content written to the client.
   */
  public void writeState(FacesContext ctx, Object state, StringBuilder stateCapture)
      throws IOException {

    Util.notNull("context", ctx);

    String id;

    UIViewRoot viewRoot = ctx.getViewRoot();

    if (!viewRoot.isTransient()) {
      if (!ctx.getAttributes().containsKey("com.sun.faces.ViewStateValue")) {
        Util.notNull("state", state);
        Object[] stateToWrite = (Object[]) state;
        ExternalContext externalContext = ctx.getExternalContext();
        Object sessionObj = externalContext.getSession(true);
        Map<String, Object> sessionMap = externalContext.getSessionMap();

        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (sessionObj) {
          Map<String, Map> logicalMap =
              TypedCollections.dynamicallyCastMap(
                  (Map) sessionMap.get(LOGICAL_VIEW_MAP), String.class, Map.class);
          if (logicalMap == null) {
            logicalMap = Collections.synchronizedMap(new LRUMap<String, Map>(numberOfLogicalViews));
            sessionMap.put(LOGICAL_VIEW_MAP, logicalMap);
          }

          Object structure = stateToWrite[0];
          Object savedState = handleSaveState(stateToWrite[1]);

          String idInLogicalMap =
              (String) RequestStateManager.get(ctx, RequestStateManager.LOGICAL_VIEW_MAP);
          if (idInLogicalMap == null) {
            idInLogicalMap =
                ((generateUniqueStateIds) ? createRandomId() : createIncrementalRequestId(ctx));
          }
          String idInActualMap = null;
          if (ctx.getPartialViewContext().isPartialRequest()) {
            // If partial request, do not change actual view Id, because page not actually changed.
            // Otherwise partial requests will soon overflow cache with values that would be never
            // used.
            idInActualMap =
                (String) RequestStateManager.get(ctx, RequestStateManager.ACTUAL_VIEW_MAP);
          }
          if (null == idInActualMap) {
            idInActualMap =
                ((generateUniqueStateIds) ? createRandomId() : createIncrementalRequestId(ctx));
          }
          Map<String, Object[]> actualMap =
              TypedCollections.dynamicallyCastMap(
                  logicalMap.get(idInLogicalMap), String.class, Object[].class);
          if (actualMap == null) {
            actualMap = new LRUMap<String, Object[]>(numberOfViews);
            logicalMap.put(idInLogicalMap, actualMap);
          }

          id = idInLogicalMap + ':' + idInActualMap;

          Object[] stateArray = actualMap.get(idInActualMap);
          // reuse the array if possible
          if (stateArray != null) {
            stateArray[0] = structure;
            stateArray[1] = savedState;
          } else {
            actualMap.put(idInActualMap, new Object[] {structure, savedState});
          }

          // always call put/setAttribute as we may be in a clustered environment.
          sessionMap.put(LOGICAL_VIEW_MAP, logicalMap);
          ctx.getAttributes().put("com.sun.faces.ViewStateValue", id);
        }
      } else {
        id = (String) ctx.getAttributes().get("com.sun.faces.ViewStateValue");
      }
    } else {
      id = "stateless";
    }

    if (stateCapture != null) {
      stateCapture.append(id);
    } else {
      ResponseWriter writer = ctx.getResponseWriter();

      writer.startElement("input", null);
      writer.writeAttribute("type", "hidden", null);

      String viewStateParam = ResponseStateManager.VIEW_STATE_PARAM;

      if ((namespaceParameters) && (viewRoot instanceof NamingContainer)) {
        String namingContainerId = viewRoot.getContainerClientId(ctx);
        if (namingContainerId != null) {
          viewStateParam = namingContainerId + viewStateParam;
        }
      }
      writer.writeAttribute("name", viewStateParam, null);
      if (webConfig.isOptionEnabled(EnableViewStateIdRendering)) {
        String viewStateId = Util.getViewStateId(ctx);
        writer.writeAttribute("id", viewStateId, null);
      }
      writer.writeAttribute("value", id, null);
      if (webConfig.isOptionEnabled(AutoCompleteOffOnViewState)) {
        writer.writeAttribute("autocomplete", "off", null);
      }
      writer.endElement("input");

      writeClientWindowField(ctx, writer);
      writeRenderKitIdField(ctx, writer);
    }
  }

  /**
   * Inspects the incoming request parameters for the standardized state parameter name. In this
   * case, the parameter value will be the composite ID generated by
   * ServerSideStateHelper#writeState(FacesContext, Object, StringBuilder).
   *
   * <p>The composite key will be used to find the appropriate view within the session obtained from
   * the provided <code>FacesContext</code>
   */
  public Object getState(FacesContext ctx, String viewId) {

    String compoundId = getStateParamValue(ctx);

    if (compoundId == null) {
      return null;
    }

    if ("stateless".equals(compoundId)) {
      return "stateless";
    }

    int sep = compoundId.indexOf(':');
    assert (sep != -1);
    assert (sep < compoundId.length());

    String idInLogicalMap = compoundId.substring(0, sep);
    String idInActualMap = compoundId.substring(sep + 1);

    ExternalContext externalCtx = ctx.getExternalContext();
    Object sessionObj = externalCtx.getSession(false);

    // stop evaluating if the session is not available
    if (sessionObj == null) {
      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.log(
            Level.FINE,
            "Unable to restore server side state for view ID {0} as no session is available",
            viewId);
      }
      return null;
    }

    //noinspection SynchronizationOnLocalVariableOrMethodParameter
    synchronized (sessionObj) {
      Map logicalMap = (Map) externalCtx.getSessionMap().get(LOGICAL_VIEW_MAP);
      if (logicalMap != null) {
        Map actualMap = (Map) logicalMap.get(idInLogicalMap);
        if (actualMap != null) {
          RequestStateManager.set(ctx, RequestStateManager.LOGICAL_VIEW_MAP, idInLogicalMap);
          Object[] state = (Object[]) actualMap.get(idInActualMap);
          Object[] restoredState = new Object[2];

          restoredState[0] = state[0];
          restoredState[1] = state[1];

          if (state != null) {
            RequestStateManager.set(ctx, RequestStateManager.ACTUAL_VIEW_MAP, idInActualMap);
            if (state.length == 2 && state[1] != null) {
              restoredState[1] = handleRestoreState(state[1]);
            }
          }

          return restoredState;
        }
      }
    }

    return null;
  }

  // ------------------------------------------------------- Protected Methods

  /**
   * Utility method for obtaining the <code>Integer</code> based configuration values used to change
   * the behavior of the <code>ServerSideStateHelper</code>.
   *
   * @param param the paramter to parse
   * @return the Integer representation of the parameter value
   */
  protected Integer getIntegerConfigValue(WebContextInitParameter param) {

    String noOfViewsStr = webConfig.getOptionValue(param);
    Integer value = null;
    try {
      value = Integer.valueOf(noOfViewsStr);
    } catch (NumberFormatException nfe) {
      String defaultValue = param.getDefaultValue();
      if (LOGGER.isLoggable(Level.WARNING)) {
        LOGGER.log(
            Level.WARNING,
            "jsf.state.server.cannot.parse.int.option",
            new Object[] {param.getQualifiedName(), defaultValue});
      }
      try {
        value = Integer.valueOf(defaultValue);
      } catch (NumberFormatException ne) {
        if (LOGGER.isLoggable(Level.FINEST)) {
          LOGGER.log(Level.FINEST, "Unable to convert number", ne);
        }
      }
    }

    return value;
  }

  /**
   * @param state the object returned from <code>UIView.processSaveState</code>
   * @return If {@link
   *     com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter#SerializeServerStateDeprecated}
   *     is <code>true</code>, serialize and return the state, otherwise, return <code>state</code>
   *     unchanged.
   */
  protected Object handleSaveState(Object state) {

    if (webConfig.isOptionEnabled(SerializeServerStateDeprecated)
        || webConfig.isOptionEnabled(SerializeServerState)) {
      ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
      ObjectOutputStream oas = null;
      try {
        oas =
            serialProvider.createObjectOutputStream(
                ((compressViewState) ? new GZIPOutputStream(baos, 1024) : baos));
        //noinspection NonSerializableObjectPassedToObjectStream
        oas.writeObject(state);
        oas.flush();
      } catch (Exception e) {
        throw new FacesException(e);
      } finally {
        if (oas != null) {
          try {
            oas.close();
          } catch (IOException ioe) {
            if (LOGGER.isLoggable(Level.FINEST)) {
              LOGGER.log(Level.FINEST, "Closing stream", ioe);
            }
          }
        }
      }
      return baos.toByteArray();
    } else {
      return state;
    }
  }

  /**
   * @param state the state as it was stored in the session
   * @return an object that can be passed to <code>UIViewRoot.processRestoreState</code>. If {@link
   *     com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter#SerializeServerStateDeprecated}
   *     de-serialize the state prior to returning it, otherwise return <code>state</code> as is.
   */
  protected Object handleRestoreState(Object state) {

    if (webConfig.isOptionEnabled(SerializeServerStateDeprecated)
        || webConfig.isOptionEnabled(SerializeServerState)) {
      ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state);
      ObjectInputStream ois = null;
      try {
        ois =
            serialProvider.createObjectInputStream(
                ((compressViewState) ? new GZIPInputStream(bais, 1024) : bais));
        return ois.readObject();
      } catch (Exception e) {
        throw new FacesException(e);
      } finally {
        if (ois != null) {
          try {
            ois.close();
          } catch (IOException ioe) {
            if (LOGGER.isLoggable(Level.FINEST)) {
              LOGGER.log(Level.FINEST, "Closing stream", ioe);
            }
          }
        }
      }
    } else {
      return state;
    }
  }

  /**
   * @param ctx the <code>FacesContext</code> for the current request
   * @return a unique ID for building the keys used to store views within a session
   */
  private String createIncrementalRequestId(FacesContext ctx) {

    Map<String, Object> sm = ctx.getExternalContext().getSessionMap();
    AtomicInteger idgen = (AtomicInteger) sm.get(STATEMANAGED_SERIAL_ID_KEY);
    if (idgen == null) {
      idgen = new AtomicInteger(1);
    }

    // always call put/setAttribute as we may be in a clustered environment.
    sm.put(STATEMANAGED_SERIAL_ID_KEY, idgen);
    return (UIViewRoot.UNIQUE_ID_PREFIX + idgen.getAndIncrement());
  }

  private String createRandomId() {

    return Long.valueOf(random.nextLong()).toString();
  }

  /**
   * Is stateless.
   *
   * @param facesContext the Faces context.
   * @param viewId the view id.
   * @return true if stateless, false otherwise.
   * @throws IllegalStateException when the request was not a postback.
   */
  @Override
  public boolean isStateless(FacesContext facesContext, String viewId)
      throws IllegalStateException {
    if (facesContext.isPostback()) {
      Object stateObject = getState(facesContext, viewId);
      if (stateObject instanceof String && "stateless".equals((String) stateObject)) {
        return true;
      }

      return false;
    }

    throw new IllegalStateException("Cannot determine whether or not the request is stateless");
  }
}
Ejemplo n.º 4
0
/** This {@link ViewHandlingStrategy} handles JSP-based views. */
public class JspViewHandlingStrategy extends ViewHandlingStrategy {

  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();
  private int responseBufferSize;

  // ------------------------------------------------------------ Constructors

  public JspViewHandlingStrategy() {

    try {
      responseBufferSize = Integer.parseInt(webConfig.getOptionValue(ResponseBufferSize));
    } catch (NumberFormatException nfe) {
      responseBufferSize = Integer.parseInt(ResponseBufferSize.getDefaultValue());
    }
  }

  // ------------------------------------ Methods from ViewDeclarationLanguage

  /**
   * Not supported in JSP-based views.
   *
   * @see
   *     javax.faces.view.ViewDeclarationLanguage#getComponentMetadata(javax.faces.context.FacesContext,
   *     javax.faces.application.Resource)
   */
  @Override
  public BeanInfo getComponentMetadata(FacesContext context, Resource componentResource) {
    throw new UnsupportedOperationException();
  }

  /**
   * Not supported in JSP-based views.
   *
   * @see javax.faces.view.ViewDeclarationLanguage#getViewMetadata(javax.faces.context.FacesContext,
   *     String)
   */
  @Override
  public ViewMetadata getViewMetadata(FacesContext context, String viewId) {
    return null;
  }

  /**
   * Not supported in JSP-based views.
   *
   * @see
   *     javax.faces.view.ViewDeclarationLanguage#getScriptComponentResource(javax.faces.context.FacesContext,
   *     javax.faces.application.Resource)
   */
  @Override
  public Resource getScriptComponentResource(FacesContext context, Resource componentResource) {
    throw new UnsupportedOperationException();
  }

  /**
   * @see javax.faces.view.ViewDeclarationLanguage#buildView(javax.faces.context.FacesContext,
   *     javax.faces.component.UIViewRoot)
   * @param context
   * @param view
   * @throws IOException
   */
  public void buildView(FacesContext context, UIViewRoot view) throws IOException {

    if (Util.isViewPopulated(context, view)) {
      return;
    }
    try {
      if (executePageToBuildView(context, view)) {
        context.getExternalContext().responseFlushBuffer();
        if (associate != null) {
          associate.responseRendered();
        }
        context.responseComplete();
        return;
      }
    } catch (IOException e) {
      throw new FacesException(e);
    }

    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.log(Level.FINE, "Completed building view for : \n" + view.getViewId());
    }
    context
        .getApplication()
        .publishEvent(context, PostAddToViewEvent.class, UIViewRoot.class, view);
    Util.setViewPopulated(context, view);
  }

  /**
   * @see javax.faces.view.ViewDeclarationLanguage#renderView(javax.faces.context.FacesContext,
   *     javax.faces.component.UIViewRoot)
   */
  public void renderView(FacesContext context, UIViewRoot view) throws IOException {

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

    ExternalContext extContext = context.getExternalContext();

    if (!Util.isViewPopulated(context, view)) {
      buildView(context, view);
    }

    // set up the ResponseWriter

    RenderKitFactory renderFactory =
        (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
    RenderKit renderKit = renderFactory.getRenderKit(context, view.getRenderKitId());

    ResponseWriter oldWriter = context.getResponseWriter();

    WriteBehindStateWriter stateWriter =
        new WriteBehindStateWriter(
            extContext.getResponseOutputWriter(), context, responseBufferSize);
    ResponseWriter newWriter;
    if (null != oldWriter) {
      newWriter = oldWriter.cloneWithWriter(stateWriter);
    } else {
      newWriter =
          renderKit.createResponseWriter(
              stateWriter, null, extContext.getRequestCharacterEncoding());
    }
    context.setResponseWriter(newWriter);

    //  Don't call startDoc and endDoc on a partial response
    if (context.getPartialViewContext().isPartialRequest()) {
      doRenderView(context, view);
      try {
        extContext.getFlash().doPostPhaseActions(context);
      } catch (UnsupportedOperationException uoe) {
        if (LOGGER.isLoggable(Level.FINE)) {
          LOGGER.fine(
              "ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable");
        }
      }
    } else {
      // render the view to the response
      newWriter.startDocument();
      doRenderView(context, view);
      try {
        extContext.getFlash().doPostPhaseActions(context);
      } catch (UnsupportedOperationException uoe) {
        if (LOGGER.isLoggable(Level.FINE)) {
          LOGGER.fine(
              "ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable");
        }
      }
      newWriter.endDocument();
    }

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

    // flush directly to the response
    if (stateWriter.stateWritten()) {
      stateWriter.flushToWriter();
    }

    // clear the ThreadLocal reference.
    stateWriter.release();

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

    // write any AFTER_VIEW_CONTENT to the response
    // side effect: AFTER_VIEW_CONTENT removed
    ViewHandlerResponseWrapper wrapper =
        (ViewHandlerResponseWrapper)
            RequestStateManager.remove(context, RequestStateManager.AFTER_VIEW_CONTENT);
    if (null != wrapper) {
      wrapper.flushToWriter(
          extContext.getResponseOutputWriter(), extContext.getResponseCharacterEncoding());
    }

    extContext.responseFlushBuffer();
  }

  @Override
  public StateManagementStrategy getStateManagementStrategy(FacesContext context, String viewId) {
    return null;
  }

  // --------------------------------------- Methods from ViewHandlingStrategy

  /**
   * This {@link ViewHandlingStrategy} <em>should</em> be the last one queried and as such we return
   * <code>true</code>.
   *
   * @see com.sun.faces.application.view.ViewHandlingStrategy#handlesViewId(String)
   */
  @Override
  public boolean handlesViewId(String viewId) {

    return true;
  }

  // --------------------------------------------------------- Private Methods

  /**
   * 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 java.io.IOException if an error occurs executing the page
   */
  private boolean executePageToBuildView(FacesContext context, UIViewRoot viewToExecute)
      throws IOException {

    if (null == context) {
      String message =
          MessageUtils.getExceptionMessageString(
              MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "context");
      throw new NullPointerException(message);
    }
    if (null == viewToExecute) {
      String message =
          MessageUtils.getExceptionMessageString(
              MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "viewToExecute");
      throw new NullPointerException(message);
    }

    ExternalContext extContext = context.getExternalContext();

    if ("/*".equals(RequestStateManager.get(context, RequestStateManager.INVOCATION_PATH))) {
      throw new FacesException(
          MessageUtils.getExceptionMessageString(MessageUtils.FACES_SERVLET_MAPPING_INCORRECT_ID));
    }

    String requestURI = viewToExecute.getViewId();

    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.fine("About to execute view " + requestURI);
    }

    // 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.
    if (extContext.getRequest() instanceof ServletRequest) {
      Config.set(
          (ServletRequest) extContext.getRequest(),
          Config.FMT_LOCALE,
          context.getViewRoot().getLocale());
    }
    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.fine("Before dispacthMessage to viewId " + requestURI);
    }

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

    // replace the response with our wrapper
    ViewHandlerResponseWrapper wrapped = getWrapper(extContext);
    extContext.setResponse(wrapped);

    try {

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

      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.fine("After dispacthMessage to viewId " + requestURI);
      }
    } finally {
      // replace the original response
      extContext.setResponse(originalResponse);
    }

    // Follow the JSTL 1.2 spec, section 7.4,
    // on handling status codes on a forward
    if (wrapped.getStatus() < 200 || wrapped.getStatus() > 299) {
      // flush the contents of the wrapper to the response
      // this is necessary as the user may be using a custom
      // error page - this content should be propagated
      wrapped.flushContentToWrappedResponse();
      return true;
    }

    // Put the AFTER_VIEW_CONTENT into request scope
    // temporarily
    RequestStateManager.set(context, RequestStateManager.AFTER_VIEW_CONTENT, wrapped);

    return false;
  }

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

    if (null != associate) {
      associate.responseRendered();
    }

    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.log(Level.FINE, "About to render view " + viewToRender.getViewId());
    }

    viewToRender.encodeAll(context);
  }

  /**
   * Simple utility method to wrap the current response with the {@link ViewHandlerResponseWrapper}.
   *
   * @param extContext the {@link ExternalContext} for this request
   * @return the current response wrapped with ViewHandlerResponseWrapper
   */
  private static ViewHandlerResponseWrapper getWrapper(ExternalContext extContext) {

    Object response = extContext.getResponse();
    if (response instanceof HttpServletResponse) {
      return new ViewHandlerResponseWrapper((HttpServletResponse) response);
    }
    throw new IllegalArgumentException();
  }
}
Ejemplo n.º 5
0
/**
 * Break out the things that are associated with the Application, but need to be present even when
 * the user has replaced the Application instance.
 *
 * <p>
 *
 * <p>For example: the user replaces ApplicationFactory, and wants to intercept calls to
 * createValueExpression() and createMethodExpression() for certain kinds of expressions, but allow
 * the existing application to handle the rest.
 */
public class ApplicationAssociate {

  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

  private ApplicationImpl app = null;

  /**
   * Overall Map containing <code>from-view-id</code> key and <code>Set</code> of <code>
   * NavigationCase</code> objects for that key; The <code>from-view-id</code> strings in this map
   * will be stored as specified in the configuration file - some of them will have a trailing
   * asterisk "*" signifying wild card, and some may be specified as an asterisk "*".
   */
  private Map<String, Set<NavigationCase>> navigationMap = null;

  // Flag indicating that a response has been rendered.
  private boolean responseRendered = false;

  private static final String ASSOCIATE_KEY = RIConstants.FACES_PREFIX + "ApplicationAssociate";

  private static ThreadLocal<ApplicationAssociate> instance =
      new ThreadLocal<ApplicationAssociate>() {
        protected ApplicationAssociate initialValue() {
          return (null);
        }
      };

  private List<ELResolver> elResolversFromFacesConfig = null;

  @SuppressWarnings("deprecation")
  private VariableResolver legacyVRChainHead = null;

  private VariableResolverChainWrapper legacyVRChainHeadWrapperForJsp = null;

  private VariableResolverChainWrapper legacyVRChainHeadWrapperForFaces = null;

  @SuppressWarnings("deprecation")
  private PropertyResolver legacyPRChainHead = null;

  private ExpressionFactory expressionFactory = null;

  @SuppressWarnings("deprecation")
  private PropertyResolver legacyPropertyResolver = null;

  @SuppressWarnings("deprecation")
  private VariableResolver legacyVariableResolver = null;

  private FacesCompositeELResolver facesELResolverForJsp = null;

  private InjectionProvider injectionProvider;
  private ResourceCache resourceCache;

  private String contextName;
  private boolean requestServiced;
  private boolean errorPagePresent;

  private BeanManager beanManager;
  private GroovyHelper groovyHelper;
  private AnnotationManager annotationManager;
  private boolean devModeEnabled;
  private Compiler compiler;
  private FaceletFactory faceletFactory;
  private ResourceManager resourceManager;
  private ApplicationStateInfo applicationStateInfo;

  private PropertyEditorHelper propertyEditorHelper;

  private NamedEventManager namedEventManager;

  public ApplicationAssociate(ApplicationImpl appImpl) {
    app = appImpl;

    propertyEditorHelper = new PropertyEditorHelper(appImpl);

    FacesContext ctx = FacesContext.getCurrentInstance();
    if (ctx == null) {
      throw new IllegalStateException(
          MessageUtils.getExceptionMessageString(
              MessageUtils.APPLICATION_ASSOCIATE_CTOR_WRONG_CALLSTACK_ID));
    }
    ExternalContext externalContext = ctx.getExternalContext();
    if (null != externalContext.getApplicationMap().get(ASSOCIATE_KEY)) {
      throw new IllegalStateException(
          MessageUtils.getExceptionMessageString(MessageUtils.APPLICATION_ASSOCIATE_EXISTS_ID));
    }
    externalContext.getApplicationMap().put(ASSOCIATE_KEY, this);
    //noinspection CollectionWithoutInitialCapacity
    navigationMap = new ConcurrentHashMap<String, Set<NavigationCase>>();
    injectionProvider = InjectionProviderFactory.createInstance(externalContext);
    WebConfiguration webConfig = WebConfiguration.getInstance(externalContext);
    beanManager =
        new BeanManager(injectionProvider, webConfig.isOptionEnabled(EnableLazyBeanValidation));
    // install the bean manager as a system event listener for custom
    // scopes being destoryed.
    app.subscribeToEvent(PreDestroyCustomScopeEvent.class, ScopeContext.class, beanManager);
    annotationManager = new AnnotationManager();

    groovyHelper = GroovyHelper.getCurrentInstance();

    devModeEnabled = (appImpl.getProjectStage() == ProjectStage.Development);
    // initialize Facelets
    if (!webConfig.isOptionEnabled(DisableFaceletJSFViewHandler)) {
      compiler = createCompiler(webConfig);
      faceletFactory = createFaceletFactory(compiler, webConfig);
    }

    if (!devModeEnabled) {
      resourceCache = new ResourceCache();
    }

    resourceManager = new ResourceManager(resourceCache);
    namedEventManager = new NamedEventManager();
    applicationStateInfo = new ApplicationStateInfo();
  }

  public static ApplicationAssociate getInstance(ExternalContext externalContext) {
    if (externalContext == null) {
      return null;
    }
    Map applicationMap = externalContext.getApplicationMap();
    return ((ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY));
  }

  public static ApplicationAssociate getInstance(ServletContext context) {
    if (context == null) {
      return null;
    }
    return (ApplicationAssociate) context.getAttribute(ASSOCIATE_KEY);
  }

  public static void setCurrentInstance(ApplicationAssociate associate) {

    if (associate == null) {
      instance.remove();
    } else {
      instance.set(associate);
    }
  }

  public static ApplicationAssociate getCurrentInstance() {

    ApplicationAssociate associate = instance.get();
    if (associate == null) {
      // Fallback to ExternalContext lookup
      FacesContext fc = FacesContext.getCurrentInstance();
      if (fc != null) {
        ExternalContext extContext = fc.getExternalContext();
        if (extContext != null) {
          return ApplicationAssociate.getInstance(extContext);
        }
      }
    }

    return associate;
  }

  public ApplicationStateInfo getApplicationStateInfo() {
    return applicationStateInfo;
  }

  public ResourceManager getResourceManager() {
    return resourceManager;
  }

  public void setResourceManager(ResourceManager resourceManager) {
    this.resourceManager = resourceManager;
  }

  public ResourceCache getResourceCache() {
    return resourceCache;
  }

  public AnnotationManager getAnnotationManager() {
    return annotationManager;
  }

  public Compiler getCompiler() {
    return compiler;
  }

  public boolean isErrorPagePresent() {
    return errorPagePresent;
  }

  public void setErrorPagePresent(boolean errorPagePresent) {
    this.errorPagePresent = errorPagePresent;
  }

  public FaceletFactory getFaceletFactory() {
    return faceletFactory;
  }

  public static void clearInstance(ExternalContext externalContext) {
    Map applicationMap = externalContext.getApplicationMap();
    ApplicationAssociate me = (ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY);
    if (null != me) {
      if (null != me.resourceBundles) {
        me.resourceBundles.clear();
      }
    }
    applicationMap.remove(ASSOCIATE_KEY);
  }

  public static void clearInstance(ServletContext sc) {
    ApplicationAssociate me = (ApplicationAssociate) sc.getAttribute(ASSOCIATE_KEY);
    if (null != me) {
      if (null != me.resourceBundles) {
        me.resourceBundles.clear();
      }
    }
    sc.removeAttribute(ASSOCIATE_KEY);
  }

  public BeanManager getBeanManager() {
    return beanManager;
  }

  public GroovyHelper getGroovyHelper() {
    return groovyHelper;
  }

  public void initializeELResolverChains() {
    // 1. initialize the chains with default values
    if (null == app.compositeELResolver) {
      app.compositeELResolver =
          new DemuxCompositeELResolver(FacesCompositeELResolver.ELResolverChainType.Faces);
      ELUtils.buildFacesResolver(app.compositeELResolver, this);
      ELResolverInitPhaseListener.populateFacesELResolverForJsp(app, this);
    }
  }

  public void installProgrammaticallyAddedResolvers() {
    // Ensure custom resolvers are inserted at the correct place.
    VariableResolver vr = this.getLegacyVariableResolver();
    if (null != vr) {
      assert (null != this.getLegacyVRChainHeadWrapperForJsp());
      this.getLegacyVRChainHeadWrapperForJsp().setWrapped(vr);
      assert (null != this.getLegacyVRChainHeadWrapperForFaces());
      this.getLegacyVRChainHeadWrapperForFaces().setWrapped(vr);
    }
  }

  public boolean isDevModeEnabled() {
    return devModeEnabled;
  }

  /**
   * Obtain the PropertyEditorHelper instance for this app.
   *
   * @return The PropertyEditorHeler instance for this app.
   */
  public PropertyEditorHelper getPropertyEditorHelper() {
    return propertyEditorHelper;
  }

  /**
   * This method is called by <code>ConfigureListener</code> and will contain any <code>
   * VariableResolvers</code> defined within faces-config configuration files.
   *
   * @param resolver VariableResolver
   */
  @SuppressWarnings("deprecation")
  public void setLegacyVRChainHead(VariableResolver resolver) {
    this.legacyVRChainHead = resolver;
  }

  @SuppressWarnings("deprecation")
  public VariableResolver getLegacyVRChainHead() {
    return legacyVRChainHead;
  }

  public VariableResolverChainWrapper getLegacyVRChainHeadWrapperForJsp() {
    return legacyVRChainHeadWrapperForJsp;
  }

  public void setLegacyVRChainHeadWrapperForJsp(
      VariableResolverChainWrapper legacyVRChainHeadWrapper) {
    this.legacyVRChainHeadWrapperForJsp = legacyVRChainHeadWrapper;
  }

  public VariableResolverChainWrapper getLegacyVRChainHeadWrapperForFaces() {
    return legacyVRChainHeadWrapperForFaces;
  }

  public void setLegacyVRChainHeadWrapperForFaces(
      VariableResolverChainWrapper legacyVRChainHeadWrapperForFaces) {
    this.legacyVRChainHeadWrapperForFaces = legacyVRChainHeadWrapperForFaces;
  }

  /**
   * This method is called by <code>ConfigureListener</code> and will contain any <code>
   * PropertyResolvers</code> defined within faces-config configuration files.
   *
   * @param resolver PropertyResolver
   */
  @SuppressWarnings("deprecation")
  public void setLegacyPRChainHead(PropertyResolver resolver) {
    this.legacyPRChainHead = resolver;
  }

  @SuppressWarnings("deprecation")
  public PropertyResolver getLegacyPRChainHead() {
    return legacyPRChainHead;
  }

  public FacesCompositeELResolver getFacesELResolverForJsp() {
    return facesELResolverForJsp;
  }

  public void setFacesELResolverForJsp(FacesCompositeELResolver celr) {
    facesELResolverForJsp = celr;
  }

  public void setELResolversFromFacesConfig(List<ELResolver> resolvers) {
    this.elResolversFromFacesConfig = resolvers;
  }

  public List<ELResolver> getELResolversFromFacesConfig() {
    return elResolversFromFacesConfig;
  }

  public void setExpressionFactory(ExpressionFactory expressionFactory) {
    this.expressionFactory = expressionFactory;
  }

  public ExpressionFactory getExpressionFactory() {
    return this.expressionFactory;
  }

  public CompositeELResolver getApplicationELResolvers() {
    return app.getApplicationELResolvers();
  }

  public InjectionProvider getInjectionProvider() {
    return injectionProvider;
  }

  public void setContextName(String contextName) {
    this.contextName = contextName;
  }

  public String getContextName() {
    return contextName;
  }

  /**
   * Maintains the PropertyResolver called through Application.setPropertyResolver()
   *
   * @param resolver PropertyResolver
   */
  @SuppressWarnings("deprecation")
  public void setLegacyPropertyResolver(PropertyResolver resolver) {
    this.legacyPropertyResolver = resolver;
  }

  /** @return the PropertyResolver called through Application.getPropertyResolver() */
  @SuppressWarnings("deprecation")
  public PropertyResolver getLegacyPropertyResolver() {
    return legacyPropertyResolver;
  }

  /**
   * Maintains the PropertyResolver called through Application.setVariableResolver()
   *
   * @param resolver VariableResolver
   */
  @SuppressWarnings("deprecation")
  public void setLegacyVariableResolver(VariableResolver resolver) {
    this.legacyVariableResolver = resolver;
  }

  /** @return the VariableResolver called through Application.getVariableResolver() */
  @SuppressWarnings("deprecation")
  public VariableResolver getLegacyVariableResolver() {
    return legacyVariableResolver;
  }

  /**
   * Called by application code to indicate we've processed the first request to the application.
   */
  public void setRequestServiced() {
    this.requestServiced = true;
  }

  /** @return <code>true</code> if we've processed a request, otherwise <code>false</code> */
  public boolean hasRequestBeenServiced() {
    return requestServiced;
  }

  /**
   * Add a navigation case to the internal case set. If a case set does not already exist in the
   * case list map containing this case (identified by <code>from-view-id</code>), start a new list,
   * add the case to it, and store the set in the case set map. If a case set already exists,
   * overwrite the previous case.
   *
   * @param navigationCase the navigation case containing navigation mapping information from the
   *     configuration file.
   */
  public void addNavigationCase(NavigationCase navigationCase) {

    String fromViewId = navigationCase.getFromViewId();
    Set<NavigationCase> caseSet = navigationMap.get(fromViewId);
    if (caseSet == null) {
      //noinspection CollectionWithoutInitialCapacity
      caseSet = new LinkedHashSet<NavigationCase>();
      caseSet.add(navigationCase);
      navigationMap.put(fromViewId, caseSet);
    } else {
      // if there already is a case existing for the
      // fromviewid/fromaction.fromoutcome combination,
      // replace it ...  (last one wins).
      caseSet.add(navigationCase);
    }
  }

  public NamedEventManager getNamedEventManager() {
    return namedEventManager;
  }

  /**
   * Return a <code>Map</code> of navigation mappings loaded from the configuration system. The key
   * for the returned <code>Map</code> is <code>from-view-id</code>, and the value is a <code>List
   * </code> of navigation cases.
   *
   * @return Map the map of navigation mappings.
   */
  public Map<String, Set<NavigationCase>> getNavigationCaseListMappings() {
    if (navigationMap == null) {
      return Collections.emptyMap();
    }
    return navigationMap;
  }

  public ResourceBundle getResourceBundle(FacesContext context, String var) {
    ApplicationResourceBundle bundle = resourceBundles.get(var);
    if (bundle == null) {
      return null;
    }
    UIViewRoot root;
    // Start out with the default locale
    Locale locale;
    Locale defaultLocale = Locale.getDefault();
    locale = defaultLocale;
    // See if this FacesContext has a ViewRoot
    if (null != (root = context.getViewRoot())) {
      // If so, ask it for its Locale
      if (null == (locale = root.getLocale())) {
        // If the ViewRoot has no Locale, fall back to the default.
        locale = defaultLocale;
      }
    }
    assert (null != locale);
    // ResourceBundleBean bean = resourceBundles.get(var);
    return bundle.getResourceBundle(locale);
  }

  /**
   * keys: <var> element from faces-config
   *
   * <p>
   *
   * <p>values: ResourceBundleBean instances.
   */
  @SuppressWarnings({"CollectionWithoutInitialCapacity"})
  Map<String, ApplicationResourceBundle> resourceBundles =
      new HashMap<String, ApplicationResourceBundle>();

  public void addResourceBundle(String var, ApplicationResourceBundle bundle) {
    resourceBundles.put(var, bundle);
  }

  public Map<String, ApplicationResourceBundle> getResourceBundles() {
    return resourceBundles;
  }

  // This is called by ViewHandlerImpl.renderView().
  public void responseRendered() {
    responseRendered = true;
  }

  public boolean isResponseRendered() {
    return responseRendered;
  }

  protected FaceletFactory createFaceletFactory(Compiler c, WebConfiguration webConfig) {

    // refresh period
    String refreshPeriod = webConfig.getOptionValue(FaceletsDefaultRefreshPeriod);
    long period = Long.parseLong(refreshPeriod);

    // resource resolver
    ResourceResolver resolver = new DefaultResourceResolver();
    String resolverName = webConfig.getOptionValue(FaceletsResourceResolver);
    if (resolverName != null && resolverName.length() > 0) {
      resolver =
          (ResourceResolver)
              ReflectionUtil.decorateInstance(resolverName, ResourceResolver.class, resolver);
    }

    FaceletCache cache = null;
    String faceletCacheName = webConfig.getOptionValue(FaceletCache);
    if (faceletCacheName != null && faceletCacheName.length() > 0) {
      try {
        com.sun.faces.facelets.FaceletCache privateApiCache =
            (com.sun.faces.facelets.FaceletCache)
                ReflectionUtil.forName(faceletCacheName).newInstance();
        cache = new PrivateApiFaceletCacheAdapter(privateApiCache);
      } catch (Exception e) {
        if (LOGGER.isLoggable(Level.SEVERE)) {
          LOGGER.log(Level.SEVERE, "Error Loading Facelet cache: " + faceletCacheName, e);
        }
      }
    }
    if (null == cache) {
      FaceletCacheFactory cacheFactory =
          (FaceletCacheFactory) FactoryFinder.getFactory(FactoryFinder.FACELET_CACHE_FACTORY);
      cache = cacheFactory.getFaceletCache();
    }

    // Resource.getResourceUrl(ctx,"/")
    FaceletFactory factory = new DefaultFaceletFactory(c, resolver, period, cache);

    // Check to see if a custom Factory has been defined
    String factoryClass = webConfig.getOptionValue(FaceletFactory);
    if (factoryClass != null && factoryClass.length() > 0) {
      factory =
          (FaceletFactory)
              ReflectionUtil.decorateInstance(factoryClass, FaceletFactory.class, factory);
    }

    return factory;
  }

  protected Compiler createCompiler(WebConfiguration webConfig) {

    Compiler c = new SAXCompiler();

    // load decorators
    String decParam = webConfig.getOptionValue(FaceletsDecorators);
    if (decParam != null) {
      decParam = decParam.trim();
      String[] decs = Util.split(decParam, ";");
      TagDecorator decObj;
      for (String decorator : decs) {
        try {
          decObj = (TagDecorator) ReflectionUtil.forName(decorator).newInstance();
          c.addTagDecorator(decObj);

          if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Successfully Loaded Decorator: {0}", decorator);
          }
        } catch (Exception e) {
          if (LOGGER.isLoggable(Level.SEVERE)) {
            LOGGER.log(Level.SEVERE, "Error Loading Decorator: " + decorator, e);
          }
        }
      }
    }

    // skip params?
    c.setTrimmingComments(
        webConfig.isOptionEnabled(BooleanWebContextInitParameter.FaceletsSkipComments));

    c.addTagLibrary(new CoreLibrary());
    c.addTagLibrary(new HtmlLibrary());
    c.addTagLibrary(new UILibrary());
    c.addTagLibrary(new JstlCoreLibrary());
    c.addTagLibrary(
        new FunctionLibrary(JstlFunction.class, "http://java.sun.com/jsp/jstl/functions"));
    if (isDevModeEnabled()) {
      c.addTagLibrary(
          new FunctionLibrary(DevTools.class, "http://java.sun.com/mojarra/private/functions"));
    }
    c.addTagLibrary(new CompositeLibrary());
    c.addTagLibrary(new XmlLibrary());

    return c;
  }
}
/** A factory for creating <code>SerializationProvider</code> instances. */
public class SerializationProviderFactory {

  /** Our default <code>SerializationProvider</code>. */
  private static final SerializationProvider JAVA_PROVIDER =
      new SerializationProviderFactory.JavaSerializationProvider();

  /**
   * The system property that will be checked for alternate <code>SerializationProvider</code>
   * implementations.
   */
  private static final String SERIALIZATION_PROVIDER_PROPERTY =
      RIConstants.FACES_PREFIX + "SerializationProvider";

  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

  /**
   * Creates a new instance of the class specified by the <code>com.sun.faces.InjectionProvider
   * </code> system property. If this propery is not defined, then a default, no-op, <code>
   * InjectionProvider</code> will be returned.
   *
   * @param extContext the ExternalContext for this application
   * @return an implementation of the <code>InjectionProvider</code> interfaces
   */
  public static SerializationProvider createInstance(ExternalContext extContext) {
    String providerClass = findProviderClass(extContext);

    SerializationProvider provider = getProviderInstance(providerClass);

    if (provider.getClass() != JavaSerializationProvider.class) {
      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.log(
            Level.FINE,
            "jsf.spi.serialization.provider_configured",
            new Object[] {provider.getClass().getName()});
      }
    }
    return provider;
  }

  private static SerializationProvider getProviderInstance(String className) {
    SerializationProvider provider = JAVA_PROVIDER;
    if (className != null) {
      try {
        Class<?> clazz = Util.loadClass(className, SerializationProviderFactory.class);
        if (implementsSerializationProvider(clazz)) {
          provider = (SerializationProvider) clazz.newInstance();
        } else {
          if (LOGGER.isLoggable(Level.SEVERE)) {
            LOGGER.log(
                Level.SEVERE,
                "jsf.spi.serialization.provider_not_implemented",
                new Object[] {className});
          }
        }
      } catch (ClassNotFoundException cnfe) {
        if (LOGGER.isLoggable(Level.SEVERE)) {
          LOGGER.log(
              Level.SEVERE, "jsf.spi.serialization.provider_not_found", new Object[] {className});
        }
      } catch (InstantiationException | IllegalAccessException ie) {
        if (LOGGER.isLoggable(Level.SEVERE)) {
          LOGGER.log(
              Level.SEVERE,
              "jsf.spi.serialization.provider_cannot_instantiate",
              new Object[] {className});
          LOGGER.log(Level.SEVERE, "", ie);
        }
      }
    }

    return provider;
  }

  /**
   * Determine if the specified class implements the <code>SerializationProvider</code> interfaces.
   *
   * @param clazz the class in question
   * @return <code>true</code> if <code>clazz</code> implements the <code>SerializationProvider
   *     </code> interface
   */
  private static boolean implementsSerializationProvider(Class<?> clazz) {
    return SerializationProvider.class.isAssignableFrom(clazz);
  }

  /**
   * Tries to find a provider class in a web context parameter. If not present it tries to find it
   * as a System property. If still not found returns null.
   *
   * @param extContext The ExternalContext for this request
   * @return The provider class name specified in the container configuration, or <code>null</code>
   *     if not found.
   */
  private static String findProviderClass(ExternalContext extContext) {

    WebConfiguration webConfig = WebConfiguration.getInstance(extContext);

    String provider = webConfig.getOptionValue(WebContextInitParameter.SerializationProviderClass);

    if (provider != null) {
      return provider;
    } else {
      return System.getProperty(SERIALIZATION_PROVIDER_PROPERTY);
    }
  }

  /**
   * An implementation of <code>SerializationProvider</code> which uses standard Java serialization.
   */
  private static final class JavaSerializationProvider implements SerializationProvider {

    /**
     * Creates a new <code>ObjectOutputStream</code> wrapping the specified <code>destination</code>
     * .
     *
     * @param destination the destination of the serialized Object(s)
     * @return an <code>ObjectOutputStream</code>
     */
    @Override
    public ObjectOutputStream createObjectOutputStream(OutputStream destination)
        throws IOException {
      return new ObjectOutputStream(destination);
    }

    /**
     * Creates a new <code>ObjectInputStream</code> wrapping the specified <code>source</code>.
     *
     * @param source the source stream from which to read the Object(s) from
     * @return an <code>ObjectInputStream</code>
     */
    @Override
    public ObjectInputStream createObjectInputStream(InputStream source) throws IOException {
      return new ApplicationObjectInputStream(source);
    }
  }
} // END InjectionProviderFactory
Ejemplo n.º 7
0
/** This {@link ViewHandlingStrategy} handles Facelets/PDL-based views. */
public class FaceletViewHandlingStrategy extends ViewHandlingStrategy {

  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

  // FaceletFactory singleton for this application
  private FaceletFactory faceletFactory;

  // Array of viewId extensions that should be handled by Facelets
  private String[] extensionsArray;

  // Array of viewId prefixes that should be handled by Facelets
  private String[] prefixesArray;

  public static final String IS_BUILDING_METADATA =
      FaceletViewHandlingStrategy.class.getName() + ".IS_BUILDING_METADATA";

  private StateManagementStrategyImpl stateManagementStrategy;

  private boolean partialStateSaving;
  private Set<String> fullStateViewIds;
  private boolean groovyAvailable;

  // ------------------------------------------------------------ Constructors

  public FaceletViewHandlingStrategy() {

    initialize();
  }

  // ------------------------------------ Methods from ViewDeclarationLanguage

  @Override
  public StateManagementStrategy getStateManagementStrategy(FacesContext context, String viewId) {

    // 'null' return here means we're defaulting to the 1.2 style state saving.
    return (context.getAttributes().containsKey("partialStateSaving")
        ? stateManagementStrategy
        : null);
  }

  @Override
  public BeanInfo getComponentMetadata(FacesContext context, Resource ccResource) {
    // PENDING this implementation is terribly wasteful.
    // Must find a better way.
    CompositeComponentBeanInfo result;
    FaceletContext ctx =
        (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
    FaceletFactory factory =
        (FaceletFactory) RequestStateManager.get(context, RequestStateManager.FACELET_FACTORY);
    VariableMapper orig = ctx.getVariableMapper();
    UIComponent tmp = context.getApplication().createComponent("javax.faces.NamingContainer");
    UIPanel facetComponent =
        (UIPanel) context.getApplication().createComponent("javax.faces.Panel");
    facetComponent.setRendererType("javax.faces.Group");
    tmp.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, facetComponent);
    // We have to put the resource in here just so the classes that eventually
    // get called by facelets have access to it.
    tmp.getAttributes().put(Resource.COMPONENT_RESOURCE_KEY, ccResource);

    Facelet f;

    try {
      f = factory.getFacelet(ccResource.getURL());
      VariableMapper wrapper =
          new VariableMapperWrapper(orig) {

            @Override
            public ValueExpression resolveVariable(String variable) {
              return super.resolveVariable(variable);
            }
          };
      ctx.setVariableMapper(wrapper);
      context.getAttributes().put(IS_BUILDING_METADATA, Boolean.TRUE);
      f.apply(context, facetComponent);
    } catch (Exception e) {
      if (e instanceof FacesException) {
        throw (FacesException) e;
      } else {
        throw new FacesException(e);
      }
    } finally {
      context.getAttributes().remove(IS_BUILDING_METADATA);
      ctx.setVariableMapper(orig);
    }
    result = (CompositeComponentBeanInfo) tmp.getAttributes().get(UIComponent.BEANINFO_KEY);

    return result;
  }

  @Override
  public ViewMetadata getViewMetadata(FacesContext context, String viewId) {

    return new ViewMetadataImpl(viewId);
  }

  /**
   * @see
   *     javax.faces.view.ViewDeclarationLanguage#getScriptComponentResource(javax.faces.context.FacesContext,
   *     javax.faces.application.Resource)
   */
  public Resource getScriptComponentResource(FacesContext context, Resource componentResource) {

    if (!groovyAvailable) {
      return null;
    }
    Resource result = null;

    String resourceName = componentResource.getResourceName();
    if (resourceName.endsWith(".xhtml")) {
      resourceName = resourceName.substring(0, resourceName.length() - 6) + ".groovy";
      ResourceHandler resourceHandler = context.getApplication().getResourceHandler();
      result = resourceHandler.createResource(resourceName, componentResource.getLibraryName());
    }

    return result;
  }

  /**
   * @see javax.faces.view.ViewDeclarationLanguage#renderView(javax.faces.context.FacesContext,
   *     javax.faces.component.UIViewRoot)
   */
  public void renderView(FacesContext ctx, UIViewRoot viewToRender) throws IOException {

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

    // log request
    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.fine("Rendering View: " + viewToRender.getViewId());
    }

    WriteBehindStateWriter stateWriter = null;
    try {
      // Only build the view if this view has not yet been built.
      if (!Util.isViewPopulated(ctx, viewToRender)) {
        this.buildView(ctx, viewToRender);
      }

      // setup writer and assign it to the ctx
      ResponseWriter origWriter = ctx.getResponseWriter();
      if (origWriter == null) {
        origWriter = createResponseWriter(ctx);
      }

      stateWriter = new WriteBehindStateWriter(origWriter, ctx, responseBufferSize);

      ResponseWriter writer = origWriter.cloneWithWriter(stateWriter);
      ctx.setResponseWriter(writer);

      // render the view to the response
      writer.startDocument();
      viewToRender.encodeAll(ctx);
      writer.endDocument();

      // finish writing
      writer.close();

      boolean writtenState = stateWriter.stateWritten();
      // flush to origWriter
      if (writtenState) {
        stateWriter.flushToWriter();
      }

    } catch (FileNotFoundException fnfe) {
      this.handleFaceletNotFound(ctx, viewToRender.getViewId(), fnfe.getMessage());
    } catch (Exception e) {
      this.handleRenderException(ctx, e);
    } finally {
      if (stateWriter != null) stateWriter.release();
    }
  }

  /**
   * If {@link UIDebug#debugRequest(javax.faces.context.FacesContext)}} is <code>true</code>, simply
   * return a new UIViewRoot(), otherwise, call the default logic.
   *
   * @see {@link
   *     javax.faces.view.ViewDeclarationLanguage#restoreView(javax.faces.context.FacesContext,
   *     String)}
   */
  @Override
  public UIViewRoot restoreView(FacesContext ctx, String viewId) {

    updateStateSavingType(ctx, viewId);
    if (UIDebug.debugRequest(ctx)) {
      ctx.getApplication().createComponent(UIViewRoot.COMPONENT_TYPE);
    }

    return super.restoreView(ctx, viewId);
  }

  /**
   * @see javax.faces.view.ViewDeclarationLanguage#createView(javax.faces.context.FacesContext,
   *     String)
   * @return
   */
  @Override
  public UIViewRoot createView(FacesContext ctx, String viewId) {

    if (UIDebug.debugRequest(ctx)) {
      UIViewRoot root =
          (UIViewRoot) ctx.getApplication().createComponent(UIViewRoot.COMPONENT_TYPE);
      root.setViewId(viewId);
      return root;
    }

    return super.createView(ctx, viewId);
  }

  // --------------------------------------- Methods from ViewHandlingStrategy

  /**
   * @param viewId the view ID to check
   * @return <code>true</code> if assuming a default configuration and the view ID's extension is
   *     <code>.xhtml</code> Otherwise try to match the view ID based on the configured extendsion
   *     and prefixes.
   * @see com.sun.faces.config.WebConfiguration.WebContextInitParameter#FaceletsViewMappings
   */
  @Override
  public boolean handlesViewId(String viewId) {
    if (viewId != null) {
      // If there's no extensions array or prefixes array, then
      // assume defaults.  .xhtml extension is handled by
      // the FaceletViewHandler and .jsp will be handled by
      // the JSP view handler
      if ((extensionsArray == null) && (prefixesArray == null)) {
        return (viewId.endsWith(ViewHandler.DEFAULT_FACELETS_SUFFIX));
      }

      if (extensionsArray != null) {
        for (int i = 0; i < extensionsArray.length; i++) {
          String extension = extensionsArray[i];
          if (viewId.endsWith(extension)) {
            return true;
          }
        }
      }

      if (prefixesArray != null) {
        for (int i = 0; i < prefixesArray.length; i++) {
          String prefix = prefixesArray[i];
          if (viewId.startsWith(prefix)) {
            return true;
          }
        }
      }
    }

    return false;
  }

  /**
   * Build the view.
   *
   * @param ctx the {@link FacesContext} for the current request
   * @param view the {@link UIViewRoot} to populate based of the Facelet template
   * @throws IOException if an error occurs building the view.
   */
  @Override
  public void buildView(FacesContext ctx, UIViewRoot view) throws IOException {

    if (Util.isViewPopulated(ctx, view)) {
      return;
    }
    updateStateSavingType(ctx, view.getViewId());
    view.setViewId(view.getViewId());

    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.fine("Building View: " + view.getViewId());
    }
    if (faceletFactory == null) {
      ApplicationAssociate associate = ApplicationAssociate.getInstance(ctx.getExternalContext());
      faceletFactory = associate.getFaceletFactory();
      assert (faceletFactory != null);
    }
    RequestStateManager.set(ctx, RequestStateManager.FACELET_FACTORY, faceletFactory);
    Facelet f = faceletFactory.getFacelet(view.getViewId());

    // populate UIViewRoot
    f.apply(ctx, view);
    doPostBuildActions(view);
    Util.setViewPopulated(ctx, view);
  }

  // ------------------------------------------------------- Protected Methods

  /** Initialize the core Facelets runtime. */
  protected void initialize() {

    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.fine("Initializing FaceletViewHandlingStrategy");
    }

    this.initializeMappings();
    WebConfiguration config = WebConfiguration.getInstance();
    partialStateSaving = config.isOptionEnabled(PartialStateSaving);

    if (partialStateSaving) {
      String[] viewIds = config.getOptionValue(FullStateSavingViewIds, ",");
      fullStateViewIds = new HashSet<String>(viewIds.length, 1.0f);
      fullStateViewIds.addAll(Arrays.asList(viewIds));
      this.stateManagementStrategy = new StateManagementStrategyImpl(this);
    }

    groovyAvailable = GroovyHelper.isGroovyAvailable(FacesContext.getCurrentInstance());

    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.fine("Initialization Successful");
    }
  }

  /** Initialize mappings, during the first request. */
  protected void initializeMappings() {

    String viewMappings = webConfig.getOptionValue(WebContextInitParameter.FaceletsViewMappings);
    if ((viewMappings != null) && (viewMappings.length() > 0)) {
      String[] mappingsArray = Util.split(viewMappings, ";");

      List<String> extensionsList = new ArrayList<String>(mappingsArray.length);
      List<String> prefixesList = new ArrayList<String>(mappingsArray.length);

      for (int i = 0; i < mappingsArray.length; i++) {
        String mapping = mappingsArray[i].trim();
        int mappingLength = mapping.length();
        if (mappingLength <= 1) {
          continue;
        }

        if (mapping.charAt(0) == '*') {
          extensionsList.add(mapping.substring(1));
        } else if (mapping.charAt(mappingLength - 1) == '*') {
          prefixesList.add(mapping.substring(0, mappingLength - 1));
        }
      }

      extensionsArray = new String[extensionsList.size()];
      extensionsList.toArray(extensionsArray);

      prefixesArray = new String[prefixesList.size()];
      prefixesList.toArray(prefixesArray);
    }
  }

  /** @return a new Compiler for Facelet processing. */
  protected Compiler createCompiler() {
    return new SAXCompiler();
  }

  /**
   * @param context the {@link FacesContext} for the current request
   * @return a {@link ResponseWriter} for processing the request
   * @throws IOException if the writer cannot be created
   */
  protected ResponseWriter createResponseWriter(FacesContext context) throws IOException {

    ExternalContext extContext = context.getExternalContext();
    RenderKit renderKit = context.getRenderKit();
    // Avoid a cryptic NullPointerException when the renderkit ID
    // is incorrectly set
    if (renderKit == null) {
      String id = context.getViewRoot().getRenderKitId();
      throw new IllegalStateException("No render kit was available for id \"" + id + "\"");
    }

    if (responseBufferSizeSet) {
      // set the buffer for content
      extContext.setResponseBufferSize(responseBufferSize);
    }

    // get our content type
    String contentType = (String) extContext.getRequestMap().get("facelets.ContentType");

    // get the encoding
    String encoding = (String) extContext.getRequestMap().get("facelets.Encoding");

    // Create a dummy ResponseWriter with a bogus writer,
    // so we can figure out what content type the ReponseWriter
    // is really going to ask for
    ResponseWriter writer =
        renderKit.createResponseWriter(NullWriter.Instance, contentType, encoding);

    contentType = getResponseContentType(context, writer.getContentType());
    encoding = getResponseEncoding(context, writer.getCharacterEncoding());

    // apply them to the response
    extContext.setResponseContentType(contentType);
    extContext.setResponseCharacterEncoding(encoding);

    // Now, clone with the real writer
    writer = writer.cloneWithWriter(extContext.getResponseOutputWriter());

    return writer;
  }

  /**
   * Handles the case where rendering throws an Exception.
   *
   * @param context the {@link FacesContext} for the current request
   * @param e the caught Exception
   * @throws IOException if the custom debug content cannot be written
   */
  protected void handleRenderException(FacesContext context, Exception e) throws IOException {

    // always log
    if (LOGGER.isLoggable(Level.SEVERE)) {
      UIViewRoot root = context.getViewRoot();
      StringBuffer sb = new StringBuffer(64);
      sb.append("Error Rendering View");
      if (root != null) {
        sb.append('[');
        sb.append(root.getViewId());
        sb.append(']');
      }
      LOGGER.log(Level.SEVERE, sb.toString(), e);
    }

    if (e instanceof RuntimeException) {
      throw (RuntimeException) e;
    } else if (e instanceof IOException) {
      throw (IOException) e;
    } else {
      throw new FacesException(e.getMessage(), e);
    }
  }

  /**
   * Handles the case where a Facelet cannot be found.
   *
   * @param context the {@link FacesContext} for the current request
   * @param viewId the view ID that was to be mapped to a Facelet
   * @param message optional message to include in the 404
   * @throws IOException if an error occurs sending the 404 to the client
   */
  protected void handleFaceletNotFound(FacesContext context, String viewId, String message)
      throws IOException {

    context
        .getExternalContext()
        .responseSendError(
            HttpServletResponse.SC_NOT_FOUND,
            ((message != null) ? (viewId + ": " + message) : viewId));
    context.responseComplete();
  }

  /**
   * @param context the {@link FacesContext} for the current request
   * @param orig the original encoding
   * @return the encoding to be used for this response
   */
  protected String getResponseEncoding(FacesContext context, String orig) {

    String encoding = orig;

    // see if we need to override the encoding
    Map<Object, Object> ctxAttributes = context.getAttributes();
    Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();

    // 1. check the request attribute
    if (ctxAttributes.containsKey("facelets.Encoding")) {
      encoding = (String) ctxAttributes.get("facelets.Encoding");
      if (LOGGER.isLoggable(Level.FINEST)) {
        LOGGER.log(Level.FINEST, "Facelet specified alternate encoding {0}", encoding);
      }
      sessionMap.put(ViewHandler.CHARACTER_ENCODING_KEY, encoding);
    }

    // 2. get it from request
    if (encoding == null) {
      encoding = context.getExternalContext().getRequestCharacterEncoding();
    }

    // 3. get it from the session
    if (encoding == null) {
      encoding = (String) sessionMap.get(ViewHandler.CHARACTER_ENCODING_KEY);
      if (LOGGER.isLoggable(Level.FINEST)) {
        LOGGER.log(Level.FINEST, "Session specified alternate encoding {0}", encoding);
      }
    }

    // 4. default it
    if (encoding == null) {
      encoding = "UTF-8";
      if (LOGGER.isLoggable(Level.FINEST)) {
        LOGGER.finest("ResponseWriter created had a null CharacterEncoding, defaulting to UTF-8");
      }
    }

    return encoding;
  }

  /**
   * @param context the {@link FacesContext} for the current request
   * @param orig the original contentType
   * @return the content type to be used for this response
   */
  protected String getResponseContentType(FacesContext context, String orig) {

    String contentType = orig;

    // see if we need to override the contentType
    Map<Object, Object> m = context.getAttributes();
    if (m.containsKey("facelets.ContentType")) {
      contentType = (String) m.get("facelets.ContentType");
      if (LOGGER.isLoggable(Level.FINEST)) {
        LOGGER.finest("Facelet specified alternate contentType '" + contentType + "'");
      }
    }

    // safety check
    if (contentType == null) {
      contentType = "text/html";
      if (LOGGER.isLoggable(Level.FINEST)) {
        LOGGER.finest("ResponseWriter created had a null ContentType, defaulting to text/html");
      }
    }

    return contentType;
  }

  private void updateStateSavingType(FacesContext ctx, String viewId) {

    if (!ctx.getAttributes().containsKey("partialStateSaving")) {
      ctx.getAttributes().put("partialStateSaving", usePartialSaving(viewId));
    }
  }

  private boolean usePartialSaving(String viewId) {
    return (partialStateSaving && !fullStateViewIds.contains(viewId));
  }

  private void doPostBuildActions(UIViewRoot root) {
    if (usePartialSaving(root.getViewId())) {
      stateManagementStrategy.notifyTrackChanges(root);
    }
  }

  // ---------------------------------------------------------- Nested Classes

  /** Simple no-op writer. */
  protected static final class NullWriter extends Writer {

    static final NullWriter Instance = new NullWriter();

    public void write(char[] buffer) {}

    public void write(char[] buffer, int off, int len) {}

    public void write(String str) {}

    public void write(int c) {}

    public void write(String str, int off, int len) {}

    public void close() {}

    public void flush() {}
  } // END NullWriter
}
Ejemplo n.º 8
0
public class GroovyScriptManager implements ScriptManager {
  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();
  private static final String SCRIPT_PATH = "/WEB-INF/groovy/";
  private static final String SUFFIX = ".groovy";
  private ServletContext servletContext;

  // Borrowed from AnnotationScanner.  Perhaps this should be made public somewhere
  public static final Set<String> FACES_ANNOTATIONS;

  static {
    HashSet<String> annotations = new HashSet<>(15, 1.0f);
    Collections.addAll(
        annotations,
        "javax.faces.component.FacesComponent",
        "javax.faces.component.*",
        "javax.faces.convert.FacesConverter",
        "javax.faces.convert.*",
        "javax.faces.validator.FacesValidator",
        "javax.faces.validator.*",
        "javax.faces.render.FacesRenderer",
        "javax.faces.render.*",
        "javax.faces.bean.ManagedBean",
        "javax.faces.bean.*",
        "javax.faces.event.NamedEvent",
        "javax.faces.event.*",
        "javax.faces.component.behavior.FacesBehavior",
        "javax.faces.component.behavior.*",
        "javax.faces.render.FacesBehaviorRenderer");
    FACES_ANNOTATIONS = Collections.unmodifiableSet(annotations);
  }

  public GroovyScriptManager(ServletContext servletContext) {
    this.servletContext = servletContext;
  }

  public Set<String> getScripts() {
    Set<String> scripts = new HashSet<>();
    processWebInfGroovy(servletContext, servletContext.getResourcePaths(SCRIPT_PATH), scripts);

    return scripts;
  }

  private void processWebInfGroovy(ServletContext sc, Set<String> paths, Set<String> classList) {
    if (paths != null && !paths.isEmpty()) {
      for (String pathElement : paths) {
        if (pathElement.endsWith("/")) {
          processWebInfGroovy(sc, sc.getResourcePaths(pathElement), classList);
        } else {
          if (pathElement.endsWith(SUFFIX)) {
            String cname = convertToClassName(SCRIPT_PATH, pathElement);
            if (containsAnnotation(sc, pathElement)) {
              if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "[WEB-INF/groovy] Found annotated Class: {0}", cname);
              }
              classList.add(cname);
            }
          }
        }
      }
    }
  }

  private boolean containsAnnotation(ServletContext sc, String pathElement) {
    boolean containsAnnotation = false;
    URL url;
    try {
      url = sc.getResource(pathElement);
    } catch (MalformedURLException ex) {
      if (LOGGER.isLoggable(Level.SEVERE)) {
        LOGGER.log(Level.SEVERE, null, ex);
      }
      return false;
    }
    try (BufferedReader in =
        new BufferedReader(new InputStreamReader(url.openStream(), RIConstants.CHAR_ENCODING))) {
      String line = in.readLine();
      while ((line != null) && (!containsAnnotation)) {
        line = line.trim();
        if (line.length() != 0) {
          for (String pattern : FACES_ANNOTATIONS) {
            if (line.contains(pattern)) {
              containsAnnotation = true;
              break;
            }
          }
        }

        line = in.readLine();
      }
    } catch (Exception ioe) {
      if (LOGGER.isLoggable(Level.SEVERE)) {
        LOGGER.log(Level.SEVERE, null, ioe);
      }
    }
    return containsAnnotation;
  }

  /**
   * Utility method for converting paths to fully qualified class names.
   *
   * @param prefix the prefix that should be stripped from the class name before converting it
   * @param pathEntry a path to a class file
   * @return a fully qualified class name using dot notation
   */
  private String convertToClassName(String prefix, String pathEntry) {
    String className = pathEntry;

    if (prefix != null) {
      // remove the prefix
      className = className.substring(prefix.length());
    }
    // remove the .class suffix
    className = className.substring(0, (className.length() - 7));

    return className.replace('/', '.');
  }
}
Ejemplo n.º 9
0
/**
 * This <code>StateHelper</code> provides the functionality associated with client-side state
 * saving.
 */
public class ClientSideStateHelper extends StateHelper {

  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

  /**
   * Enabled encryption of view state. Encryption is disabled by default.
   *
   * @see {@link
   *     com.sun.faces.config.WebConfiguration.WebEnvironmentEntry#ClientStateSavingPassword}
   */
  private ByteArrayGuard guard;

  /**
   * Flag indicating whether or not client view state will be manipulated for and checked against a
   * configured timeout value.
   *
   * <p>This flag is configured via the <code>WebContextInitParameter.ClientStateTimeout</code>
   * configuration option of <code>WebConfiguration</code> and is disabled by default.
   *
   * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout}
   */
  private boolean stateTimeoutEnabled;

  /**
   * If <code>stateTimeoutEnabled</code> is <code>true</code> this value will represent the time in
   * seconds that a particular client view state is valid for.
   *
   * @see {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout}
   */
  private long stateTimeout;

  /**
   * Client state is generally large, so this allows some tuning to control the buffer that's used
   * to write the client state.
   *
   * <p>The value specified must be divisable by two as the buffer is split between character and
   * bytes (due to how client state is written). By default, the buffer size is 8192 (per request).
   *
   * @see {@link
   *     com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateWriteBufferSize}
   */
  private int csBuffSize;

  // ------------------------------------------------------------ Constructors

  /** Construct a new <code>ClientSideStateHelper</code> instance. */
  public ClientSideStateHelper() {

    init();
  }

  // ------------------------------------------------ Methods from StateHelper

  /**
   * Writes the view state as a String generated by Base64 encoding the Java Serialziation
   * representation of the provided <code>state</code>
   *
   * <p>If <code>stateCapture</code> is <code>null</code>, the Base64 encoded state will be written
   * to the client as a hidden field using the <code>ResponseWriter</code> from the provided <code>
   * FacesContext</code>.
   *
   * <p>If <code>stateCapture</code> is not <code>null</code>, the Base64 encoded state will be
   * appended to the provided <code>StringBuilder</code> without any markup included or any content
   * written to the client.
   *
   * @see {@link com.sun.faces.renderkit.StateHelper#writeState(javax.faces.context.FacesContext,
   *     Object, StringBuilder)}
   */
  public void writeState(FacesContext ctx, Object state, StringBuilder stateCapture)
      throws IOException {

    if (stateCapture != null) {
      doWriteState(state, new StringBuilderWriter(stateCapture));
    } else {
      ResponseWriter writer = ctx.getResponseWriter();
      writer.write(stateFieldStart);
      String viewStateId = Util.getViewStateId(ctx);
      writer.write(viewStateId);
      writer.write(fieldMiddle);
      doWriteState(state, writer);
      writer.write(fieldEnd);
      writeWindowIdField(ctx, writer);
      writeRenderKitIdField(ctx, writer);
    }
  }

  /**
   * Inspects the incoming request parameters for the standardized state parameter name. In this
   * case, the parameter value will be a Base64 encoded string previously encoded by {@link
   * com.sun.faces.renderkit.ServerSideStateHelper#writeState(javax.faces.context.FacesContext,
   * Object, StringBuilder)}.
   *
   * <p>The string will be Base64-decoded and the state reconstructed using standard Java
   * serialization.
   *
   * @see {@link com.sun.faces.renderkit.StateHelper#getState(javax.faces.context.FacesContext,
   *     String)}
   */
  public Object getState(FacesContext ctx, String viewId) throws IOException {

    String stateString = getStateParamValue(ctx);
    if (stateString == null) {
      return null;
    }
    return doGetState(stateString);
  }

  // ------------------------------------------------------- Protected Methods

  /**
   * Rebuilds the view state from the Base64 included String included with the request.
   *
   * @param stateString the Base64 encoded view state
   * @return the view state reconstructed from <code>stateString</code>
   */
  protected Object doGetState(String stateString) {
    ObjectInputStream ois = null;
    InputStream bis = new Base64InputStream(stateString);
    try {
      if (guard != null) {
        byte[] bytes = stateString.getBytes(RIConstants.CHAR_ENCODING);
        int numRead = bis.read(bytes, 0, bytes.length);
        byte[] decodedBytes = new byte[numRead];
        bis.reset();
        bis.read(decodedBytes, 0, decodedBytes.length);

        bytes = guard.decrypt(decodedBytes);
        if (bytes == null) return null;
        bis = new ByteArrayInputStream(bytes);
      }

      if (compressViewState) {
        bis = new GZIPInputStream(bis);
      }

      ois = serialProvider.createObjectInputStream(bis);

      long stateTime = 0;
      if (stateTimeoutEnabled) {
        try {
          stateTime = ois.readLong();
        } catch (IOException ioe) {
          // we've caught an exception trying to read the time
          // marker.  This most likely means a view that has been
          // around before upgrading to the release that included
          // this feature.  So, no marker, return null now to
          // cause a ViewExpiredException
          if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine(
                "Client state timeout is enabled, but unable to find the "
                    + "time marker in the serialized state.  Assuming state "
                    + "to be old and returning null.");
          }
          return null;
        }
      }
      Object structure = ois.readObject();
      Object state = ois.readObject();
      if (stateTime != 0 && hasStateExpired(stateTime)) {
        // return null if state has expired.  This should cause
        // a ViewExpiredException to be thrown
        return null;
      }

      return new Object[] {structure, state};

    } catch (java.io.OptionalDataException ode) {
      if (LOGGER.isLoggable(Level.SEVERE)) {
        LOGGER.log(Level.SEVERE, ode.getMessage(), ode);
      }
      throw new FacesException(ode);
    } catch (ClassNotFoundException cnfe) {
      if (LOGGER.isLoggable(Level.SEVERE)) {
        LOGGER.log(Level.SEVERE, cnfe.getMessage(), cnfe);
      }
      throw new FacesException(cnfe);
    } catch (IOException iox) {
      if (LOGGER.isLoggable(Level.SEVERE)) {
        LOGGER.log(Level.SEVERE, iox.getMessage(), iox);
      }
      throw new FacesException(iox);
    } finally {
      if (ois != null) {
        try {
          ois.close();
        } catch (IOException ioe) {
          // ignore
        }
      }
    }
  }

  /**
   * Serializes and Base64 encodes the provided <code>state</code> to the provided <code>writer
   * </code>/
   *
   * @param state view state
   * @param writer the <code>Writer</code> to write the content to
   * @throws IOException if an error occurs writing the state to the client
   */
  protected void doWriteState(Object state, Writer writer) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    OutputStream base = null;
    if (compressViewState) {
      base = new GZIPOutputStream(baos, csBuffSize);
    } else {
      base = baos;
    }

    ObjectOutputStream oos = null;

    try {
      oos = serialProvider.createObjectOutputStream(new BufferedOutputStream(base));

      if (stateTimeoutEnabled) {
        oos.writeLong(System.currentTimeMillis());
      }

      Object[] stateToWrite = (Object[]) state;

      //noinspection NonSerializableObjectPassedToObjectStream
      oos.writeObject(stateToWrite[0]);
      //noinspection NonSerializableObjectPassedToObjectStream
      oos.writeObject(stateToWrite[1]);

      oos.flush();
      oos.close();
      oos = null;

      // get bytes for encrypting
      byte[] bytes = baos.toByteArray();

      if (guard != null) {
        // this will MAC
        bytes = guard.encrypt(bytes);
      }

      // Base 64 encode
      Base64OutputStreamWriter bos = new Base64OutputStreamWriter(bytes.length, writer);
      bos.write(bytes, 0, bytes.length);
      bos.finish();

      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.log(
            Level.FINE,
            "Client State: total number of characters written: {0}",
            bos.getTotalCharsWritten());
      }
    } finally {
      if (oos != null) {
        try {
          oos.close();
        } catch (IOException ioe) {
          // ignore
        }
      }
    }
  }

  /**
   * If the {@link com.sun.faces.config.WebConfiguration.WebContextInitParameter#ClientStateTimeout}
   * init parameter is set, calculate the elapsed time between the time the client state was written
   * and the time this method was invoked during restore. If the client state has expired, return
   * <code>true</code>. If the client state hasn't expired, or the init parameter wasn't set, return
   * <code>false</code>.
   *
   * @param stateTime the time in milliseconds that the state was written to the client
   * @return <code>false</code> if the client state hasn't timed out, otherwise return <code>true
   *     </code>
   */
  protected boolean hasStateExpired(long stateTime) {

    if (stateTimeoutEnabled) {
      long elapsed = (System.currentTimeMillis() - stateTime) / 60000;
      return (elapsed > stateTimeout);
    } else {
      return false;
    }
  }

  /** Initialze the various configuration options for client-side sate saving. */
  protected void init() {

    if (!webConfig.isSet(BooleanWebContextInitParameter.DisableClientStateEncryption)) {
      guard = new ByteArrayGuard();
    } else {
      if (LOGGER.isLoggable(Level.FINE)) {
        LOGGER.log(Level.FINE, "jsf.config.webconfig.enventry.clientencrypt");
      }
    }

    stateTimeoutEnabled = webConfig.isSet(ClientStateTimeout);
    if (stateTimeoutEnabled) {
      String timeout = webConfig.getOptionValue(ClientStateTimeout);
      try {
        stateTimeout = Long.parseLong(timeout);
      } catch (NumberFormatException nfe) {
        stateTimeout = Long.parseLong(ClientStateTimeout.getDefaultValue());
      }
    }

    String size = webConfig.getOptionValue(ClientStateWriteBufferSize);
    String defaultSize = ClientStateWriteBufferSize.getDefaultValue();
    try {
      csBuffSize = Integer.parseInt(size);
      if (csBuffSize % 2 != 0) {
        if (LOGGER.isLoggable(Level.WARNING)) {
          LOGGER.log(
              Level.WARNING,
              "jsf.renderkit.resstatemgr.clientbuf_div_two",
              new Object[] {ClientStateWriteBufferSize.getQualifiedName(), size, defaultSize});
        }
        csBuffSize = Integer.parseInt(defaultSize);
      } else {
        csBuffSize /= 2;
        if (LOGGER.isLoggable(Level.FINE)) {
          LOGGER.fine("Using client state buffer size of " + csBuffSize);
        }
      }
    } catch (NumberFormatException nfe) {
      if (LOGGER.isLoggable(Level.WARNING)) {
        LOGGER.log(
            Level.WARNING,
            "jsf.renderkit.resstatemgr.clientbuf_not_integer",
            new Object[] {ClientStateWriteBufferSize.getQualifiedName(), size, defaultSize});
      }
      csBuffSize = Integer.parseInt(defaultSize);
    }
  }

  // ----------------------------------------------------------- Inner Classes

  /**
   * A simple <code>Writer</code> implementation to encapsulate a <code>StringBuilder</code>
   * instance.
   */
  protected static final class StringBuilderWriter extends Writer {

    private StringBuilder sb;

    // -------------------------------------------------------- Constructors

    protected StringBuilderWriter(StringBuilder sb) {

      this.sb = sb;
    }

    // ------------------------------------------------- Methods from Writer

    @Override
    public void write(int c) throws IOException {

      sb.append((char) c);
    }

    @Override
    public void write(char cbuf[]) throws IOException {

      sb.append(cbuf);
    }

    @Override
    public void write(String str) throws IOException {

      sb.append(str);
    }

    @Override
    public void write(String str, int off, int len) throws IOException {

      sb.append(str.toCharArray(), off, len);
    }

    @Override
    public Writer append(CharSequence csq) throws IOException {

      sb.append(csq);
      return this;
    }

    @Override
    public Writer append(CharSequence csq, int start, int end) throws IOException {

      sb.append(csq, start, end);
      return this;
    }

    @Override
    public Writer append(char c) throws IOException {

      sb.append(c);
      return this;
    }

    public void write(char cbuf[], int off, int len) throws IOException {

      sb.append(cbuf, off, len);
    }

    public void flush() throws IOException {

      // no-op

    }

    public void close() throws IOException {

      // no-op

    }
  } // END StringBuilderWriter
}
Ejemplo n.º 10
0
/** Helper class to interface with the Groovy runtime. */
public class GroovyHelperImpl extends GroovyHelper {

  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();
  private static final String SCRIPT_PATH = "/WEB-INF/groovy/";

  private MojarraGroovyClassLoader loader;

  // ------------------------------------------------------------ Constructors

  GroovyHelperImpl() throws Exception {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext extContext = facesContext.getExternalContext();
    ClassLoader curLoader = Thread.currentThread().getContextClassLoader();

    URL combinedRoots[] = getResourceRoots(extContext, curLoader);

    if (0 < combinedRoots.length) {
      GroovyScriptEngine engine = new GroovyScriptEngine(combinedRoots, curLoader);
      //            Class<?> c = Util.loadClass("groovy.util.GroovyScriptEngine",
      // GroovyHelperFactory.class);
      //            Constructor<?> ctor = c.getConstructor(URL[].class, ClassLoader.class);
      //            GroovyScriptEngine engine = (GroovyScriptEngine)ctor.newInstance(combinedRoots,
      // curLoader);
      loader = new MojarraGroovyClassLoader(engine);
      if (LOGGER.isLoggable(Level.INFO)) {
        LOGGER.log(Level.INFO, "Groovy support enabled.");
      }
      extContext.getApplicationMap().put("com.sun.faces.groovyhelper", this);
      ((ServletContext) (extContext.getContext())).setAttribute("com.sun.faces.groovyhelper", this);
    }
  }

  private URL[] getResourceRoots(ExternalContext extContext, ClassLoader curLoader)
      throws IOException {
    URL[] combinedRoots;
    Enumeration<URL> classpathResourceEnumeration = curLoader.getResources("META-INF/resources/");
    List<URL> classpathResourceList = new ArrayList<URL>();
    while (classpathResourceEnumeration.hasMoreElements()) {
      classpathResourceList.add(classpathResourceEnumeration.nextElement());
    }

    // only called during init - safe to cast and save.
    URL u = extContext.getResource(SCRIPT_PATH);
    URL webappRoots[] = getWebappResourceRoots(extContext),
        classpathRoots[] = new URL[classpathResourceList.size()];
    classpathResourceList.toArray(classpathRoots);

    if (null != u || 0 < webappRoots.length || 0 < classpathRoots.length) {
      combinedRoots = new URL[webappRoots.length + classpathRoots.length + (null != u ? 1 : 0)];
      System.arraycopy(webappRoots, 0, combinedRoots, 0, webappRoots.length);
      System.arraycopy(classpathRoots, 0, combinedRoots, webappRoots.length, classpathRoots.length);
      if (null != u) {
        combinedRoots[webappRoots.length + classpathRoots.length] = u;
      }
    } else {
      combinedRoots = new URL[0];
    }

    return combinedRoots;
  }

  private URL[] getWebappResourceRoots(ExternalContext extContext) {
    URL[] result = null;
    int size = 0, i = 0;
    Set<String> resourceRoots = extContext.getResourcePaths("/resources/");
    if (null != resourceRoots && !resourceRoots.isEmpty()) {
      // Determine the size of script roots that end with "/"
      for (String cur : resourceRoots) {
        if (cur.endsWith("/")) {
          size++;
        }
      }
      result = new URL[size];
      for (String cur : resourceRoots) {
        if (cur.endsWith("/")) {
          try {
            result[i++] = extContext.getResource(cur);
          } catch (MalformedURLException ex) {
            if (LOGGER.isLoggable(Level.SEVERE)) {
              LOGGER.log(Level.SEVERE, null, ex);
            }
          }
        }
      }
    }
    if (null == result) {
      result = new URL[0];
    }
    return result;
  }

  public void addURL(URL toAdd) {
    loader.getGroovyScriptEngine().getGroovyClassLoader().addURL(toAdd);
  }

  // ---------------------------------------------------------- Public Methods

  public Class<?> loadScript(String name) {
    try {
      String script = name;
      if (script.endsWith(".groovy")) {
        script = script.substring(0, script.indexOf(".groovy"));
      }
      // return engine.loadScriptByName(script);
      return Util.loadClass(script, this);
    } catch (Exception e) {
      throw new FacesException(e);
    }
  }

  public void setClassLoader() {
    if (loader != null) {
      Thread.currentThread().setContextClassLoader(loader);
    }
  }

  // ----------------------------------------------------------- Inner Classes

  public static final class MojarraGroovyClassLoader extends URLClassLoader {

    private GroovyScriptEngine gse;

    public MojarraGroovyClassLoader(GroovyScriptEngine gse) {
      super(new URL[0], gse.getGroovyClassLoader());
      gse.getGroovyClassLoader().setShouldRecompile(Boolean.TRUE);
      this.gse = gse;
    }

    public GroovyScriptEngine getGroovyScriptEngine() {
      return gse;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
      if (name == null) {
        throw new NullPointerException();
      }
      Class<?> c;
      try {
        c = gse.loadScriptByName(name);
      } catch (Exception e) {
        try {
          c = gse.getGroovyClassLoader().loadClass(name);
        } catch (ClassNotFoundException cnfe) {
          throw new ClassNotFoundException(name, cnfe);
        }
      }
      if (c == null) {
        throw new ClassNotFoundException(name);
      }
      return c;
    }
  }
}
Ejemplo n.º 11
0
/**
 * Break out the things that are associated with the Application, but need to be present even when
 * the user has replaced the Application instance.
 *
 * <p>
 *
 * <p>For example: the user replaces ApplicationFactory, and wants to intercept calls to
 * createValueExpression() and createMethodExpression() for certain kinds of expressions, but allow
 * the existing application to handle the rest.
 */
public class ApplicationAssociate {

  private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

  private ApplicationImpl app = null;

  /**
   * Overall Map containing <code>from-view-id</code> key and <code>ArrayList</code> of <code>
   * ConfigNavigationCase</code> objects for that key; The <code>from-view-id</code> strings in this
   * map will be stored as specified in the configuration file - some of them will have a trailing
   * asterisk "*" signifying wild card, and some may be specified as an asterisk "*".
   */
  private Map<String, List<ConfigNavigationCase>> caseListMap = null;

  /**
   * The List that contains all view identifier strings ending in an asterisk "*". The entries are
   * stored without the trailing asterisk.
   */
  private TreeSet<String> wildcardMatchList = null;

  // Flag indicating that a response has been rendered.
  private boolean responseRendered = false;

  private static final String ASSOCIATE_KEY = RIConstants.FACES_PREFIX + "ApplicationAssociate";

  private static ThreadLocal<ApplicationAssociate> instance =
      new ThreadLocal<ApplicationAssociate>() {
        protected ApplicationAssociate initialValue() {
          return (null);
        }
      };

  private List<ELResolver> elResolversFromFacesConfig = null;

  @SuppressWarnings("deprecation")
  private VariableResolver legacyVRChainHead = null;

  @SuppressWarnings("deprecation")
  private PropertyResolver legacyPRChainHead = null;

  private ExpressionFactory expressionFactory = null;

  @SuppressWarnings("deprecation")
  private PropertyResolver legacyPropertyResolver = null;

  @SuppressWarnings("deprecation")
  private VariableResolver legacyVariableResolver = null;

  private CompositeELResolver facesELResolverForJsp = null;

  private InjectionProvider injectionProvider;
  private ResourceCache resourceCache;

  private String contextName;
  private boolean requestServiced;

  private BeanManager beanManager;
  private GroovyHelper groovyHelper;
  private AnnotationManager annotationManager;
  private boolean devModeEnabled;
  private Compiler compiler;
  private FaceletFactory faceletFactory;
  private ResourceManager resourceManager;

  private PropertyEditorHelper propertyEditorHelper;

  public ApplicationAssociate(ApplicationImpl appImpl) {
    app = appImpl;

    propertyEditorHelper = new PropertyEditorHelper(appImpl);

    FacesContext ctx = FacesContext.getCurrentInstance();
    if (ctx == null) {
      throw new IllegalStateException(
          MessageUtils.getExceptionMessageString(
              MessageUtils.APPLICATION_ASSOCIATE_CTOR_WRONG_CALLSTACK_ID));
    }
    ExternalContext externalContext = ctx.getExternalContext();
    if (null != externalContext.getApplicationMap().get(ASSOCIATE_KEY)) {
      throw new IllegalStateException(
          MessageUtils.getExceptionMessageString(MessageUtils.APPLICATION_ASSOCIATE_EXISTS_ID));
    }
    externalContext.getApplicationMap().put(ASSOCIATE_KEY, this);
    //noinspection CollectionWithoutInitialCapacity
    caseListMap = new HashMap<String, List<ConfigNavigationCase>>();
    wildcardMatchList = new TreeSet<String>(new SortIt());
    injectionProvider = InjectionProviderFactory.createInstance(externalContext);
    WebConfiguration webConfig = WebConfiguration.getInstance(externalContext);
    beanManager =
        new BeanManager(
            injectionProvider,
            webConfig.isOptionEnabled(BooleanWebContextInitParameter.EnableLazyBeanValidation));
    annotationManager = new AnnotationManager();

    groovyHelper = GroovyHelper.getCurrentInstance();

    // initialize Facelets
    if (!webConfig.isOptionEnabled(BooleanWebContextInitParameter.DisableFaceletJSFViewHandler)) {
      compiler = createCompiler(webConfig);
      faceletFactory = createFaceletFactory(compiler, webConfig);
      devModeEnabled = (appImpl.getProjectStage() == ProjectStage.Development);
    }

    if (devModeEnabled) {
      resourceCache = new ResourceCache();
    }
    resourceManager = new ResourceManager(resourceCache);
  }

  public static ApplicationAssociate getInstance(ExternalContext externalContext) {
    if (externalContext == null) {
      return null;
    }
    Map applicationMap = externalContext.getApplicationMap();
    return ((ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY));
  }

  public static ApplicationAssociate getInstance(ServletContext context) {
    if (context == null) {
      return null;
    }
    return (ApplicationAssociate) context.getAttribute(ASSOCIATE_KEY);
  }

  public static void setCurrentInstance(ApplicationAssociate associate) {

    if (associate == null) {
      instance.remove();
    } else {
      instance.set(associate);
    }
  }

  public static ApplicationAssociate getCurrentInstance() {

    ApplicationAssociate associate = instance.get();
    if (associate == null) {
      // Fallback to ExternalContext lookup
      FacesContext fc = FacesContext.getCurrentInstance();
      if (fc != null) {
        ExternalContext extContext = fc.getExternalContext();
        if (extContext != null) {
          return ApplicationAssociate.getInstance(extContext);
        }
      }
    }

    return associate;
  }

  public ResourceManager getResourceManager() {
    return resourceManager;
  }

  public void setResourceManager(ResourceManager resourceManager) {
    this.resourceManager = resourceManager;
  }

  public ResourceCache getResourceCache() {
    return resourceCache;
  }

  public AnnotationManager getAnnotationManager() {
    return annotationManager;
  }

  public Compiler getCompiler() {
    return compiler;
  }

  public FaceletFactory getFaceletFactory() {
    return faceletFactory;
  }

  public static void clearInstance(ExternalContext externalContext) {
    Map applicationMap = externalContext.getApplicationMap();
    ApplicationAssociate me = (ApplicationAssociate) applicationMap.get(ASSOCIATE_KEY);
    if (null != me) {
      if (null != me.resourceBundles) {
        me.resourceBundles.clear();
      }
    }
    applicationMap.remove(ASSOCIATE_KEY);
  }

  public BeanManager getBeanManager() {
    return beanManager;
  }

  public GroovyHelper getGroovyHelper() {
    return groovyHelper;
  }

  public boolean isDevModeEnabled() {
    return devModeEnabled;
  }

  /**
   * Obtain the PropertyEditorHelper instance for this app.
   *
   * @return
   */
  public PropertyEditorHelper getPropertyEditorHelper() {
    return propertyEditorHelper;
  }

  /**
   * This method is called by <code>ConfigureListener</code> and will contain any <code>
   * VariableResolvers</code> defined within faces-config configuration files.
   *
   * @param resolver VariableResolver
   */
  @SuppressWarnings("deprecation")
  public void setLegacyVRChainHead(VariableResolver resolver) {
    this.legacyVRChainHead = resolver;
  }

  @SuppressWarnings("deprecation")
  public VariableResolver getLegacyVRChainHead() {
    return legacyVRChainHead;
  }

  /**
   * This method is called by <code>ConfigureListener</code> and will contain any <code>
   * PropertyResolvers</code> defined within faces-config configuration files.
   *
   * @param resolver PropertyResolver
   */
  @SuppressWarnings("deprecation")
  public void setLegacyPRChainHead(PropertyResolver resolver) {
    this.legacyPRChainHead = resolver;
  }

  @SuppressWarnings("deprecation")
  public PropertyResolver getLegacyPRChainHead() {
    return legacyPRChainHead;
  }

  public CompositeELResolver getFacesELResolverForJsp() {
    return facesELResolverForJsp;
  }

  public void setFacesELResolverForJsp(CompositeELResolver celr) {
    facesELResolverForJsp = celr;
  }

  public void setELResolversFromFacesConfig(List<ELResolver> resolvers) {
    this.elResolversFromFacesConfig = resolvers;
  }

  public List<ELResolver> getELResolversFromFacesConfig() {
    return elResolversFromFacesConfig;
  }

  public void setExpressionFactory(ExpressionFactory expressionFactory) {
    this.expressionFactory = expressionFactory;
  }

  public ExpressionFactory getExpressionFactory() {
    return this.expressionFactory;
  }

  public List<ELResolver> getApplicationELResolvers() {
    return app.getApplicationELResolvers();
  }

  public InjectionProvider getInjectionProvider() {
    return injectionProvider;
  }

  public void setContextName(String contextName) {
    this.contextName = contextName;
  }

  public String getContextName() {
    return contextName;
  }

  /**
   * Maintains the PropertyResolver called through Application.setPropertyResolver()
   *
   * @param resolver PropertyResolver
   */
  @SuppressWarnings("deprecation")
  public void setLegacyPropertyResolver(PropertyResolver resolver) {
    this.legacyPropertyResolver = resolver;
  }

  /** @return the PropertyResolver called through Application.getPropertyResolver() */
  @SuppressWarnings("deprecation")
  public PropertyResolver getLegacyPropertyResolver() {
    return legacyPropertyResolver;
  }

  /**
   * Maintains the PropertyResolver called through Application.setVariableResolver()
   *
   * @param resolver VariableResolver
   */
  @SuppressWarnings("deprecation")
  public void setLegacyVariableResolver(VariableResolver resolver) {
    this.legacyVariableResolver = resolver;
  }

  /** @return the VariableResolver called through Application.getVariableResolver() */
  @SuppressWarnings("deprecation")
  public VariableResolver getLegacyVariableResolver() {
    return legacyVariableResolver;
  }

  /**
   * Called by application code to indicate we've processed the first request to the application.
   */
  public void setRequestServiced() {
    this.requestServiced = true;
  }

  /** @return <code>true</code> if we've processed a request, otherwise <code>false</code> */
  public boolean hasRequestBeenServiced() {
    return requestServiced;
  }

  /**
   * Add a navigation case to the internal case list. If a case list does not already exist in the
   * case list map containing this case (identified by <code>from-view-id</code>), start a new list,
   * add the case to it, and store the list in the case list map. If a case list already exists, see
   * if a case entry exists in the list with a matching <code>from-view-id</code><code>from-action
   * </code> <code>from-outcome</code> combination. If there is suach an entry, overwrite it with
   * this new case. Otherwise, add the case to the list.
   *
   * @param navigationCase the navigation case containing navigation mapping information from the
   *     configuration file.
   */
  public void addNavigationCase(ConfigNavigationCase navigationCase) {

    String fromViewId = navigationCase.getFromViewId();
    List<ConfigNavigationCase> caseList = caseListMap.get(fromViewId);
    if (caseList == null) {
      //noinspection CollectionWithoutInitialCapacity
      caseList = new ArrayList<ConfigNavigationCase>();
      caseList.add(navigationCase);
      caseListMap.put(fromViewId, caseList);
    } else {
      String key = navigationCase.getKey();
      boolean foundIt = false;
      for (int i = 0; i < caseList.size(); i++) {
        ConfigNavigationCase navCase = caseList.get(i);
        // if there already is a case existing for the
        // fromviewid/fromaction.fromoutcome combination,
        // replace it ...  (last one wins).
        //
        if (key.equals(navCase.getKey())) {
          caseList.set(i, navigationCase);
          foundIt = true;
          break;
        }
      }
      if (!foundIt) {
        caseList.add(navigationCase);
      }
    }
    if (fromViewId.endsWith("*")) {
      fromViewId = fromViewId.substring(0, fromViewId.lastIndexOf('*'));
      wildcardMatchList.add(fromViewId);
    }
  }

  /**
   * Return a <code>Map</code> of navigation mappings loaded from the configuration system. The key
   * for the returned <code>Map</code> is <code>from-view-id</code>, and the value is a <code>List
   * </code> of navigation cases.
   *
   * @return Map the map of navigation mappings.
   */
  public Map<String, List<ConfigNavigationCase>> getNavigationCaseListMappings() {
    if (caseListMap == null) {
      return Collections.emptyMap();
    }
    return caseListMap;
  }

  /**
   * Return all navigation mappings whose <code>from-view-id</code> contained a trailing "*".
   *
   * @return <code>TreeSet</code> The navigation mappings sorted in descending order.
   */
  public TreeSet<String> getNavigationWildCardList() {
    return wildcardMatchList;
  }

  public ResourceBundle getResourceBundle(FacesContext context, String var) {
    ApplicationResourceBundle bundle = resourceBundles.get(var);
    if (bundle == null) {
      return null;
    }
    UIViewRoot root;
    // Start out with the default locale
    Locale locale;
    Locale defaultLocale = Locale.getDefault();
    locale = defaultLocale;
    // See if this FacesContext has a ViewRoot
    if (null != (root = context.getViewRoot())) {
      // If so, ask it for its Locale
      if (null == (locale = root.getLocale())) {
        // If the ViewRoot has no Locale, fall back to the default.
        locale = defaultLocale;
      }
    }
    assert (null != locale);
    // ResourceBundleBean bean = resourceBundles.get(var);
    return bundle.getResourceBundle(locale);
  }

  /**
   * keys: <var> element from faces-config
   *
   * <p>
   *
   * <p>values: ResourceBundleBean instances.
   */
  @SuppressWarnings({"CollectionWithoutInitialCapacity"})
  Map<String, ApplicationResourceBundle> resourceBundles =
      new HashMap<String, ApplicationResourceBundle>();

  public void addResourceBundle(String var, ApplicationResourceBundle bundle) {
    resourceBundles.put(var, bundle);
  }

  public Map<String, ApplicationResourceBundle> getResourceBundles() {
    return resourceBundles;
  }

  // This is called by ViewHandlerImpl.renderView().
  public void responseRendered() {
    responseRendered = true;
  }

  public boolean isResponseRendered() {
    return responseRendered;
  }

  protected FaceletFactory createFaceletFactory(Compiler c, WebConfiguration webConfig) {

    // refresh period
    String refreshPeriod =
        webConfig.getOptionValue(
            WebConfiguration.WebContextInitParameter.FaceletsDefaultRefreshPeriod);
    long period = Long.parseLong(refreshPeriod);

    // resource resolver
    ResourceResolver resolver = new DefaultResourceResolver();
    String resolverName =
        webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.FaceletsResourceResolver);
    if (resolverName != null && resolverName.length() > 0) {
      try {
        resolver = (ResourceResolver) ReflectionUtil.forName(resolverName).newInstance();
      } catch (Exception e) {
        throw new FacesException("Error Initializing ResourceResolver[" + resolverName + "]", e);
      }
    }

    // Resource.getResourceUrl(ctx,"/")
    return new DefaultFaceletFactory(c, resolver, period);
  }

  protected Compiler createCompiler(WebConfiguration webConfig) {

    Compiler c = new SAXCompiler();

    // load decorators
    String decParam =
        webConfig.getOptionValue(WebConfiguration.WebContextInitParameter.FaceletsDecorators);
    if (decParam != null) {
      decParam = decParam.trim();
      String[] decs = Util.split(decParam, ";");
      TagDecorator decObj;
      for (int i = 0; i < decs.length; i++) {
        try {
          decObj = (TagDecorator) ReflectionUtil.forName(decs[i]).newInstance();
          c.addTagDecorator(decObj);

          if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Successfully Loaded Decorator: {0}", decs[i]);
          }
        } catch (Exception e) {
          if (LOGGER.isLoggable(Level.SEVERE)) {
            LOGGER.log(Level.SEVERE, "Error Loading Decorator: " + decs[i], e);
          }
        }
      }
    }

    // skip params?
    c.setTrimmingComments(
        webConfig.isOptionEnabled(BooleanWebContextInitParameter.FaceletsSkipComments));

    c.addTagLibrary(new CoreLibrary());
    c.addTagLibrary(new HtmlLibrary());
    c.addTagLibrary(new UILibrary());
    c.addTagLibrary(new JstlCoreLibrary());
    c.addTagLibrary(new JstlFnLibrary());
    c.addTagLibrary(new CompositeLibrary());

    return c;
  }

  /**
   * This Comparator class will help sort the <code>ConfigNavigationCase</code> objects based on
   * their <code>fromViewId</code> properties in descending order - largest string to smallest
   * string.
   */
  static class SortIt implements Comparator<String> {

    public int compare(String fromViewId1, String fromViewId2) {
      return -(fromViewId1.compareTo(fromViewId2));
    }
  }
}
Ejemplo n.º 12
0
/** This {@link ViewHandler} implementation handles both JSP-based and Facelets/PDL-based views. */
public class MultiViewHandler extends ViewHandler {

  // Log instance for this class
  private static final Logger logger = FacesLogger.APPLICATION.getLogger();

  private String[] configuredExtensions;
  private boolean extensionsSet;

  private ViewDeclarationLanguageFactory vdlFactory;

  // ------------------------------------------------------------ Constructors

  public MultiViewHandler() {

    WebConfiguration config = WebConfiguration.getInstance();

    configuredExtensions =
        config.getOptionValue(WebConfiguration.WebContextInitParameter.DefaultSuffix, " ");
    extensionsSet = config.isSet(WebConfiguration.WebContextInitParameter.DefaultSuffix);
    vdlFactory =
        (ViewDeclarationLanguageFactory)
            FactoryFinder.getFactory(FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY);
  }

  // ------------------------------------------------ Methods from ViewHandler

  /**
   * Do not call the default implementation of {@link
   * javax.faces.application.ViewHandler#initView(javax.faces.context.FacesContext)} if the {@link
   * javax.faces.context.ExternalContext#getRequestCharacterEncoding()} returns a <code>non-null
   * </code> result.
   *
   * @see javax.faces.application.ViewHandler#initView(javax.faces.context.FacesContext)
   */
  @Override
  public void initView(FacesContext context) throws FacesException {

    if (context.getExternalContext().getRequestCharacterEncoding() == null) {
      super.initView(context);
    }
  }

  /**
   * Call {@link ViewDeclarationLanguage#renderView(javax.faces.context.FacesContext,
   * javax.faces.component.UIViewRoot)} if the view can be rendered.
   *
   * @see ViewHandler#renderView(javax.faces.context.FacesContext, javax.faces.component.UIViewRoot)
   */
  public void renderView(FacesContext context, UIViewRoot viewToRender)
      throws IOException, FacesException {

    Util.notNull("context", context);
    Util.notNull("viewToRender", viewToRender);

    vdlFactory
        .getViewDeclarationLanguage(viewToRender.getViewId())
        .renderView(context, viewToRender);
  }

  /**
   * Call {@link ViewDeclarationLanguage#restoreView(javax.faces.context.FacesContext, String)}.
   *
   * @see ViewHandler#restoreView(javax.faces.context.FacesContext, String)
   */
  public UIViewRoot restoreView(FacesContext context, String viewId) {

    Util.notNull("context", context);
    String actualViewId = derivePhysicalViewId(context, viewId, false);
    return vdlFactory.getViewDeclarationLanguage(actualViewId).restoreView(context, actualViewId);
  }

  /**
   * Derive the actual view ID (i.e. the physical resource) and call call {@link
   * ViewDeclarationLanguage#createView(javax.faces.context.FacesContext, String)}.
   *
   * @see ViewHandler#restoreView(javax.faces.context.FacesContext, String)
   */
  public UIViewRoot createView(FacesContext context, String viewId) {

    Util.notNull("context", context);
    String actualViewId = derivePhysicalViewId(context, viewId, false);
    return vdlFactory.getViewDeclarationLanguage(actualViewId).createView(context, actualViewId);
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#calculateLocale(javax.faces.context.FacesContext)
   */
  public Locale calculateLocale(FacesContext context) {

    Util.notNull("context", context);

    Locale result = null;
    // determine the locales that are acceptable to the client based on the
    // Accept-Language header and the find the best match among the
    // supported locales specified by the client.
    Iterator<Locale> locales = context.getExternalContext().getRequestLocales();
    while (locales.hasNext()) {
      Locale perf = locales.next();
      result = findMatch(context, perf);
      if (result != null) {
        break;
      }
    }
    // no match is found.
    if (result == null) {
      if (context.getApplication().getDefaultLocale() == null) {
        result = Locale.getDefault();
      } else {
        result = context.getApplication().getDefaultLocale();
      }
    }
    return result;
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#calculateRenderKitId(javax.faces.context.FacesContext)
   */
  public String calculateRenderKitId(FacesContext context) {

    Util.notNull("context", context);

    Map<String, String> requestParamMap = context.getExternalContext().getRequestParameterMap();
    String result = requestParamMap.get(ResponseStateManager.RENDER_KIT_ID_PARAM);

    if (result == null) {
      if (null == (result = context.getApplication().getDefaultRenderKitId())) {
        result = RenderKitFactory.HTML_BASIC_RENDER_KIT;
      }
    }
    return result;
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#writeState(javax.faces.context.FacesContext)
   */
  public void writeState(FacesContext context) throws IOException {

    Util.notNull("context", context);
    if (!context.getPartialViewContext().isAjaxRequest() && !context.getViewRoot().isTransient()) {
      if (logger.isLoggable(Level.FINE)) {
        logger.fine("Begin writing marker for viewId " + context.getViewRoot().getViewId());
      }

      WriteBehindStateWriter writer = WriteBehindStateWriter.getCurrentInstance();
      if (writer != null) {
        writer.writingState();
      }
      context.getResponseWriter().write(RIConstants.SAVESTATE_FIELD_MARKER);
      if (logger.isLoggable(Level.FINE)) {
        logger.fine("End writing marker for viewId " + context.getViewRoot().getViewId());
      }
    }
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#getActionURL(javax.faces.context.FacesContext, String)
   */
  public String getActionURL(FacesContext context, String viewId) {

    Util.notNull("context", context);
    Util.notNull("viewId", viewId);

    if (viewId.charAt(0) != '/') {
      String message =
          MessageUtils.getExceptionMessageString(MessageUtils.ILLEGAL_VIEW_ID_ID, viewId);
      if (logger.isLoggable(Level.SEVERE)) {
        logger.log(Level.SEVERE, "jsf.illegal_view_id_error", viewId);
      }
      throw new IllegalArgumentException(message);
    }

    // Acquire the context path, which we will prefix on all results
    ExternalContext extContext = context.getExternalContext();
    String contextPath = extContext.getRequestContextPath();

    // Acquire the mapping used to execute this request (if any)
    String mapping = Util.getFacesMapping(context);

    // If no mapping can be identified, just return a server-relative path
    if (mapping == null) {
      return (contextPath + viewId);
    }

    // Deal with prefix mapping
    if (Util.isPrefixMapped(mapping)) {
      if (mapping.equals("/*")) {
        return (contextPath + viewId);
      } else {
        return (contextPath + mapping + viewId);
      }
    }

    // Deal with extension mapping
    int period = viewId.lastIndexOf('.');
    if (period < 0) {
      return (contextPath + viewId + mapping);
    } else if (!viewId.endsWith(mapping)) {

      for (String ext : configuredExtensions) {
        if (viewId.endsWith(ext)) {
          return (contextPath + viewId.substring(0, viewId.indexOf(ext)) + mapping);
        }
      }

      return (contextPath + viewId.substring(0, period) + mapping);
    } else {
      return (contextPath + viewId);
    }
  }

  /**
   * This code is currently common to all {@link ViewHandlingStrategy} instances.
   *
   * @see ViewHandler#getResourceURL(javax.faces.context.FacesContext, String)
   */
  public String getResourceURL(FacesContext context, String path) {

    ExternalContext extContext = context.getExternalContext();
    if (path.charAt(0) == '/') {
      return (extContext.getRequestContextPath() + path);
    } else {
      return path;
    }
  }

  @Override
  public String getBookmarkableURL(
      FacesContext context,
      String viewId,
      Map<String, List<String>> parameters,
      boolean includeViewParams) {

    Map<String, List<String>> params;
    if (includeViewParams) {
      params = getFullParameterList(context, viewId, parameters);
    } else {
      params = parameters;
    }
    ExternalContext ectx = context.getExternalContext();
    return ectx.encodeActionURL(
        ectx.encodeBookmarkableURL(
            Util.getViewHandler(context).getActionURL(context, viewId), params));
  }

  /**
   * @see ViewHandler#getRedirectURL(javax.faces.context.FacesContext, String, java.util.Map,
   *     boolean)
   */
  @Override
  public String getRedirectURL(
      FacesContext context,
      String viewId,
      Map<String, List<String>> parameters,
      boolean includeViewParams) {

    Map<String, List<String>> params;
    if (includeViewParams) {
      params = getFullParameterList(context, viewId, parameters);
    } else {
      params = parameters;
    }
    ExternalContext ectx = context.getExternalContext();
    return ectx.encodeActionURL(
        ectx.encodeRedirectURL(Util.getViewHandler(context).getActionURL(context, viewId), params));
  }

  /** @see ViewHandler#getViewDeclarationLanguage(javax.faces.context.FacesContext, String) */
  @Override
  public ViewDeclarationLanguage getViewDeclarationLanguage(FacesContext context, String viewId) {

    String actualViewId = derivePhysicalViewId(context, viewId, false);
    return vdlFactory.getViewDeclarationLanguage(actualViewId);
  }

  @Override
  public String deriveViewId(FacesContext context, String rawViewId) {

    return derivePhysicalViewId(context, rawViewId, true);
  }

  // ------------------------------------------------------- Protected Methods

  /**
   * if the specified mapping is a prefix mapping, and the provided request URI (usually the value
   * from <code>ExternalContext.getRequestServletPath()</code>) starts with <code>mapping + '/'
   * </code>, prune the mapping from the URI and return it, otherwise, return the original URI.
   *
   * @param uri the servlet request path
   * @param mapping the FacesServlet mapping used for this request
   * @return the URI without additional FacesServlet mappings
   * @since 1.2
   */
  protected String normalizeRequestURI(String uri, String mapping) {

    if (mapping == null || !Util.isPrefixMapped(mapping)) {
      return uri;
    } else {
      int length = mapping.length() + 1;
      StringBuilder builder = new StringBuilder(length);
      builder.append(mapping).append('/');
      String mappingMod = builder.toString();
      boolean logged = false;
      while (uri.startsWith(mappingMod)) {
        if (!logged && logger.isLoggable(Level.WARNING)) {
          logged = true;
          logger.log(
              Level.WARNING, "jsf.viewhandler.requestpath.recursion", new Object[] {uri, mapping});
        }
        uri = uri.substring(length - 1);
      }
      return uri;
    }
  }

  /**
   * Adjust the viewID per the requirements of {@link #renderView}.
   *
   * @param context current {@link javax.faces.context.FacesContext}
   * @param viewId incoming view ID
   * @return the view ID with an altered suffix mapping (if necessary)
   */
  protected String convertViewId(FacesContext context, String viewId) {

    // if the viewId doesn't already use the above suffix,
    // replace or append.
    int extIdx = viewId.lastIndexOf('.');
    int length = viewId.length();
    StringBuilder buffer = new StringBuilder(length);

    for (String ext : configuredExtensions) {
      if (viewId.endsWith(ext)) {
        return viewId;
      }

      appendOrReplaceExtension(viewId, ext, length, extIdx, buffer);

      String convertedViewId = buffer.toString();
      try {
        if (context.getExternalContext().getResource(convertedViewId) != null) {
          // RELEASE_PENDING (rlubke,driscoll) cache the lookup
          return convertedViewId;
        }
      } catch (MalformedURLException e) {
        if (logger.isLoggable(Level.SEVERE)) {
          logger.log(Level.SEVERE, e.toString(), e);
        }
      }
    }

    // unable to find any resource match that the default ViewHandler
    // can deal with.  Fall back to legacy (JSF 1.2) id conversion.
    return legacyConvertViewId(viewId, length, extIdx, buffer);
  }

  protected String derivePhysicalViewId(FacesContext ctx, String rawViewId, boolean checkPhysical) {
    if (rawViewId != null) {
      String mapping = Util.getFacesMapping(ctx);
      String viewId;
      if (mapping != null) {
        if (!Util.isPrefixMapped(mapping)) {
          viewId = convertViewId(ctx, rawViewId);
        } else {
          viewId = normalizeRequestURI(rawViewId, mapping);
          if (viewId.equals(mapping)) {
            // The request was to the FacesServlet only - no
            // path info
            // on some containers this causes a recursion in the
            // RequestDispatcher and the request appears to hang.
            // If this is detected, return status 404
            send404Error(ctx);
          }
        }
        try {
          if (checkPhysical) {
            return ((ctx.getExternalContext().getResource(viewId) != null) ? viewId : null);
          } else {
            return viewId;
          }
        } catch (MalformedURLException mue) {
          if (logger.isLoggable(Level.SEVERE)) {
            logger.log(Level.SEVERE, mue.toString(), mue);
          }
          return null;
        }
      }
    }
    return rawViewId;
  }

  protected Map<String, List<String>> getFullParameterList(
      FacesContext ctx, String viewId, Map<String, List<String>> existingParameters) {

    Map<String, List<String>> copy;
    if (existingParameters == null || existingParameters.isEmpty()) {
      copy = new LinkedHashMap<String, List<String>>(4);
    } else {
      copy = new LinkedHashMap<String, List<String>>(existingParameters);
    }
    addViewParameters(ctx, viewId, copy);
    return copy;
  }

  protected void addViewParameters(
      FacesContext ctx, String viewId, Map<String, List<String>> existingParameters) {

    UIViewRoot currentRoot = ctx.getViewRoot();
    String currentViewId = currentRoot.getViewId();
    Collection<UIViewParameter> toViewParams;
    Collection<UIViewParameter> currentViewParams;
    boolean currentIsSameAsNew = false;
    currentViewParams = ViewMetadata.getViewParameters(currentRoot);

    if (currentViewId.equals(viewId)) {
      currentIsSameAsNew = true;
      toViewParams = currentViewParams;
    } else {
      ViewDeclarationLanguage pdl = getViewDeclarationLanguage(ctx, viewId);
      ViewMetadata viewMetadata = pdl.getViewMetadata(ctx, viewId);
      UIViewRoot root = viewMetadata.createMetadataView(ctx);
      toViewParams = ViewMetadata.getViewParameters(root);
    }

    if (toViewParams.isEmpty()) {
      return;
    }

    for (UIViewParameter viewParam : toViewParams) {
      String value;
      // don't bother looking at view parameter if it's been overridden
      if (existingParameters.containsKey(viewParam.getName())) {
        continue;
      } else if (paramHasValueExpression(viewParam)) {
        value = viewParam.getStringValueFromModel(ctx);
      } else {
        // Anonymous view parameter:
        // Get string value from UIViewParameter instance stored in current view
        if (currentIsSameAsNew) {
          value = viewParam.getStringValue(ctx);
        }
        // ...or transfer string value from matching UIViewParameter instance stored in current view
        else {
          value = getStringValueToTransfer(ctx, viewParam, currentViewParams);
        }
      }
      if (value != null) {
        List<String> existing = existingParameters.get(viewParam.getName());
        if (existing == null) {
          existing = new ArrayList<String>(4);
          existingParameters.put(viewParam.getName(), existing);
        }
        existing.add(value);
      }
    }
  }

  /**
   * Attempts to find a matching locale based on <code>pref</code> and list of supported locales,
   * using the matching algorithm as described in JSTL 8.3.2.
   *
   * @param context the <code>FacesContext</code> for the current request
   * @param pref the preferred locale
   * @return the Locale based on pref and the matching alogritm specified in JSTL 8.3.2
   */
  protected Locale findMatch(FacesContext context, Locale pref) {

    Locale result = null;
    Iterator<Locale> it = context.getApplication().getSupportedLocales();
    while (it.hasNext()) {
      Locale supportedLocale = it.next();

      if (pref.equals(supportedLocale)) {
        // exact match
        result = supportedLocale;
        break;
      } else {
        // Make sure the preferred locale doesn't have country
        // set, when doing a language match, For ex., if the
        // preferred locale is "en-US", if one of supported
        // locales is "en-UK", even though its language matches
        // that of the preferred locale, we must ignore it.
        if (pref.getLanguage().equals(supportedLocale.getLanguage())
            && supportedLocale.getCountry().length() == 0) {
          result = supportedLocale;
        }
      }
    }
    // if it's not in the supported locales,
    if (null == result) {
      Locale defaultLocale = context.getApplication().getDefaultLocale();
      if (defaultLocale != null) {
        if (pref.equals(defaultLocale)) {
          // exact match
          result = defaultLocale;
        } else {
          // Make sure the preferred locale doesn't have country
          // set, when doing a language match, For ex., if the
          // preferred locale is "en-US", if one of supported
          // locales is "en-UK", even though its language matches
          // that of the preferred locale, we must ignore it.
          if (pref.getLanguage().equals(defaultLocale.getLanguage())
              && defaultLocale.getCountry().length() == 0) {
            result = defaultLocale;
          }
        }
      }
    }

    return result;
  }

  /**
   * Send {@link HttpServletResponse#SC_NOT_FOUND} (404) to the client.
   *
   * @param context the {@link FacesContext} for the current request
   */
  protected void send404Error(FacesContext context) {

    try {
      context.responseComplete();
      context.getExternalContext().responseSendError(HttpServletResponse.SC_NOT_FOUND, "");
    } catch (IOException ioe) {
      throw new FacesException(ioe);
    }
  }

  // --------------------------------------------------------- Private Methods

  private static boolean paramHasValueExpression(UIViewParameter param) {

    return (param.getValueExpression("value") != null);
  }

  private static String getStringValueToTransfer(
      FacesContext context, UIViewParameter param, Collection<UIViewParameter> viewParams) {

    if (viewParams != null && !viewParams.isEmpty()) {
      for (UIViewParameter candidate : viewParams) {
        if ((null != candidate.getName() && null != param.getName())
            && candidate.getName().equals(param.getName())) {
          return candidate.getStringValue(context);
        } else {
          return param.getStringValue(context);
        }
      }
    }

    return null;
  }

  // Utility method used by viewId conversion.  Appends the extension
  // if no extension is present.  Otherwise, replaces the extension.
  private void appendOrReplaceExtension(
      String viewId, String ext, int length, int extIdx, StringBuilder buffer) {

    buffer.setLength(0);
    buffer.append(viewId);

    if (extIdx != -1) {
      buffer.replace(extIdx, length, ext);
    } else {
      // no extension in the provided viewId, append the suffix
      buffer.append(ext);
    }
  }

  private String legacyConvertViewId(String viewId, int length, int extIdx, StringBuilder buffer) {

    // In 1.2, the viewId was converted by replacing the extension
    // with the single extension specified by javax.faces.DEFAULT_SUFFIX,
    // which defaulted to ".jsp".  In 2.0, javax.faces.DEFAULT_SUFFIX
    // may specify multiple extensions.  If javax.faces.DEFAULT_SUFFIX is
    // explicitly set, we honor it and pick off the first specified
    // extension.  If javax.faces.DEFAULT_SUFFIX is not explicitly set,
    // we honor the default 1.2 behavior and use ".jsp" as the suffix.

    String ext =
        (extensionsSet && !(configuredExtensions.length == 0)) ? configuredExtensions[0] : ".jsp";

    if (viewId.endsWith(ext)) {
      return viewId;
    }

    appendOrReplaceExtension(viewId, ext, length, extIdx, buffer);

    return buffer.toString();
  }
}