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
    20  package org.apache.myfaces.orchestra.viewController;
    21
    22  import java.util.Arrays;
    23  import java.util.HashSet;
    24  import java.util.Set;
    25
    26  /**
    27   * Map view-ids to bean names, using a dirSubdirPage style format.
    28   * <p>
    29   * The strategy of this mapper is as follows:
    30   * <ul>
    31   * <li>The first character after every slash (except the first one) is converted to uppercase;</li>
    32   * <li>All slashes ('/') are removed;</li>
    33   * <li>All characters following the last dot ('.') are removed;</li>
    34   * <li>Reserved words {@link #RESERVED_WORDS} are prefixed with an underscore ('_');</li>
    35   * <li>Resulting bean-names starting with a digit are also prefixed with an underscore.</li>
    36   * </ul>
    37   * <p>
    38   * Examples:
    39   * <ul>
    40   * <li>"/userInfo.jsp" becomes "userInfo"
    41   * <li>"/SecureArea/userPassword.xhtml" becomes "secureAreaUserPassword"
    42   * </ul>
    43   * <p>
    44   * Using a bean naming scheme provides the following benefits:
    45   * <ul>
    46   * <li>The backing bean code does not need to be aware of the view path;
    47   * <li>The backing bean class can be in any package;
    48   * <li>Moving the view does not require alteration to the java code;
    49   * <li>There is no separate "view mapping" configuration file required with
    50   * its own unique format, just normal managed-bean configuration.
    51   * <li>It is easy to see from the managed-bean declarations which view a bean is
    52   * associated with. This information can be extremely useful when developing
    53   * a large application, so a bean naming convention of this type is useful
    54   * even when not being used for the purposes of view lifecycle events.
    55   * </ul>
    56   * <p>
    57   * In particular, the separation between "UI designer" and "coder" remains; the
    58   * UI designer can move and rearrange views without touching java code. They do
    59   * need to change the bean mapping files, but that is not so significant.
    60   * <p>
    61   * The following limitations apply to this approach:
    62   * <ul>
    63   * <li>Only one bean can be mapped to a view. However this can be worked around
    64   * by providing a "relay" bean that delegates calls to all of the beans that
    65   * have been injected into it.
    66   * <li>When a view is moved, lifecycle events will silently stop occurring if
    67   * the bean-name is not been updated.
    68   * <li>When a view is moved, the bean-name has to change. If the bean handling
    69   * viewcontroller events is also referenced by other expressions in the page,
    70   * then all those expressions must also be changed. Dependency-injection
    71   * configuration that uses that bean-name must also be updated. However see
    72   * below for information on "bean aliasing".
    73   * <li>When a view is deeply nested within a directory tree, the bean name
    74   * generated from the view name can become long and inconvenient to use within
    75   * expressions. This is only an issue if the bean handling lifecycle events
    76   * is also the target of expressions in the page, but that is often the case.
    77   * </ul>
    78   * <p>
    79   * Some dependency-injection frameworks allow bean-name "aliases" to be defined,
    80   * ie for a single managed-bean to have multiple names. This can be used to define
    81   * one name that expressions reference the bean through, and a separate name
    82   * that is used only in order to link that bean with the corresponding view. With
    83   * this configuration, moving a view simply requires changing the name of the
    84   * alias. If appropriate these aliases can be defined in a different configuration
    85   * file from the "real" bean definitions (eg for the use of UI Designers). This
    86   * approach does increase the number of managed-bean declarations required, so
    87   * should only be applied where useful.
    88   * <p>
    89   * It is possible to define a very simple request-scoped bean for viewcontroller
    90   * event handling that just delegates to another bean that is injected into it.
    91   * This has the same effect as "bean aliasing" although it is implementable without
    92   * "aliasing" support (and particularly, in plain JSF 1.1). This simple bean can be
    93   * named after the view it controls, while the "real" backing bean can be named
    94   * however it wishes. The same benefits and drawbacks apply as for the "aliases"
    95   * approach described above.
    96   * <p>
    97   * It may be possible to also define aliases within the page definitions. In
    98   * particular, for JSF the Apache Myfaces Tomahawk AliasBean can be used to define
    99   * a local alias for a bean. If this is done at the top of a file, then when the
    100  * view is moved, only that alias entry in the page needs to be altered rather
    101  * than all expressions in the page.
    102  */
    103 public class DefaultViewControllerNameMapper implements ViewControllerNameMapper
    104 {
    105     /**
    106      * An unmodifiable set of strings which are not permitted as bean-names.
    107      * <p>
    108      * If the mapping of any viewId to a bean-name results in one of these values,
    109      * then the name-mapper must modify the result.
    110      * <p>
    111      * TODO: move this list to some shared class. Other ViewControllerNameMapper
    112      * implementations could find this list useful. Note, however, that it is
    113      * servlet-specific. This class is supposed to not assume any particular
    114      * request/response technology.
    115      */
    116     private static final Set RESERVED_WORDS = new HashSet(Arrays.asList(
    117             new String[]
    118             {
    119                 "applicationScope",
    120                 "cookie",
    121                 "facesContext",
    122                 "header",
    123                 "headerValues",
    124                 "initParam",
    125                 "param",
    126                 "paramValues",
    127                 "requestScope",
    128                 "sessionScope",
    129                 "view"
    130             }
    131     ));
    132
    133
    134     public String mapViewId(String viewId)
    135     {
    136         if (viewId == null)
    137         {
    138             return null;
    139         }
    140
    141         boolean nextUpper = false;
    142
    143         StringBuffer sb = new StringBuffer(viewId);
    144         for (int i = 0; i < sb.length(); i++)
    145         {
    146             char c = sb.charAt(i);
    147             if (c == '/')
    148             {
    149                 if (i > 0)
    150                 {
    151                     nextUpper = true;
    152                 }
    153                 sb.deleteCharAt(i);
    154                 i--;
    155             }
    156             else if (c == '.')
    157             {
    158                 sb.delete(i, sb.length());
    159                 break;
    160             }
    161             else if (nextUpper)
    162             {
    163                 sb.setCharAt(i, Character.toUpperCase(c));
    164                 nextUpper = false;
    165             }
    166         }
    167
    168         if (sb.length() > 0)
    169         {
    170             sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
    171         }
    172
    173         String beanName = sb.toString();
    174         if (RESERVED_WORDS.contains(beanName))
    175         {
    176             return "_" + beanName;
    177         }
    178
    179         if (beanName.length() > 0 && Character.isDigit(beanName.charAt(0)))
    180         {
    181             return "_" + beanName;
    182         }
    183
    184         return beanName;
    185     }
    186 }