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 }