001: /*
002: * $Id: ActionConfigMatcher.java 471754 2006-11-06 14:55:09Z husted $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts.config;
022:
023: import org.apache.commons.beanutils.BeanUtils;
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.apache.struts.action.ActionForward;
027: import org.apache.struts.util.WildcardHelper;
028:
029: import java.io.Serializable;
030:
031: import java.util.ArrayList;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Map;
036: import java.util.Properties;
037:
038: /**
039: * <p> Matches paths against pre-compiled wildcard expressions pulled from
040: * action configs. It uses the wildcard matcher from the Apache Cocoon
041: * project. Patterns will be matched in the order they exist in the Struts
042: * config file. The last match wins, so more specific patterns should be
043: * defined after less specific patterns.
044: *
045: * @since Struts 1.2
046: */
047: public class ActionConfigMatcher implements Serializable {
048: /**
049: * <p> The logging instance </p>
050: */
051: private static final Log log = LogFactory
052: .getLog(ActionConfigMatcher.class);
053:
054: /**
055: * <p> Handles all wildcard pattern matching. </p>
056: */
057: private static final WildcardHelper wildcard = new WildcardHelper();
058:
059: /**
060: * <p> The compiled paths and their associated ActionConfig's </p>
061: */
062: private List compiledPaths;
063:
064: /**
065: * <p> Finds and precompiles the wildcard patterns from the ActionConfig
066: * "path" attributes. ActionConfig's will be evaluated in the order they
067: * exist in the Struts config file. Only paths that actually contain a
068: * wildcard will be compiled. </p>
069: *
070: * @param configs An array of ActionConfig's to process
071: */
072: public ActionConfigMatcher(ActionConfig[] configs) {
073: compiledPaths = new ArrayList();
074:
075: int[] pattern;
076: String path;
077:
078: for (int x = 0; x < configs.length; x++) {
079: path = configs[x].getPath();
080:
081: if ((path != null) && (path.indexOf('*') > -1)) {
082: if ((path.length() > 0) && (path.charAt(0) == '/')) {
083: path = path.substring(1);
084: }
085:
086: if (log.isDebugEnabled()) {
087: log.debug("Compiling action config path '" + path
088: + "'");
089: }
090:
091: pattern = wildcard.compilePattern(path);
092: compiledPaths.add(new Mapping(pattern, configs[x]));
093: }
094: }
095: }
096:
097: /**
098: * <p> Matches the path against the compiled wildcard patterns. </p>
099: *
100: * @param path The portion of the request URI for selecting a config.
101: * @return The action config if matched, else null
102: */
103: public ActionConfig match(String path) {
104: ActionConfig config = null;
105:
106: if (compiledPaths.size() > 0) {
107: if (log.isDebugEnabled()) {
108: log.debug("Attempting to match '" + path
109: + "' to a wildcard pattern");
110: }
111:
112: if ((path.length() > 0) && (path.charAt(0) == '/')) {
113: path = path.substring(1);
114: }
115:
116: Mapping m;
117: HashMap vars = new HashMap();
118:
119: for (Iterator i = compiledPaths.iterator(); i.hasNext();) {
120: m = (Mapping) i.next();
121:
122: if (wildcard.match(vars, path, m.getPattern())) {
123: if (log.isDebugEnabled()) {
124: log.debug("Path matches pattern '"
125: + m.getActionConfig().getPath() + "'");
126: }
127:
128: config = convertActionConfig(path, (ActionConfig) m
129: .getActionConfig(), vars);
130: }
131: }
132: }
133:
134: return config;
135: }
136:
137: /**
138: * <p> Clones the ActionConfig and its children, replacing various
139: * properties with the values of the wildcard-matched strings. </p>
140: *
141: * @param path The requested path
142: * @param orig The original ActionConfig
143: * @param vars A Map of wildcard-matched strings
144: * @return A cloned ActionConfig with appropriate properties replaced with
145: * wildcard-matched values
146: */
147: protected ActionConfig convertActionConfig(String path,
148: ActionConfig orig, Map vars) {
149: ActionConfig config = null;
150:
151: try {
152: config = (ActionConfig) BeanUtils.cloneBean(orig);
153: } catch (Exception ex) {
154: log.warn(
155: "Unable to clone action config, recommend not using "
156: + "wildcards", ex);
157:
158: return null;
159: }
160:
161: config.setName(convertParam(orig.getName(), vars));
162:
163: if ((path.length() == 0) || (path.charAt(0) != '/')) {
164: path = "/" + path;
165: }
166:
167: config.setPath(path);
168: config.setType(convertParam(orig.getType(), vars));
169: config.setRoles(convertParam(orig.getRoles(), vars));
170: config.setParameter(convertParam(orig.getParameter(), vars));
171: config.setAttribute(convertParam(orig.getAttribute(), vars));
172: config.setForward(convertParam(orig.getForward(), vars));
173: config.setInclude(convertParam(orig.getInclude(), vars));
174: config.setInput(convertParam(orig.getInput(), vars));
175: config.setCatalog(convertParam(orig.getCatalog(), vars));
176: config.setCommand(convertParam(orig.getCommand(), vars));
177: config.setMultipartClass(convertParam(orig.getMultipartClass(),
178: vars));
179: config.setPrefix(convertParam(orig.getPrefix(), vars));
180: config.setSuffix(convertParam(orig.getSuffix(), vars));
181:
182: ForwardConfig[] fConfigs = orig.findForwardConfigs();
183: ForwardConfig cfg;
184:
185: for (int x = 0; x < fConfigs.length; x++) {
186: cfg = new ActionForward();
187: cfg.setName(fConfigs[x].getName());
188: cfg.setPath(convertParam(fConfigs[x].getPath(), vars));
189: cfg.setRedirect(fConfigs[x].getRedirect());
190: cfg
191: .setCommand(convertParam(fConfigs[x].getCommand(),
192: vars));
193: cfg
194: .setCatalog(convertParam(fConfigs[x].getCatalog(),
195: vars));
196: cfg.setModule(convertParam(fConfigs[x].getModule(), vars));
197:
198: replaceProperties(fConfigs[x].getProperties(), cfg
199: .getProperties(), vars);
200:
201: config.removeForwardConfig(fConfigs[x]);
202: config.addForwardConfig(cfg);
203: }
204:
205: replaceProperties(orig.getProperties(), config.getProperties(),
206: vars);
207:
208: ExceptionConfig[] exConfigs = orig.findExceptionConfigs();
209:
210: for (int x = 0; x < exConfigs.length; x++) {
211: config.addExceptionConfig(exConfigs[x]);
212: }
213:
214: config.freeze();
215:
216: return config;
217: }
218:
219: /**
220: * <p> Replaces placeholders from one Properties values set to another.
221: * </p>
222: *
223: * @param orig The original properties set with placehold values
224: * @param props The target properties to store the processed values
225: * @param vars A Map of wildcard-matched strings
226: */
227: protected void replaceProperties(Properties orig, Properties props,
228: Map vars) {
229: Map.Entry entry = null;
230:
231: for (Iterator i = orig.entrySet().iterator(); i.hasNext();) {
232: entry = (Map.Entry) i.next();
233: props.setProperty((String) entry.getKey(), convertParam(
234: (String) entry.getValue(), vars));
235: }
236: }
237:
238: /**
239: * <p> Inserts into a value wildcard-matched strings where specified.
240: * </p>
241: *
242: * @param val The value to convert
243: * @param vars A Map of wildcard-matched strings
244: * @return The new value
245: */
246: protected String convertParam(String val, Map vars) {
247: if (val == null) {
248: return null;
249: } else if (val.indexOf("{") == -1) {
250: return val;
251: }
252:
253: Map.Entry entry;
254: StringBuffer key = new StringBuffer("{0}");
255: StringBuffer ret = new StringBuffer(val);
256: String keyTmp;
257: int x;
258:
259: for (Iterator i = vars.entrySet().iterator(); i.hasNext();) {
260: entry = (Map.Entry) i.next();
261: key.setCharAt(1, ((String) entry.getKey()).charAt(0));
262: keyTmp = key.toString();
263:
264: // Replace all instances of the placeholder
265: while ((x = ret.toString().indexOf(keyTmp)) > -1) {
266: ret.replace(x, x + 3, (String) entry.getValue());
267: }
268: }
269:
270: return ret.toString();
271: }
272:
273: /**
274: * <p> Stores a compiled wildcard pattern and the ActionConfig it came
275: * from. </p>
276: */
277: private class Mapping implements Serializable {
278: /**
279: * <p> The compiled pattern. </p>
280: */
281: private int[] pattern;
282:
283: /**
284: * <p> The original ActionConfig. </p>
285: */
286: private ActionConfig config;
287:
288: /**
289: * <p> Contructs a read-only Mapping instance. </p>
290: *
291: * @param pattern The compiled pattern
292: * @param config The original ActionConfig
293: */
294: public Mapping(int[] pattern, ActionConfig config) {
295: this .pattern = pattern;
296: this .config = config;
297: }
298:
299: /**
300: * <p> Gets the compiled wildcard pattern. </p>
301: *
302: * @return The compiled pattern
303: */
304: public int[] getPattern() {
305: return this .pattern;
306: }
307:
308: /**
309: * <p> Gets the ActionConfig that contains the pattern. </p>
310: *
311: * @return The associated ActionConfig
312: */
313: public ActionConfig getActionConfig() {
314: return this.config;
315: }
316: }
317: }
|