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 }