001: /*
002: * $Id: URIBuilder.java 10489 2008-01-23 17:53:38Z dfeist $
003: * --------------------------------------------------------------------------------------
004: * Copyright (c) MuleSource, Inc. All rights reserved. http://www.mulesource.com
005: *
006: * The software in this package is published under the terms of the CPAL v1.0
007: * license, a copy of which has been included with this distribution in the
008: * LICENSE.txt file.
009: */
010:
011: package org.mule.endpoint;
012:
013: import org.mule.api.endpoint.EndpointException;
014: import org.mule.api.endpoint.EndpointURI;
015: import org.mule.util.ClassUtils;
016:
017: import java.util.HashMap;
018: import java.util.Iterator;
019: import java.util.LinkedList;
020: import java.util.List;
021: import java.util.Map;
022: import java.util.StringTokenizer;
023: import java.util.TreeMap;
024:
025: import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicReference;
026:
027: /**
028: * This has the following logic:
029: * - if an address is specified, it is used verbatim (except for parameters); this is consistent with the generic case
030: * - otherwise, we construct from components, omitting things that aren't specified as much as possible
031: * (use required attributes to guarantee entries)
032: *
033: * In addition, parameters are handled as follows:
034: * - parameters can be given in the uri, the queryMap, or both
035: * - queryMap values override uri values
036: * - the order of parameters in the uri remains the same (even if values change)
037: * - queryMap parameters are appended after uri parameters
038: * (I don't think ordering should matter, but XFire seems to require it)
039: *
040: * TODO - check that we have sufficient control via XML (what about empty strings?)
041: *
042: * Not called EndpointURIBuilder because of {@link org.mule.api.endpoint.EndpointURIBuilder}
043: */
044: public class URIBuilder {
045:
046: private static final String DOTS = ":";
047: private static final String DOTS_SLASHES = DOTS + "//";
048: private static final String QUERY = "?";
049: private static final String AND = "&";
050: private static final String EQUALS = "=";
051:
052: public static final String META = "meta";
053: public static final String PROTOCOL = "protocol";
054: public static final String USER = "user";
055: public static final String PASSWORD = "password";
056: public static final String HOST = "host";
057: public static final String ADDRESS = "address";
058: public static final String PORT = "port";
059: public static final String PATH = "path";
060:
061: public static final String[] ALL_ATTRIBUTES = new String[] { META,
062: PROTOCOL, USER, PASSWORD, HOST, ADDRESS, PORT, PATH };
063: // combinations used in various endpoint parsers to validate required attributes
064: public static final String[] PATH_ATTRIBUTES = new String[] { PATH };
065: public static final String[] HOST_ATTRIBUTES = new String[] { HOST };
066: public static final String[] SOCKET_ATTRIBUTES = new String[] {
067: HOST, PORT };
068: public static final String[] USERHOST_ATTRIBUTES = new String[] {
069: USER, HOST };
070: // this doesn't include address, since that is handled separately (and is exclusive with these)
071: public static final String[] ALL_TRANSPORT_ATTRIBUTES = new String[] {
072: USER, PASSWORD, HOST, PORT, PATH };
073:
074: private String address;
075: private String meta;
076: private String protocol;
077: private String user;
078: private String password;
079: private String host;
080: private Integer port;
081: private String path;
082: private Map queryMap;
083:
084: private AtomicReference cache = new AtomicReference();
085:
086: public URIBuilder() {
087: // default
088: }
089:
090: public URIBuilder(EndpointURI endpointURI) {
091: cache.set(endpointURI);
092: }
093:
094: public URIBuilder(String address) {
095: // separate meta from address, if necessary
096: int dots = address.indexOf(DOTS);
097: int dotsSlashes = address.indexOf(DOTS_SLASHES);
098: if (dots > -1 && dots < dotsSlashes) {
099: this .meta = address.substring(0, dots);
100: address = address.substring(dots + 1);
101: }
102: this .address = address;
103: }
104:
105: public void setUser(String user) {
106: assertNotUsed();
107: this .user = user;
108: }
109:
110: public void setPassword(String password) {
111: assertNotUsed();
112: this .password = password;
113: }
114:
115: public void setHost(String host) {
116: assertNotUsed();
117: this .host = host;
118: }
119:
120: public void setAddress(String address) {
121: assertNotUsed();
122: this .address = address;
123: assertAddressConsistent();
124: }
125:
126: public void setPort(int port) {
127: assertNotUsed();
128: this .port = new Integer(port);
129: }
130:
131: public void setProtocol(String protocol) {
132: assertNotUsed();
133: this .protocol = protocol;
134: assertAddressConsistent();
135: }
136:
137: public void setMeta(String meta) {
138: assertNotUsed();
139: this .meta = meta;
140: }
141:
142: public void setPath(String path) {
143: assertNotUsed();
144: if (null != path && path.indexOf(DOTS_SLASHES) > -1) {
145: throw new IllegalArgumentException(
146: "Unusual syntax in path: '" + path + "' contains "
147: + DOTS_SLASHES);
148: }
149: this .path = path;
150: }
151:
152: public void setQueryMap(Map queryMap) {
153: assertNotUsed();
154: this .queryMap = queryMap;
155: }
156:
157: public EndpointURI getEndpoint() {
158: if (null == cache.get()) {
159: try {
160: EndpointURI endpointUri = new MuleEndpointURI(
161: getConstructor());
162: cache.compareAndSet(null, endpointUri);
163: } catch (EndpointException e) {
164: throw (IllegalStateException) new IllegalStateException(
165: "Bad endpoint configuration").initCause(e);
166: }
167: }
168: return (EndpointURI) cache.get();
169: }
170:
171: /**
172: * @return The String supplied to the delegate constructor
173: */
174: protected String getConstructor() {
175: StringBuffer buffer = new StringBuffer();
176: appendMeta(buffer);
177: OrderedQueryParameters uriQueries = appendAddress(buffer);
178: uriQueries.override(queryMap);
179: buffer.append(uriQueries.toString());
180: return buffer.toString();
181: }
182:
183: private void appendMeta(StringBuffer buffer) {
184: if (null != meta) {
185: buffer.append(meta);
186: buffer.append(DOTS);
187: }
188: }
189:
190: private OrderedQueryParameters appendAddress(StringBuffer buffer) {
191: if (null != address) {
192: int index = address.indexOf(QUERY);
193: if (index > -1) {
194: buffer.append(address.substring(0, index));
195: return parseQueries(address.substring(index + 1));
196: } else {
197: buffer.append(address);
198: return new OrderedQueryParameters();
199: }
200: } else {
201: constructAddress(buffer);
202: return new OrderedQueryParameters();
203: }
204: }
205:
206: private OrderedQueryParameters parseQueries(String queries) {
207: OrderedQueryParameters map = new OrderedQueryParameters();
208: StringTokenizer query = new StringTokenizer(queries, AND);
209: while (query.hasMoreTokens()) {
210: StringTokenizer nameValue = new StringTokenizer(query
211: .nextToken(), EQUALS);
212: String name = nameValue.nextToken();
213: String value = null;
214: if (nameValue.hasMoreTokens()) {
215: value = nameValue.nextToken();
216: }
217: map.put(name, value);
218: }
219: return map;
220: }
221:
222: private void constructAddress(StringBuffer buffer) {
223: buffer.append(protocol);
224: buffer.append(DOTS_SLASHES);
225: boolean atStart = true;
226: if (null != user) {
227: buffer.append(user);
228: if (null != password) {
229: buffer.append(":");
230: buffer.append(password);
231: }
232: buffer.append("@");
233: atStart = false;
234: }
235: if (null != host) {
236: buffer.append(host);
237: if (null != port) {
238: buffer.append(":");
239: buffer.append(port);
240: }
241: atStart = false;
242: }
243: if (null != path) {
244: if (!atStart) {
245: buffer.append("/");
246: }
247: buffer.append(path);
248: }
249: }
250:
251: protected void assertNotUsed() {
252: if (null != cache.get()) {
253: throw new IllegalStateException(
254: "Too late to set values - builder already used");
255: }
256: }
257:
258: protected void assertAddressConsistent() {
259: if (null != meta) {
260: if (null != address) {
261: if (address.startsWith(meta + DOTS)) {
262: throw new IllegalArgumentException(
263: "Meta-protocol '"
264: + meta
265: + "' should not be specified in the address '"
266: + address
267: + "' - it is implicit in the element namespace.");
268: }
269: if (null != protocol) {
270: assertProtocolConsistent();
271: } else {
272: if (address.indexOf(DOTS_SLASHES) == -1) {
273: throw new IllegalArgumentException(
274: "Address '"
275: + address
276: + "' does not have a transport protocol prefix "
277: + "(omit the meta protocol prefix, '"
278: + meta
279: + DOTS
280: + "' - it is implicit in the element namespace).");
281: }
282: }
283: }
284: } else {
285: assertProtocolConsistent();
286: }
287: }
288:
289: protected void assertProtocolConsistent() {
290: if (null != protocol && null != address
291: && !address.startsWith(protocol + DOTS_SLASHES)) {
292: throw new IllegalArgumentException("Address '" + address
293: + "' for protocol '" + protocol
294: + "' should start with " + protocol + DOTS_SLASHES);
295: }
296: }
297:
298: public String toString() {
299: return getConstructor();
300: }
301:
302: public boolean equals(Object other) {
303: if (null == other || !getClass().equals(other.getClass()))
304: return false;
305: if (this == other)
306: return true;
307:
308: URIBuilder builder = (URIBuilder) other;
309: return equal(address, builder.address)
310: && equal(meta, builder.meta)
311: && equal(protocol, builder.protocol)
312: && equal(user, builder.user)
313: && equal(password, builder.password)
314: && equal(host, builder.host)
315: && equal(port, builder.port)
316: && equal(path, builder.path)
317: && equal(queryMap, builder.queryMap);
318: }
319:
320: protected static boolean equal(Object a, Object b) {
321: return ClassUtils.equal(a, b);
322: }
323:
324: public int hashCode() {
325: return ClassUtils.hash(new Object[] { address, meta, protocol,
326: user, password, host, port, path, queryMap });
327: }
328:
329: private static class OrderedQueryParameters {
330:
331: private Map values = new HashMap();
332: private List orderedKeys = new LinkedList();
333:
334: public void put(String name, String value) {
335: values.put(name, value);
336: orderedKeys.add(name);
337: }
338:
339: public void override(Map map) {
340: if (null != map) {
341: // order additional parameters
342: Iterator names = new TreeMap(map).keySet().iterator();
343: while (names.hasNext()) {
344: String name = (String) names.next();
345: String value = (String) map.get(name);
346: if (!values.keySet().contains(name)) {
347: orderedKeys.add(name);
348: }
349: values.put(name, value);
350: }
351: }
352: }
353:
354: public String toString() {
355: StringBuffer buffer = new StringBuffer();
356: Iterator keys = orderedKeys.iterator();
357: boolean first = true;
358: while (keys.hasNext()) {
359: if (first) {
360: buffer.append(QUERY);
361: first = false;
362: } else {
363: buffer.append(AND);
364: }
365: String key = (String) keys.next();
366: buffer.append(key);
367: String value = (String) values.get(key);
368: if (null != value) {
369: buffer.append(EQUALS);
370: buffer.append(value);
371: }
372: }
373: return buffer.toString();
374: }
375:
376: }
377:
378: }
|