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