001: /*
002: * $Id: MethodConfigurationProvider.java 502296 2007-02-01 17:33:39Z niallp $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.config;
022:
023: import com.opensymphony.xwork2.config.ConfigurationProvider;
024: import com.opensymphony.xwork2.config.Configuration;
025: import com.opensymphony.xwork2.config.ConfigurationException;
026: import com.opensymphony.xwork2.config.RuntimeConfiguration;
027: import com.opensymphony.xwork2.config.entities.ActionConfig;
028: import com.opensymphony.xwork2.config.entities.PackageConfig;
029: import com.opensymphony.xwork2.inject.ContainerBuilder;
030: import com.opensymphony.xwork2.util.location.LocatableProperties;
031: import com.opensymphony.xwork2.ObjectFactory;
032:
033: import java.util.*;
034: import java.lang.reflect.Method;
035:
036: /**
037: * MethodConfigurationProvider creates ActionConfigs for potential action
038: * methods that lack a corresponding action mapping,
039: * so that these methods can be invoked without extra or redundant configuration.
040: * <p/>
041: * As a dynamic method, the behavior of this class could be represented as:
042: * <p/>
043: * <code>
044: * int bang = name.indexOf('!');
045: * if (bang != -1) {
046: * String method = name.substring(bang + 1);
047: * mapping.setMethod(method);
048: * name = name.substring(0, bang);
049: * }
050: * </code>
051: * <p/>
052: * If the action URL is "foo!bar", the the "foo" action is invoked,
053: * calling "bar" instead of "execute".
054: * <p/>
055: * Instead of scanning each request at runtime, the provider creates action mappings
056: * for each method that could be matched using a dynamic approach.
057: * Advantages over a dynamic approach are that:
058: * <p/>
059: * <ul>
060: * <ol>The "dynamic" methods are not a special case, but just another action mapping,
061: * with all the features of a hardcoded mapping.
062: * <ol>When needed, a manual action can be provided for a method and invoked with the same
063: * syntax as an automatic action.
064: * <ol>The ConfigBrowser can display all potential actions.
065: * </ul>
066: */
067: public class MethodConfigurationProvider implements
068: ConfigurationProvider {
069:
070: /**
071: * Stores configuration property.
072: */
073: private Configuration configuration;
074:
075: /**
076: * Updates configuration property.
077: * @param configuration New configuration
078: */
079: public void setConfiguration(Configuration configuration) {
080: this .configuration = configuration;
081: }
082:
083: // See superclass for Javadoc
084: public void destroy() {
085: // Override to provide functionality
086: }
087:
088: // See superclass for Javadoc
089: public void init(Configuration configuration)
090: throws ConfigurationException {
091: setConfiguration(configuration);
092: configuration.rebuildRuntimeConfiguration();
093: }
094:
095: // See superclass for Javadoc
096: public void register(ContainerBuilder containerBuilder,
097: LocatableProperties locatableProperties)
098: throws ConfigurationException {
099: // Override to provide functionality
100: }
101:
102: // See superclass for Javadoc
103: public void loadPackages() throws ConfigurationException {
104:
105: Set namespaces = Collections.EMPTY_SET;
106: RuntimeConfiguration rc = configuration
107: .getRuntimeConfiguration();
108: Map allActionConfigs = rc.getActionConfigs();
109: if (allActionConfigs != null) {
110: namespaces = allActionConfigs.keySet();
111: }
112:
113: if (namespaces.size() == 0) {
114: throw new ConfigurationException(
115: "MethodConfigurationProvider.loadPackages: namespaces.size == 0");
116: }
117:
118: boolean added = false;
119: for (Object namespace : namespaces) {
120: Map actions = (Map) allActionConfigs.get(namespace);
121: Set actionNames = actions.keySet();
122: for (Object actionName : actionNames) {
123: ActionConfig actionConfig = (ActionConfig) actions
124: .get(actionName);
125: added = added
126: | addDynamicMethods(actions,
127: (String) actionName, actionConfig);
128: }
129: }
130:
131: reload = added;
132: }
133:
134: /**
135: * Store needsReload property.
136: */
137: boolean reload;
138:
139: // See superclass for Javadoc
140: public boolean needsReload() {
141: return reload;
142: }
143:
144: /**
145: * Stores ObjectFactory property.
146: */
147: ObjectFactory factory;
148:
149: /**
150: * Updates ObjectFactory property.
151: * @param factory
152: */
153: public void setObjectFactory(ObjectFactory factory) {
154: this .factory = factory;
155: }
156:
157: /**
158: * Provides ObjectFactory property.
159: * @return
160: * @throws ConfigurationException if ObjectFactory has not been set.
161: */
162: private ObjectFactory getObjectFactory()
163: throws ConfigurationException {
164: if (factory == null) {
165: factory = ObjectFactory.getObjectFactory();
166: if (factory == null)
167: throw new ConfigurationException(
168: "MethodConfigurationProvider.getObjectFactory: ObjectFactory==null");
169: }
170: return factory;
171: }
172:
173: /**
174: * Verifies that character at a String position is upper case.
175: * @param pos Position to test
176: * @param string Text containing position
177: * @return True if character at a String position is upper case
178: */
179: private boolean upperAt(int pos, String string) {
180: int len = string.length();
181: if (len < pos)
182: return false;
183: String ch = string.substring(pos, pos + 1);
184: return ch.equals(ch.toUpperCase());
185: }
186:
187: /**
188: * Scans class for potential Action mehods,
189: * automatically generating and registering ActionConfigs as needed.
190: * <p/>
191: * The system iterates over the set of namespaces and the set of actionNames
192: * in a Configuration and retrieves each ActionConfig.
193: * For each ActionConfig that invokes the default "execute" method,
194: * the provider inspects the className class for other non-void,
195: * no-argument methods that do not begin with "getX" or "isX".
196: * For each qualifying method, the provider looks for another actionName in
197: * the same namespace that equals action.name + "!" + method.name.
198: * If that actionName is not found, System copies the ActionConfig,
199: * changes the method property, and adds it to the package configuration
200: * under the new actionName (action!method).
201: * <p/>
202: * The system ignores ActionConfigs with a method property set so as to
203: * avoid creating alias methods for alias methods.
204: * The system ignores "getX" and "isX" methods since these would appear to be
205: * JavaBeans property and would not be intended as action methods.
206: * (The X represents any upper character or non-letter.)
207: * @param actions All ActionConfigs in namespace
208: * @param actionName Name of ActionConfig to analyze
209: * @param actionConfig ActionConfig corresponding to actionName
210: */
211: protected boolean addDynamicMethods(Map actions, String actionName,
212: ActionConfig actionConfig) throws ConfigurationException {
213:
214: String configMethod = actionConfig.getMethodName();
215: boolean hasMethod = (configMethod != null)
216: && (configMethod.length() > 0);
217: if (hasMethod)
218: return false;
219:
220: String className = actionConfig.getClassName();
221: Set actionMethods = new HashSet();
222: Class actionClass;
223: ObjectFactory factory = getObjectFactory();
224: try {
225: actionClass = factory.getClassInstance(className);
226: } catch (ClassNotFoundException e) {
227: throw new ConfigurationException(e);
228: }
229:
230: Method[] methods = actionClass.getMethods();
231: for (Method method : methods) {
232: String returnString = method.getReturnType().getName();
233: boolean isString = "java.lang.String".equals(returnString);
234: if (isString) {
235: Class[] parameterTypes = method.getParameterTypes();
236: boolean noParameters = (parameterTypes.length == 0);
237: String methodString = method.getName();
238: boolean notGetMethod = !((methodString
239: .startsWith("get")) && upperAt(3, methodString));
240: boolean notIsMethod = !((methodString.startsWith("is")) && upperAt(
241: 2, methodString));
242: boolean notToString = !("toString".equals(methodString));
243: boolean notExecute = !("execute".equals(methodString));
244: boolean qualifies = noParameters && notGetMethod
245: && notIsMethod && notToString && notExecute;
246: if (qualifies) {
247: actionMethods.add(methodString);
248: }
249: }
250: }
251:
252: for (Object actionMethod : actionMethods) {
253: String methodName = (String) actionMethod;
254: StringBuilder sb = new StringBuilder();
255: sb.append(actionName);
256: sb.append("!"); // TODO: Make "!" a configurable character
257: sb.append(methodName);
258: String newActionName = sb.toString();
259: boolean haveAction = actions.containsKey(newActionName);
260: if (haveAction)
261: continue;
262: ActionConfig newActionConfig = new ActionConfig(
263: newActionName, actionConfig.getClassName(),
264: actionConfig.getParams(),
265: actionConfig.getResults(), actionConfig
266: .getInterceptors(), actionConfig
267: .getExceptionMappings());
268: newActionConfig.setMethodName(methodName);
269: String packageName = actionConfig.getPackageName();
270: newActionConfig.setPackageName(packageName);
271: PackageConfig packageConfig = configuration
272: .getPackageConfig(packageName);
273: packageConfig.addActionConfig(newActionName, actionConfig);
274: }
275:
276: return (actionMethods.size() > 0);
277: }
278: }
|