View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.myfaces.orchestra.urlParamNav;
    20
    21  import javax.faces.FacesException;
    22  import javax.faces.application.ViewHandler;
    23  import javax.faces.component.UIViewRoot;
    24  import javax.faces.context.FacesContext;
    25  import javax.faces.el.ValueBinding;
    26
    27  import org.apache.myfaces.orchestra.lib.OrchestraException;
    28
    29  import java.io.IOException;
    30  import java.lang.reflect.Method;
    31  import java.util.Locale;
    32
    33  /**
    34   * Allow the to-view-id URL in a faces-config navigation case to include
    35   * query parameters and EL expressions.
    36   * <p>
    37   * This class plays a few tricks to hide from the real NavigationHandler
    38   * and ViewHandler classes the fact that a URL contains non-standard data.
    39   * <p>
    40   * This class also plays a few reflection-based tricks so that the code can
    41   * be compiled against JSF1.1, and work with both JSF1.1 and JSF1.2. The
    42   * code is a little fragile and will probably need to be updated to work
    43   * correctly with JSF2.0, but that is the fault of the JSF spec.
    44   */
    45  public class UrlParameterViewHandler extends ViewHandler
    46  {
    47      private static final Method CALC_CHAR_ENC_METHOD;
    48      private static final Method INIT_VIEW_METHOD;
    49
    50      private final ViewHandler original;
    51
    52      /**
    53       * Static initialization block.
    54       */
    55      static
    56      {
    57          CALC_CHAR_ENC_METHOD = getMethodOpt(ViewHandler.class,
    58                  "calculateCharacterEncoding",
    59                  new Class[] {FacesContext.class});
    60
    61          INIT_VIEW_METHOD = getMethodOpt(ViewHandler.class,
    62                  "initView",
    63                  new Class[] {FacesContext.class});
    64      }
    65
    66      /**
    67       * If the specified class has a method with the specified name and params, return
    68       * it else return null.
    69       */
    70      private static Method getMethodOpt(Class clazz, String methodName, Class[] args)
    71      {
    72          try
    73          {
    74              return clazz.getMethod(methodName, args);
    75          }
    76          catch(NoSuchMethodException e)
    77          {
    78              return null;
    79          }
    80      }
    81
    82      /**
    83       * Constructor.
    84       */
    85      public UrlParameterViewHandler(final ViewHandler original)
    86      {
    87          this.original = original;
    88      }
    89
    90      /**
    91       * Delegate to wrapped instance.
    92       * <p>
    93       * This method was added in JSF1.2. We must therefore use reflection
    94       * to invoke the method on the wrapped instance. Note that this method
    95       * is never invoked unless this is a JSF1.2 environment.
    96       *
    97       * @since 1.3
    98       */
    99      public java.lang.String calculateCharacterEncoding(FacesContext context)
    100     {
    101         try
    102         {
    103             Object ret = CALC_CHAR_ENC_METHOD.invoke(original, new Object[] {context});
    104             return (String) ret;
    105         }
    106         catch(Exception e)
    107         {
    108             throw new OrchestraException("Unable to invoke calculateCharacterEncoding on wrapped ViewHandler");
    109         }
    110     }
    111
    112     /**
    113      * Delegate to wrapped instance.
    114      * <p>
    115      * This method was added in JSF1.2. We must therefore use reflection
    116      * to invoke the method on the wrapped instance. Note that this method
    117      * is never invoked unless this is a JSF1.2 environment.
    118      *
    119      * @since 1.3
    120      */
    121     public void initView(FacesContext context)
    122     throws FacesException
    123     {
    124         try
    125         {
    126             INIT_VIEW_METHOD.invoke(original, new Object[] {context});
    127         }
    128         catch(Exception e)
    129         {
    130             throw new OrchestraException("Unable to invoke initView on wrapped ViewHandler");
    131         }
    132     }
    133
    134     public Locale calculateLocale(FacesContext context)
    135     {
    136         return original.calculateLocale(context);
    137     }
    138
    139     public String calculateRenderKitId(FacesContext context)
    140     {
    141         return original.calculateRenderKitId(context);
    142     }
    143
    144     public UIViewRoot createView(FacesContext context, String viewId)
    145     {
    146         return original.createView(context, viewId);
    147     }
    148
    149     public String getActionURL(FacesContext context, String viewId)
    150     {
    151         if (viewId != null)
    152         {
    153             // Expand any EL expression in the URL.
    154             //
    155             // This handles a call from a NavigationHandler which is processing a redirect
    156             // navigation case. A NavigationHandler must call the following in order:
    157             //  * ViewHandler.getActionURL,
    158             //  * ExternalContext.encodeActionURL
    159             //  * ExternalContext.redirect
    160             //
    161             // Orchestra hooks into ExternalContext.encodeActionURL to trigger the
    162             // RequestParameterProviderManager which then inserts various query params
    163             // into the URL.
    164             //
    165             // So here, ensure that any EL expressions are expanded before the
    166             // RequestParameterProviderManager is invoked. An alternative would be for
    167             // the RequestParameterProviderManager to do the encoding, but at the current
    168             // time that class is not JSF-dependent in any way, so calling JSF expression
    169             // expansion from there is not possible.
    170             //
    171             // Note that this method is also called from a Form component when rendering
    172             // its 'action' attribute. This code therefore has the side-effect of
    173             // permitting EL expressions in a form's action. This is not particularly
    174             // useful, however, as they are expected to have been expanded before this
    175             // method is invoked..
    176             viewId = expandExpressions(context, viewId);
    177
    178             // Hide query parameters from the standard ViewHandlerImpl. The standard
    179             // implementation of ViewHandlerImpl.getActionUrl method does not handle
    180             // query params well. So strip them off, invoke the processing, then reattach
    181             // them afterwards.
    182             int pos = viewId.indexOf('?');
    183             if (pos > -1)
    184             {
    185                 String realViewId = viewId.substring(0, pos);
    186                 String params = viewId.substring(pos);
    187
    188                 return original.getActionURL(context, realViewId) + params;
    189             }
    190         }
    191         return original.getActionURL(context, viewId);
    192     }
    193
    194     public String getResourceURL(FacesContext context, String path)
    195     {
    196         return original.getResourceURL(context, path);
    197     }
    198
    199     public void renderView(FacesContext context, UIViewRoot viewToRender)
    200         throws IOException, FacesException
    201     {
    202         original.renderView(context, viewToRender);
    203     }
    204
    205     public UIViewRoot restoreView(FacesContext context, String viewId)
    206     {
    207         return original.restoreView(context, viewId);
    208     }
    209
    210     public void writeState(FacesContext context)
    211         throws IOException
    212     {
    213         original.writeState(context);
    214     }
    215
    216     private static String expandExpressions(FacesContext context, String url)
    217     {
    218         int pos = url.indexOf("#{");
    219         if (pos > -1 && url.indexOf("}", pos) > -1)
    220         {
    221             // There is at least one EL expression, so evaluate the whole url string.
    222             // Note that something like "aaa#{foo}bbb#{bar}ccc" is fine; both the
    223             // el expressions will get replaced.
    224             ValueBinding vb = context.getApplication().createValueBinding(url);
    225             return (String) vb.getValue(context);
    226         }
    227
    228         return url;
    229     }
    230 }