KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > cjdbc > driver > CjdbcUrl


1 /**
2  * C-JDBC: Clustered JDBC.
3  * Copyright (C) 2005 Emic Networks.
4  * Contact: c-jdbc@objectweb.org
5  *
6  * This library is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation; either version 2.1 of the License, or any later
9  * version.
10  *
11  * This library is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19  *
20  * Initial developer(s): Emmanuel Cecchet.
21  * Contributor(s): ______________________.
22  */

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

47 public class CjdbcUrl
48 {
49   private String JavaDoc url;
50   private String JavaDoc databaseName;
51   private ControllerInfo[] controllerList;
52   private HashMap JavaDoc parameters;
53   private AbstractControllerConnectPolicy controllerConnectPolicy;
54
55   // Debug information
56
private int debugLevel;
57   /** Most verbose level of debug */
58   public static final int DEBUG_LEVEL_DEBUG = 2;
59   /** Informational level of debug */
60   public static final int DEBUG_LEVEL_INFO = 1;
61   /** No debug messages */
62   public static final int DEBUG_LEVEL_OFF = 0;
63
64   /**
65    * Creates a new <code>CjdbcUrl</code> object, parse it and instantiate the
66    * connection creation policy.
67    *
68    * @param url the URL to parse
69    * @throws SQLException if an error occurs while parsing the url
70    */

71   public CjdbcUrl(String JavaDoc url) throws SQLException JavaDoc
72   {
73     this.url = url;
74     parseUrl();
75     String JavaDoc debugProperty = (String JavaDoc) parameters.get(Driver.DEBUG_PROPERTY);
76     debugLevel = DEBUG_LEVEL_OFF;
77     if (debugProperty != null)
78     {
79       if ("debug".equals(debugProperty))
80         debugLevel = DEBUG_LEVEL_DEBUG;
81       else if ("info".equals(debugProperty))
82         debugLevel = DEBUG_LEVEL_INFO;
83     }
84     controllerConnectPolicy = createConnectionPolicy();
85   }
86
87   /**
88    * Returns true if debugging is set to debug level 'debug'
89    *
90    * @return true if debugging is enabled
91    */

92   public boolean isDebugEnabled()
93   {
94     return debugLevel == DEBUG_LEVEL_DEBUG;
95   }
96
97   /**
98    * Returns true if debug level is 'info' or greater
99    *
100    * @return true if debug level is 'info' or greater
101    */

102   public boolean isInfoEnabled()
103   {
104     return debugLevel >= DEBUG_LEVEL_INFO;
105   }
106
107   /**
108    * Returns the controllerConnectPolicy value.
109    *
110    * @return Returns the controllerConnectPolicy.
111    */

112   public AbstractControllerConnectPolicy getControllerConnectPolicy()
113   {
114     return controllerConnectPolicy;
115   }
116
117   /**
118    * Returns the controllerList value.
119    *
120    * @return Returns the controllerList.
121    */

122   public ControllerInfo[] getControllerList()
123   {
124     return controllerList;
125   }
126
127   /**
128    * Returns the database name.
129    *
130    * @return Returns the database name.
131    */

132   public String JavaDoc getDatabaseName()
133   {
134     return databaseName;
135   }
136
137   /**
138    * Returns the URL parameters/value in a HashMap (warning this is not a
139    * clone).
140    * <p>
141    * The HashMap is 'parameter name'=>'value'
142    *
143    * @return Returns the parameters and their value in a Hasmap.
144    */

145   public HashMap JavaDoc getParameters()
146   {
147     return parameters;
148   }
149
150   /**
151    * Returns the url value.
152    *
153    * @return Returns the url.
154    */

155   public String JavaDoc getUrl()
156   {
157     return url;
158   }
159
160   /**
161    * Sets the url value.
162    *
163    * @param url The url to set.
164    */

165   public void setUrl(String JavaDoc url)
166   {
167     this.url = url;
168   }
169
170   //
171
// Private methods (mainly parsing)
172
//
173

174   /**
175    * Create the corresponding controller connect policy according to what is
176    * found in the URL. If no policy was specified then a
177    * <code>RandomConnectPolicy</code> is returned.
178    *
179    * @return an <code>AbstractControllerConnectPolicy</code>
180    */

181   private AbstractControllerConnectPolicy createConnectionPolicy()
182   {
183     if (controllerList.length == 1)
184       return new SingleConnectPolicy(controllerList, debugLevel);
185
186     String JavaDoc policy = (String JavaDoc) parameters
187         .get(Driver.PREFERRED_CONTROLLER_PROPERTY);
188     String JavaDoc retryInterval = (String JavaDoc) parameters
189         .get(Driver.RETRY_INTERVAL_IN_MS_PROPERTY);
190     long retryIntervalInMs;
191
192     // Default is random policy with retry interval of 5 seconds
193
if (retryInterval == null)
194       retryIntervalInMs = Driver.DEFAULT_RETRY_INTERVAL_IN_MS;
195     else
196       retryIntervalInMs = Long.parseLong(retryInterval);
197
198     if (policy == null)
199       return new RandomConnectPolicy(controllerList, retryIntervalInMs,
200           debugLevel);
201
202     // preferredController: defines the strategy to use to choose a preferred
203
// controller to connect to
204
// - jdbc:cjdbc://node1,node2,node3/myDB?preferredController=roundRobin
205
// round robin starting with first node in URL
206
if (policy.equals("roundRobin"))
207       return new RoundRobinConnectPolicy(controllerList, retryIntervalInMs,
208           debugLevel);
209
210     // - jdbc:cjdbc://node1,node2,node3/myDB?preferredController=ordered
211
// Always connect to node1, and if not available then try to node2 and
212
// finally if none are available try node3.
213
if (policy.equals("ordered"))
214       return new OrderedConnectPolicy(controllerList, retryIntervalInMs,
215           debugLevel);
216
217     // - jdbc:cjdbc://node1,node2,node3/myDB?preferredController=random
218
// default strategy
219
if (policy.equals("random"))
220       return new RandomConnectPolicy(controllerList, retryIntervalInMs,
221           debugLevel);
222
223     // - jdbc:cjdbc://node1,node2,node3/myDB?preferredController=node2,node3
224
// same as above but round-robin (or random?) between 2 and 3
225
return new PreferredListConnectPolicy(controllerList, retryIntervalInMs,
226         policy, debugLevel);
227   }
228
229   /**
230    * Checks for URL correctness and extract database name, controller list and
231    * parameters.
232    *
233    * @exception SQLException if an error occurs.
234    */

235   private void parseUrl() throws SQLException JavaDoc
236   {
237     // Find the hostname and check for URL correctness
238
if (url == null)
239     {
240       throw new IllegalArgumentException JavaDoc(
241           "Illegal null URL in parseURL(String) method");
242     }
243
244     if (!url.toLowerCase().startsWith(Driver.CJDBC_URL_HEADER))
245       throw new SQLException JavaDoc("Malformed header from URL '" + url
246           + "' (expected '" + Driver.CJDBC_URL_HEADER + "')");
247     else
248     {
249       // Get the controllers list
250
int nextSlash = url.indexOf('/', Driver.CJDBC_URL_HEADER_LENGTH);
251       if (nextSlash == -1)
252         // Missing '/' between hostname and database name.
253
throw new SQLException JavaDoc("Malformed URL '" + url + "' (expected '"
254             + Driver.CJDBC_URL_HEADER + "<hostname>/<database>')");
255
256       // Found end of database name
257
int questionMark = url.indexOf('?', nextSlash);
258       questionMark = (questionMark == -1)
259           ? url.indexOf(';', nextSlash)
260           : questionMark;
261
262       String JavaDoc controllerURLs = url.substring(Driver.CJDBC_URL_HEADER_LENGTH,
263           nextSlash);
264       // Check the validity of each controller in the list
265
// empty tokens (when successive delims) are ignored
266
StringTokenizer JavaDoc controllers = new StringTokenizer JavaDoc(controllerURLs, ",",
267           false);
268       int tokenNumber = controllers.countTokens();
269       if (tokenNumber == 0)
270       {
271         throw new SQLException JavaDoc("Empty controller name in '" + controllerURLs
272             + "' in URL '" + url + "'");
273       }
274       controllerList = new ControllerInfo[tokenNumber];
275       int i = 0;
276       String JavaDoc token;
277       // TODO: the following code does not recognize the following buggy urls:
278
// jdbc:cjdbc://,localhost:/tpcw or jdbc:cjdbc://host1,,host2:/tpcw
279
while (controllers.hasMoreTokens())
280       {
281         token = controllers.nextToken().trim();
282         if (token.equals("")) // whitespace tokens
283
{
284           throw new SQLException JavaDoc("Empty controller name in '" + controllerURLs
285               + "' in URL '" + url + "'");
286         }
287         controllerList[i] = parseController(token);
288         i++;
289       }
290
291       // Check database name validity
292
databaseName = (questionMark == -1) ? url.substring(nextSlash + 1, url
293           .length()) : url.substring(nextSlash + 1, questionMark);
294       Character JavaDoc c = validDatabaseName(databaseName);
295       if (c != null)
296         throw new SQLException JavaDoc(
297             "Unable to validate database name (unacceptable character '" + c
298                 + "' in database '" + databaseName + "' from URL '" + url
299                 + "')");
300
301       // Get the parameters from the url
302
parameters = parseUrlParams(url);
303     }
304   }
305
306   /**
307    * Parse the given URL and returns the parameters in a HashMap containing
308    * ParamaterName=>Value.
309    *
310    * @param urlString the URL to parse
311    * @return a Hashmap of param name=>value possibly empty
312    * @throws SQLException if an error occurs
313    */

314   private HashMap JavaDoc parseUrlParams(String JavaDoc urlString) throws SQLException JavaDoc
315   {
316     HashMap JavaDoc props = parseUrlParams(urlString, '?', "&", "=");
317     if (props == null)
318       props = parseUrlParams(urlString, ';', ";", "=");
319     if (props == null)
320       props = new HashMap JavaDoc();
321
322     return props;
323   }
324
325   /**
326    * Parse the given URL looking for parameters starting after the beginMarker,
327    * using parameterSeparator as the separator between parameters and equal as
328    * the delimiter between a parameter and its value.
329    *
330    * @param urlString the URL to parse
331    * @param beginMarker delimiter for beginning of parameters
332    * @param parameterSeparator delimiter between parameters
333    * @param equal delimiter between parameter and its value
334    * @return HashMap of ParameterName=>Value
335    * @throws SQLException if an error occurs
336    */

337   private HashMap JavaDoc parseUrlParams(String JavaDoc urlString, char beginMarker,
338       String JavaDoc parameterSeparator, String JavaDoc equal) throws SQLException JavaDoc
339   {
340     int questionMark = urlString.indexOf(beginMarker, urlString
341         .lastIndexOf('/'));
342     if (questionMark == -1)
343       return null;
344     else
345     {
346       HashMap JavaDoc props = new HashMap JavaDoc();
347       String JavaDoc params = urlString.substring(questionMark + 1);
348       StringTokenizer JavaDoc st1 = new StringTokenizer JavaDoc(params, parameterSeparator);
349       while (st1.hasMoreTokens())
350       {
351         String JavaDoc param = st1.nextToken();
352         StringTokenizer JavaDoc st2 = new StringTokenizer JavaDoc(param, equal);
353         if (st2.hasMoreTokens())
354         {
355           try
356           {
357             String JavaDoc paramName = st2.nextToken();
358             String JavaDoc paramValue = (st2.hasMoreTokens()) ? st2.nextToken() : "";
359             props.put(paramName, paramValue);
360           }
361           catch (Exception JavaDoc e) // TODOC: what are we supposed to catch here?
362
{
363             throw new SQLException JavaDoc("Invalid parameter in URL: " + urlString);
364           }
365         }
366       }
367       return props;
368     }
369   }
370
371   /**
372    * Checks the validity of the hostname, port number and controller name given
373    * in the URL and build the full URL used to lookup a controller.
374    *
375    * @param controller information regarding a controller.
376    * @return a <code>ControllerInfo</code> object
377    * @exception SQLException if an error occurs.
378    */

379   public static ControllerInfo parseController(String JavaDoc controller)
380       throws SQLException JavaDoc
381   {
382     ControllerInfo controllerInfo = new ControllerInfo();
383
384     // Check controller syntax
385
StringTokenizer JavaDoc controllerURL = new StringTokenizer JavaDoc(controller, ":", true);
386
387     // Get hostname
388
controllerInfo.setHostname(controllerURL.nextToken());
389     Character JavaDoc c = validHostname(controllerInfo.getHostname());
390     if (c != null)
391       throw new SQLException JavaDoc(
392           "Unable to validate hostname (unacceptable character '" + c
393               + "' in hostname '" + controllerInfo.getHostname()
394               + "' from the URL part '" + controller + "')");
395
396     if (!controllerURL.hasMoreTokens())
397       controllerInfo.setPort(ControllerConstants.DEFAULT_PORT);
398     else
399     {
400       controllerURL.nextToken(); // should be ':'
401
if (!controllerURL.hasMoreTokens())
402         controllerInfo.setPort(ControllerConstants.DEFAULT_PORT);
403       else
404       { // Get the port number
405
String JavaDoc port = controllerURL.nextToken();
406         if (controllerURL.hasMoreTokens())
407           throw new SQLException JavaDoc(
408               "Invalid controller definition with more than one semicolon in URL part '"
409                   + controller + "'");
410
411         // Check the port number validity
412
try
413         {
414           controllerInfo.setPort(Integer.parseInt(port));
415         }
416         catch (NumberFormatException JavaDoc ne)
417         {
418           throw new SQLException JavaDoc(
419               "Unable to validate port number (unacceptable port number '"
420                   + port + "' in this URL part '" + controller + "')");
421         }
422       }
423     }
424     return controllerInfo;
425   }
426
427   /**
428    * Checks that the given name contains acceptable characters for a hostname
429    * name ([0-9][A-Z][a-z][["-_."]).
430    *
431    * @param hostname name to check (caller must check that it is not
432    * <code>null</code>).
433    * @return <code>null</code> if the hostname is acceptable, else the
434    * character that causes the fault.
435    */

436   private static Character JavaDoc validHostname(String JavaDoc hostname)
437   {
438     char[] name = hostname.toCharArray();
439     int size = hostname.length();
440     char c;
441     //boolean lastCharWasPoint = false; // used to avoid '..' in hostname
442
char lastChar = ' ';
443
444     for (int i = 0; i < size; i++)
445     {
446       c = name[i];
447
448       if (c == '.' || c == '-')
449       {
450         if (lastChar == '.' || lastChar == '-' || (i == size - 1) || (i == 0))
451         {
452           // . or - cannot be the first or the last char of hostname
453
// hostname cannot contain '..' or '.-' or '-.' or '--'
454
return new Character JavaDoc(c);
455         }
456       }
457       else
458       {
459         if (((c < '0') || (c > 'z') || ((c > '9') && (c < 'A'))
460             || ((c > 'Z') && (c < '_')) || (c == '`')))
461         {
462           return new Character JavaDoc(c);
463         }
464       }
465       lastChar = c;
466     }
467     return null;
468   }
469
470   /**
471    * Checks that the given name contains acceptable characters for a database
472    * name ([0-9][A-Z][a-z]["-_"]).
473    *
474    * @param databaseName name to check (caller must check that it is not
475    * <code>null</code>).
476    * @return <code>null</code> if the name is acceptable, else the character
477    * that causes the fault.
478    */

479   private static Character JavaDoc validDatabaseName(String JavaDoc databaseName)
480   {
481     char[] name = databaseName.toCharArray();
482     int size = databaseName.length();
483     char c;
484
485     for (int i = 0; i < size; i++)
486     {
487       c = name[i];
488       if ((c < '-') || (c > 'z') || (c == '/') || (c == '.') || (c == '`')
489           || ((c > '9') && (c < 'A')) || ((c > 'Z') && (c < '_')))
490         return new Character JavaDoc(c);
491     }
492     return null;
493   }
494 }
495
Popular Tags