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 }