View Javadoc

1   /*
2    * $Id: MethodConfigurationProvider.java 502296 2007-02-01 17:33:39Z niallp $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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      // See superclass for Javadoc
83      public void destroy() {
84          // Override to provide functionality
85      }
86  
87      // See superclass for Javadoc
88      public void init(Configuration configuration) throws ConfigurationException {
89          setConfiguration(configuration);
90          configuration.rebuildRuntimeConfiguration();
91      }
92  
93      // See superclass for Javadoc
94      public void register(ContainerBuilder containerBuilder, LocatableProperties locatableProperties) throws ConfigurationException {
95          // Override to provide functionality
96      }
97  
98      // See superclass for Javadoc
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     // See superclass for Javadoc
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("!"); // TODO: Make "!" a configurable character
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 }