001 /* 002 * JBoss, Home of Professional Open Source. 003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors 004 * as indicated by the @author tags. See the copyright.txt file in the 005 * distribution for a full listing of individual contributors. 006 * 007 * This is free software; you can redistribute it and/or modify it 008 * under the terms of the GNU Lesser General Public License as 009 * published by the Free Software Foundation; either version 2.1 of 010 * the License, or (at your option) any later version. 011 * 012 * This software is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this software; if not, write to the Free 019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 021 */ 022 package org.jboss.dna.connector.jbosscache; 023 024 import java.io.ByteArrayInputStream; 025 import java.io.ByteArrayOutputStream; 026 import java.io.IOException; 027 import java.io.ObjectInputStream; 028 import java.io.ObjectOutputStream; 029 import java.util.Enumeration; 030 import java.util.HashMap; 031 import java.util.Hashtable; 032 import java.util.Map; 033 import java.util.UUID; 034 import java.util.concurrent.atomic.AtomicInteger; 035 import javax.naming.BinaryRefAddr; 036 import javax.naming.Context; 037 import javax.naming.InitialContext; 038 import javax.naming.RefAddr; 039 import javax.naming.Reference; 040 import javax.naming.Referenceable; 041 import javax.naming.StringRefAddr; 042 import javax.naming.spi.ObjectFactory; 043 import net.jcip.annotations.ThreadSafe; 044 import org.jboss.cache.Cache; 045 import org.jboss.cache.CacheFactory; 046 import org.jboss.cache.DefaultCacheFactory; 047 import org.jboss.dna.common.i18n.I18n; 048 import org.jboss.dna.graph.DnaLexicon; 049 import org.jboss.dna.graph.cache.CachePolicy; 050 import org.jboss.dna.graph.connectors.RepositoryConnection; 051 import org.jboss.dna.graph.connectors.RepositoryContext; 052 import org.jboss.dna.graph.connectors.RepositorySource; 053 import org.jboss.dna.graph.connectors.RepositorySourceCapabilities; 054 import org.jboss.dna.graph.connectors.RepositorySourceException; 055 import org.jboss.dna.graph.properties.Name; 056 import org.jboss.dna.graph.properties.Property; 057 058 /** 059 * A repository source that uses a JBoss Cache instance to manage the content. This source is capable of using an existing 060 * {@link Cache} instance or creating a new instance. This process is controlled entirely by the JavaBean properties of the 061 * JBossCacheSource instance. 062 * <p> 063 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it attempts to 064 * create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 065 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache configuration 066 * name} if supplied or the default configuration if not set. 067 * </p> 068 * <p> 069 * Like other {@link RepositorySource} classes, instances of JBossCacheSource can be placed into JNDI and do support the creation 070 * of {@link Referenceable JNDI referenceable} objects and resolution of references into JBossCacheSource. 071 * </p> 072 * 073 * @author Randall Hauch 074 */ 075 @ThreadSafe 076 public class JBossCacheSource implements RepositorySource, ObjectFactory { 077 078 private static final long serialVersionUID = 1L; 079 /** 080 * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source. 081 */ 082 public static final int DEFAULT_RETRY_LIMIT = 0; 083 public static final String DEFAULT_UUID_PROPERTY_NAME = DnaLexicon.UUID.getString(); 084 085 protected static final String ROOT_NODE_UUID = "rootNodeUuid"; 086 protected static final String SOURCE_NAME = "sourceName"; 087 protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy"; 088 protected static final String CACHE_CONFIGURATION_NAME = "cacheConfigurationName"; 089 protected static final String CACHE_FACTORY_JNDI_NAME = "cacheFactoryJndiName"; 090 protected static final String CACHE_JNDI_NAME = "cacheJndiName"; 091 protected static final String UUID_PROPERTY_NAME = "uuidPropertyName"; 092 protected static final String RETRY_LIMIT = "retryLimit"; 093 094 private String name; 095 private UUID rootNodeUuid = UUID.randomUUID(); 096 private CachePolicy defaultCachePolicy; 097 private String cacheConfigurationName; 098 private String cacheFactoryJndiName; 099 private String cacheJndiName; 100 private String uuidPropertyName = DEFAULT_UUID_PROPERTY_NAME; 101 private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT); 102 private transient Cache<Name, Object> cache; 103 private transient Context jndiContext; 104 private transient RepositoryContext repositoryContext; 105 106 /** 107 * Create a repository source instance. 108 */ 109 public JBossCacheSource() { 110 } 111 112 /** 113 * {@inheritDoc} 114 * 115 * @see org.jboss.dna.graph.connectors.RepositorySource#initialize(org.jboss.dna.graph.connectors.RepositoryContext) 116 */ 117 public void initialize( RepositoryContext context ) throws RepositorySourceException { 118 this.repositoryContext = context; 119 } 120 121 /** 122 * @return repositoryContext 123 */ 124 public RepositoryContext getRepositoryContext() { 125 return repositoryContext; 126 } 127 128 /** 129 * {@inheritDoc} 130 */ 131 public String getName() { 132 return this.name; 133 } 134 135 /** 136 * {@inheritDoc} 137 * 138 * @see org.jboss.dna.graph.connectors.RepositorySource#getRetryLimit() 139 */ 140 public int getRetryLimit() { 141 return retryLimit.get(); 142 } 143 144 /** 145 * {@inheritDoc} 146 * 147 * @see org.jboss.dna.graph.connectors.RepositorySource#setRetryLimit(int) 148 */ 149 public void setRetryLimit( int limit ) { 150 retryLimit.set(limit < 0 ? 0 : limit); 151 } 152 153 /** 154 * Set the name of this source 155 * 156 * @param name the name for this source 157 */ 158 public synchronized void setName( String name ) { 159 if (this.name == name || this.name != null && this.name.equals(name)) return; // unchanged 160 this.name = name; 161 } 162 163 /** 164 * Get the default cache policy for this source, or null if the global default cache policy should be used 165 * 166 * @return the default cache policy, or null if this source has no explicit default cache policy 167 */ 168 public CachePolicy getDefaultCachePolicy() { 169 return defaultCachePolicy; 170 } 171 172 /** 173 * @param defaultCachePolicy Sets defaultCachePolicy to the specified value. 174 */ 175 public synchronized void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) { 176 if (this.defaultCachePolicy == defaultCachePolicy || this.defaultCachePolicy != null 177 && this.defaultCachePolicy.equals(defaultCachePolicy)) return; // unchanged 178 this.defaultCachePolicy = defaultCachePolicy; 179 } 180 181 /** 182 * Get the name in JNDI of a {@link Cache} instance that should be used by this source. 183 * <p> 184 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 185 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 186 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 187 * configuration name} if supplied or the default configuration if not set. 188 * </p> 189 * 190 * @return the JNDI name of the {@link Cache} instance that should be used, or null if the cache is to be created with a cache 191 * factory {@link #getCacheFactoryJndiName() found in JNDI} using the specified {@link #getCacheConfigurationName() 192 * cache configuration name}. 193 * @see #setCacheJndiName(String) 194 * @see #getCacheConfigurationName() 195 * @see #getCacheFactoryJndiName() 196 */ 197 public String getCacheJndiName() { 198 return cacheJndiName; 199 } 200 201 /** 202 * Set the name in JNDI of a {@link Cache} instance that should be used by this source. 203 * <p> 204 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 205 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 206 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 207 * configuration name} if supplied or the default configuration if not set. 208 * </p> 209 * 210 * @param cacheJndiName the JNDI name of the {@link Cache} instance that should be used, or null if the cache is to be created 211 * with a cache factory {@link #getCacheFactoryJndiName() found in JNDI} using the specified 212 * {@link #getCacheConfigurationName() cache configuration name}. 213 * @see #getCacheJndiName() 214 * @see #getCacheConfigurationName() 215 * @see #getCacheFactoryJndiName() 216 */ 217 public synchronized void setCacheJndiName( String cacheJndiName ) { 218 if (this.cacheJndiName == cacheJndiName || this.cacheJndiName != null && this.cacheJndiName.equals(cacheJndiName)) return; // unchanged 219 this.cacheJndiName = cacheJndiName; 220 } 221 222 /** 223 * Get the name in JNDI of a {@link CacheFactory} instance that should be used to create the cache for this source. 224 * <p> 225 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 226 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 227 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 228 * configuration name} if supplied or the default configuration if not set. 229 * </p> 230 * 231 * @return the JNDI name of the {@link CacheFactory} instance that should be used, or null if the {@link DefaultCacheFactory} 232 * should be used if a cache is to be created 233 * @see #setCacheFactoryJndiName(String) 234 * @see #getCacheConfigurationName() 235 * @see #getCacheJndiName() 236 */ 237 public String getCacheFactoryJndiName() { 238 return cacheFactoryJndiName; 239 } 240 241 /** 242 * Set the name in JNDI of a {@link CacheFactory} instance that should be used to obtain the {@link Cache} instance used by 243 * this source. 244 * <p> 245 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 246 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 247 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 248 * configuration name} if supplied or the default configuration if not set. 249 * </p> 250 * 251 * @param jndiName the JNDI name of the {@link CacheFactory} instance that should be used, or null if the 252 * {@link DefaultCacheFactory} should be used if a cache is to be created 253 * @see #setCacheFactoryJndiName(String) 254 * @see #getCacheConfigurationName() 255 * @see #getCacheJndiName() 256 */ 257 public synchronized void setCacheFactoryJndiName( String jndiName ) { 258 if (this.cacheFactoryJndiName == jndiName || this.cacheFactoryJndiName != null 259 && this.cacheFactoryJndiName.equals(jndiName)) return; // unchanged 260 this.cacheFactoryJndiName = jndiName; 261 } 262 263 /** 264 * Get the name of the configuration that should be used if a {@link Cache cache} is to be created using the 265 * {@link CacheFactory} found in JNDI or the {@link DefaultCacheFactory} if needed. 266 * <p> 267 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 268 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 269 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 270 * configuration name} if supplied or the default configuration if not set. 271 * </p> 272 * 273 * @return the name of the configuration that should be passed to the {@link CacheFactory}, or null if the default 274 * configuration should be used 275 * @see #setCacheConfigurationName(String) 276 * @see #getCacheFactoryJndiName() 277 * @see #getCacheJndiName() 278 */ 279 public String getCacheConfigurationName() { 280 return cacheConfigurationName; 281 } 282 283 /** 284 * Get the name of the configuration that should be used if a {@link Cache cache} is to be created using the 285 * {@link CacheFactory} found in JNDI or the {@link DefaultCacheFactory} if needed. 286 * <p> 287 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 288 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 289 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 290 * configuration name} if supplied or the default configuration if not set. 291 * </p> 292 * 293 * @param cacheConfigurationName the name of the configuration that should be passed to the {@link CacheFactory}, or null if 294 * the default configuration should be used 295 * @see #getCacheConfigurationName() 296 * @see #getCacheFactoryJndiName() 297 * @see #getCacheJndiName() 298 */ 299 public synchronized void setCacheConfigurationName( String cacheConfigurationName ) { 300 if (this.cacheConfigurationName == cacheConfigurationName || this.cacheConfigurationName != null 301 && this.cacheConfigurationName.equals(cacheConfigurationName)) return; // unchanged 302 this.cacheConfigurationName = cacheConfigurationName; 303 } 304 305 /** 306 * Get the UUID of the root node for the cache. If the cache exists, this UUID is not used but is instead set to the UUID of 307 * the existing root node. 308 * 309 * @return the UUID of the root node for the cache. 310 */ 311 public String getRootNodeUuid() { 312 return this.rootNodeUuid.toString(); 313 } 314 315 /** 316 * Get the UUID of the root node for the cache. If the cache exists, this UUID is not used but is instead set to the UUID of 317 * the existing root node. 318 * 319 * @return the UUID of the root node for the cache. 320 */ 321 public UUID getRootNodeUuidObject() { 322 return this.rootNodeUuid; 323 } 324 325 /** 326 * Set the UUID of the root node in this repository. If the cache exists, this UUID is not used but is instead set to the UUID 327 * of the existing root node. 328 * 329 * @param rootNodeUuid the UUID of the root node for the cache, or null if the UUID should be randomly generated 330 */ 331 public synchronized void setRootNodeUuid( String rootNodeUuid ) { 332 UUID uuid = null; 333 if (rootNodeUuid == null) uuid = UUID.randomUUID(); 334 else uuid = UUID.fromString(rootNodeUuid); 335 if (this.rootNodeUuid.equals(uuid)) return; // unchanged 336 this.rootNodeUuid = uuid; 337 } 338 339 /** 340 * Get the {@link Property#getName() property name} where the UUID is stored for each node. 341 * 342 * @return the name of the UUID property; never null 343 */ 344 public String getUuidPropertyName() { 345 return this.uuidPropertyName; 346 } 347 348 /** 349 * Set the {@link Property#getName() property name} where the UUID is stored for each node. 350 * 351 * @param uuidPropertyName the name of the UUID property, or null if the {@link #DEFAULT_UUID_PROPERTY_NAME default name} 352 * should be used 353 */ 354 public synchronized void setUuidPropertyName( String uuidPropertyName ) { 355 if (uuidPropertyName == null || uuidPropertyName.trim().length() == 0) uuidPropertyName = DEFAULT_UUID_PROPERTY_NAME; 356 if (this.uuidPropertyName.equals(uuidPropertyName)) return; // unchanged 357 this.uuidPropertyName = uuidPropertyName; 358 } 359 360 /** 361 * {@inheritDoc} 362 * 363 * @see org.jboss.dna.graph.connectors.RepositorySource#getConnection() 364 */ 365 @SuppressWarnings( "unchecked" ) 366 public RepositoryConnection getConnection() throws RepositorySourceException { 367 if (getName() == null) { 368 I18n msg = JBossCacheConnectorI18n.propertyIsRequired; 369 throw new RepositorySourceException(getName(), msg.text("name")); 370 } 371 if (getUuidPropertyName() == null) { 372 I18n msg = JBossCacheConnectorI18n.propertyIsRequired; 373 throw new RepositorySourceException(getName(), msg.text("uuidPropertyName")); 374 } 375 if (this.cache == null) { 376 // First look for an existing cache instance in JNDI ... 377 Context context = getContext(); 378 String jndiName = this.getCacheJndiName(); 379 if (jndiName != null && jndiName.trim().length() != 0) { 380 Object object = null; 381 try { 382 if (context == null) context = new InitialContext(); 383 object = context.lookup(jndiName); 384 if (object != null) cache = (Cache<Name, Object>)object; 385 } catch (ClassCastException err) { 386 I18n msg = JBossCacheConnectorI18n.objectFoundInJndiWasNotCache; 387 String className = object != null ? object.getClass().getName() : "null"; 388 throw new RepositorySourceException(getName(), msg.text(jndiName, this.getName(), className), err); 389 } catch (Throwable err) { 390 // try loading 391 } 392 } 393 if (cache == null) { 394 // Then look for a cache factory in JNDI ... 395 CacheFactory<Name, Object> cacheFactory = null; 396 jndiName = getCacheFactoryJndiName(); 397 if (jndiName != null && jndiName.trim().length() != 0) { 398 Object object = null; 399 try { 400 if (context == null) context = new InitialContext(); 401 object = context.lookup(jndiName); 402 if (object != null) cacheFactory = (CacheFactory<Name, Object>)object; 403 } catch (ClassCastException err) { 404 I18n msg = JBossCacheConnectorI18n.objectFoundInJndiWasNotCacheFactory; 405 String className = object != null ? object.getClass().getName() : "null"; 406 throw new RepositorySourceException(getName(), msg.text(jndiName, this.getName(), className), err); 407 } catch (Throwable err) { 408 // try loading 409 } 410 } 411 if (cacheFactory == null) cacheFactory = new DefaultCacheFactory<Name, Object>(); 412 413 // Now, get the configuration name ... 414 String configName = this.getCacheConfigurationName(); 415 if (configName != null) { 416 cache = cacheFactory.createCache(configName); 417 } else { 418 cache = cacheFactory.createCache(); 419 } 420 } 421 } 422 return new JBossCacheConnection(this, this.cache); 423 } 424 425 protected Context getContext() { 426 return this.jndiContext; 427 } 428 429 protected synchronized void setContext( Context context ) { 430 this.jndiContext = context; 431 } 432 433 /** 434 * {@inheritDoc} 435 */ 436 @Override 437 public boolean equals( Object obj ) { 438 if (obj == this) return true; 439 if (obj instanceof JBossCacheSource) { 440 JBossCacheSource that = (JBossCacheSource)obj; 441 if (this.getName() == null) { 442 if (that.getName() != null) return false; 443 } else { 444 if (!this.getName().equals(that.getName())) return false; 445 } 446 return true; 447 } 448 return false; 449 } 450 451 /** 452 * {@inheritDoc} 453 */ 454 public synchronized Reference getReference() { 455 String className = getClass().getName(); 456 String factoryClassName = this.getClass().getName(); 457 Reference ref = new Reference(className, factoryClassName, null); 458 459 if (getName() != null) { 460 ref.add(new StringRefAddr(SOURCE_NAME, getName())); 461 } 462 if (getRootNodeUuid() != null) { 463 ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid().toString())); 464 } 465 if (getUuidPropertyName() != null) { 466 ref.add(new StringRefAddr(UUID_PROPERTY_NAME, getUuidPropertyName())); 467 } 468 if (getCacheJndiName() != null) { 469 ref.add(new StringRefAddr(CACHE_JNDI_NAME, getCacheJndiName())); 470 } 471 if (getCacheFactoryJndiName() != null) { 472 ref.add(new StringRefAddr(CACHE_FACTORY_JNDI_NAME, getCacheFactoryJndiName())); 473 } 474 if (getCacheConfigurationName() != null) { 475 ref.add(new StringRefAddr(CACHE_CONFIGURATION_NAME, getCacheConfigurationName())); 476 } 477 if (getDefaultCachePolicy() != null) { 478 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 479 CachePolicy policy = getDefaultCachePolicy(); 480 try { 481 ObjectOutputStream oos = new ObjectOutputStream(baos); 482 oos.writeObject(policy); 483 ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray())); 484 } catch (IOException e) { 485 I18n msg = JBossCacheConnectorI18n.errorSerializingCachePolicyInSource; 486 throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e); 487 } 488 } 489 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit()))); 490 return ref; 491 } 492 493 /** 494 * {@inheritDoc} 495 */ 496 public Object getObjectInstance( Object obj, 497 javax.naming.Name name, 498 Context nameCtx, 499 Hashtable<?, ?> environment ) throws Exception { 500 if (obj instanceof Reference) { 501 Map<String, Object> values = new HashMap<String, Object>(); 502 Reference ref = (Reference)obj; 503 Enumeration<?> en = ref.getAll(); 504 while (en.hasMoreElements()) { 505 RefAddr subref = (RefAddr)en.nextElement(); 506 if (subref instanceof StringRefAddr) { 507 String key = subref.getType(); 508 Object value = subref.getContent(); 509 if (value != null) values.put(key, value.toString()); 510 } else if (subref instanceof BinaryRefAddr) { 511 String key = subref.getType(); 512 Object value = subref.getContent(); 513 if (value instanceof byte[]) { 514 // Deserialize ... 515 ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value); 516 ObjectInputStream ois = new ObjectInputStream(bais); 517 value = ois.readObject(); 518 values.put(key, value); 519 } 520 } 521 } 522 String sourceName = (String)values.get(SOURCE_NAME); 523 String rootNodeUuidString = (String)values.get(ROOT_NODE_UUID); 524 String uuidPropertyName = (String)values.get(UUID_PROPERTY_NAME); 525 String cacheJndiName = (String)values.get(CACHE_JNDI_NAME); 526 String cacheFactoryJndiName = (String)values.get(CACHE_FACTORY_JNDI_NAME); 527 String cacheConfigurationName = (String)values.get(CACHE_CONFIGURATION_NAME); 528 Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY); 529 String retryLimit = (String)values.get(RETRY_LIMIT); 530 531 // Create the source instance ... 532 JBossCacheSource source = new JBossCacheSource(); 533 if (sourceName != null) source.setName(sourceName); 534 if (rootNodeUuidString != null) source.setRootNodeUuid(rootNodeUuidString); 535 if (uuidPropertyName != null) source.setUuidPropertyName(uuidPropertyName); 536 if (cacheJndiName != null) source.setCacheJndiName(cacheJndiName); 537 if (cacheFactoryJndiName != null) source.setCacheFactoryJndiName(cacheFactoryJndiName); 538 if (cacheConfigurationName != null) source.setCacheConfigurationName(cacheConfigurationName); 539 if (defaultCachePolicy instanceof CachePolicy) { 540 source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy); 541 } 542 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit)); 543 return source; 544 } 545 return null; 546 } 547 548 /** 549 * {@inheritDoc} 550 * 551 * @see org.jboss.dna.graph.connectors.RepositorySource#getCapabilities() 552 */ 553 public RepositorySourceCapabilities getCapabilities() { 554 return new Capabilities(); 555 } 556 557 protected class Capabilities implements RepositorySourceCapabilities { 558 public boolean supportsSameNameSiblings() { 559 return true; 560 } 561 562 public boolean supportsUpdates() { 563 return true; 564 } 565 } 566 }