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 }