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 }