View Javadoc

    1   /*
    2    * $Id: ActionConfigMatcher.java 530002 2007-04-18 12:35:44Z pbenedict $
    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.struts.config;
    22
    23  import org.apache.commons.beanutils.BeanUtils;
    24  import org.apache.commons.logging.Log;
    25  import org.apache.commons.logging.LogFactory;
    26  import org.apache.struts.action.ActionForward;
    27  import org.apache.struts.util.WildcardHelper;
    28
    29  import java.io.Serializable;
    30
    31  import java.util.ArrayList;
    32  import java.util.HashMap;
    33  import java.util.Iterator;
    34  import java.util.List;
    35  import java.util.Map;
    36  import java.util.Properties;
    37
    38  /**
    39   * <p> Matches paths against pre-compiled wildcard expressions pulled from
    40   * action configs. It uses the wildcard matcher from the Apache Cocoon
    41   * project. Patterns will be matched in the order they exist in the Struts
    42   * config file. The last match wins, so more specific patterns should be
    43   * defined after less specific patterns.
    44   *
    45   * @since Struts 1.2
    46   */
    47  public class ActionConfigMatcher implements Serializable {
    48      /**
    49       * <p> The logging instance </p>
    50       */
    51      private static final Log log = LogFactory.getLog(ActionConfigMatcher.class);
    52
    53      /**
    54       * <p> Handles all wildcard pattern matching. </p>
    55       */
    56      private static final WildcardHelper wildcard = new WildcardHelper();
    57
    58      /**
    59       * <p> The compiled paths and their associated ActionConfig's </p>
    60       */
    61      private List compiledPaths;
    62
    63      /**
    64       * <p> Finds and precompiles the wildcard patterns from the ActionConfig
    65       * "path" attributes. ActionConfig's will be evaluated in the order they
    66       * exist in the Struts config file. Only paths that actually contain a
    67       * wildcard will be compiled. </p>
    68       *
    69       * @param configs An array of ActionConfig's to process
    70       */
    71      public ActionConfigMatcher(ActionConfig[] configs) {
    72          compiledPaths = new ArrayList();
    73
    74          int[] pattern;
    75          String path;
    76
    77          for (int x = 0; x < configs.length; x++) {
    78              path = configs[x].getPath();
    79
    80              if ((path != null) && (path.indexOf('*') > -1)) {
    81                  if ((path.length() > 0) && (path.charAt(0) == '/')) {
    82                      path = path.substring(1);
    83                  }
    84
    85                  if (log.isDebugEnabled()) {
    86                      log.debug("Compiling action config path '" + path + "'");
    87                  }
    88
    89                  pattern = wildcard.compilePattern(path);
    90                  compiledPaths.add(new Mapping(pattern, configs[x]));
    91              }
    92          }
    93      }
    94
    95      /**
    96       * <p> Matches the path against the compiled wildcard patterns. </p>
    97       *
    98       * @param path The portion of the request URI for selecting a config.
    99       * @return The action config if matched, else null
    100      */
    101     public ActionConfig match(String path) {
    102         ActionConfig config = null;
    103
    104         if (compiledPaths.size() > 0) {
    105             if (log.isDebugEnabled()) {
    106                 log.debug("Attempting to match '" + path
    107                     + "' to a wildcard pattern");
    108             }
    109
    110             if ((path.length() > 0) && (path.charAt(0) == '/')) {
    111                 path = path.substring(1);
    112             }
    113
    114             Mapping m;
    115             HashMap vars = new HashMap();
    116
    117             for (Iterator i = compiledPaths.iterator(); i.hasNext();) {
    118                 m = (Mapping) i.next();
    119
    120                 if (wildcard.match(vars, path, m.getPattern())) {
    121                     if (log.isDebugEnabled()) {
    122                         log.debug("Path matches pattern '"
    123                             + m.getActionConfig().getPath() + "'");
    124                     }
    125
    126                     config =
    127                         convertActionConfig(path,
    128                             (ActionConfig) m.getActionConfig(), vars);
    129                 }
    130             }
    131         }
    132
    133         return config;
    134     }
    135
    136     /**
    137      * <p> Clones the ActionConfig and its children, replacing various
    138      * properties with the values of the wildcard-matched strings. </p>
    139      *
    140      * @param path The requested path
    141      * @param orig The original ActionConfig
    142      * @param vars A Map of wildcard-matched strings
    143      * @return A cloned ActionConfig with appropriate properties replaced with
    144      *         wildcard-matched values
    145      */
    146     protected ActionConfig convertActionConfig(String path, ActionConfig orig,
    147         Map vars) {
    148         ActionConfig config = null;
    149
    150         try {
    151             config = (ActionConfig) BeanUtils.cloneBean(orig);
    152         } catch (Exception ex) {
    153             log.warn("Unable to clone action config, recommend not using "
    154                 + "wildcards", ex);
    155
    156             return null;
    157         }
    158
    159         config.setName(convertParam(orig.getName(), vars));
    160
    161         if ((path.length() == 0) || (path.charAt(0) != '/')) {
    162             path = "/" + path;
    163         }
    164
    165         config.setPath(path);
    166         config.setType(convertParam(orig.getType(), vars));
    167         config.setRoles(convertParam(orig.getRoles(), vars));
    168         config.setParameter(convertParam(orig.getParameter(), vars));
    169         config.setAttribute(convertParam(orig.getAttribute(), vars));
    170         config.setForward(convertParam(orig.getForward(), vars));
    171         config.setInclude(convertParam(orig.getInclude(), vars));
    172         config.setInput(convertParam(orig.getInput(), vars));
    173         config.setCatalog(convertParam(orig.getCatalog(), vars));
    174         config.setCommand(convertParam(orig.getCommand(), vars));
    175         config.setMultipartClass(convertParam(orig.getMultipartClass(), vars));
    176         config.setPrefix(convertParam(orig.getPrefix(), vars));
    177         config.setSuffix(convertParam(orig.getSuffix(), vars));
    178
    179         ForwardConfig[] fConfigs = orig.findForwardConfigs();
    180         ForwardConfig cfg;
    181
    182         for (int x = 0; x < fConfigs.length; x++) {
    183             try {
    184                 cfg = (ActionForward) BeanUtils.cloneBean(fConfigs[x]);
    185             } catch (Exception ex) {
    186                 log.warn("Unable to clone action config, recommend not using "
    187                         + "wildcards", ex);
    188                 return null;
    189             }
    190             cfg.setName(fConfigs[x].getName());
    191             cfg.setPath(convertParam(fConfigs[x].getPath(), vars));
    192             cfg.setRedirect(fConfigs[x].getRedirect());
    193             cfg.setCommand(convertParam(fConfigs[x].getCommand(), vars));
    194             cfg.setCatalog(convertParam(fConfigs[x].getCatalog(), vars));
    195             cfg.setModule(convertParam(fConfigs[x].getModule(), vars));
    196
    197             replaceProperties(fConfigs[x].getProperties(), cfg.getProperties(),
    198                 vars);
    199
    200             config.removeForwardConfig(fConfigs[x]);
    201             config.addForwardConfig(cfg);
    202         }
    203
    204         replaceProperties(orig.getProperties(), config.getProperties(), vars);
    205
    206         ExceptionConfig[] exConfigs = orig.findExceptionConfigs();
    207
    208         for (int x = 0; x < exConfigs.length; x++) {
    209             config.addExceptionConfig(exConfigs[x]);
    210         }
    211
    212         config.freeze();
    213
    214         return config;
    215     }
    216
    217     /**
    218      * <p> Replaces placeholders from one Properties values set to another.
    219      * </p>
    220      *
    221      * @param orig  The original properties set with placehold values
    222      * @param props The target properties to store the processed values
    223      * @param vars  A Map of wildcard-matched strings
    224      */
    225     protected void replaceProperties(Properties orig, Properties props, Map vars) {
    226         Map.Entry entry = null;
    227
    228         for (Iterator i = orig.entrySet().iterator(); i.hasNext();) {
    229             entry = (Map.Entry) i.next();
    230             props.setProperty((String) entry.getKey(),
    231                 convertParam((String) entry.getValue(), vars));
    232         }
    233     }
    234
    235     /**
    236      * <p> Inserts into a value wildcard-matched strings where specified.
    237      * </p>
    238      *
    239      * @param val  The value to convert
    240      * @param vars A Map of wildcard-matched strings
    241      * @return The new value
    242      */
    243     protected String convertParam(String val, Map vars) {
    244         if (val == null) {
    245             return null;
    246         } else if (val.indexOf("{") == -1) {
    247             return val;
    248         }
    249
    250         Map.Entry entry;
    251         StringBuffer key = new StringBuffer("{0}");
    252         StringBuffer ret = new StringBuffer(val);
    253         String keyTmp;
    254         int x;
    255
    256         for (Iterator i = vars.entrySet().iterator(); i.hasNext();) {
    257             entry = (Map.Entry) i.next();
    258             key.setCharAt(1, ((String) entry.getKey()).charAt(0));
    259             keyTmp = key.toString();
    260
    261             // Replace all instances of the placeholder
    262             while ((x = ret.toString().indexOf(keyTmp)) > -1) {
    263                 ret.replace(x, x + 3, (String) entry.getValue());
    264             }
    265         }
    266
    267         return ret.toString();
    268     }
    269
    270     /**
    271      * <p> Stores a compiled wildcard pattern and the ActionConfig it came
    272      * from. </p>
    273      */
    274     private class Mapping implements Serializable {
    275         /**
    276          * <p> The compiled pattern. </p>
    277          */
    278         private int[] pattern;
    279
    280         /**
    281          * <p> The original ActionConfig. </p>
    282          */
    283         private ActionConfig config;
    284
    285         /**
    286          * <p> Contructs a read-only Mapping instance. </p>
    287          *
    288          * @param pattern The compiled pattern
    289          * @param config  The original ActionConfig
    290          */
    291         public Mapping(int[] pattern, ActionConfig config) {
    292             this.pattern = pattern;
    293             this.config = config;
    294         }
    295
    296         /**
    297          * <p> Gets the compiled wildcard pattern. </p>
    298          *
    299          * @return The compiled pattern
    300          */
    301         public int[] getPattern() {
    302             return this.pattern;
    303         }
    304
    305         /**
    306          * <p> Gets the ActionConfig that contains the pattern. </p>
    307          *
    308          * @return The associated ActionConfig
    309          */
    310         public ActionConfig getActionConfig() {
    311             return this.config;
    312         }
    313     }
    314 }