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 }