View Javadoc

1   /*
2    * $Id: URIBuilder.java 11620 2008-04-22 02:37:40Z dfeist $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.endpoint;
12  
13  import org.mule.api.endpoint.EndpointException;
14  import org.mule.api.endpoint.EndpointURI;
15  import org.mule.util.ClassUtils;
16  
17  import java.util.HashMap;
18  import java.util.Iterator;
19  import java.util.LinkedList;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.StringTokenizer;
23  import java.util.TreeMap;
24  
25  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicReference;
26  
27  /**
28   * This has the following logic:
29   * - if an address is specified, it is used verbatim (except for parameters); this is consistent with the generic case
30   * - otherwise, we construct from components, omitting things that aren't specified as much as possible
31   * (use required attributes to guarantee entries)
32   *
33   * In addition, parameters are handled as follows:
34   * - parameters can be given in the uri, the queryMap, or both
35   * - queryMap values override uri values
36   * - the order of parameters in the uri remains the same (even if values change)
37   * - queryMap parameters are appended after uri parameters
38   * (I don't think ordering should matter, but XFire seems to require it) 
39   *
40   * TODO - check that we have sufficient control via XML (what about empty strings?)
41   *
42   * Not called EndpointURIBuilder because of {@link org.mule.api.endpoint.EndpointURIBuilder}
43   */
44  public class URIBuilder
45  {
46  
47      private static final String DOTS = ":";
48      private static final String DOTS_SLASHES = DOTS + "//";
49      private static final String QUERY = "?";
50      private static final String AND = "&";
51      private static final String EQUALS = "=";
52  
53      public static final String META = "meta";
54      public static final String PROTOCOL = "protocol";
55      public static final String USER = "user";
56      public static final String PASSWORD = "password";
57      public static final String HOST = "host";
58      public static final String ADDRESS = "address";
59      public static final String PORT = "port";
60      public static final String PATH = "path";
61  
62      public static final String[] ALL_ATTRIBUTES =
63              new String[]{META, PROTOCOL, USER, PASSWORD, HOST, ADDRESS, PORT, PATH};
64      // combinations used in various endpoint parsers to validate required attributes
65      public static final String[] PATH_ATTRIBUTES = new String[]{PATH};
66      public static final String[] HOST_ATTRIBUTES = new String[]{HOST};
67      public static final String[] SOCKET_ATTRIBUTES = new String[]{HOST, PORT};
68      public static final String[] USERHOST_ATTRIBUTES = new String[]{USER, HOST};
69      // this doesn't include address, since that is handled separately (and is exclusive with these)
70      public static final String[] ALL_TRANSPORT_ATTRIBUTES = new String[]{USER, PASSWORD, HOST, PORT, PATH};
71  
72      private String address;
73      private String meta;
74      private String protocol;
75      private String user;
76      private String password;
77      private String host;
78      private Integer port;
79      private String path;
80      private Map queryMap;
81  
82      private AtomicReference cache = new AtomicReference();
83  
84      public URIBuilder()
85      {
86          // default
87      }
88  
89      public URIBuilder(EndpointURI endpointURI)
90      {
91          cache.set(endpointURI);
92      }
93  
94      public URIBuilder(String address)
95      {
96          // separate meta from address, if necessary
97          int dots = address.indexOf(DOTS);
98          int dotsSlashes = address.indexOf(DOTS_SLASHES);
99          if (dots > -1 && dots < dotsSlashes)
100         {
101             this.meta = address.substring(0, dots);
102             address = address.substring(dots+1);
103         }
104         this.address = address;
105     }
106 
107     public void setUser(String user)
108     {
109         assertNotUsed();
110         this.user = user;
111     }
112 
113     public void setPassword(String password)
114     {
115         assertNotUsed();
116         this.password = password;
117     }
118 
119     public void setHost(String host)
120     {
121         assertNotUsed();
122         this.host = host;
123     }
124 
125     public void setAddress(String address)
126     {
127         assertNotUsed();
128         this.address = address;
129         assertAddressConsistent();
130     }
131 
132     public void setPort(int port)
133     {
134         assertNotUsed();
135         this.port = new Integer(port);
136     }
137 
138     public void setProtocol(String protocol)
139     {
140         assertNotUsed();
141         this.protocol = protocol;
142         assertAddressConsistent();
143     }
144 
145     public void setMeta(String meta)
146     {
147         assertNotUsed();
148         this.meta = meta;
149     }
150 
151     public void setPath(String path)
152     {
153         assertNotUsed();
154         if (null != path && path.indexOf(DOTS_SLASHES) > -1)
155         {
156             throw new IllegalArgumentException("Unusual syntax in path: '" + path + "' contains " + DOTS_SLASHES);
157         }
158         this.path = path;
159     }
160 
161     public void setQueryMap(Map queryMap)
162     {
163         assertNotUsed();
164         this.queryMap = queryMap;
165     }
166 
167     public EndpointURI getEndpoint()
168     {
169         if (null == cache.get())
170         {
171             try
172             {
173                 EndpointURI endpointUri = new MuleEndpointURI(getConstructor());
174                 cache.compareAndSet(null, endpointUri);
175             }
176             catch (EndpointException e)
177             {
178                 throw (IllegalStateException)new IllegalStateException("Bad endpoint configuration").initCause(e);
179             }
180         }
181         return (EndpointURI)cache.get();
182     }
183 
184     /**
185      * @return The String supplied to the delegate constructor
186      */
187     protected String getConstructor()
188     {
189         StringBuffer buffer = new StringBuffer();
190         appendMeta(buffer);
191         OrderedQueryParameters uriQueries = appendAddress(buffer);
192         uriQueries.override(queryMap);
193         buffer.append(uriQueries.toString());
194         return buffer.toString();
195     }
196 
197     private void appendMeta(StringBuffer buffer)
198     {
199         if (null != meta)
200         {
201             buffer.append(meta);
202             buffer.append(DOTS);
203         }
204     }
205 
206     private OrderedQueryParameters appendAddress(StringBuffer buffer)
207     {
208         if (null != address)
209         {
210             int index = address.indexOf(QUERY);
211             if (index > -1)
212             {
213                 buffer.append(address.substring(0, index));
214                 return parseQueries(address.substring(index + 1));
215             }
216             else
217             {
218                 buffer.append(address);
219                 return new OrderedQueryParameters();
220             }
221         }
222         else
223         {
224             constructAddress(buffer);
225             return new OrderedQueryParameters();
226         }
227     }
228 
229     private OrderedQueryParameters parseQueries(String queries)
230     {
231         OrderedQueryParameters map = new OrderedQueryParameters();
232         StringTokenizer query = new StringTokenizer(queries, AND);
233         while (query.hasMoreTokens())
234         {
235             StringTokenizer nameValue = new StringTokenizer(query.nextToken(), EQUALS);
236             String name = nameValue.nextToken();
237             String value = null;
238             if (nameValue.hasMoreTokens())
239             {
240                 value = nameValue.nextToken();
241             }
242             map.put(name, value);
243         }
244         return map;
245     }
246 
247     private void constructAddress(StringBuffer buffer)
248     {
249         buffer.append(protocol);
250         buffer.append(DOTS_SLASHES);
251         boolean atStart = true;
252         if (null != user)
253         {
254             buffer.append(user);
255             if (null != password)
256             {
257                 buffer.append(":");
258                 buffer.append(password);
259             }
260             buffer.append("@");
261             atStart = false;
262         }
263         if (null != host)
264         {
265             buffer.append(host);
266             if (null != port)
267             {
268                 buffer.append(":");
269                 buffer.append(port);
270             }
271             atStart = false;
272         }
273         if (null != path)
274         {
275             if (! atStart)
276             {
277                 buffer.append("/");
278             }
279             buffer.append(path);
280         }
281     }
282 
283     protected void assertNotUsed()
284     {
285         if (null != cache.get())
286         {
287             throw new IllegalStateException("Too late to set values - builder already used");
288         }
289     }
290 
291     protected void assertAddressConsistent()
292     {
293         if (null != meta)
294         {
295             if (null != address)
296             {
297                 if (address.startsWith(meta + DOTS))
298                 {
299                     throw new IllegalArgumentException("Meta-protocol '" + meta +
300                             "' should not be specified in the address '" + address +
301                             "' - it is implicit in the element namespace.");
302                 }
303                 if (null != protocol)
304                 {
305                     assertProtocolConsistent();
306                 }
307                 else
308                 {
309                     if (address.indexOf(DOTS_SLASHES) == -1)
310                     {
311                         throw new IllegalArgumentException("Address '" + address +
312                                 "' does not have a transport protocol prefix " +
313                                 "(omit the meta protocol prefix, '" + meta + DOTS +
314                                 "' - it is implicit in the element namespace).");
315                     }
316                 }
317             }
318         }
319         else
320         {
321             assertProtocolConsistent();
322         }
323     }
324 
325     protected void assertProtocolConsistent()
326     {
327         if (null != protocol && null != address && !address.startsWith(protocol + DOTS_SLASHES))
328         {
329             throw new IllegalArgumentException("Address '" + address + "' for protocol '" + protocol +
330                     "' should start with " + protocol + DOTS_SLASHES);
331         }
332     }
333 
334     public String toString()
335     {
336         return getConstructor();
337     }
338 
339     public boolean equals(Object other)
340     {
341         if (null == other || !getClass().equals(other.getClass())) return false;
342         if (this == other) return true;
343 
344         URIBuilder builder = (URIBuilder) other;
345         return equal(address, builder.address)
346                 && equal(meta, builder.meta)
347                 && equal(protocol, builder.protocol)
348                 && equal(user, builder.user)
349                 && equal(password, builder.password)
350                 && equal(host, builder.host)
351                 && equal(port, builder.port)
352                 && equal(path, builder.path)
353                 && equal(queryMap, builder.queryMap);
354     }
355 
356     protected static boolean equal(Object a, Object b)
357     {
358         return ClassUtils.equal(a, b);
359     }
360 
361     public int hashCode()
362     {
363         return ClassUtils.hash(new Object[]{address, meta, protocol, user, password, host, port, path, queryMap});
364     }
365 
366     private static class OrderedQueryParameters
367     {
368 
369         private Map values = new HashMap();
370         private List orderedKeys = new LinkedList();
371 
372         public void put(String name, String value)
373         {
374             values.put(name, value);
375             orderedKeys.add(name);
376         }
377 
378         public void override(Map map)
379         {
380             if (null != map)
381             {
382                 // order additional parameters
383                 Iterator names = new TreeMap(map).keySet().iterator();
384                 while (names.hasNext())
385                 {
386                     String name = (String) names.next();
387                     String value  =(String) map.get(name);
388                     if (! values.keySet().contains(name))
389                     {
390                         orderedKeys.add(name);
391                     }
392                     values.put(name, value);
393                 }
394             }
395         }
396 
397         public String toString()
398         {
399             StringBuffer buffer = new StringBuffer();
400             Iterator keys = orderedKeys.iterator();
401             boolean first = true;
402             while (keys.hasNext())
403             {
404                 if (first)
405                 {
406                     buffer.append(QUERY);
407                     first = false;
408                 }
409                 else
410                 {
411                     buffer.append(AND);
412                 }
413                 String key = (String)keys.next();
414                 buffer.append(key);
415                 String value = (String)values.get(key);
416                 if (null != value)
417                 {
418                     buffer.append(EQUALS);
419                     buffer.append(value);
420                 }
421             }
422             return buffer.toString();
423         }
424 
425     }
426 
427 }