View Javadoc

1   /*
2    * $Id: ActionConfigMatcher.java 471754 2006-11-06 14:55:09Z husted $
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             cfg = new ActionForward();
184             cfg.setName(fConfigs[x].getName());
185             cfg.setPath(convertParam(fConfigs[x].getPath(), vars));
186             cfg.setRedirect(fConfigs[x].getRedirect());
187             cfg.setCommand(convertParam(fConfigs[x].getCommand(), vars));
188             cfg.setCatalog(convertParam(fConfigs[x].getCatalog(), vars));
189             cfg.setModule(convertParam(fConfigs[x].getModule(), vars));
190 
191             replaceProperties(fConfigs[x].getProperties(), cfg.getProperties(),
192                 vars);
193 
194             config.removeForwardConfig(fConfigs[x]);
195             config.addForwardConfig(cfg);
196         }
197 
198         replaceProperties(orig.getProperties(), config.getProperties(), vars);
199 
200         ExceptionConfig[] exConfigs = orig.findExceptionConfigs();
201 
202         for (int x = 0; x < exConfigs.length; x++) {
203             config.addExceptionConfig(exConfigs[x]);
204         }
205 
206         config.freeze();
207 
208         return config;
209     }
210 
211     /**
212      * <p> Replaces placeholders from one Properties values set to another.
213      * </p>
214      *
215      * @param orig  The original properties set with placehold values
216      * @param props The target properties to store the processed values
217      * @param vars  A Map of wildcard-matched strings
218      */
219     protected void replaceProperties(Properties orig, Properties props, Map vars) {
220         Map.Entry entry = null;
221 
222         for (Iterator i = orig.entrySet().iterator(); i.hasNext();) {
223             entry = (Map.Entry) i.next();
224             props.setProperty((String) entry.getKey(),
225                 convertParam((String) entry.getValue(), vars));
226         }
227     }
228 
229     /**
230      * <p> Inserts into a value wildcard-matched strings where specified.
231      * </p>
232      *
233      * @param val  The value to convert
234      * @param vars A Map of wildcard-matched strings
235      * @return The new value
236      */
237     protected String convertParam(String val, Map vars) {
238         if (val == null) {
239             return null;
240         } else if (val.indexOf("{") == -1) {
241             return val;
242         }
243 
244         Map.Entry entry;
245         StringBuffer key = new StringBuffer("{0}");
246         StringBuffer ret = new StringBuffer(val);
247         String keyTmp;
248         int x;
249 
250         for (Iterator i = vars.entrySet().iterator(); i.hasNext();) {
251             entry = (Map.Entry) i.next();
252             key.setCharAt(1, ((String) entry.getKey()).charAt(0));
253             keyTmp = key.toString();
254 
255             // Replace all instances of the placeholder
256             while ((x = ret.toString().indexOf(keyTmp)) > -1) {
257                 ret.replace(x, x + 3, (String) entry.getValue());
258             }
259         }
260 
261         return ret.toString();
262     }
263 
264     /**
265      * <p> Stores a compiled wildcard pattern and the ActionConfig it came
266      * from. </p>
267      */
268     private class Mapping implements Serializable {
269         /**
270          * <p> The compiled pattern. </p>
271          */
272         private int[] pattern;
273 
274         /**
275          * <p> The original ActionConfig. </p>
276          */
277         private ActionConfig config;
278 
279         /**
280          * <p> Contructs a read-only Mapping instance. </p>
281          *
282          * @param pattern The compiled pattern
283          * @param config  The original ActionConfig
284          */
285         public Mapping(int[] pattern, ActionConfig config) {
286             this.pattern = pattern;
287             this.config = config;
288         }
289 
290         /**
291          * <p> Gets the compiled wildcard pattern. </p>
292          *
293          * @return The compiled pattern
294          */
295         public int[] getPattern() {
296             return this.pattern;
297         }
298 
299         /**
300          * <p> Gets the ActionConfig that contains the pattern. </p>
301          *
302          * @return The associated ActionConfig
303          */
304         public ActionConfig getActionConfig() {
305             return this.config;
306         }
307     }
308 }