001 /* 002 * JBoss DNA (http://www.jboss.org/dna) 003 * See the COPYRIGHT.txt file distributed with this work for information 004 * regarding copyright ownership. Some portions may be licensed 005 * to Red Hat, Inc. under one or more contributor license agreements. 006 * See the AUTHORS.txt file in the distribution for a full listing of 007 * individual contributors. 008 * 009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA 010 * is licensed to you under the terms of the GNU Lesser General Public License as 011 * published by the Free Software Foundation; either version 2.1 of 012 * the License, or (at your option) any later version. 013 * 014 * JBoss DNA is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this software; if not, write to the Free 021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 023 */ 024 package org.jboss.dna.connector.jdbc; 025 026 import java.io.ByteArrayInputStream; 027 import java.io.ByteArrayOutputStream; 028 import java.io.IOException; 029 import java.io.ObjectInputStream; 030 import java.io.ObjectOutputStream; 031 import java.util.Enumeration; 032 import java.util.HashMap; 033 import java.util.Hashtable; 034 import java.util.Map; 035 import java.util.UUID; 036 import java.util.concurrent.atomic.AtomicBoolean; 037 import java.util.concurrent.atomic.AtomicInteger; 038 import javax.naming.BinaryRefAddr; 039 import javax.naming.Context; 040 import javax.naming.Name; 041 import javax.naming.RefAddr; 042 import javax.naming.Reference; 043 import javax.naming.StringRefAddr; 044 import javax.naming.spi.ObjectFactory; 045 046 import net.jcip.annotations.ThreadSafe; 047 import org.jboss.dna.common.i18n.I18n; 048 import org.jboss.dna.common.jdbc.provider.DataSourceDatabaseMetadataProvider; 049 import org.jboss.dna.common.jdbc.provider.DefaultDataSourceDatabaseMetadataProvider; 050 import org.jboss.dna.common.jdbc.provider.DefaultDriverDatabaseMetadataProvider; 051 import org.jboss.dna.common.jdbc.provider.DriverDatabaseMetadataProvider; 052 import org.jboss.dna.graph.cache.CachePolicy; 053 import org.jboss.dna.graph.connector.RepositoryConnection; 054 import org.jboss.dna.graph.connector.RepositoryContext; 055 import org.jboss.dna.graph.connector.RepositorySource; 056 import org.jboss.dna.graph.connector.RepositorySourceCapabilities; 057 import org.jboss.dna.graph.connector.RepositorySourceException; 058 059 /** 060 * A description of a JDBC resource that can be used to access database information. 061 * 062 * @author <a href="mailto:litsenko_sergey@yahoo.com">Sergiy Litsenko</a> 063 */ 064 public class JdbcRepositorySource implements RepositorySource, ObjectFactory { 065 private static final long serialVersionUID = 3380130639143030018L; 066 067 /** 068 * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source. 069 */ 070 public static final int DEFAULT_RETRY_LIMIT = 0; 071 072 /** 073 * This source supports updates by default, but each instance may be configured to {@link #setSupportsUpdates(boolean) be 074 * read-only or updateable}. 075 */ 076 public static final boolean DEFAULT_SUPPORTS_UPDATES = true; 077 /** 078 * The default UUID that is used for root nodes in a JDBC connector. 079 */ 080 public static final String DEFAULT_ROOT_NODE_UUID = "9f9a52c8-0a4d-40d0-ac58-7c77b24b3155"; 081 082 /** 083 * This source supports events. 084 */ 085 protected static final boolean SUPPORTS_EVENTS = true; 086 /** 087 * This source does not support same-name-siblings. 088 */ 089 protected static final boolean SUPPORTS_SAME_NAME_SIBLINGS = false; 090 091 private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT); 092 protected String name; 093 protected final Capabilities capabilities = new Capabilities(); 094 protected transient RepositoryContext repositoryContext; 095 protected CachePolicy defaultCachePolicy; 096 protected transient DriverDatabaseMetadataProvider driverProvider; 097 protected transient DataSourceDatabaseMetadataProvider dataSourceProvider; 098 protected transient UUID rootUuid = UUID.fromString(DEFAULT_ROOT_NODE_UUID); 099 100 protected static final String SOURCE_NAME = "sourceName"; 101 protected static final String ROOT_NODE_UUID = "rootNodeUuid"; 102 protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy"; 103 protected static final String DATA_SOURCE_JNDI_NAME = "dataSourceJndiName"; 104 protected static final String USERNAME = "username"; 105 protected static final String PASSWORD = "password"; 106 protected static final String URL = "url"; 107 protected static final String DRIVER_CLASS_NAME = "driverClassName"; 108 protected static final String RETRY_LIMIT = "retryLimit"; 109 110 /** 111 * Get and optionally create driver based provider 112 * @param create create provider 113 * @return driverProvider 114 */ 115 protected DriverDatabaseMetadataProvider getDriverProvider(boolean create) { 116 // lazy creation 117 if (driverProvider == null) { 118 driverProvider = new DefaultDriverDatabaseMetadataProvider(); 119 } 120 return driverProvider; 121 } 122 123 /** 124 * Get and optionally create data source based provider 125 * @param create create provider 126 * @return dataSourceProvider 127 */ 128 protected DataSourceDatabaseMetadataProvider getDataSourceProvider(boolean create) { 129 // lazy creation 130 if (dataSourceProvider == null && create) { 131 dataSourceProvider = new DefaultDataSourceDatabaseMetadataProvider(); 132 } 133 return dataSourceProvider; 134 } 135 136 /** 137 * default constructor 138 */ 139 public JdbcRepositorySource() { 140 } 141 142 /** 143 * {@inheritDoc} 144 * 145 * @see org.jboss.dna.graph.connector.RepositorySource#getCapabilities() 146 */ 147 public RepositorySourceCapabilities getCapabilities() { 148 return capabilities; 149 } 150 151 /** 152 * {@inheritDoc} 153 * 154 * @see org.jboss.dna.graph.connector.RepositorySource#getConnection() 155 */ 156 public RepositoryConnection getConnection() throws RepositorySourceException { 157 String errMsg = null; 158 // check name 159 if (getName() == null) { 160 errMsg = JdbcMetadataI18n.propertyIsRequired.text("name"); 161 throw new RepositorySourceException(errMsg); 162 } 163 164 // create Jdbc connection using data source first 165 try { 166 if (dataSourceProvider != null) { 167 // create wrapper for Jdbc connection 168 return new JdbcConnection(getName(), 169 getDefaultCachePolicy(), 170 dataSourceProvider.getConnection(), 171 rootUuid); 172 } 173 } catch (Exception e) { 174 errMsg = JdbcMetadataI18n.unableToGetConnectionUsingDriver.text(getName(), getDriverClassName(), getDatabaseUrl()); 175 throw new RepositorySourceException(errMsg, e); 176 } 177 178 // create Jdbc connection using driver and database URL 179 try { 180 if (driverProvider != null) { 181 // create wrapper for Jdbc connection 182 return new JdbcConnection(getName(), 183 getDefaultCachePolicy(), 184 driverProvider.getConnection(), 185 rootUuid); 186 } 187 } catch (Exception e) { 188 errMsg = JdbcMetadataI18n.unableToGetConnectionUsingDataSource.text(getName(), getDataSourceName()); 189 throw new RepositorySourceException(errMsg, e); 190 } 191 192 // Either data source name or JDBC driver connection properties must be defined 193 errMsg = JdbcMetadataI18n.oneOfPropertiesIsRequired.text(getName()); 194 throw new RepositorySourceException(errMsg); 195 } 196 197 /** 198 * {@inheritDoc} 199 * 200 * @see org.jboss.dna.graph.connector.RepositorySource#getName() 201 */ 202 public String getName() { 203 return name; 204 } 205 206 /** 207 * Set the name of this source 208 * 209 * @param name the name for this source 210 */ 211 public void setName( String name ) { 212 this.name = name; 213 } 214 215 /** 216 * {@inheritDoc} 217 * 218 * @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit() 219 */ 220 public int getRetryLimit() { 221 return retryLimit.get(); 222 } 223 224 /** 225 * {@inheritDoc} 226 * 227 * @see org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext) 228 */ 229 public void initialize( RepositoryContext context ) throws RepositorySourceException { 230 this.repositoryContext = context; 231 } 232 233 /** 234 * {@inheritDoc} 235 * 236 * @see org.jboss.dna.graph.connector.RepositorySource#setRetryLimit(int) 237 */ 238 public void setRetryLimit( int limit ) { 239 retryLimit.set(limit < 0 ? 0 : limit); 240 } 241 242 /** 243 * Get whether this source supports updates. 244 * 245 * @return true if this source supports updates, or false if this source only supports reading content. 246 */ 247 public boolean getSupportsUpdates() { 248 return capabilities.supportsUpdates(); 249 } 250 251 /** 252 * Set whether this source supports updates. 253 * 254 * @param supportsUpdates true if this source supports updating content, or false if this source only supports reading 255 * content. 256 */ 257 public synchronized void setSupportsUpdates( boolean supportsUpdates ) { 258 capabilities.setSupportsUpdates(supportsUpdates); 259 } 260 261 /** 262 * Get the default cache policy for this source, or null if the global default cache policy should be used 263 * 264 * @return the default cache policy, or null if this source has no explicit default cache policy 265 */ 266 public CachePolicy getDefaultCachePolicy() { 267 return defaultCachePolicy; 268 } 269 270 /** 271 * @param defaultCachePolicy Sets defaultCachePolicy to the specified value. 272 */ 273 public synchronized void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) { 274 this.defaultCachePolicy = defaultCachePolicy; 275 } 276 277 /** 278 * @return rootNodeUuid 279 */ 280 public String getRootNodeUuid() { 281 return rootUuid != null? rootUuid.toString() : null; 282 } 283 284 /** 285 * @param rootNodeUuid Sets rootNodeUuid to the specified value. 286 * @throws IllegalArgumentException if the string value cannot be converted to UUID 287 */ 288 public void setRootNodeUuid( String rootNodeUuid ) { 289 if (rootNodeUuid != null && rootNodeUuid.trim().length() == 0) rootNodeUuid = DEFAULT_ROOT_NODE_UUID; 290 this.rootUuid = UUID.fromString(rootNodeUuid); 291 } 292 293 /** 294 * {@inheritDoc} 295 */ 296 @Override 297 public boolean equals( Object obj ) { 298 if (obj == this) return true; 299 if (obj instanceof JdbcRepositorySource) { 300 JdbcRepositorySource that = (JdbcRepositorySource)obj; 301 if (this.getName() == null) { 302 if (that.getName() != null) return false; 303 } else { 304 if (!this.getName().equals(that.getName())) return false; 305 } 306 return true; 307 } 308 return false; 309 } 310 311 /** 312 * Gets JDBC driver class name 313 * 314 * @return the JDBC driver class name if any 315 */ 316 public String getDriverClassName() { 317 // get provider 318 DriverDatabaseMetadataProvider provider = getDriverProvider(false); 319 // return 320 return (provider != null)? provider.getDriverClassName() : null; 321 } 322 323 /** 324 * Sets JDBC driver class name 325 * 326 * @param driverClassName the JDBC driver class name 327 */ 328 public void setDriverClassName( String driverClassName ) { 329 if (driverClassName == null) { 330 driverProvider = null; 331 } else { 332 // get/create provider 333 DriverDatabaseMetadataProvider provider = getDriverProvider(true); 334 // set 335 provider.setDriverClassName(driverClassName); 336 } 337 } 338 339 /** 340 * Gets database URL as string 341 * 342 * @return database URL as string 343 */ 344 public String getDatabaseUrl() { 345 // get provider 346 DriverDatabaseMetadataProvider provider = getDriverProvider(false); 347 // return 348 return (provider != null)? provider.getDatabaseUrl() : null; 349 } 350 351 /** 352 * Sets the database URL as string 353 * 354 * @param databaseUrl the database URL as string 355 */ 356 public void setDatabaseUrl( String databaseUrl ) { 357 if (databaseUrl == null) { 358 driverProvider = null; 359 } else { 360 // get/create provider 361 DriverDatabaseMetadataProvider provider = getDriverProvider(true); 362 // set 363 provider.setDatabaseUrl(databaseUrl); 364 } 365 } 366 367 /** 368 * Gets the user name 369 * 370 * @return the user name 371 */ 372 public String getUserName() { 373 // get provider 374 DriverDatabaseMetadataProvider provider = getDriverProvider(false); 375 return (provider != null)? provider.getUserName() : null; 376 } 377 378 /** 379 * Sets the user name 380 * 381 * @param userName the user name 382 */ 383 public void setUserName( String userName ) { 384 if (userName == null) { 385 driverProvider = null; 386 } else { 387 // get provider 388 DriverDatabaseMetadataProvider provider = getDriverProvider(true); 389 provider.setUserName(userName); 390 } 391 } 392 393 /** 394 * Get user's password 395 * 396 * @return user's password 397 */ 398 public String getPassword() { 399 // get provider 400 DriverDatabaseMetadataProvider provider = getDriverProvider(false); 401 return (provider != null)? provider.getPassword() : null; 402 } 403 404 /** 405 * Sets the user's password 406 * 407 * @param password the user's password 408 */ 409 public void setPassword( String password ) { 410 if (password == null) { 411 driverProvider = null; 412 } else { 413 // get provider 414 DriverDatabaseMetadataProvider provider = getDriverProvider(true); 415 provider.setPassword(password); 416 } 417 } 418 419 /** 420 * Sets data source JNDI name 421 * 422 * @return data source JNDI name 423 */ 424 public String getDataSourceName() { 425 // get provider 426 DataSourceDatabaseMetadataProvider provider = getDataSourceProvider(false); 427 return (provider != null)? provider.getDataSourceName() : null; 428 } 429 430 /** 431 * Sets data source JNDI name 432 * 433 * @param dataSourceName the data source JNDI name 434 */ 435 public void setDataSourceName( String dataSourceName ) { 436 if (dataSourceName == null) { 437 dataSourceProvider = null; 438 } else { 439 // get provider 440 DataSourceDatabaseMetadataProvider provider = getDataSourceProvider(true); 441 provider.setDataSourceName(dataSourceName); 442 } 443 } 444 445 /** 446 * {@inheritDoc} 447 * 448 * @see javax.naming.Referenceable#getReference() 449 */ 450 public Reference getReference() { 451 String className = getClass().getName(); 452 String factoryClassName = this.getClass().getName(); 453 Reference ref = new Reference(className, factoryClassName, null); 454 455 if (getName() != null) { 456 ref.add(new StringRefAddr(SOURCE_NAME, getName())); 457 } 458 459 if (getRootNodeUuid() != null) { 460 ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid())); 461 } 462 if (getDataSourceName() != null) { 463 ref.add(new StringRefAddr(DATA_SOURCE_JNDI_NAME, getDataSourceName())); 464 } 465 466 if (getUserName() != null) { 467 ref.add(new StringRefAddr(USERNAME, getUserName())); 468 } 469 470 if (getPassword() != null) { 471 ref.add(new StringRefAddr(PASSWORD, getPassword())); 472 } 473 474 if (getDatabaseUrl() != null) { 475 ref.add(new StringRefAddr(URL, getDatabaseUrl())); 476 } 477 if (getDriverClassName() != null) { 478 ref.add(new StringRefAddr(DRIVER_CLASS_NAME, getDriverClassName())); 479 } 480 481 if (getDefaultCachePolicy() != null) { 482 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 483 CachePolicy policy = getDefaultCachePolicy(); 484 try { 485 ObjectOutputStream oos = new ObjectOutputStream(baos); 486 oos.writeObject(policy); 487 ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray())); 488 } catch (IOException e) { 489 I18n msg = JdbcMetadataI18n.errorSerializingCachePolicyInSource; 490 throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e); 491 } 492 } 493 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit()))); 494 // return it 495 return ref; 496 } 497 498 /** 499 * {@inheritDoc} 500 * 501 * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, 502 * java.util.Hashtable) 503 */ 504 public Object getObjectInstance( Object obj, 505 Name name, 506 Context nameCtx, 507 Hashtable<?, ?> environment ) throws Exception { 508 if (obj instanceof Reference) { 509 Map<String, Object> values = new HashMap<String, Object>(); 510 Reference ref = (Reference)obj; 511 Enumeration<?> en = ref.getAll(); 512 while (en.hasMoreElements()) { 513 RefAddr subref = (RefAddr)en.nextElement(); 514 if (subref instanceof StringRefAddr) { 515 String key = subref.getType(); 516 Object value = subref.getContent(); 517 if (value != null) values.put(key, value.toString()); 518 } else if (subref instanceof BinaryRefAddr) { 519 String key = subref.getType(); 520 Object value = subref.getContent(); 521 if (value instanceof byte[]) { 522 // Deserialize ... 523 ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value); 524 ObjectInputStream ois = new ObjectInputStream(bais); 525 value = ois.readObject(); 526 values.put(key, value); 527 } 528 } 529 } 530 // get individual properties 531 String sourceName = (String)values.get(SOURCE_NAME); 532 String rootNodeUuid = (String)values.get(ROOT_NODE_UUID); 533 String dataSourceJndiName = (String)values.get(DATA_SOURCE_JNDI_NAME); 534 String userName = (String)values.get(USERNAME); 535 String password = (String)values.get(PASSWORD); 536 String url = (String)values.get(URL); 537 String driverClassName = (String)values.get(DRIVER_CLASS_NAME); 538 539 Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY); 540 String retryLimit = (String)values.get(RETRY_LIMIT); 541 542 // Create the source instance ... 543 JdbcRepositorySource source = new JdbcRepositorySource(); 544 if (sourceName != null) source.setName(sourceName); 545 if (rootNodeUuid != null) source.setRootNodeUuid(rootNodeUuid); 546 if (dataSourceJndiName != null) source.setDataSourceName(dataSourceJndiName); 547 if (userName != null) source.setUserName(userName); 548 if (password != null) source.setPassword(password); 549 if (url != null) source.setDatabaseUrl(url); 550 if (driverClassName != null) source.setDriverClassName(driverClassName); 551 if (defaultCachePolicy instanceof CachePolicy) { 552 source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy); 553 } 554 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit)); 555 return source; 556 } 557 return null; 558 } 559 560 @ThreadSafe 561 protected class Capabilities extends RepositorySourceCapabilities { 562 private final AtomicBoolean supportsUpdates = new AtomicBoolean(DEFAULT_SUPPORTS_UPDATES); 563 564 /*package*/Capabilities() { 565 super(DEFAULT_SUPPORTS_UPDATES, SUPPORTS_EVENTS); 566 } 567 568 /*package*/void setSupportsUpdates( boolean supportsUpdates ) { 569 this.supportsUpdates.set(supportsUpdates); 570 } 571 572 @Override 573 public boolean supportsUpdates() { 574 return this.supportsUpdates.get(); 575 } 576 } 577 }