View Javadoc

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