1 /*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.resource.adapter.jdbc.local;
23
24 import org.jboss.resource.adapter.jdbc.URLSelectorStrategy;
25 import java.io.ByteArrayInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.sql.Connection;
29 import java.sql.Driver;
30 import java.sql.DriverManager;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.Iterator;
34 import java.util.Properties;
35 import java.util.List;
36 import java.util.Set;
37
38 import javax.resource.ResourceException;
39 import javax.resource.spi.ConnectionManager;
40 import javax.resource.spi.ConnectionRequestInfo;
41 import javax.resource.spi.ManagedConnection;
42 import javax.security.auth.Subject;
43
44 import org.jboss.resource.JBossResourceException;
45 import org.jboss.resource.adapter.jdbc.BaseWrapperManagedConnectionFactory;
46 import org.jboss.util.NestedRuntimeException;
47
48 /**
49 * LocalManagedConnectionFactory
50 *
51 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
52 * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
53 * @version $Revision: 73443 $
54 */
55 public class LocalManagedConnectionFactory extends BaseWrapperManagedConnectionFactory
56 {
57 static final long serialVersionUID = 4698955390505160469L;
58
59 private String driverClass;
60
61 private transient Driver driver;
62
63 private String connectionURL;
64
65 private URLSelectorStrategy urlSelector;
66
67 protected String connectionProperties;
68
69 public LocalManagedConnectionFactory()
70 {
71
72 }
73
74 @Override
75 public Object createConnectionFactory(ConnectionManager cm) throws ResourceException
76 {
77 // check some invariants before they come back to haunt us
78 if(driverClass == null)
79 throw new JBossResourceException("driverClass is null");
80 if(connectionURL == null)
81 throw new JBossResourceException("connectionURL is null");
82
83 return super.createConnectionFactory(cm);
84 }
85
86 /**
87 * Get the value of ConnectionURL.
88 *
89 * @return value of ConnectionURL.
90 */
91 public String getConnectionURL()
92 {
93 return connectionURL;
94 }
95
96 /**
97 * Set the value of ConnectionURL.
98 *
99 * @param connectionURL Value to assign to ConnectionURL.
100 */
101 public void setConnectionURL(final String connectionURL)
102 //throws ResourceException
103 {
104 this.connectionURL = connectionURL;
105 if(urlDelimiter!=null)
106 {
107 initUrlSelector();
108 }
109 }
110
111 /**
112 * Get the DriverClass value.
113 *
114 * @return the DriverClass value.
115 */
116 public String getDriverClass()
117 {
118 return driverClass;
119 }
120
121 /**
122 * Set the DriverClass value.
123 *
124 * @param driverClass The new DriverClass value.
125 */
126 public synchronized void setDriverClass(final String driverClass)
127 {
128 this.driverClass = driverClass;
129 driver = null;
130 }
131
132 /**
133 * Get the value of connectionProperties.
134 *
135 * @return value of connectionProperties.
136 */
137 public String getConnectionProperties()
138 {
139 return connectionProperties;
140 }
141
142 /**
143 * Set the value of connectionProperties.
144 *
145 * @param connectionProperties Value to assign to connectionProperties.
146 */
147 public void setConnectionProperties(String connectionProperties)
148 {
149 this.connectionProperties = connectionProperties;
150 connectionProps.clear();
151 if (connectionProperties != null)
152 {
153 // Map any \ to \\
154 connectionProperties = connectionProperties.replaceAll("\\\\", "\\\\\\\\");
155
156 InputStream is = new ByteArrayInputStream(connectionProperties.getBytes());
157 try
158 {
159 connectionProps.load(is);
160 }
161 catch (IOException ioe)
162 {
163 throw new NestedRuntimeException("Could not load connection properties", ioe);
164 }
165 }
166 }
167
168 public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo cri)
169 throws javax.resource.ResourceException
170 {
171 Properties props = getConnectionProperties(subject, cri);
172 // Some friendly drivers (Oracle, you guessed right) modify the props you supply.
173 // Since we use our copy to identify compatibility in matchManagedConnection, we need
174 // a pristine copy for our own use. So give the friendly driver a copy.
175 Properties copy = (Properties) props.clone();
176 boolean trace = log.isTraceEnabled();
177 if (trace)
178 {
179 // Make yet another copy to mask the password
180 Properties logCopy = copy;
181 if (copy.getProperty("password") != null)
182 {
183 logCopy = (Properties) props.clone();
184 logCopy.setProperty("password", "--hidden--");
185 }
186 log.trace("Using properties: " + logCopy);
187 }
188
189 if(urlSelector!=null)
190 {
191 return getHALocalManagedConnection(props,copy);
192 }
193 else
194 {
195 return getLocalManagedConnection(props,copy);
196 }
197 }
198
199 private LocalManagedConnection getLocalManagedConnection(Properties props,Properties copy)
200 throws JBossResourceException
201 {
202 Connection con = null;
203 try
204 {
205 String url = getConnectionURL();
206 Driver d = getDriver(url);
207 con = d.connect(url, copy);
208 if (con == null)
209 throw new JBossResourceException("Wrong driver class for this connection URL");
210
211 return new LocalManagedConnection(this, con, props, transactionIsolation, preparedStatementCacheSize);
212 }
213 catch (Throwable e)
214 {
215 if (con != null)
216 {
217 try
218 {
219 con.close();
220 }
221 catch (Throwable ignored)
222 {
223 }
224 }
225 throw new JBossResourceException("Could not create connection", e);
226 }
227 }
228
229 private LocalManagedConnection getHALocalManagedConnection(Properties props,Properties copy)
230 throws JBossResourceException
231 {
232 boolean trace = log.isTraceEnabled();
233
234 // try to get a connection as many times as many urls we have in the list
235 for(int i = 0; i < urlSelector.getCustomSortedUrls().size(); ++i)
236 {
237 String url = (String)urlSelector.getUrlObject();
238 if(trace)
239 {
240 log.trace("Trying to create a connection to " + url);
241 }
242 Connection con = null;
243 try
244 {
245 Driver d = getDriver(url);
246 con = d.connect(url, copy);
247 if(con == null)
248 {
249 log.warn("Wrong driver class for this connection URL: " + url);
250 urlSelector.failedUrlObject(url);
251 }
252 else
253 {
254 return new LocalManagedConnection(this, con, props, transactionIsolation, preparedStatementCacheSize);
255 }
256 }
257 catch(Exception e)
258 {
259 if (con != null)
260 {
261 try
262 {
263 con.close();
264 }
265 catch (Throwable ignored)
266 {
267 }
268 }
269 log.warn("Failed to create connection for " + url + ": " + e.getMessage());
270 urlSelector.failedUrlObject(url);
271 }
272 }
273
274 // we have supposedly tried all the urls
275 throw new JBossResourceException(
276 "Could not create connection using any of the URLs: " + urlSelector.getAllUrlObjects());
277 }
278
279 public void setURLDelimiter(String urlDelimiter)
280 //throws ResourceException
281 {
282 super.urlDelimiter = urlDelimiter;
283 if(getConnectionURL() != null)
284 {
285 initUrlSelector();
286 }
287 }
288
289 protected void initUrlSelector()
290 //throws ResourceException
291 {
292 boolean trace = log.isTraceEnabled();
293
294 List urlsList = new ArrayList();
295 String urlsStr = getConnectionURL();
296 String url;
297 int urlStart = 0;
298 int urlEnd = urlsStr.indexOf(urlDelimiter);
299 while(urlEnd > 0)
300 {
301 url = urlsStr.substring(urlStart, urlEnd);
302 urlsList.add(url);
303 urlStart = ++urlEnd;
304 urlEnd = urlsStr.indexOf(urlDelimiter, urlEnd);
305 if (trace)
306 log.trace("added HA connection url: " + url);
307 }
308
309 if(urlStart != urlsStr.length())
310 {
311 url = urlsStr.substring(urlStart, urlsStr.length());
312 urlsList.add(url);
313 if (trace)
314 log.trace("added HA connection url: " + url);
315 }
316 if(getUrlSelectorStrategyClassName()==null)
317 {
318 this.urlSelector = new URLSelector(urlsList);
319 log.debug("Default URLSelectorStrategy is being used : "+urlSelector);
320 }
321 else
322 {
323 this.urlSelector = (URLSelectorStrategy)loadClass(getUrlSelectorStrategyClassName(),urlsList);
324 log.debug("Customized URLSelectorStrategy is being used : "+urlSelector);
325 }
326 }
327
328 // Default Implementaion of the URLSelectorStrategy
329 public static class URLSelector implements URLSelectorStrategy
330 {
331 private final List urls;
332 private int urlIndex;
333 private String url;
334
335 public URLSelector(List urls)
336 {
337 if(urls == null || urls.size() == 0)
338 {
339 throw new IllegalStateException("Expected non-empty list of connection URLs but got: " + urls);
340 }
341 this.urls = Collections.unmodifiableList(urls);
342 }
343
344 public synchronized String getUrl()
345 {
346 if(url == null)
347 {
348 if(urlIndex == urls.size())
349 {
350 urlIndex = 0;
351 }
352 url = (String)urls.get(urlIndex++);
353 }
354 return url;
355 }
356
357 public synchronized void failedUrl(String url)
358 {
359 if(url.equals(this.url))
360 {
361 this.url = null;
362 }
363 }
364
365 /* URLSelectorStrategy Implementation goes here*/
366 public List getCustomSortedUrls()
367 {
368 return urls;
369 }
370 public void failedUrlObject(Object urlObject)
371 {
372 failedUrl((String)urlObject);
373 }
374 public List getAllUrlObjects()
375 {
376 return urls;
377 }
378 public Object getUrlObject()
379 {
380 return getUrl();
381 }
382
383 }
384
385 public ManagedConnection matchManagedConnections(final Set mcs, final Subject subject,
386 final ConnectionRequestInfo cri) throws ResourceException
387 {
388 Properties newProps = getConnectionProperties(subject, cri);
389
390 for (Iterator i = mcs.iterator(); i.hasNext();)
391 {
392 Object o = i.next();
393
394 if (o instanceof LocalManagedConnection)
395 {
396 LocalManagedConnection mc = (LocalManagedConnection) o;
397
398 //First check the properties
399 if (mc.getProperties().equals(newProps))
400 {
401 //Next check to see if we are validating on matchManagedConnections
402 if ((getValidateOnMatch() && mc.checkValid()) || !getValidateOnMatch())
403 {
404
405 return mc;
406
407 }
408
409 }
410 }
411 }
412
413 return null;
414 }
415
416 public int hashCode()
417 {
418 int result = 17;
419 result = result * 37 + ((connectionURL == null) ? 0 : connectionURL.hashCode());
420 result = result * 37 + ((driverClass == null) ? 0 : driverClass.hashCode());
421 result = result * 37 + ((userName == null) ? 0 : userName.hashCode());
422 result = result * 37 + ((password == null) ? 0 : password.hashCode());
423 result = result * 37 + transactionIsolation;
424 return result;
425 }
426
427 public boolean equals(Object other)
428 {
429 if (this == other)
430 return true;
431 if (getClass() != other.getClass())
432 return false;
433 LocalManagedConnectionFactory otherMcf = (LocalManagedConnectionFactory) other;
434 return this.connectionURL.equals(otherMcf.connectionURL) && this.driverClass.equals(otherMcf.driverClass)
435 && ((this.userName == null) ? otherMcf.userName == null : this.userName.equals(otherMcf.userName))
436 && ((this.password == null) ? otherMcf.password == null : this.password.equals(otherMcf.password))
437 && this.transactionIsolation == otherMcf.transactionIsolation;
438
439 }
440
441 /**
442 * Check the driver for the given URL. If it is not registered already
443 * then register it.
444 *
445 * @param url The JDBC URL which we need a driver for.
446 */
447 protected synchronized Driver getDriver(final String url) throws ResourceException
448 {
449 boolean trace = log.isTraceEnabled();
450
451 // don't bother if it is loaded already
452 if (driver != null)
453 {
454 return driver;
455 }
456 if (trace)
457 log.trace("Checking driver for URL: " + url);
458
459 if (driverClass == null)
460 {
461 throw new JBossResourceException("No Driver class specified (url = " + url + ")!");
462 }
463
464 // Check if the driver is already loaded, if not then try to load it
465
466 if (isDriverLoadedForURL(url))
467 {
468 return driver;
469 } // end of if ()
470
471 try
472 {
473 //try to load the class... this should register with DriverManager.
474 Class clazz = Class.forName(driverClass, true, Thread.currentThread().getContextClassLoader());
475 if (isDriverLoadedForURL(url))
476 //return immediately, some drivers (Cloudscape) do not let you create an instance.
477 return driver;
478
479 //We loaded the class, but either it didn't register
480 //and is not spec compliant, or is the wrong class.
481 driver = (Driver) clazz.newInstance();
482 DriverManager.registerDriver(driver);
483 if (isDriverLoadedForURL(url))
484 return driver;
485 //We can even instantiate one, it must be the wrong class for the URL.
486 }
487 catch (Exception e)
488 {
489 throw new JBossResourceException("Failed to register driver for: " + driverClass, e);
490 }
491
492 throw new JBossResourceException("Apparently wrong driver class specified for URL: class: " + driverClass
493 + ", url: " + url);
494 }
495
496 private boolean isDriverLoadedForURL(String url)
497 {
498 boolean trace = log.isTraceEnabled();
499
500 try
501 {
502 driver = DriverManager.getDriver(url);
503 if (trace)
504 log.trace("Driver already registered for url: " + url);
505 return true;
506 }
507 catch (Exception e)
508 {
509 if (trace)
510 log.trace("Driver not yet registered for url: " + url);
511 return false;
512 }
513 }
514
515 protected String internalGetConnectionURL()
516 {
517 return connectionURL;
518 }
519 }