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 }