1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts2.config;
22
23 import com.opensymphony.xwork2.config.ConfigurationProvider;
24 import com.opensymphony.xwork2.config.Configuration;
25 import com.opensymphony.xwork2.config.ConfigurationException;
26 import com.opensymphony.xwork2.config.RuntimeConfiguration;
27 import com.opensymphony.xwork2.config.entities.ActionConfig;
28 import com.opensymphony.xwork2.config.entities.PackageConfig;
29 import com.opensymphony.xwork2.inject.ContainerBuilder;
30 import com.opensymphony.xwork2.util.location.LocatableProperties;
31 import com.opensymphony.xwork2.ObjectFactory;
32
33 import java.util.*;
34 import java.lang.reflect.Method;
35
36 /***
37 * MethodConfigurationProvider creates ActionConfigs for potential action
38 * methods that lack a corresponding action mapping,
39 * so that these methods can be invoked without extra or redundant configuration.
40 * <p/>
41 * As a dynamic method, the behavior of this class could be represented as:
42 * <p/>
43 * <code>
44 * int bang = name.indexOf('!');
45 * if (bang != -1) {
46 * String method = name.substring(bang + 1);
47 * mapping.setMethod(method);
48 * name = name.substring(0, bang);
49 * }
50 * </code>
51 * <p/>
52 * If the action URL is "foo!bar", the the "foo" action is invoked,
53 * calling "bar" instead of "execute".
54 * <p/>
55 * Instead of scanning each request at runtime, the provider creates action mappings
56 * for each method that could be matched using a dynamic approach.
57 * Advantages over a dynamic approach are that:
58 * <p/>
59 * <ul>
60 * <ol>The "dynamic" methods are not a special case, but just another action mapping,
61 * with all the features of a hardcoded mapping.
62 * <ol>When needed, a manual action can be provided for a method and invoked with the same
63 * syntax as an automatic action.
64 * <ol>The ConfigBrowser can display all potential actions.
65 * </ul>
66 */
67 public class MethodConfigurationProvider implements ConfigurationProvider {
68
69 /***
70 * Stores configuration property.
71 */
72 private Configuration configuration;
73
74 /***
75 * Updates configuration property.
76 * @param configuration New configuration
77 */
78 public void setConfiguration(Configuration configuration) {
79 this.configuration = configuration;
80 }
81
82
83 public void destroy() {
84
85 }
86
87
88 public void init(Configuration configuration) throws ConfigurationException {
89 setConfiguration(configuration);
90 configuration.rebuildRuntimeConfiguration();
91 }
92
93
94 public void register(ContainerBuilder containerBuilder, LocatableProperties locatableProperties) throws ConfigurationException {
95
96 }
97
98
99 public void loadPackages() throws ConfigurationException {
100
101 Set namespaces = Collections.EMPTY_SET;
102 RuntimeConfiguration rc = configuration.getRuntimeConfiguration();
103 Map allActionConfigs = rc.getActionConfigs();
104 if (allActionConfigs != null) {
105 namespaces = allActionConfigs.keySet();
106 }
107
108 if (namespaces.size() == 0) {
109 throw new ConfigurationException("MethodConfigurationProvider.loadPackages: namespaces.size == 0");
110 }
111
112 boolean added = false;
113 for (Object namespace : namespaces) {
114 Map actions = (Map) allActionConfigs.get(namespace);
115 Set actionNames = actions.keySet();
116 for (Object actionName : actionNames) {
117 ActionConfig actionConfig = (ActionConfig) actions.get(actionName);
118 added = added | addDynamicMethods(actions, (String) actionName, actionConfig);
119 }
120 }
121
122 reload = added;
123 }
124
125 /***
126 * Store needsReload property.
127 */
128 boolean reload;
129
130
131 public boolean needsReload() {
132 return reload;
133 }
134
135 /***
136 * Stores ObjectFactory property.
137 */
138 ObjectFactory factory;
139
140 /***
141 * Updates ObjectFactory property.
142 * @param factory
143 */
144 public void setObjectFactory(ObjectFactory factory) {
145 this.factory = factory;
146 }
147
148 /***
149 * Provides ObjectFactory property.
150 * @return
151 * @throws ConfigurationException if ObjectFactory has not been set.
152 */
153 private ObjectFactory getObjectFactory() throws ConfigurationException {
154 if (factory == null) {
155 factory = ObjectFactory.getObjectFactory();
156 if (factory == null) throw new
157 ConfigurationException("MethodConfigurationProvider.getObjectFactory: ObjectFactory==null");
158 }
159 return factory;
160 }
161
162 /***
163 * Verifies that character at a String position is upper case.
164 * @param pos Position to test
165 * @param string Text containing position
166 * @return True if character at a String position is upper case
167 */
168 private boolean upperAt(int pos, String string) {
169 int len = string.length();
170 if (len < pos) return false;
171 String ch = string.substring(pos, pos+1);
172 return ch.equals(ch.toUpperCase());
173 }
174
175 /***
176 * Scans class for potential Action mehods,
177 * automatically generating and registering ActionConfigs as needed.
178 * <p/>
179 * The system iterates over the set of namespaces and the set of actionNames
180 * in a Configuration and retrieves each ActionConfig.
181 * For each ActionConfig that invokes the default "execute" method,
182 * the provider inspects the className class for other non-void,
183 * no-argument methods that do not begin with "getX" or "isX".
184 * For each qualifying method, the provider looks for another actionName in
185 * the same namespace that equals action.name + "!" + method.name.
186 * If that actionName is not found, System copies the ActionConfig,
187 * changes the method property, and adds it to the package configuration
188 * under the new actionName (action!method).
189 * <p/>
190 * The system ignores ActionConfigs with a method property set so as to
191 * avoid creating alias methods for alias methods.
192 * The system ignores "getX" and "isX" methods since these would appear to be
193 * JavaBeans property and would not be intended as action methods.
194 * (The X represents any upper character or non-letter.)
195 * @param actions All ActionConfigs in namespace
196 * @param actionName Name of ActionConfig to analyze
197 * @param actionConfig ActionConfig corresponding to actionName
198 */
199 protected boolean addDynamicMethods(Map actions, String actionName, ActionConfig actionConfig) throws ConfigurationException {
200
201 String configMethod = actionConfig.getMethodName();
202 boolean hasMethod = (configMethod != null) && (configMethod.length() > 0);
203 if (hasMethod) return false;
204
205 String className = actionConfig.getClassName();
206 Set actionMethods = new HashSet();
207 Class actionClass;
208 ObjectFactory factory = getObjectFactory();
209 try {
210 actionClass = factory.getClassInstance(className);
211 } catch (ClassNotFoundException e) {
212 throw new ConfigurationException(e);
213 }
214
215 Method[] methods = actionClass.getMethods();
216 for (Method method : methods) {
217 String returnString = method.getReturnType().getName();
218 boolean isString = "java.lang.String".equals(returnString);
219 if (isString) {
220 Class[] parameterTypes = method.getParameterTypes();
221 boolean noParameters = (parameterTypes.length == 0);
222 String methodString = method.getName();
223 boolean notGetMethod = !((methodString.startsWith("get")) && upperAt(3, methodString));
224 boolean notIsMethod = !((methodString.startsWith("is")) && upperAt(2, methodString));
225 boolean notToString = !("toString".equals(methodString));
226 boolean notExecute = !("execute".equals(methodString));
227 boolean qualifies = noParameters && notGetMethod && notIsMethod && notToString && notExecute;
228 if (qualifies) {
229 actionMethods.add(methodString);
230 }
231 }
232 }
233
234 for (Object actionMethod : actionMethods) {
235 String methodName = (String) actionMethod;
236 StringBuilder sb = new StringBuilder();
237 sb.append(actionName);
238 sb.append("!");
239 sb.append(methodName);
240 String newActionName = sb.toString();
241 boolean haveAction = actions.containsKey(newActionName);
242 if (haveAction) continue;
243 ActionConfig newActionConfig = new ActionConfig(
244 newActionName,
245 actionConfig.getClassName(),
246 actionConfig.getParams(),
247 actionConfig.getResults(),
248 actionConfig.getInterceptors(),
249 actionConfig.getExceptionMappings());
250 newActionConfig.setMethodName(methodName);
251 String packageName = actionConfig.getPackageName();
252 newActionConfig.setPackageName(packageName);
253 PackageConfig packageConfig = configuration.getPackageConfig(packageName);
254 packageConfig.addActionConfig(newActionName, actionConfig);
255 }
256
257 return (actionMethods.size() > 0);
258 }
259 }