KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > continuent > sequoia > driver > SequoiaUrl


1 /**
2  * Sequoia: Database clustering technology.
3  * Copyright (C) 2005 Emic Networks.
4  * Contact: sequoia@continuent.org
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * Initial developer(s): Emmanuel Cecchet.
19  * Contributor(s): ______________________.
20  */

21
22 package org.continuent.sequoia.driver;
23
24 import java.sql.SQLException JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Properties JavaDoc;
27 import java.util.StringTokenizer JavaDoc;
28
29 import org.continuent.sequoia.driver.connectpolicy.AbstractControllerConnectPolicy;
30 import org.continuent.sequoia.driver.connectpolicy.OrderedConnectPolicy;
31 import org.continuent.sequoia.driver.connectpolicy.PreferredListConnectPolicy;
32 import org.continuent.sequoia.driver.connectpolicy.RandomConnectPolicy;
33 import org.continuent.sequoia.driver.connectpolicy.RoundRobinConnectPolicy;
34 import org.continuent.sequoia.driver.connectpolicy.SingleConnectPolicy;
35
36 /**
37  * This class defines a Sequoia url with parsed Metadata and so on. The
38  * connection policy is interpreted while reading the URL. We could rename it to
39  * ParsedURL.
40  *
41  * @author <a HREF="mailto:emmanuel.cecchet@emicnetworks.com">Emmanuel Cecchet
42  * </a>
43  * @version 1.0
44  */

