001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.server.dispatch;
031:
032: import com.caucho.server.util.CauchoSystem;
033: import com.caucho.util.CharBuffer;
034:
035: import javax.servlet.ServletException;
036: import java.util.ArrayList;
037: import java.util.regex.Matcher;
038: import java.util.regex.Pattern;
039: import java.util.regex.PatternSyntaxException;
040:
041: /**
042: * Maps uris to objects, using the syntax in the servlet2.2 deployment
043: * descriptors:
044: *
045: * /foo/bar -- exact match
046: * /foo/bar/* -- matches anything with the /foo/bar prefix
047: * *.jsp -- matches anything with the .jsp suffix
048: */
049: public class UrlMap<E> {
050: // List of matching regular expressions
051: private ArrayList<RegexpEntry<E>> _regexps;
052: // If true, use the shortest match
053: private boolean _bestShort;
054:
055: /**
056: * Create a new map
057: */
058: public UrlMap() {
059: _regexps = new ArrayList<RegexpEntry<E>>();
060: }
061:
062: /**
063: * Create a new map preferring a short match.
064: *
065: * @param bestShort if true, use the shortest match
066: */
067: public UrlMap(boolean bestShort) {
068: _regexps = new ArrayList<RegexpEntry<E>>();
069: _bestShort = bestShort;
070: }
071:
072: /**
073: * If set to true, this map uses the shortest match instead of the
074: * longest.
075: *
076: * @param bestShort if true, use the shortest match
077: */
078: void setBestShort(boolean bestShort) {
079: _bestShort = bestShort;
080: }
081:
082: int size() {
083: return _regexps.size();
084: }
085:
086: public void addMap(String pattern, E value)
087: throws PatternSyntaxException {
088: addMap(pattern, null, value);
089: }
090:
091: /**
092: * Adds a new url-pattern and its corresponding value to the map
093: *
094: * @param pattern servlet2.2 url-pattern
095: * @param value object stored as the value
096: */
097: public void addMap(String pattern, String flags, E value)
098: throws PatternSyntaxException {
099: if (pattern.length() == 0 || pattern.length() == 1
100: && pattern.charAt(0) == '/') {
101: addRegexp(-1, "", flags, value, true);
102: return;
103: }
104:
105: else if (pattern.equals("/*")) {
106: addRegexp(1, "/*", "", flags, value, true);
107: return;
108: }
109:
110: int length = pattern.length();
111: boolean isExact = true;
112:
113: if (pattern.charAt(0) != '/' && pattern.charAt(0) != '*') {
114: pattern = "/" + pattern;
115: length++;
116: }
117:
118: int prefixLength = -1;
119: boolean isShort = false;
120: CharBuffer cb = new CharBuffer();
121: cb.append("^");
122: for (int i = 0; i < length; i++) {
123: char ch = pattern.charAt(i);
124:
125: if (ch == '*' && i + 1 == length && i > 0) {
126: isExact = false;
127:
128: if (pattern.charAt(i - 1) == '/') {
129: cb.setLength(cb.length() - 1);
130:
131: if (prefixLength < 0)
132: prefixLength = i - 1;
133:
134: } else if (prefixLength < 0)
135: prefixLength = i;
136:
137: if (prefixLength == 0)
138: prefixLength = 1;
139: } else if (ch == '*') {
140: isExact = false;
141: cb.append(".*");
142: if (prefixLength < 0)
143: prefixLength = i;
144:
145: if (i == 0)
146: isShort = true;
147: } else if (ch == '.' || ch == '[' || ch == '^' || ch == '$'
148: || ch == '{' || ch == '}' || ch == '|' || ch == '('
149: || ch == ')' || ch == '?') {
150: cb.append('\\');
151: cb.append(ch);
152: } else
153: cb.append(ch);
154: }
155:
156: if (isExact)
157: cb.append("$");
158: else {
159: cb.append("(?=/)|" + cb.toString() + "\\z");
160: }
161:
162: if (prefixLength < 0)
163: prefixLength = pattern.length();
164: else if (prefixLength < pattern.length()
165: && pattern.charAt(prefixLength) == '/')
166: prefixLength--;
167:
168: if (cb.length() > 0 && cb.charAt(0) == '/')
169: cb.insert(0, '^');
170:
171: addRegexp(prefixLength, pattern, cb.close(), flags, value,
172: isShort);
173: }
174:
175: public static String urlPatternToRegexpPattern(String pattern) {
176: if (pattern.length() == 0 || pattern.length() == 1
177: && pattern.charAt(0) == '/') {
178: return "^.*$";
179: }
180:
181: else if (pattern.equals("/*"))
182: return "^.*$";
183:
184: int length = pattern.length();
185:
186: if (pattern.charAt(0) != '/' && pattern.charAt(0) != '*') {
187: pattern = "/" + pattern;
188: length++;
189: }
190:
191: boolean isExact = true;
192: CharBuffer cb = new CharBuffer();
193: cb.append("^");
194: for (int i = 0; i < length; i++) {
195: char ch = pattern.charAt(i);
196:
197: if (ch == '*' && i + 1 == length && i > 0) {
198: isExact = false;
199:
200: if (pattern.charAt(i - 1) == '/') {
201: cb.setLength(cb.length() - 1);
202: }
203: } else if (ch == '*') {
204: isExact = false;
205: cb.append(".*");
206: } else if (ch == '.' || ch == '[' || ch == '^' || ch == '$'
207: || ch == '{' || ch == '}' || ch == '|' || ch == '('
208: || ch == ')' || ch == '?') {
209: cb.append('\\');
210: cb.append(ch);
211: } else
212: cb.append(ch);
213: }
214:
215: if (isExact)
216: cb.append("\\z");
217: else
218: cb.append("(?=/)|" + cb.toString() + "\\z");
219:
220: if (cb.length() > 0 && cb.charAt(0) == '/')
221: cb.insert(0, '^');
222:
223: return cb.close();
224: }
225:
226: /**
227: * Adds a new url-pattern and its corresponding value to the map
228: *
229: * @param pattern servlet2.2 url-pattern
230: * @param value object stored as the value
231: */
232: public void addStrictMap(String pattern, String flags, E value)
233: throws PatternSyntaxException, ServletException {
234: if (pattern.length() == 0 || pattern.length() == 1
235: && pattern.charAt(0) == '/') {
236: addRegexp(-1, "^.*$", flags, value, true);
237: return;
238: }
239:
240: int length = pattern.length();
241: boolean isExact = true;
242:
243: if (pattern.charAt(0) != '/' && pattern.charAt(0) != '*') {
244: pattern = "/" + pattern;
245: length++;
246: }
247:
248: if (pattern.indexOf('*') < pattern.lastIndexOf('*'))
249: throw new ServletException("at most one '*' is allowed");
250:
251: int prefixLength = -1;
252: boolean isShort = false;
253: CharBuffer cb = new CharBuffer();
254: cb.append('^');
255:
256: for (int i = 0; i < length; i++) {
257: char ch = pattern.charAt(i);
258:
259: switch (ch) {
260: case '*':
261: if (i > 0 && i + 1 == length
262: && pattern.charAt(i - 1) == '/') {
263: cb.append(".*");
264: } else if (i == 0 && length > 1
265: && pattern.charAt(1) == '.'
266: && pattern.lastIndexOf('/') < 0) {
267: cb.append(".*");
268: } else
269: throw new ServletException("illegal url-pattern `"
270: + pattern + "'");
271: break;
272:
273: case '.':
274: case '[':
275: case '^':
276: case '$':
277: case '{':
278: case '}':
279: case '|':
280: case '(':
281: case '?':
282: cb.append('\\');
283: cb.append(ch);
284: break;
285:
286: default:
287: cb.append(ch);
288: }
289: }
290:
291: cb.append("$");
292:
293: addRegexp(prefixLength, pattern, cb.close(), flags, value,
294: isShort);
295: }
296:
297: public void addRegexp(String regexp, String flags, E value)
298: throws PatternSyntaxException {
299: addRegexp(0, regexp, flags, value, false);
300: }
301:
302: public void addRegexp(String regexp, E value)
303: throws PatternSyntaxException {
304: addRegexp(0, regexp, null, value, false);
305: }
306:
307: /**
308: * Adds a regular expression to the map.
309: *
310: * @param prefixLength the length of the pattern's mandatory prefix
311: * @param regexp the regexp pattern to add
312: * @param flags regexp flags, like "i" for case insensitive
313: * @param value the value for matching the pattern
314: * @param isShort if true, this regexp expects to be shorter than others
315: */
316: public void addRegexp(int prefixLength, String regexp,
317: String flags, E value, boolean isShort)
318: throws PatternSyntaxException {
319: RegexpEntry<E> entry = new RegexpEntry<E>(prefixLength, regexp,
320: flags, value);
321:
322: for (int i = 0; i < _regexps.size(); i++) {
323: RegexpEntry<E> re = _regexps.get(i);
324:
325: if (re.equals(entry)) {
326: _regexps.remove(i);
327: break;
328: }
329: }
330:
331: if (isShort)
332: entry.setShortMatch();
333:
334: _regexps.add(entry);
335: }
336:
337: /**
338: * Adds a regular expression to the map.
339: *
340: * @param prefixLength the length of the pattern's mandatory prefix
341: * @param pattern the regexp pattern to add
342: * @param regexp the regexp pattern to add
343: * @param flags regexp flags, like "i" for case insensitive
344: * @param value the value for matching the pattern
345: * @param isShort if true, this regexp expects to be shorter than others
346: */
347: public void addRegexp(int prefixLength, String pattern,
348: String regexp, String flags, E value, boolean isShort)
349: throws PatternSyntaxException {
350: RegexpEntry<E> entry = new RegexpEntry<E>(prefixLength,
351: pattern, regexp, flags, value);
352:
353: for (int i = _regexps.size() - 1; i >= 0; i--) {
354: RegexpEntry<E> re = _regexps.get(i);
355:
356: if (re.equals(entry)) {
357: _regexps.remove(i);
358: }
359: }
360:
361: if (isShort)
362: entry.setShortMatch();
363:
364: _regexps.add(entry);
365: }
366:
367: /**
368: * Finds the best match for the uri. In the case of a servlet dispatch,
369: * match is servletPath and replacement is pathInfo.
370: *
371: * @param uri uri to match
372: *
373: * @return matching object
374: */
375: public E map(String uri) {
376: return map(uri, null);
377: }
378:
379: /**
380: * Finds the best match for the uri. In the case of a servlet dispatch,
381: * match is servletPath and replacement is pathInfo.
382: *
383: * @param uri uri to match
384: * @param vars a list of the regexp variables.
385: *
386: * @return matching object
387: */
388: public E map(String uri, ArrayList<String> vars) {
389: E best = null;
390:
391: if (vars != null)
392: vars.add(uri);
393:
394: int bestPrefixLength = -2;
395: int bestMinLength = -2;
396: int bestMaxLength = Integer.MAX_VALUE;
397:
398: for (int i = 0; i < _regexps.size(); i++) {
399: RegexpEntry<E> entry = _regexps.get(i);
400:
401: if ("plugin_match".equals(entry._value)
402: || "plugin-match".equals(entry._value))
403: continue;
404: if ("plugin_ignore".equals(entry._value)
405: || "plugin-ignore".equals(entry._value))
406: continue;
407: if (entry._prefixLength < bestPrefixLength)
408: continue;
409:
410: Matcher matcher = entry._regexp.matcher(uri);
411:
412: if (!matcher.find())
413: continue;
414:
415: int begin = matcher.start();
416: int end = matcher.end();
417:
418: int length = end - begin;
419:
420: boolean bestShort = entry.isShortMatch();
421:
422: // Earlier matches override later ones
423: if (bestPrefixLength < entry._prefixLength
424: || bestMinLength < length) {
425: if (vars != null) {
426: vars.clear();
427:
428: vars.add(uri.substring(0, end));
429: for (int j = 1; j <= matcher.groupCount(); j++)
430: vars.add(matcher.group(j));
431: }
432:
433: best = entry._value;
434: bestPrefixLength = entry._prefixLength;
435: bestMaxLength = length;
436: if (!entry.isShortMatch())
437: bestMinLength = length;
438: if (entry._prefixLength > bestMinLength)
439: bestMinLength = entry._prefixLength;
440: }
441: }
442:
443: return best;
444: }
445:
446: /**
447: * Return the matching url patterns.
448: */
449: public ArrayList<String> getURLPatterns() {
450: ArrayList<String> patterns = new ArrayList<String>();
451:
452: for (int i = 0; i < _regexps.size(); i++) {
453: RegexpEntry<E> entry = _regexps.get(i);
454:
455: String urlPattern = entry.getURLPattern();
456:
457: if (urlPattern != null)
458: patterns.add(urlPattern);
459: }
460:
461: return patterns;
462: }
463:
464: static class RegexpEntry<E> {
465: String _urlPattern;
466: String _pattern;
467: int _flags;
468: Pattern _regexp;
469: E _value;
470: int _prefixLength;
471: boolean _shortMatch;
472:
473: RegexpEntry(int prefixLength, String pattern, String flags,
474: E value) throws PatternSyntaxException {
475: this (prefixLength, pattern, pattern, flags, value);
476: }
477:
478: RegexpEntry(int prefixLength, String urlPattern,
479: String pattern, String flags, E value)
480: throws PatternSyntaxException {
481: _urlPattern = urlPattern;
482: _prefixLength = prefixLength;
483: _pattern = pattern;
484:
485: if (flags == null && CauchoSystem.isCaseInsensitive())
486: _flags = Pattern.CASE_INSENSITIVE;
487: else if (flags != null && flags.equals("i"))
488: _flags = Pattern.CASE_INSENSITIVE;
489:
490: _regexp = Pattern.compile(pattern, _flags);
491: _value = value;
492: }
493:
494: void setShortMatch() {
495: _shortMatch = true;
496: }
497:
498: boolean isShortMatch() {
499: return _shortMatch;
500: }
501:
502: String getURLPattern() {
503: return _urlPattern;
504: }
505:
506: String getPattern() {
507: return _pattern;
508: }
509:
510: public int hashCode() {
511: if (_urlPattern != null)
512: return _urlPattern.hashCode();
513: else if (_pattern != null)
514: return _pattern.hashCode();
515: else
516: return 17;
517: }
518:
519: public boolean equals(Object o) {
520: if (!(o instanceof RegexpEntry))
521: return false;
522:
523: RegexpEntry re = (RegexpEntry) o;
524:
525: if (_urlPattern != null)
526: return _urlPattern.equals(re._urlPattern);
527: else if (_pattern != null)
528: return _pattern.equals(re._pattern);
529: else
530: return false;
531: }
532:
533: public String toString() {
534: if (_urlPattern != null)
535: return "RegexpEntry[" + _urlPattern + "]";
536: else if (_pattern != null)
537: return "RegexpEntry[" + _pattern + "]";
538: else
539: return super.toString();
540: }
541: }
542: }
|