1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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 }