45 public class SequoiaUrl
46 {
47   private Driver driver;
48   private String JavaDoc url;
49   private String JavaDoc databaseName;
50   private ControllerInfo[] controllerList;
51   /** This includes parameters parsed from the url */
52   private final HashMap JavaDoc parameters;
53   private AbstractControllerConnectPolicy controllerConnectPolicy;
54
55   /**
56    * Default port number to connect to the Sequoia controller
57    */

58   public static final int DEFAULT_CONTROLLER_PORT = 25322;
59
60   // Debug information
61
private int debugLevel;
62   /** Most verbose level of debug */
63   public static final int DEBUG_LEVEL_DEBUG = 2;
64   /** Informational level of debug */
65   public static final int DEBUG_LEVEL_INFO = 1;
66   /** No debug messages */
67   public static final int DEBUG_LEVEL_OFF = 0;
68
69   /**
70    * Creates a new <code>SequoiaUrl</code> object, parse it and instantiate
71    * the connection creation policy.
72    *
73    * @param driver driver used to create the connections
74    * @param url the URL to parse
75    * @param props optional filtered connection properties. Must not be null.
76    * @throws SQLException if an error occurs while parsing the url
77    */

78   public SequoiaUrl(Driver driver, String JavaDoc url, Properties JavaDoc props)
79       throws SQLException JavaDoc
80   {
81     this.driver = driver;
82     this.url = url;
83
84     parameters = new HashMap JavaDoc();
85     // Initialize parameters
86
parameters.putAll(props);
87     // Parse URL now, will override any parameter defined as a property
88
// by the previous line. Fixes SEQUOIA-105.
89
parseUrl();
90
91     String JavaDoc debugProperty = (String JavaDoc) parameters.get(Driver.DEBUG_PROPERTY);
92     debugLevel = DEBUG_LEVEL_OFF;
93     if (debugProperty != null)
94     {
95       if ("debug".equals(debugProperty))
96         debugLevel = DEBUG_LEVEL_DEBUG;
97       else if ("info".equals(debugProperty))
98         debugLevel = DEBUG_LEVEL_INFO;
99     }
100     controllerConnectPolicy = createConnectionPolicy();
101   }
102
103   /**
104    * Returns the debugLevel value.
105    *
106    * @return Returns the debugLevel.
107    */

108   public int getDebugLevel()
109   {
110     return debugLevel;
111   }
112
113   /**
114    * Returns true if debugging is set to debug level 'debug'
115    *
116    * @return true if debugging is enabled
117    */

118   public boolean isDebugEnabled()
119   {
120     return debugLevel == DEBUG_LEVEL_DEBUG;
121   }
122
123   /**
124    * Returns true if debug level is 'info' or greater
125    *
126    * @return true if debug level is 'info' or greater
127    */

128   public boolean isInfoEnabled()
129   {
130     return debugLevel >= DEBUG_LEVEL_INFO;
131   }
132
133   /**
134    * Returns the controllerConnectPolicy value.
135    *
136    * @return Returns the controllerConnectPolicy.
137    */

138   public AbstractControllerConnectPolicy getControllerConnectPolicy()
139   {
140     return controllerConnectPolicy;
141   }
142
143   /**
144    * Returns the controllerList value.
145    *
146    * @return Returns the controllerList.
147    */

148   public ControllerInfo[] getControllerList()
149   {
150     return controllerList;
151   }
152
153   /**
154    * Returns the database name.
155    *
156    * @return Returns the database name.
157    */

158   public String JavaDoc getDatabaseName()
159   {
160     return databaseName;
161   }
162
163   /**
164    * Returns the URL parameters/value in a HashMap (warning this is not a
165    * clone). Parameters come from both the parsed URL and the properties
166    * argument if any.
167    * <p>
168    * The HashMap is 'parameter name'=>'value'
169    *
170    * @return Returns the parameters and their value in a Hasmap.
171    */

172   public HashMap JavaDoc getParameters()
173   {
174     return parameters;
175   }
176
177   /**
178    * Returns the url value.
179    *
180    * @return Returns the url.
181    */

182   public String JavaDoc getUrl()
183   {
184     return url;
185   }
186
187   /**
188    * Sets the url value.
189    *
190    * @param url The url to set.
191    */

192   public void setUrl(String JavaDoc url)
193   {
194     this.url = url;
195   }
196
197   //
198
// Private methods (mainly parsing)
199
//
200

201   /**
202    * Create the corresponding controller connect policy according to what is
203    * found in the URL. If no policy was specified then a
204    * <code>RandomConnectPolicy</code> is returned.
205    *
206    * @return an <code>AbstractControllerConnectPolicy</code>
207    */

208   private AbstractControllerConnectPolicy createConnectionPolicy()
209   {
210     if (controllerList.length == 1)
211       return new SingleConnectPolicy(controllerList, debugLevel);
212
213     String JavaDoc policy = (String JavaDoc) parameters
214         .get(Driver.PREFERRED_CONTROLLER_PROPERTY);
215     String JavaDoc retryInterval = (String JavaDoc) parameters
216         .get(Driver.RETRY_INTERVAL_IN_MS_PROPERTY);
217     long retryIntervalInMs;
218
219     // Default is random policy with retry interval of 5 seconds
220
if (retryInterval == null)
221       retryIntervalInMs = Driver.DEFAULT_RETRY_INTERVAL_IN_MS;
222     else
223       retryIntervalInMs = Long.parseLong(retryInterval);
224
225     // preferredController: defines the strategy to use to choose a preferred
226
// controller to connect to
227
// - jdbc:sequoia://node1,node2,node3/myDB?preferredController=roundRobin
228
// round robin starting with first node in URL
229
// This is the default policy.
230
if ((policy == null) || policy.equals("roundRobin"))
231       return new RoundRobinConnectPolicy(controllerList, retryIntervalInMs,
232           debugLevel);
233
234     // - jdbc:sequoia://node1,node2,node3/myDB?preferredController=ordered
235
// Always connect to node1, and if not available then try to node2 and
236
// finally if none are available try node3.
237
if (policy.equals("ordered"))
238       return new OrderedConnectPolicy(controllerList, retryIntervalInMs,
239           debugLevel);
240
241     // - jdbc:sequoia://node1,node2,node3/myDB?preferredController=random
242
// Pick a node randomly amongst active controllers.
243
if (policy.equals("random"))
244       return new RandomConnectPolicy(controllerList, retryIntervalInMs,
245           debugLevel);
246
247     // - jdbc:sequoia://node1,node2,node3/myDB?preferredController=node2,node3
248
// same as above but round-robin (or random?) between 2 and 3
249
return new PreferredListConnectPolicy(controllerList, retryIntervalInMs,
250         policy, debugLevel);
251   }
252
253   /**
254    * Checks for URL correctness and extract database name, controller list and
255    * parameters into the map "this.parameters".
256    *
257    * @exception SQLException if an error occurs.
258    */

259   private void parseUrl() throws SQLException JavaDoc
260   {
261     // Find the hostname and check for URL correctness
262
if (url == null)
263     {
264       throw new IllegalArgumentException JavaDoc(
265           "Illegal null URL in parseURL(String) method");
266     }
267
268     if (!url.toLowerCase().startsWith(driver.sequoiaUrlHeader))
269       throw new SQLException JavaDoc("Malformed header from URL '" + url
270           + "' (expected '" + driver.sequoiaUrlHeader + "')");
271     else
272     {
273       // Get the controllers list
274
int nextSlash = url.indexOf('/', driver.sequoiaUrlHeaderLength);
275       if (nextSlash == -1)
276         // Missing '/' between hostname and database name.
277
throw new SQLException JavaDoc("Malformed URL '" + url + "' (expected '"
278             + driver.sequoiaUrlHeader + "<hostname>/<database>')");
279
280       // Found end of database name
281
int questionMark = url.indexOf('?', nextSlash);
282       questionMark = (questionMark == -1)
283           ? url.indexOf(';', nextSlash)
284           : questionMark;
285
286       String JavaDoc controllerURLs = url.substring(driver.sequoiaUrlHeaderLength,
287           nextSlash);
288       // Check the validity of each controller in the list
289
// empty tokens (when successive delims) are ignored
290
StringTokenizer JavaDoc controllers = new StringTokenizer JavaDoc(controllerURLs, ",",
291           false);
292       int tokenNumber = controllers.countTokens();
293       if (tokenNumber == 0)
294       {
295         throw new SQLException JavaDoc("Empty controller name in '" + controllerURLs
296             + "' in URL '" + url + "'");
297       }
298       controllerList = new ControllerInfo[tokenNumber];
299       int i = 0;
300       String JavaDoc token;
301       // TODO: the following code does not recognize the following buggy urls:
302
// jdbc:sequoia://,localhost:/tpcw or jdbc:sequoia://host1,,host2:/tpcw
303
while (controllers.hasMoreTokens())
304       {
305         token = controllers.nextToken().trim();
306         if (token.equals("")) // whitespace tokens
307
{
308           throw new SQLException JavaDoc("Empty controller name in '" + controllerURLs
309               + "' in URL '" + url + "'");
310         }
311         controllerList[i] = parseController(token);
312         i++;
313       }
314
315       // Check database name validity
316
databaseName = (questionMark == -1) ? url.substring(nextSlash + 1, url
317           .length()) : url.substring(nextSlash + 1, questionMark);
318       Character JavaDoc c = validDatabaseName(databaseName);
319       if (c != null)
320         throw new SQLException JavaDoc(
321             "Unable to validate database name (unacceptable character '" + c
322                 + "' in database '" + databaseName + "' from URL '" + url
323                 + "')");
324
325       // Get the parameters from the url
326
parameters.putAll(parseUrlParams(url));
327     }
328   }
329
330   /**
331    * Parse the given URL and returns the parameters in a HashMap containing
332    * ParamaterName=>Value.
333    *
334    * @param urlString the URL to parse
335    * @return a Hashmap of param name=>value possibly empty
336    * @throws SQLException if an error occurs
337    */

338   private HashMap JavaDoc parseUrlParams(String JavaDoc urlString) throws SQLException JavaDoc
339   {
340     HashMap JavaDoc props = parseUrlParams(urlString, '?', "&", "=");
341     if (props == null)
342       props = parseUrlParams(urlString, ';', ";", "=");
343     if (props == null)
344       props = new HashMap JavaDoc();
345
346     return props;
347   }
348
349   /**
350    * Parse the given URL looking for parameters starting after the beginMarker,
351    * using parameterSeparator as the separator between parameters and equal as
352    * the delimiter between a parameter and its value.
353    *
354    * @param urlString the URL to parse
355    * @param beginMarker delimiter for beginning of parameters
356    * @param parameterSeparator delimiter between parameters
357    * @param equal delimiter between parameter and its value
358    * @return HashMap of ParameterName=>Value
359    * @throws SQLException if an error occurs
360    */

361   private HashMap JavaDoc parseUrlParams(String JavaDoc urlString, char beginMarker,
362       String JavaDoc parameterSeparator, String JavaDoc equal) throws SQLException JavaDoc
363   {
364     int questionMark = urlString.indexOf(beginMarker);
365     if (questionMark == -1)
366       return null;
367     else
368     {
369       HashMap JavaDoc props = new HashMap JavaDoc();
370       String JavaDoc params = urlString.substring(questionMark + 1);
371       StringTokenizer JavaDoc st1 = new StringTokenizer JavaDoc(params, parameterSeparator);
372       while (st1.hasMoreTokens())
373       {
374         String JavaDoc param = st1.nextToken();
375         StringTokenizer JavaDoc st2 = new StringTokenizer JavaDoc(param, equal);
376         if (st2.hasMoreTokens())
377         {
378           try
379           {
380             String JavaDoc paramName = st2.nextToken();
381             String JavaDoc paramValue = (st2.hasMoreTokens()) ? st2.nextToken() : "";
382             props.put(paramName, paramValue);
383           }
384           catch (Exception JavaDoc e) // TODOC: what are we supposed to catch here?
385
{
386             throw new SQLException JavaDoc("Invalid parameter in URL: " + urlString);
387           }
388         }
389       }
390       return props;
391     }
392   }
393
394   /**
395    * Checks the validity of the hostname, port number and controller name given
396    * in the URL and build the full URL used to lookup a controller.
397    *
398    * @param controller information regarding a controller.
399    * @return a <code>ControllerInfo</code> object
400    * @exception SQLException if an error occurs.
401    */

402   public static ControllerInfo parseController(String JavaDoc controller)
403       throws SQLException JavaDoc
404   {
405     ControllerInfo controllerInfo = new ControllerInfo();
406
407     // Check controller syntax
408
StringTokenizer JavaDoc controllerURL = new StringTokenizer JavaDoc(controller, ":", true);
409
410     // Get hostname
411
controllerInfo.setHostname(controllerURL.nextToken());
412     Character JavaDoc c = validHostname(controllerInfo.getHostname());
413     if (c != null)
414       throw new SQLException JavaDoc(
415           "Unable to validate hostname (unacceptable character '" + c
416               + "' in hostname '" + controllerInfo.getHostname()
417               + "' from the URL part '" + controller + "')");
418
419     if (!controllerURL.hasMoreTokens())
420       controllerInfo.setPort(DEFAULT_CONTROLLER_PORT);
421     else
422     {
423       controllerURL.nextToken(); // should be ':'
424
if (!controllerURL.hasMoreTokens())
425         controllerInfo.setPort(DEFAULT_CONTROLLER_PORT);
426       else
427       { // Get the port number
428
String JavaDoc port = controllerURL.nextToken();
429         if (controllerURL.hasMoreTokens())
430           throw new SQLException JavaDoc(
431               "Invalid controller definition with more than one semicolon in URL part '"
432                   + controller + "'");
433
434         // Check the port number validity
435
try
436         {
437           controllerInfo.setPort(Integer.parseInt(port));
438         }
439         catch (NumberFormatException JavaDoc ne)
440         {
441           throw new SQLException JavaDoc(
442               "Unable to validate port number (unacceptable port number '"
443                   + port + "' in this URL part '" + controller + "')");
444         }
445       }
446     }
447     return controllerInfo;
448   }
449
450   /**
451    * Checks that the given name contains acceptable characters for a hostname
452    * name ([0-9][A-Z][a-z][["-_."]).
453    *
454    * @param hostname name to check (caller must check that it is not
455    * <code>null</code>).
456    * @return <code>null</code> if the hostname is acceptable, else the
457    * character that causes the fault.
458    */

459   private static Character JavaDoc validHostname(String JavaDoc hostname)
460   {
461     char[] name = hostname.toCharArray();
462     int size = hostname.length();
463     char c;
464     // boolean lastCharWasPoint = false; // used to avoid '..' in hostname
465
char lastChar = ' ';
466
467     for (int i = 0; i < size; i++)
468     {
469       c = name[i];
470
471       if (c == '.' || c == '-')
472       {
473         if (lastChar == '.' || lastChar == '-' || (i == size - 1) || (i == 0))
474         {
475           // . or - cannot be the first or the last char of hostname
476
// hostname cannot contain '..' or '.-' or '-.' or '--'
477
return new Character JavaDoc(c);
478         }
479       }
480       else
481       {
482         if (((c < '0') || (c > 'z') || ((c > '9') && (c < 'A'))
483             || ((c > 'Z') && (c < '_')) || (c == '`')))
484         {
485           return new Character JavaDoc(c);
486         }
487       }
488       lastChar = c;
489     }
490     return null;
491   }
492
493   /**
494    * Checks that the given name contains acceptable characters for a database
495    * name ([0-9][A-Z][a-z]["-_"]).
496    *
497    * @param databaseName name to check (caller must check that it is not
498    * <code>null</code>).
499    * @return <code>null</code> if the name is acceptable, else the character
500    * that causes the fault.
501    */

502   private static Character JavaDoc validDatabaseName(String JavaDoc databaseName)
503   {
504     char[] name = databaseName.toCharArray();
505     int size = databaseName.length();
506     char c;
507
508     for (int i = 0; i < size; i++)
509     {
510       c = name[i];
511       if ((c < '-') || (c > 'z') || (c == '/') || (c == '.') || (c == '`')
512           || ((c > '9') && (c < 'A')) || ((c > 'Z') && (c < '_')))
513         return new Character JavaDoc(c);
514     }
515     return null;
516   }
517
518   /**
519    * @see java.lang.Object#equals(java.lang.Object)
520    */

521   public boolean equals(Object JavaDoc other)
522   {
523     if (!(other instanceof SequoiaUrl))
524       return false;
525     SequoiaUrl castedOther = (SequoiaUrl) other;
526     return (url.equals(castedOther.url) && parameters
527         .equals(castedOther.parameters));
528   }
529
530   /**
531    * @see java.lang.Object#hashCode()
532    */

533   public int hashCode()
534   {
535     return toString().hashCode();
536   }
537
538   /**
539    * @see java.lang.Object#toString()
540    */

541   public String JavaDoc toString()
542   {
543     return (url + parameters);
544   }
545 }
546
Popular Tags