0001: /*
0002: * Copyright 2005-2007 Noelios Consulting.
0003: *
0004: * The contents of this file are subject to the terms of the Common Development
0005: * and Distribution License (the "License"). You may not use this file except in
0006: * compliance with the License.
0007: *
0008: * You can obtain a copy of the license at
0009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
0010: * language governing permissions and limitations under the License.
0011: *
0012: * When distributing Covered Code, include this CDDL HEADER in each file and
0013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
0014: * applicable, add the following below this CDDL HEADER, with the fields
0015: * enclosed by brackets "[]" replaced with your own identifying information:
0016: * Portions Copyright [yyyy] [name of copyright owner]
0017: */
0018:
0019: package org.restlet.util;
0020:
0021: import java.util.List;
0022: import java.util.Map;
0023: import java.util.concurrent.ConcurrentHashMap;
0024: import java.util.concurrent.CopyOnWriteArrayList;
0025: import java.util.logging.Logger;
0026: import java.util.regex.Matcher;
0027: import java.util.regex.Pattern;
0028:
0029: import org.restlet.data.Reference;
0030: import org.restlet.data.Request;
0031: import org.restlet.data.Response;
0032:
0033: /**
0034: * String template with a model is based on a request. Supports both formatting
0035: * and parsing. The template variables can be inserted using the "{name}" syntax
0036: * and described using the modifiable map of variable descriptors. When no
0037: * descriptor is found for a given variable, the template logic uses its default
0038: * variable property initialized using the default {@link Variable} constructor.
0039: *
0040: * <table>
0041: * <tr>
0042: * <th>Model property</th>
0043: * <th>Variable name</th>
0044: * <th>Content type</th>
0045: * </tr>
0046: * <tr>
0047: * <td>request.confidential</td>
0048: * <td>c</td>
0049: * <td>boolean (true|false)</td>
0050: * </tr>
0051: * <tr>
0052: * <td>request.clientInfo.address</td>
0053: * <td>cia</td>
0054: * <td>String</td>
0055: * </tr>
0056: * <tr>
0057: * <td>request.clientInfo.agent</td>
0058: * <td>cig</td>
0059: * <td>String</td>
0060: * </tr>
0061: * <tr>
0062: * <td>request.challengeResponse.identifier</td>
0063: * <td>cri</td>
0064: * <td>String</td>
0065: * </tr>
0066: * <tr>
0067: * <td>request.challengeResponse.scheme</td>
0068: * <td>crs</td>
0069: * <td>String</td>
0070: * </tr>
0071: * <tr>
0072: * <td>request.entity.characterSet</td>
0073: * <td>ecs</td>
0074: * <td>String</td>
0075: * </tr>
0076: * <tr>
0077: * <td>response.entity.characterSet</td>
0078: * <td>ECS</td>
0079: * <td>String</td>
0080: * </tr>
0081: * <tr>
0082: * <td>request.entity.encoding</td>
0083: * <td>ee</td>
0084: * <td>String</td>
0085: * </tr>
0086: * <tr>
0087: * <td>response.entity.encoding</td>
0088: * <td>EE</td>
0089: * <td>String</td>
0090: * </tr>
0091: * <tr>
0092: * <td>request.entity.expirationDate</td>
0093: * <td>eed</td>
0094: * <td>Date (HTTP format)</td>
0095: * </tr>
0096: * <tr>
0097: * <td>response.entity.expirationDate</td>
0098: * <td>EED</td>
0099: * <td>Date (HTTP format)</td>
0100: * </tr>
0101: * <tr>
0102: * <td>request.entity.language</td>
0103: * <td>el</td>
0104: * <td>String</td>
0105: * </tr>
0106: * <tr>
0107: * <td>response.entity.language</td>
0108: * <td>EL</td>
0109: * <td>String</td>
0110: * </tr>
0111: * <tr>
0112: * <td>request.entity.modificationDate</td>
0113: * <td>emd</td>
0114: * <td>Date (HTTP format)</td>
0115: * </tr>
0116: * <tr>
0117: * <td>response.entity.modificationDate</td>
0118: * <td>EMD</td>
0119: * <td>Date (HTTP format)</td>
0120: * </tr>
0121: * <tr>
0122: * <td>request.entity.mediaType</td>
0123: * <td>emt</td>
0124: * <td>String</td>
0125: * </tr>
0126: * <tr>
0127: * <td>response.entity.mediaType</td>
0128: * <td>EMT</td>
0129: * <td>String</td>
0130: * </tr>
0131: * <tr>
0132: * <td>request.entity.size</td>
0133: * <td>es</td>
0134: * <td>Integer</td>
0135: * </tr>
0136: * <tr>
0137: * <td>response.entity.size</td>
0138: * <td>ES</td>
0139: * <td>Integer</td>
0140: * </tr>
0141: * <tr>
0142: * <td>request.entity.tag</td>
0143: * <td>et</td>
0144: * <td>String</td>
0145: * </tr>
0146: * <tr>
0147: * <td>response.entity.tag</td>
0148: * <td>ET</td>
0149: * <td>String</td>
0150: * </tr>
0151: * <tr>
0152: * <td>request.referrerRef</td>
0153: * <td>f*</td>
0154: * <td>Reference (see table below variable name sub-parts)</td>
0155: * </tr>
0156: * <tr>
0157: * <td>request.hostRef</td>
0158: * <td>h*</td>
0159: * <td>Reference (see table below variable name sub-parts)</td>
0160: * </tr>
0161: * <tr>
0162: * <td>request.method</td>
0163: * <td>m</td>
0164: * <td>String</td>
0165: * </tr>
0166: * <tr>
0167: * <td>request.rootRef</td>
0168: * <td>o*</td>
0169: * <td>Reference (see table below variable name sub-parts)</td>
0170: * </tr>
0171: * <tr>
0172: * <td>request.protocol</td>
0173: * <td>p</td>
0174: * <td>String</td>
0175: * </tr>
0176: * <tr>
0177: * <td>request.resourceRef</td>
0178: * <td>r*</td>
0179: * <td>Reference (see table below variable name sub-parts)</td>
0180: * </tr>
0181: * <tr>
0182: * <td>response.redirectRef</td>
0183: * <td>R*</td>
0184: * <td>Reference (see table below variable name sub-parts)</td>
0185: * </tr>
0186: * <tr>
0187: * <td>response.status</td>
0188: * <td>S</td>
0189: * <td>Integer</td>
0190: * </tr>
0191: * <tr>
0192: * <td>response.serverInfo.address</td>
0193: * <td>SIA</td>
0194: * <td>String</td>
0195: * </tr>
0196: * <tr>
0197: * <td>response.serverInfo.agent</td>
0198: * <td>SIG</td>
0199: * <td>String</td>
0200: * </tr>
0201: * <tr>
0202: * <td>response.serverInfo.port</td>
0203: * <td>SIP</td>
0204: * <td>Integer</td>
0205: * </tr>
0206: * </table> <br/>
0207: *
0208: * Below is the list of name sub-parts, for Reference variables, that can
0209: * replace the asterix in the variable names above:<br/><br/>
0210: *
0211: * <table>
0212: * <tr>
0213: * <th>Reference property</th>
0214: * <th>Sub-part name</th>
0215: * <th>Content type</th>
0216: * </tr>
0217: * <tr>
0218: * <td>authority</td>
0219: * <td>a</td>
0220: * <td>String</td>
0221: * </tr>
0222: * <tr>
0223: * <td>baseRef</td>
0224: * <td>b*</td>
0225: * <td>Reference</td>
0226: * </tr>
0227: * <tr>
0228: * <td>relativePart</td>
0229: * <td>e</td>
0230: * <td>String</td>
0231: * </tr>
0232: * <tr>
0233: * <td>fragment</td>
0234: * <td>f</td>
0235: * <td>String</td>
0236: * </tr>
0237: * <tr>
0238: * <td>hostIdentifier</td>
0239: * <td>h</td>
0240: * <td>String</td>
0241: * </tr>
0242: * <tr>
0243: * <td>identifier</td>
0244: * <td>i</td>
0245: * <td>String</td>
0246: * </tr>
0247: * <tr>
0248: * <td>path</td>
0249: * <td>p</td>
0250: * <td>String</td>
0251: * </tr>
0252: * <tr>
0253: * <td>query</td>
0254: * <td>q</td>
0255: * <td>String</td>
0256: * </tr>
0257: * <tr>
0258: * <td>remainingPart</td>
0259: * <td>r</td>
0260: * <td>String</td>
0261: * </tr>
0262: * </table>
0263: *
0264: * @see <a href="http://bitworking.org/projects/URI-Templates/">URI Template
0265: * specification</a>
0266: * @author Jerome Louvel (contact@noelios.com)
0267: */
0268: public class Template {
0269: public static final int MODE_STARTS_WITH = 1;
0270:
0271: public static final int MODE_EQUALS = 2;
0272:
0273: /**
0274: * Indicates if the given character is alphabetical (a-z or A-Z).
0275: *
0276: * @param character
0277: * The character to test.
0278: * @return True if the given character is alphabetical (a-z or A-Z).
0279: */
0280: private static boolean isAlpha(int character) {
0281: return isUpperCase(character) || isLowerCase(character);
0282: }
0283:
0284: /**
0285: * Indicates if the given character is a digit (0-9).
0286: *
0287: * @param character
0288: * The character to test.
0289: * @return True if the given character is a digit (0-9).
0290: */
0291: private static boolean isDigit(int character) {
0292: return (character >= '0') && (character <= '9');
0293: }
0294:
0295: /**
0296: * Indicates if the given character is lower case (a-z).
0297: *
0298: * @param character
0299: * The character to test.
0300: * @return True if the given character is lower case (a-z).
0301: */
0302: private static boolean isLowerCase(int character) {
0303: return (character >= 'a') && (character <= 'z');
0304: }
0305:
0306: /**
0307: * Indicates if the given character is an unreserved URI character.
0308: *
0309: * @param character
0310: * The character to test.
0311: * @return True if the given character is an unreserved URI character.
0312: */
0313: private static boolean isUnreserved(int character) {
0314: return isAlpha(character) || isDigit(character)
0315: || (character == '-') || (character == '.')
0316: || (character == '_') || (character == '~');
0317: }
0318:
0319: /**
0320: * Indicates if the given character is upper case (A-Z).
0321: *
0322: * @param character
0323: * The character to test.
0324: * @return True if the given character is upper case (A-Z).
0325: */
0326: private static boolean isUpperCase(int character) {
0327: return (character >= 'A') && (character <= 'Z');
0328: }
0329:
0330: /** The pattern to use for formatting or parsing. */
0331: private volatile String pattern;
0332:
0333: /** The default variable to use when no matching variable descriptor exists. */
0334: private volatile Variable defaultVariable;
0335:
0336: /** The logger to use. */
0337: private volatile Logger logger;
0338:
0339: /** The matching mode to use when parsing a formatted reference. */
0340: private volatile int matchingMode;
0341:
0342: /** The map of variables associated to the route's template. */
0343: private final Map<String, Variable> variables;
0344:
0345: /** The internal Regex pattern. */
0346: private volatile Pattern regexPattern;
0347:
0348: /** The sequence of Regex variable names as found in the pattern string. */
0349: private final List<String> regexVariables;
0350:
0351: /**
0352: * Default constructor. Each variable matches any sequence of characters by
0353: * default. When parsing, the template will attempt to match the whole
0354: * template. When formatting, the variable are replaced by an empty string
0355: * if they don't exist in the model.
0356: *
0357: * @param logger
0358: * The logger to use.
0359: * @param pattern
0360: * The pattern to use for formatting or parsing.
0361: */
0362: public Template(Logger logger, String pattern) {
0363: this (logger, pattern, MODE_EQUALS, Variable.TYPE_ALL, "", true,
0364: false);
0365: }
0366:
0367: /**
0368: * Constructor.
0369: *
0370: * @param logger
0371: * The logger to use.
0372: * @param pattern
0373: * The pattern to use for formatting or parsing.
0374: * @param matchingMode
0375: * The matching mode to use when parsing a formatted
0376: * reference.
0377: */
0378: public Template(Logger logger, String pattern, int matchingMode) {
0379: this (logger, pattern, matchingMode, Variable.TYPE_ALL, "",
0380: true, false);
0381: }
0382:
0383: /**
0384: * Constructor.
0385: *
0386: * @param logger
0387: * The logger to use.
0388: * @param pattern
0389: * The pattern to use for formatting or parsing.
0390: * @param matchingMode
0391: * The matching mode to use when parsing a formatted
0392: * reference.
0393: * @param defaultType
0394: * The default type of variables with no descriptor.
0395: * @param defaultDefaultValue
0396: * The default value for null variables with no descriptor.
0397: * @param defaultRequired
0398: * The default required flag for variables with no
0399: * descriptor.
0400: * @param defaultFixed
0401: * The default fixed value for variables with no descriptor.
0402: */
0403: public Template(Logger logger, String pattern, int matchingMode,
0404: int defaultType, String defaultDefaultValue,
0405: boolean defaultRequired, boolean defaultFixed) {
0406: this .logger = logger;
0407: this .pattern = pattern;
0408: this .defaultVariable = new Variable(defaultType,
0409: defaultDefaultValue, defaultRequired, defaultFixed);
0410: this .matchingMode = matchingMode;
0411: this .variables = new ConcurrentHashMap<String, Variable>();
0412: this .regexPattern = null;
0413: this .regexVariables = new CopyOnWriteArrayList<String>();
0414: }
0415:
0416: /**
0417: * Creates a formatted string based on the given request.
0418: *
0419: * @param request
0420: * The request to use as a model.
0421: * @param response
0422: * The response to use as a model.
0423: * @return The formatted string.
0424: */
0425: public String format(Request request, Response response) {
0426: return format(new CallVariableResolver(request, response));
0427: }
0428:
0429: /**
0430: * Creates a formatted string based on the given request.
0431: *
0432: * @param variables
0433: * The variables to use when formatting.
0434: * @return The formatted string.
0435: */
0436: public String format(Map<String, Object> variables) {
0437: return format(new MapVariableResolver(variables));
0438: }
0439:
0440: /**
0441: * Creates a formatted string based on the given variable resolver.
0442: *
0443: * @param resolver
0444: * The variable resolver to use.
0445: * @return The formatted string.
0446: */
0447: private String format(VariableResolver resolver) {
0448: StringBuilder result = new StringBuilder();
0449: StringBuilder varBuffer = null;
0450: char next;
0451: boolean inVariable = false;
0452: for (int i = 0; i < getPattern().length(); i++) {
0453: next = getPattern().charAt(i);
0454:
0455: if (inVariable) {
0456: if (isUnreserved(next)) {
0457: // Append to the variable name
0458: varBuffer.append(next);
0459: } else if (next == '}') {
0460: // End of variable detected
0461: if (varBuffer.length() == 0) {
0462: getLogger().warning(
0463: "Empty pattern variables are not allowed : "
0464: + this .regexPattern);
0465: } else {
0466: String varName = varBuffer.toString();
0467: result.append(resolver.resolve(varName));
0468:
0469: // Reset the variable name buffer
0470: varBuffer = new StringBuilder();
0471: }
0472: inVariable = false;
0473: } else {
0474: getLogger().warning(
0475: "An invalid character was detected inside a pattern variable : "
0476: + this .regexPattern);
0477: }
0478: } else {
0479: if (next == '{') {
0480: inVariable = true;
0481: varBuffer = new StringBuilder();
0482: } else if (next == '}') {
0483: getLogger().warning(
0484: "An invalid character was detected inside a pattern variable : "
0485: + this .regexPattern);
0486: } else {
0487: result.append(next);
0488: }
0489: }
0490: }
0491:
0492: return result.toString();
0493: }
0494:
0495: /**
0496: * Returns the default variable.
0497: *
0498: * @return The default variable.
0499: */
0500: public Variable getDefaultVariable() {
0501: return this .defaultVariable;
0502: }
0503:
0504: /**
0505: * Returns the logger to use.
0506: *
0507: * @return The logger to use.
0508: */
0509: public Logger getLogger() {
0510: return this .logger;
0511: }
0512:
0513: /**
0514: * Returns the matching mode to use when parsing a formatted reference.
0515: *
0516: * @return The matching mode to use when parsing a formatted reference.
0517: */
0518: public int getMatchingMode() {
0519: return this .matchingMode;
0520: }
0521:
0522: /**
0523: * Returns the pattern to use for formatting or parsing.
0524: *
0525: * @return The pattern to use for formatting or parsing.
0526: */
0527: public String getPattern() {
0528: return this .pattern;
0529: }
0530:
0531: /**
0532: * Compiles the URI pattern into a Regex pattern.
0533: *
0534: * @param uriPattern
0535: * The URI pattern.
0536: * @return The Regex pattern.
0537: */
0538: private synchronized Pattern getRegexPattern() {
0539: if (this .regexPattern == null) {
0540: StringBuilder patternBuffer = new StringBuilder();
0541: StringBuilder varBuffer = null;
0542: char next;
0543: boolean inVariable = false;
0544: for (int i = 0; i < getPattern().length(); i++) {
0545: next = getPattern().charAt(i);
0546:
0547: if (inVariable) {
0548: if (isUnreserved(next)) {
0549: // Append to the variable name
0550: varBuffer.append(next);
0551: } else if (next == '}') {
0552: // End of variable detected
0553: if (varBuffer.length() == 0) {
0554: getLogger().warning(
0555: "Empty pattern variables are not allowed : "
0556: + this .regexPattern);
0557: } else {
0558: String varName = varBuffer.toString();
0559: int varIndex = getRegexVariables().indexOf(
0560: varName);
0561:
0562: if (varIndex != -1) {
0563: // The variable is used several times in the
0564: // pattern, ensure that this constraint is
0565: // enforced when parsing.
0566: patternBuffer.append("\\"
0567: + (varIndex + 1));
0568: } else {
0569: // New variable detected. Insert a capturing
0570: // group.
0571: getRegexVariables().add(varName);
0572: Variable var = getVariables().get(
0573: varName);
0574: if (var == null)
0575: var = getDefaultVariable();
0576: patternBuffer
0577: .append(getVariableRegex(var));
0578: }
0579:
0580: // Reset the variable name buffer
0581: varBuffer = new StringBuilder();
0582: }
0583: inVariable = false;
0584:
0585: } else {
0586: getLogger().warning(
0587: "An invalid character was detected inside a pattern variable : "
0588: + this .regexPattern);
0589: }
0590: } else {
0591: if (next == '{') {
0592: inVariable = true;
0593: varBuffer = new StringBuilder();
0594: } else if (next == '}') {
0595: getLogger().warning(
0596: "An invalid character was detected inside a pattern variable : "
0597: + this .regexPattern);
0598: } else {
0599: patternBuffer.append(quote(next));
0600: }
0601: }
0602: }
0603:
0604: this .regexPattern = Pattern.compile(patternBuffer
0605: .toString());
0606: }
0607:
0608: return this .regexPattern;
0609: }
0610:
0611: /**
0612: * Quotes special characters that could be taken for special Regex
0613: * characters.
0614: *
0615: * @param character
0616: * The character to quote if necessary.
0617: * @return The quoted character.
0618: */
0619: private static String quote(char character) {
0620: switch (character) {
0621: case '[':
0622: return "\\[";
0623: case ']':
0624: return "\\]";
0625: case '.':
0626: return "\\.";
0627: case '\\':
0628: return "\\\\";
0629: case '$':
0630: return "\\$";
0631: case '^':
0632: return "\\^";
0633: case '?':
0634: return "\\?";
0635: case '*':
0636: return "\\*";
0637: case '|':
0638: return "\\|";
0639: case '(':
0640: return "\\(";
0641: case ')':
0642: return "\\)";
0643: case ':':
0644: return "\\:";
0645: case '-':
0646: return "\\-";
0647: case '!':
0648: return "\\!";
0649: case '<':
0650: return "\\<";
0651: case '>':
0652: return "\\>";
0653: default:
0654: return Character.toString(character);
0655: }
0656: }
0657:
0658: /**
0659: * Returns the sequence of Regex variable names as found in the pattern
0660: * string.
0661: *
0662: * @return The sequence of Regex variable names as found in the pattern
0663: * string.
0664: */
0665: private List<String> getRegexVariables() {
0666: return this .regexVariables;
0667: }
0668:
0669: /**
0670: * Returns the content corresponding to a reference property.
0671: *
0672: * @param partName
0673: * The variable sub-part name.
0674: * @param reference
0675: * The reference to use as a model.
0676: * @return The content corresponding to a reference property.
0677: */
0678: private static String getReferenceContent(String partName,
0679: Reference reference) {
0680: String result = null;
0681:
0682: if (reference != null) {
0683: if (partName.equals("a")) {
0684: result = reference.getAuthority();
0685: } else if (partName.startsWith("b")) {
0686: result = getReferenceContent(partName.substring(1),
0687: reference.getBaseRef());
0688: } else if (partName.equals("e")) {
0689: result = reference.getRelativePart();
0690: } else if (partName.equals("f")) {
0691: result = reference.getFragment();
0692: } else if (partName.equals("h")) {
0693: result = reference.getHostIdentifier();
0694: } else if (partName.equals("i")) {
0695: result = reference.getIdentifier();
0696: } else if (partName.equals("p")) {
0697: result = reference.getPath();
0698: } else if (partName.equals("q")) {
0699: result = reference.getQuery();
0700: } else if (partName.equals("r")) {
0701: result = reference.getRemainingPart();
0702: }
0703: }
0704:
0705: return result;
0706: }
0707:
0708: /**
0709: * Returns the Regex pattern string corresponding to a variable.
0710: *
0711: * @param variable
0712: * The variable.
0713: * @return The Regex pattern string corresponding to a variable.
0714: */
0715: private static String getVariableRegex(Variable variable) {
0716: String result = null;
0717:
0718: if (variable.isFixed()) {
0719: result = Pattern.quote(variable.getDefaultValue());
0720: } else {
0721: // Expressions to create character classes
0722: final String ALL = ".";
0723: final String ALPHA = "a-zA-Z";
0724: final String DIGIT = "0-9";
0725: final String ALPHA_DIGIT = ALPHA + DIGIT;
0726: final String HEXA = DIGIT + "ABCDEFabcdef";
0727: final String URI_UNRESERVED = ALPHA_DIGIT + "\\-\\.\\_\\~";
0728: final String URI_GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@";
0729: final String URI_SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=";
0730: final String URI_RESERVED = URI_GEN_DELIMS + URI_SUB_DELIMS;
0731: final String URI_ALL = URI_RESERVED + URI_UNRESERVED;
0732: final String WORD = "\\w";
0733:
0734: // Expressions to create non-capturing groups
0735: final String PCT_ENCODED = "\\%[" + HEXA + "][" + HEXA
0736: + "]";
0737: // final String PCHAR = "[" + URI_UNRESERVED + "]|(?:" + PCT_ENCODED
0738: // + ")|[" + URI_SUB_DELIMS + "]|\\:|\\@";
0739: final String PCHAR = "[" + URI_UNRESERVED + URI_SUB_DELIMS
0740: + "\\:\\@]|(?:" + PCT_ENCODED + ")";
0741: final String QUERY = PCHAR + "|\\/|\\?";
0742: final String FRAGMENT = QUERY;
0743:
0744: StringBuilder coreRegex = new StringBuilder();
0745:
0746: switch (variable.getType()) {
0747: case Variable.TYPE_ALL:
0748: appendClass(coreRegex, ALL, variable.isRequired());
0749: break;
0750: case Variable.TYPE_ALPHA:
0751: appendClass(coreRegex, ALPHA, variable.isRequired());
0752: break;
0753: case Variable.TYPE_DIGIT:
0754: appendClass(coreRegex, DIGIT, variable.isRequired());
0755: break;
0756: case Variable.TYPE_ALPHA_DIGIT:
0757: appendClass(coreRegex, ALPHA_DIGIT, variable
0758: .isRequired());
0759: break;
0760: case Variable.TYPE_URI_ALL:
0761: appendClass(coreRegex, URI_ALL, variable.isRequired());
0762: break;
0763: case Variable.TYPE_URI_UNRESERVED:
0764: appendClass(coreRegex, URI_UNRESERVED, variable
0765: .isRequired());
0766: break;
0767: case Variable.TYPE_WORD:
0768: appendClass(coreRegex, WORD, variable.isRequired());
0769: break;
0770:
0771: case Variable.TYPE_URI_FRAGMENT:
0772: appendGroup(coreRegex, FRAGMENT, variable.isRequired());
0773: break;
0774: case Variable.TYPE_URI_QUERY:
0775: appendGroup(coreRegex, QUERY, variable.isRequired());
0776: break;
0777: case Variable.TYPE_URI_SEGMENT:
0778: appendGroup(coreRegex, PCHAR, variable.isRequired());
0779: break;
0780: }
0781:
0782: result = coreRegex.toString();
0783: }
0784:
0785: return result;
0786: }
0787:
0788: /**
0789: * Appends to a pattern a repeating group of a given content based on a
0790: * class of characters.
0791: *
0792: * @param pattern
0793: * The pattern to append to.
0794: * @param content
0795: * The content of the group.
0796: * @param required
0797: * Indicates if the group is required.
0798: */
0799: private static void appendClass(StringBuilder pattern,
0800: String content, boolean required) {
0801:
0802: pattern.append("(");
0803:
0804: if (content.equals(".")) {
0805: // Special case for the TYPE_ALL variable type because the
0806: // dot looses its meaning inside a character class
0807: pattern.append(content);
0808: } else {
0809: pattern.append("[").append(content).append(']');
0810:
0811: }
0812:
0813: if (required) {
0814: pattern.append("+");
0815: } else {
0816: pattern.append("*");
0817: }
0818:
0819: pattern.append(")");
0820: }
0821:
0822: /**
0823: * Appends to a pattern a repeating group of a given content based on a
0824: * non-capturing group.
0825: *
0826: * @param pattern
0827: * The pattern to append to.
0828: * @param content
0829: * The content of the group.
0830: * @param required
0831: * Indicates if the group is required.
0832: */
0833: private static void appendGroup(StringBuilder pattern,
0834: String content, boolean required) {
0835: pattern.append("((?:").append(content).append(')');
0836:
0837: if (required) {
0838: pattern.append("+");
0839: } else {
0840: pattern.append("*");
0841: }
0842:
0843: pattern.append(")");
0844: }
0845:
0846: /**
0847: * Returns the modifiable map of variables.
0848: *
0849: * @return The modifiable map of variables.
0850: */
0851: public Map<String, Variable> getVariables() {
0852: return this .variables;
0853: }
0854:
0855: /**
0856: * Indicates if the current pattern matches the given formatted string.
0857: *
0858: * @param formattedString
0859: * The formatted string to match.
0860: * @return The number of matched characters or -1 if the match failed.
0861: */
0862: public int match(String formattedString) {
0863: int result = -1;
0864:
0865: try {
0866: if (formattedString != null) {
0867: Matcher matcher = getRegexPattern().matcher(
0868: formattedString);
0869:
0870: if ((getMatchingMode() == MODE_EQUALS)
0871: && matcher.matches()) {
0872: result = matcher.end();
0873: } else if ((getMatchingMode() == MODE_STARTS_WITH)
0874: && matcher.lookingAt()) {
0875: result = matcher.end();
0876: }
0877: }
0878: } catch (StackOverflowError soe) {
0879: getLogger().warning(
0880: "StackOverflowError exception encountered while matching this string : "
0881: + formattedString);
0882: }
0883:
0884: return result;
0885: }
0886:
0887: /**
0888: * Attempts to parse a formatted reference. If the parsing succeeds, the
0889: * given request's attributes are updated.<br>
0890: * Note that the values parsed are directly extracted from the formatted
0891: * reference and are therefore not percent-decoded.
0892: *
0893: * @see Reference#decode(String)
0894: *
0895: * @param formattedString
0896: * The string to parse.
0897: * @param request
0898: * The request to update.
0899: * @return The number of matched characters or -1 if no character matched.
0900: */
0901: public int parse(String formattedString, Request request) {
0902: return parse(formattedString, request.getAttributes());
0903: }
0904:
0905: /**
0906: * Attempts to parse a formatted reference. If the parsing succeeds, the
0907: * given request's attributes are updated.<br>
0908: * Note that the values parsed are directly extracted from the formatted
0909: * reference and are therefore not percent-decoded.
0910: *
0911: * @see Reference#decode(String)
0912: *
0913: * @param formattedString
0914: * The string to parse.
0915: * @param variables
0916: * The map of variables to update.
0917: * @return The number of matched characters or -1 if no character matched.
0918: */
0919: public int parse(String formattedString,
0920: Map<String, Object> variables) {
0921: int result = -1;
0922: try {
0923:
0924: Matcher matcher = getRegexPattern()
0925: .matcher(formattedString);
0926: boolean matched = ((getMatchingMode() == MODE_EQUALS) && matcher
0927: .matches())
0928: || ((getMatchingMode() == MODE_STARTS_WITH) && matcher
0929: .lookingAt());
0930:
0931: if (matched) {
0932: // Update the number of matched characters
0933: result = matcher.end();
0934:
0935: // Update the attributes with the variables value
0936: String attributeName = null;
0937: String attributeValue = null;
0938: for (int i = 0; i < getRegexVariables().size(); i++) {
0939: attributeName = getRegexVariables().get(i);
0940: attributeValue = matcher.group(i + 1);
0941: variables.put(attributeName, attributeValue);
0942: }
0943: }
0944: } catch (StackOverflowError soe) {
0945: getLogger().warning(
0946: "StackOverflowError exception encountered while matching this string : "
0947: + formattedString);
0948: }
0949:
0950: return result;
0951: }
0952:
0953: /**
0954: * Sets the pattern to use for formatting or parsing.
0955: *
0956: * @param pattern
0957: * The pattern to use for formatting or parsing.
0958: */
0959: public void setPattern(String pattern) {
0960: this .pattern = pattern;
0961: }
0962:
0963: /**
0964: * Sets the matching mode to use when parsing a formatted reference.
0965: *
0966: * @param matchingMode
0967: * The matching mode to use when parsing a formatted
0968: * reference.
0969: */
0970: public void setMatchingMode(int matchingMode) {
0971: this .matchingMode = matchingMode;
0972: }
0973:
0974: /**
0975: * Resolves variable values.
0976: *
0977: * @author Jerome Louvel (contact@noelios.com)
0978: */
0979: private abstract class VariableResolver {
0980: public abstract String resolve(String variableName);
0981: }
0982:
0983: /**
0984: * Resolves variable values based on a request and a response.
0985: *
0986: * @author Jerome Louvel (contact@noelios.com)
0987: */
0988: private class CallVariableResolver extends VariableResolver {
0989: /** The request to use as a model. */
0990: private Request request;
0991:
0992: /** The response to use as a model. */
0993: private Response response;
0994:
0995: /**
0996: * Constructor.
0997: *
0998: * @param request
0999: * The request to use as a model.
1000: * @param response
1001: * The response to use as a model.
1002: */
1003: public CallVariableResolver(Request request, Response response) {
1004: this .request = request;
1005: this .response = response;
1006: }
1007:
1008: @Override
1009: public String resolve(String variableName) {
1010: String result = null;
1011:
1012: Variable var = getVariables().get(variableName);
1013: if (var == null)
1014: var = getDefaultVariable();
1015:
1016: // Check for a matching request attribute
1017: if (request != null) {
1018: Object variable = request.getAttributes().get(
1019: variableName);
1020: if (variable != null) {
1021: result = variable.toString();
1022: }
1023: }
1024:
1025: // Check for a matching response attribute
1026: if ((result == null)
1027: && (response != null)
1028: && response.getAttributes().containsKey(
1029: variableName)) {
1030: result = response.getAttributes().get(variableName)
1031: .toString();
1032: }
1033:
1034: // Check for a matching request or response property
1035: if (result == null) {
1036: if (request != null) {
1037: if (variableName.equals("c")) {
1038: result = Boolean.toString(request
1039: .isConfidential());
1040: } else if (variableName.equals("cia")) {
1041: result = request.getClientInfo().getAddress();
1042: } else if (variableName.equals("cig")) {
1043: result = request.getClientInfo().getAgent();
1044: } else if (variableName.equals("cri")) {
1045: result = request.getChallengeResponse()
1046: .getIdentifier();
1047: } else if (variableName.equals("crs")) {
1048: if (request.getChallengeResponse().getScheme() != null) {
1049: result = request.getChallengeResponse()
1050: .getScheme().getTechnicalName();
1051: }
1052: } else if (variableName.equals("ecs")) {
1053: if ((request.getEntity() != null)
1054: && (request.getEntity()
1055: .getCharacterSet() != null)) {
1056: result = request.getEntity()
1057: .getCharacterSet().getName();
1058: }
1059: } else if (variableName.equals("ee")) {
1060: if ((request.getEntity() != null)
1061: && (!request.getEntity().getEncodings()
1062: .isEmpty())) {
1063: StringBuilder value = new StringBuilder();
1064: for (int i = 0; i < request.getEntity()
1065: .getEncodings().size(); i++) {
1066: if (i > 0)
1067: value.append(", ");
1068: value.append(request.getEntity()
1069: .getEncodings().get(i)
1070: .getName());
1071: }
1072: result = value.toString();
1073: }
1074: } else if (variableName.equals("eed")) {
1075: if ((request.getEntity() != null)
1076: && (request.getEntity()
1077: .getExpirationDate() != null)) {
1078: result = DateUtils.format(request
1079: .getEntity().getExpirationDate(),
1080: DateUtils.FORMAT_RFC_1123.get(0));
1081: }
1082: } else if (variableName.equals("el")) {
1083: if ((request.getEntity() != null)
1084: && (!request.getEntity().getLanguages()
1085: .isEmpty())) {
1086: StringBuilder value = new StringBuilder();
1087: for (int i = 0; i < request.getEntity()
1088: .getLanguages().size(); i++) {
1089: if (i > 0)
1090: value.append(", ");
1091: value.append(request.getEntity()
1092: .getLanguages().get(i)
1093: .getName());
1094: }
1095: result = value.toString();
1096: }
1097: } else if (variableName.equals("emd")) {
1098: if ((request.getEntity() != null)
1099: && (request.getEntity()
1100: .getModificationDate() != null)) {
1101: result = DateUtils.format(request
1102: .getEntity().getModificationDate(),
1103: DateUtils.FORMAT_RFC_1123.get(0));
1104: }
1105: } else if (variableName.equals("emt")) {
1106: if ((request.getEntity() != null)
1107: && (request.getEntity().getMediaType() != null)) {
1108: result = request.getEntity().getMediaType()
1109: .getName();
1110: }
1111: } else if (variableName.equals("es")) {
1112: if ((request.getEntity() != null)
1113: && (request.getEntity().getSize() != -1)) {
1114: result = Long.toString(request.getEntity()
1115: .getSize());
1116: }
1117: } else if (variableName.equals("et")) {
1118: if ((request.getEntity() != null)
1119: && (request.getEntity().getTag() != null)) {
1120: result = request.getEntity().getTag()
1121: .getName();
1122: }
1123: } else if (variableName.startsWith("f")) {
1124: result = getReferenceContent(variableName
1125: .substring(1), request.getReferrerRef());
1126: } else if (variableName.startsWith("h")) {
1127: result = getReferenceContent(variableName
1128: .substring(1), request.getHostRef());
1129: } else if (variableName.equals("m")) {
1130: if (request.getMethod() != null) {
1131: result = request.getMethod().getName();
1132: }
1133: } else if (variableName.startsWith("o")) {
1134: result = getReferenceContent(variableName
1135: .substring(1), request.getRootRef());
1136: } else if (variableName.equals("p")) {
1137: if (request.getProtocol() != null) {
1138: result = request.getProtocol().getName();
1139: }
1140: } else if (variableName.startsWith("r")) {
1141: result = getReferenceContent(variableName
1142: .substring(1), request.getResourceRef());
1143: }
1144: }
1145:
1146: if ((result == null) && (response != null)) {
1147: if (variableName.equals("ECS")) {
1148: if ((response.getEntity() != null)
1149: && (response.getEntity()
1150: .getCharacterSet() != null)) {
1151: result = response.getEntity()
1152: .getCharacterSet().getName();
1153: }
1154: } else if (variableName.equals("EE")) {
1155: if ((response.getEntity() != null)
1156: && (!response.getEntity()
1157: .getEncodings().isEmpty())) {
1158: StringBuilder value = new StringBuilder();
1159: for (int i = 0; i < response.getEntity()
1160: .getEncodings().size(); i++) {
1161: if (i > 0)
1162: value.append(", ");
1163: value.append(response.getEntity()
1164: .getEncodings().get(i)
1165: .getName());
1166: }
1167: result = value.toString();
1168: }
1169: } else if (variableName.equals("EED")) {
1170: if ((response.getEntity() != null)
1171: && (response.getEntity()
1172: .getExpirationDate() != null)) {
1173: result = DateUtils.format(response
1174: .getEntity().getExpirationDate(),
1175: DateUtils.FORMAT_RFC_1123.get(0));
1176: }
1177: } else if (variableName.equals("EL")) {
1178: if ((response.getEntity() != null)
1179: && (!response.getEntity()
1180: .getLanguages().isEmpty())) {
1181: StringBuilder value = new StringBuilder();
1182: for (int i = 0; i < response.getEntity()
1183: .getLanguages().size(); i++) {
1184: if (i > 0)
1185: value.append(", ");
1186: value.append(response.getEntity()
1187: .getLanguages().get(i)
1188: .getName());
1189: }
1190: result = value.toString();
1191: }
1192: } else if (variableName.equals("EMD")) {
1193: if ((response.getEntity() != null)
1194: && (response.getEntity()
1195: .getModificationDate() != null)) {
1196: result = DateUtils.format(response
1197: .getEntity().getModificationDate(),
1198: DateUtils.FORMAT_RFC_1123.get(0));
1199: }
1200: } else if (variableName.equals("EMT")) {
1201: if ((response.getEntity() != null)
1202: && (response.getEntity().getMediaType() != null)) {
1203: result = response.getEntity()
1204: .getMediaType().getName();
1205: }
1206: } else if (variableName.equals("ES")) {
1207: if ((response.getEntity() != null)
1208: && (response.getEntity().getSize() != -1)) {
1209: result = Long.toString(response.getEntity()
1210: .getSize());
1211: }
1212: } else if (variableName.equals("ET")) {
1213: if ((response.getEntity() != null)
1214: && (response.getEntity().getTag() != null)) {
1215: result = response.getEntity().getTag()
1216: .getName();
1217: }
1218: } else if (variableName.startsWith("R")) {
1219: result = getReferenceContent(variableName
1220: .substring(1), response
1221: .getRedirectRef());
1222: } else if (variableName.equals("S")) {
1223: if (response.getStatus() != null) {
1224: result = Integer.toString(response
1225: .getStatus().getCode());
1226: }
1227: } else if (variableName.equals("SIA")) {
1228: result = response.getServerInfo().getAddress();
1229: } else if (variableName.equals("SIG")) {
1230: result = response.getServerInfo().getAgent();
1231: } else if (variableName.equals("SIP")) {
1232: if (response.getServerInfo().getPort() != -1) {
1233: result = Integer.toString(response
1234: .getServerInfo().getPort());
1235: }
1236: }
1237: }
1238: }
1239:
1240: if (result == null) {
1241: // Use the default value instead
1242: result = var.getDefaultValue();
1243: }
1244:
1245: return result;
1246: }
1247: }
1248:
1249: /**
1250: * Resolves variable values based on a map.
1251: *
1252: * @author Jerome Louvel (contact@noelios.com)
1253: */
1254: private class MapVariableResolver extends VariableResolver {
1255: /** The variables to use when formatting. */
1256: private Map<String, Object> map;
1257:
1258: /**
1259: * Constructor.
1260: *
1261: * @param map
1262: * The variables to use when formatting.
1263: */
1264: public MapVariableResolver(Map<String, Object> map) {
1265: this .map = map;
1266: }
1267:
1268: @Override
1269: public String resolve(String variableName) {
1270: Object value = this.map.get(variableName);
1271: return (value == null) ? null : value.toString();
1272: }
1273: }
1274:
1275: }
|