001: /*
002: * <copyright>
003: *
004: * Copyright 2000-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026: package org.cougaar.lib.web.arch.util;
027:
028: import java.io.UnsupportedEncodingException;
029: import java.net.URLDecoder;
030: import java.net.URLEncoder;
031: import java.util.ArrayList;
032: import java.util.Collections;
033: import java.util.List;
034:
035: /**
036: * Parses a URL path into its name, is_node_of, options, and subpath.
037: * <p>
038: * "is_node_of" is a special flag. It's equivalent short-hand is "~". It
039: * takes precidence over all other options.
040: * <p>
041: * For example, "/$(a,b)~foo/bar" is parsed into:<pre>
042: * name: "foo"
043: * is_node_of: true
044: * options: ["a", "b"]
045: * subpath: "/bar"
046: * </pre>
047: * In the above example, if "foo" is local to our node then we'll invoke
048: * our <i>node's</i> "/bar" servlet, otherwise we'll attempt the "a"
049: * redirector then the "b" redirector.
050: * <p>
051: * Additional examples:<ol>
052: * <li>"/$x" == local x, else use default redirectors</li>
053: * <li>"/$()x" == local x, else fail</li>
054: * <li>"/$~x" == local node if x is local, else use default redirectors</li>
055: * <li>"/$(~)x" == local node if x is local, else use default redirectors</li>
056: * <li>"/$()~x" == local node if x is local, else fail</li>
057: * <li>"/$!x" == local x, else use "!" redirector</li>
058: * <li>"/$(!)x" == local x, else use "!" redirector</li>
059: * <li>"/$(!)~x" == local node if x is local, else use "!" redirector</li>
060: * <li>"/$(!,~)x" == local node if x is local, else use "!" redirector</li>
061: * </ol>
062: * <p>
063: * We separate out our {@link #isNodeOf} flag from the {@link #getOptions} to
064: * distinguish between "/$~x" and "/$()~x". If the "~" was in the options list
065: * then we couldn't distinguish between these two cases.
066: */
067: public final class PathParser {
068:
069: private final String encName;
070: private final boolean is_node_of;
071: private final List options;
072: private final String subpath;
073:
074: public PathParser(String path) {
075: int pathLength = (path == null ? 0 : path.length());
076:
077: // look for "/$" prefix
078: if (pathLength < 2 || path.charAt(0) != '/'
079: || path.charAt(1) != '$') {
080: // name not specified
081: encName = null;
082: is_node_of = false;
083: options = null;
084: subpath = path;
085: return;
086: }
087:
088: // parse "/$" + (options+name) + ["/" subpath]
089: int sep = path.indexOf('/', 1);
090: if (sep < 0) {
091: sep = pathLength;
092: }
093: this .subpath = (sep >= pathLength ? "" : path.substring(sep));
094: String enc = path.substring(2, sep);
095:
096: char ch = (enc.length() > 0 ? enc.charAt(0) : 'a');
097: if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
098: || (ch >= '0' && ch <= '9')) {
099: // no options
100: this .encName = enc;
101: this .is_node_of = false;
102: this .options = null;
103: return;
104: }
105:
106: // separate options from name
107: //
108: // Options are either leading control characters ([^a-zA-Z0-9])
109: // or comma-separated strings within a "(..)" block. Whitespace is
110: // ignored.
111: //
112: // Examples:
113: // "foo" -> [] and "foo"
114: // "_foo" -> ["_"] and "foo"
115: // "(_)foo" -> ["_"] and "foo"
116: // "_^foo" -> ["_", "^"] and "foo"
117: // "(alpha,beta)foo" -> ["alpha", "beta"] and "foo"
118: String s = decode(enc);
119: boolean node_of = false;
120: int len = s.length();
121: int i;
122: List l = null;
123: for (i = 0; i < len; i++) {
124: ch = s.charAt(i);
125: if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
126: || (ch >= '0' && ch <= '9')) {
127: // end of options
128: break;
129: }
130: if (ch == ' ' || ch == '\t') {
131: continue;
132: }
133: if (ch != '(') {
134: if (ch == '~') {
135: node_of = true;
136: } else {
137: if (l == null) {
138: l = new ArrayList();
139: }
140: l.add(String.valueOf(ch));
141: }
142: continue;
143: }
144: // find end ')', tokenize
145: i++;
146: int k = s.indexOf(')', i);
147: if (k < 0) {
148: k = len;
149: }
150: if (l == null) {
151: // ensure that l is non-null, to act as an explicit non-default
152: // "empty" options case
153: l = new ArrayList();
154: }
155: while (true) {
156: int j = s.indexOf(',', i);
157: boolean end = (j < 0 || j >= k);
158: if (end) {
159: j = k;
160: }
161: String s2 = s.substring(i, j).trim();
162: if (s2.length() > 0) {
163: if ("~".equals(s2) || "is_node_of".equals(s2)) {
164: node_of = true;
165: } else {
166: l.add(s2);
167: }
168: }
169: if (end) {
170: break;
171: }
172: i = j + 1;
173: }
174: i = k;
175: }
176: this .encName = (i < len ? encode(s.substring(i)) : null);
177: this .is_node_of = node_of;
178: this .options = (l == null ? null : Collections
179: .unmodifiableList(l));
180: }
181:
182: public String getName() {
183: return encName;
184: }
185:
186: public boolean isNodeOf() {
187: return is_node_of;
188: }
189:
190: public List getOptions() {
191: return options;
192: }
193:
194: public String getSubpath() {
195: return subpath;
196: }
197:
198: public String toString() {
199: return "(path" + " name=" + encName + " is_node_of="
200: + is_node_of + " options=" + options + " subpath="
201: + subpath + ")";
202: }
203:
204: private static final String encode(String raw) {
205: try {
206: return URLEncoder.encode(raw, "UTF-8");
207: } catch (UnsupportedEncodingException uee) {
208: throw new RuntimeException("Invalid name: " + raw, uee);
209: }
210: }
211:
212: private static final String decode(String enc) {
213: try {
214: return URLDecoder.decode(enc, "UTF-8");
215: } catch (UnsupportedEncodingException uee) {
216: throw new RuntimeException("Invalid name: " + enc, uee);
217: }
218: }
219: }
|