View Javadoc

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