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 RepositorySourceCapabilities CAPABILITIES = new RepositorySourceCapabilities(true, true); 086 087 protected static final String ROOT_NODE_UUID = "rootNodeUuid"; 088 protected static final String SOURCE_NAME = "sourceName"; 089 protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy"; 090 protected static final String CACHE_CONFIGURATION_NAME = "cacheConfigurationName"; 091 protected static final String CACHE_FACTORY_JNDI_NAME = "cacheFactoryJndiName"; 092 protected static final String CACHE_JNDI_NAME = "cacheJndiName"; 093 protected static final String UUID_PROPERTY_NAME = "uuidPropertyName"; 094 protected static final String RETRY_LIMIT = "retryLimit"; 095 096 private String name; 097 private UUID rootNodeUuid = UUID.randomUUID(); 098 private CachePolicy defaultCachePolicy; 099 private String cacheConfigurationName; 100 private String cacheFactoryJndiName; 101 private String cacheJndiName; 102 private String uuidPropertyName = DEFAULT_UUID_PROPERTY_NAME; 103 private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT); 104 private transient Cache<Name, Object> cache; 105 private transient Context jndiContext; 106 private transient RepositoryContext repositoryContext; 107 108 /** 109 * Create a repository source instance. 110 */ 111 public JBossCacheSource() { 112 } 113 114 /** 115 * {@inheritDoc} 116 * 117 * @see org.jboss.dna.graph.connectors.RepositorySource#initialize(org.jboss.dna.graph.connectors.RepositoryContext) 118 */ 119 public void initialize( RepositoryContext context ) throws RepositorySourceException { 120 this.repositoryContext = context; 121 } 122 123 /** 124 * @return repositoryContext 125 */ 126 public RepositoryContext getRepositoryContext() { 127 return repositoryContext; 128 } 129 130 /** 131 * {@inheritDoc} 132 */ 133 public String getName() { 134 return this.name; 135 } 136 137 /** 138 * {@inheritDoc} 139 * 140 * @see org.jboss.dna.graph.connectors.RepositorySource#getRetryLimit() 141 */ 142 public int getRetryLimit() { 143 return retryLimit.get(); 144 } 145 146 /** 147 * {@inheritDoc} 148 * 149 * @see org.jboss.dna.graph.connectors.RepositorySource#setRetryLimit(int) 150 */ 151 public void setRetryLimit( int limit ) { 152 retryLimit.set(limit < 0 ? 0 : limit); 153 } 154 155 /** 156 * Set the name of this source 157 * 158 * @param name the name for this source 159 */ 160 public synchronized void setName( String name ) { 161 if (this.name == name || this.name != null && this.name.equals(name)) return; // unchanged 162 this.name = name; 163 } 164 165 /** 166 * Get the default cache policy for this source, or null if the global default cache policy should be used 167 * 168 * @return the default cache policy, or null if this source has no explicit default cache policy 169 */ 170 public CachePolicy getDefaultCachePolicy() { 171 return defaultCachePolicy; 172 } 173 174 /** 175 * @param defaultCachePolicy Sets defaultCachePolicy to the specified value. 176 */ 177 public synchronized void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) { 178 if (this.defaultCachePolicy == defaultCachePolicy || this.defaultCachePolicy != null 179 && this.defaultCachePolicy.equals(defaultCachePolicy)) return; // unchanged 180 this.defaultCachePolicy = defaultCachePolicy; 181 } 182 183 /** 184 * Get the name in JNDI of a {@link Cache} instance that should be used by this source. 185 * <p> 186 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 187 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 188 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 189 * configuration name} if supplied or the default configuration if not set. 190 * </p> 191 * 192 * @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 193 * factory {@link #getCacheFactoryJndiName() found in JNDI} using the specified {@link #getCacheConfigurationName() 194 * cache configuration name}. 195 * @see #setCacheJndiName(String) 196 * @see #getCacheConfigurationName() 197 * @see #getCacheFactoryJndiName() 198 */ 199 public String getCacheJndiName() { 200 return cacheJndiName; 201 } 202 203 /** 204 * Set the name in JNDI of a {@link Cache} instance that should be used by this source. 205 * <p> 206 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 207 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 208 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 209 * configuration name} if supplied or the default configuration if not set. 210 * </p> 211 * 212 * @param cacheJndiName the JNDI name of the {@link Cache} instance that should be used, or null if the cache is to be created 213 * with a cache factory {@link #getCacheFactoryJndiName() found in JNDI} using the specified 214 * {@link #getCacheConfigurationName() cache configuration name}. 215 * @see #getCacheJndiName() 216 * @see #getCacheConfigurationName() 217 * @see #getCacheFactoryJndiName() 218 */ 219 public synchronized void setCacheJndiName( String cacheJndiName ) { 220 if (this.cacheJndiName == cacheJndiName || this.cacheJndiName != null && this.cacheJndiName.equals(cacheJndiName)) return; // unchanged 221 this.cacheJndiName = cacheJndiName; 222 } 223 224 /** 225 * Get the name in JNDI of a {@link CacheFactory} instance that should be used to create the cache for this source. 226 * <p> 227 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 228 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 229 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 230 * configuration name} if supplied or the default configuration if not set. 231 * </p> 232 * 233 * @return the JNDI name of the {@link CacheFactory} instance that should be used, or null if the {@link DefaultCacheFactory} 234 * should be used if a cache is to be created 235 * @see #setCacheFactoryJndiName(String) 236 * @see #getCacheConfigurationName() 237 * @see #getCacheJndiName() 238 */ 239 public String getCacheFactoryJndiName() { 240 return cacheFactoryJndiName; 241 } 242 243 /** 244 * Set the name in JNDI of a {@link CacheFactory} instance that should be used to obtain the {@link Cache} instance used by 245 * this source. 246 * <p> 247 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 248 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 249 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 250 * configuration name} if supplied or the default configuration if not set. 251 * </p> 252 * 253 * @param jndiName the JNDI name of the {@link CacheFactory} instance that should be used, or null if the 254 * {@link DefaultCacheFactory} should be used if a cache is to be created 255 * @see #setCacheFactoryJndiName(String) 256 * @see #getCacheConfigurationName() 257 * @see #getCacheJndiName() 258 */ 259 public synchronized void setCacheFactoryJndiName( String jndiName ) { 260 if (this.cacheFactoryJndiName == jndiName || this.cacheFactoryJndiName != null 261 && this.cacheFactoryJndiName.equals(jndiName)) return; // unchanged 262 this.cacheFactoryJndiName = jndiName; 263 } 264 265 /** 266 * Get the name of the configuration that should be used if a {@link Cache cache} is to be created using the 267 * {@link CacheFactory} found in JNDI or the {@link DefaultCacheFactory} if needed. 268 * <p> 269 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 270 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 271 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 272 * configuration name} if supplied or the default configuration if not set. 273 * </p> 274 * 275 * @return the name of the configuration that should be passed to the {@link CacheFactory}, or null if the default 276 * configuration should be used 277 * @see #setCacheConfigurationName(String) 278 * @see #getCacheFactoryJndiName() 279 * @see #getCacheJndiName() 280 */ 281 public String getCacheConfigurationName() { 282 return cacheConfigurationName; 283 } 284 285 /** 286 * Get the name of the configuration that should be used if a {@link Cache cache} is to be created using the 287 * {@link CacheFactory} found in JNDI or the {@link DefaultCacheFactory} if needed. 288 * <p> 289 * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it 290 * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the 291 * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache 292 * configuration name} if supplied or the default configuration if not set. 293 * </p> 294 * 295 * @param cacheConfigurationName the name of the configuration that should be passed to the {@link CacheFactory}, or null if 296 * the default configuration should be used 297 * @see #getCacheConfigurationName() 298 * @see #getCacheFactoryJndiName() 299 * @see #getCacheJndiName() 300 */ 301 public synchronized void setCacheConfigurationName( String cacheConfigurationName ) { 302 if (this.cacheConfigurationName == cacheConfigurationName || this.cacheConfigurationName != null 303 && this.cacheConfigurationName.equals(cacheConfigurationName)) return; // unchanged 304 this.cacheConfigurationName = cacheConfigurationName; 305 } 306 307 /** 308 * 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 309 * the existing root node. 310 * 311 * @return the UUID of the root node for the cache. 312 */ 313 public String getRootNodeUuid() { 314 return this.rootNodeUuid.toString(); 315 } 316 317 /** 318 * 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 319 * the existing root node. 320 * 321 * @return the UUID of the root node for the cache. 322 */ 323 public UUID getRootNodeUuidObject() { 324 return this.rootNodeUuid; 325 } 326 327 /** 328 * 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 329 * of the existing root node. 330 * 331 * @param rootNodeUuid the UUID of the root node for the cache, or null if the UUID should be randomly generated 332 */ 333 public synchronized void setRootNodeUuid( String rootNodeUuid ) { 334 UUID uuid = null; 335 if (rootNodeUuid == null) uuid = UUID.randomUUID(); 336 else uuid = UUID.fromString(rootNodeUuid); 337 if (this.rootNodeUuid.equals(uuid)) return; // unchanged 338 this.rootNodeUuid = uuid; 339 } 340 341 /** 342 * Get the {@link Property#getName() property name} where the UUID is stored for each node. 343 * 344 * @return the name of the UUID property; never null 345 */ 346 public String getUuidPropertyName() { 347 return this.uuidPropertyName; 348 } 349 350 /** 351 * Set the {@link Property#getName() property name} where the UUID is stored for each node. 352 * 353 * @param uuidPropertyName the name of the UUID property, or null if the {@link #DEFAULT_UUID_PROPERTY_NAME default name} 354 * should be used 355 */ 356 public synchronized void setUuidPropertyName( String uuidPropertyName ) { 357 if (uuidPropertyName == null || uuidPropertyName.trim().length() == 0) uuidPropertyName = DEFAULT_UUID_PROPERTY_NAME; 358 if (this.uuidPropertyName.equals(uuidPropertyName)) return; // unchanged 359 this.uuidPropertyName = uuidPropertyName; 360 } 361 362 /** 363 * {@inheritDoc} 364 * 365 * @see org.jboss.dna.graph.connectors.RepositorySource#getConnection() 366 */ 367 @SuppressWarnings( "unchecked" ) 368 public RepositoryConnection getConnection() throws RepositorySourceException { 369 if (getName() == null) { 370 I18n msg = JBossCacheConnectorI18n.propertyIsRequired; 371 throw new RepositorySourceException(getName(), msg.text("name")); 372 } 373 if (getUuidPropertyName() == null) { 374 I18n msg = JBossCacheConnectorI18n.propertyIsRequired; 375 throw new RepositorySourceException(getName(), msg.text("uuidPropertyName")); 376 } 377 if (this.cache == null) { 378 // First look for an existing cache instance in JNDI ... 379 Context context = getContext(); 380 String jndiName = this.getCacheJndiName(); 381 if (jndiName != null && jndiName.trim().length() != 0) { 382 Object object = null; 383 try { 384 if (context == null) context = new InitialContext(); 385 object = context.lookup(jndiName); 386 if (object != null) cache = (Cache<Name, Object>)object; 387 } catch (ClassCastException err) { 388 I18n msg = JBossCacheConnectorI18n.objectFoundInJndiWasNotCache; 389 String className = object != null ? object.getClass().getName() : "null"; 390 throw new RepositorySourceException(getName(), msg.text(jndiName, this.getName(), className), err); 391 } catch (Throwable err) { 392 // try loading 393 } 394 } 395 if (cache == null) { 396 // Then look for a cache factory in JNDI ... 397 CacheFactory<Name, Object> cacheFactory = null; 398 jndiName = getCacheFactoryJndiName(); 399 if (jndiName != null && jndiName.trim().length() != 0) { 400 Object object = null; 401 try { 402 if (context == null) context = new InitialContext(); 403 object = context.lookup(jndiName); 404 if (object != null) cacheFactory = (CacheFactory<Name, Object>)object; 405 } catch (ClassCastException err) { 406 I18n msg = JBossCacheConnectorI18n.objectFoundInJndiWasNotCacheFactory; 407 String className = object != null ? object.getClass().getName() : "null"; 408 throw new RepositorySourceException(getName(), msg.text(jndiName, this.getName(), className), err); 409 } catch (Throwable err) { 410 // try loading 411 } 412 } 413 if (cacheFactory == null) cacheFactory = new DefaultCacheFactory<Name, Object>(); 414 415 // Now, get the configuration name ... 416 String configName = this.getCacheConfigurationName(); 417 if (configName != null) { 418 cache = cacheFactory.createCache(configName); 419 } else { 420 cache = cacheFactory.createCache(); 421 } 422 } 423 } 424 return new JBossCacheConnection(this, this.cache); 425 } 426 427 protected Context getContext() { 428 return this.jndiContext; 429 } 430 431 protected synchronized void setContext( Context context ) { 432 this.jndiContext = context; 433 } 434 435 /** 436 * {@inheritDoc} 437 */ 438 @Override 439 public boolean equals( Object obj ) { 440 if (obj == this) return true; 441 if (obj instanceof JBossCacheSource) { 442 JBossCacheSource that = (JBossCacheSource)obj; 443 if (this.getName() == null) { 444 if (that.getName() != null) return false; 445 } else { 446 if (!this.getName().equals(that.getName())) return false; 447 } 448 return true; 449 } 450 return false; 451 } 452 453 /** 454 * {@inheritDoc} 455 */ 456 public synchronized Reference getReference() { 457 String className = getClass().getName(); 458 String factoryClassName = this.getClass().getName(); 459 Reference ref = new Reference(className, factoryClassName, null); 460 461 if (getName() != null) { 462 ref.add(new StringRefAddr(SOURCE_NAME, getName())); 463 } 464 if (getRootNodeUuid() != null) { 465 ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid().toString())); 466 } 467 if (getUuidPropertyName() != null) { 468 ref.add(new StringRefAddr(UUID_PROPERTY_NAME, getUuidPropertyName())); 469 } 470 if (getCacheJndiName() != null) { 471 ref.add(new StringRefAddr(CACHE_JNDI_NAME, getCacheJndiName())); 472 } 473 if (getCacheFactoryJndiName() != null) { 474 ref.add(new StringRefAddr(CACHE_FACTORY_JNDI_NAME, getCacheFactoryJndiName())); 475 } 476 if (getCacheConfigurationName() != null) { 477 ref.add(new StringRefAddr(CACHE_CONFIGURATION_NAME, getCacheConfigurationName())); 478 } 479 if (getDefaultCachePolicy() != null) { 480 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 481 CachePolicy policy = getDefaultCachePolicy(); 482 try { 483 ObjectOutputStream oos = new ObjectOutputStream(baos); 484 oos.writeObject(policy); 485 ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray())); 486 } catch (IOException e) { 487 I18n msg = JBossCacheConnectorI18n.errorSerializingCachePolicyInSource; 488 throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e); 489 } 490 } 491 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit()))); 492 return ref; 493 } 494 495 /** 496 * {@inheritDoc} 497 */ 498 public Object getObjectInstance( Object obj, 499 javax.naming.Name name, 500 Context nameCtx, 501 Hashtable<?, ?> environment ) throws Exception { 502 if (obj instanceof Reference) { 503 Map<String, Object> values = new HashMap<String, Object>(); 504 Reference ref = (Reference)obj; 505 Enumeration<?> en = ref.getAll(); 506 while (en.hasMoreElements()) { 507 RefAddr subref = (RefAddr)en.nextElement(); 508 if (subref instanceof StringRefAddr) { 509 String key = subref.getType(); 510 Object value = subref.getContent(); 511 if (value != null) values.put(key, value.toString()); 512 } else if (subref instanceof BinaryRefAddr) { 513 String key = subref.getType(); 514 Object value = subref.getContent(); 515 if (value instanceof byte[]) { 516 // Deserialize ... 517 ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value); 518 ObjectInputStream ois = new ObjectInputStream(bais); 519 value = ois.readObject(); 520 values.put(key, value); 521 } 522 } 523 } 524 String sourceName = (String)values.get(SOURCE_NAME); 525 String rootNodeUuidString = (String)values.get(ROOT_NODE_UUID); 526 String uuidPropertyName = (String)values.get(UUID_PROPERTY_NAME); 527 String cacheJndiName = (String)values.get(CACHE_JNDI_NAME); 528 String cacheFactoryJndiName = (String)values.get(CACHE_FACTORY_JNDI_NAME); 529 String cacheConfigurationName = (String)values.get(CACHE_CONFIGURATION_NAME); 530 Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY); 531 String retryLimit = (String)values.get(RETRY_LIMIT); 532 533 // Create the source instance ... 534 JBossCacheSource source = new JBossCacheSource(); 535 if (sourceName != null) source.setName(sourceName); 536 if (rootNodeUuidString != null) source.setRootNodeUuid(rootNodeUuidString); 537 if (uuidPropertyName != null) source.setUuidPropertyName(uuidPropertyName); 538 if (cacheJndiName != null) source.setCacheJndiName(cacheJndiName); 539 if (cacheFactoryJndiName != null) source.setCacheFactoryJndiName(cacheFactoryJndiName); 540 if (cacheConfigurationName != null) source.setCacheConfigurationName(cacheConfigurationName); 541 if (defaultCachePolicy instanceof CachePolicy) { 542 source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy); 543 } 544 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit)); 545 return source; 546 } 547 return null; 548 } 549 550 /** 551 * {@inheritDoc} 552 * 553 * @see org.jboss.dna.graph.connectors.RepositorySource#getCapabilities() 554 */ 555 public RepositorySourceCapabilities getCapabilities() { 556 return CAPABILITIES; 557 } 558 }