View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.connector.jbosscache;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.ByteArrayOutputStream;
28  import java.io.IOException;
29  import java.io.ObjectInputStream;
30  import java.io.ObjectOutputStream;
31  import java.util.Enumeration;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.Hashtable;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.UUID;
39  import javax.naming.BinaryRefAddr;
40  import javax.naming.Context;
41  import javax.naming.InitialContext;
42  import javax.naming.NamingException;
43  import javax.naming.RefAddr;
44  import javax.naming.Reference;
45  import javax.naming.Referenceable;
46  import javax.naming.StringRefAddr;
47  import javax.naming.spi.ObjectFactory;
48  import net.jcip.annotations.GuardedBy;
49  import net.jcip.annotations.ThreadSafe;
50  import org.jboss.cache.Cache;
51  import org.jboss.cache.CacheFactory;
52  import org.jboss.cache.DefaultCacheFactory;
53  import org.jboss.cache.config.ConfigurationException;
54  import org.modeshape.common.annotation.Category;
55  import org.modeshape.common.annotation.Description;
56  import org.modeshape.common.annotation.Label;
57  import org.modeshape.common.i18n.I18n;
58  import org.modeshape.common.util.HashCode;
59  import org.modeshape.common.util.Logger;
60  import org.modeshape.common.util.StringUtil;
61  import org.modeshape.graph.ModeShapeLexicon;
62  import org.modeshape.graph.cache.CachePolicy;
63  import org.modeshape.graph.connector.RepositoryConnection;
64  import org.modeshape.graph.connector.RepositoryContext;
65  import org.modeshape.graph.connector.RepositorySource;
66  import org.modeshape.graph.connector.RepositorySourceCapabilities;
67  import org.modeshape.graph.connector.RepositorySourceException;
68  import org.modeshape.graph.connector.base.BaseRepositorySource;
69  import org.modeshape.graph.connector.base.Connection;
70  import org.modeshape.graph.observe.Observer;
71  
72  /**
73   * A repository source that uses a JBoss Cache instance to manage the content. This source is capable of using an existing
74   * {@link Cache} instance or creating a new instance. This process is controlled entirely by the JavaBean properties of the
75   * JBossCacheSource instance.
76   * <p>
77   * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it attempts to
78   * create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
79   * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache configuration
80   * name} if supplied or the default configuration if not set.
81   * </p>
82   * <p>
83   * Like other {@link RepositorySource} classes, instances of JBossCacheSource can be placed into JNDI and do support the creation
84   * of {@link Referenceable JNDI referenceable} objects and resolution of references into JBossCacheSource.
85   * </p>
86   */
87  @ThreadSafe
88  public class JBossCacheSource implements BaseRepositorySource, ObjectFactory {
89  
90      private static final long serialVersionUID = 2L;
91      /**
92       * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source.
93       */
94      public static final int DEFAULT_RETRY_LIMIT = 0;
95      public static final String DEFAULT_UUID_PROPERTY_NAME = ModeShapeLexicon.UUID.getString();
96  
97      /**
98       * The initial {@link #getDefaultWorkspaceName() name of the default workspace} is "{@value} ", unless otherwise specified.
99       */
100     public static final String DEFAULT_NAME_OF_DEFAULT_WORKSPACE = "default";
101 
102     /**
103      * The initial value for whether updates are allowed is "{@value} ", unless otherwise specified.
104      */
105     public static final boolean DEFAULT_UPDATES_ALLOWED = true;
106 
107     protected static final String ROOT_NODE_UUID = "rootNodeUuid";
108     protected static final String SOURCE_NAME = "sourceName";
109     protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy";
110     protected static final String CACHE_CONFIGURATION_NAME = "cacheConfigurationName";
111     protected static final String CACHE_FACTORY_JNDI_NAME = "cacheFactoryJndiName";
112     protected static final String CACHE_JNDI_NAME = "cacheJndiName";
113     protected static final String RETRY_LIMIT = "retryLimit";
114     protected static final String DEFAULT_WORKSPACE = "defaultWorkspace";
115     protected static final String PREDEFINED_WORKSPACE_NAMES = "predefinedWorkspaceNames";
116     protected static final String ALLOW_CREATING_WORKSPACES = "allowCreatingWorkspaces";
117     protected static final String UPDATES_ALLOWED = "updatesAllowed";
118 
119     @Description( i18n = JBossCacheConnectorI18n.class, value = "namePropertyDescription" )
120     @Label( i18n = JBossCacheConnectorI18n.class, value = "namePropertyLabel" )
121     @Category( i18n = JBossCacheConnectorI18n.class, value = "namePropertyCategory" )
122     private volatile String name;
123 
124     @Description( i18n = JBossCacheConnectorI18n.class, value = "rootNodeUuidPropertyDescription" )
125     @Label( i18n = JBossCacheConnectorI18n.class, value = "rootNodeUuidPropertyLabel" )
126     @Category( i18n = JBossCacheConnectorI18n.class, value = "rootNodeUuidPropertyCategory" )
127     private volatile UUID rootNodeUuid = UUID.randomUUID();
128 
129     @Description( i18n = JBossCacheConnectorI18n.class, value = "cacheConfigurationNamePropertyDescription" )
130     @Label( i18n = JBossCacheConnectorI18n.class, value = "cacheConfigurationNamePropertyLabel" )
131     @Category( i18n = JBossCacheConnectorI18n.class, value = "cacheConfigurationNamePropertyCategory" )
132     private volatile String cacheConfigurationName;
133 
134     @Description( i18n = JBossCacheConnectorI18n.class, value = "cacheFactoryJndiNamePropertyDescription" )
135     @Label( i18n = JBossCacheConnectorI18n.class, value = "cacheFactoryJndiNamePropertyLabel" )
136     @Category( i18n = JBossCacheConnectorI18n.class, value = "cacheFactoryrJndiNamePropertyCategory" )
137     private volatile String cacheFactoryJndiName;
138 
139     @Description( i18n = JBossCacheConnectorI18n.class, value = "cacheJndiNamePropertyDescription" )
140     @Label( i18n = JBossCacheConnectorI18n.class, value = "cacheJndiNamePropertyLabel" )
141     @Category( i18n = JBossCacheConnectorI18n.class, value = "cacheJndiNamePropertyCategory" )
142     private volatile String cacheJndiName;
143 
144     @Description( i18n = JBossCacheConnectorI18n.class, value = "retryLimitPropertyDescription" )
145     @Label( i18n = JBossCacheConnectorI18n.class, value = "retryLimitPropertyLabel" )
146     @Category( i18n = JBossCacheConnectorI18n.class, value = "retryLimitPropertyCategory" )
147     private volatile int retryLimit = DEFAULT_RETRY_LIMIT;
148 
149     @Description( i18n = JBossCacheConnectorI18n.class, value = "defaultWorkspaceNamePropertyDescription" )
150     @Label( i18n = JBossCacheConnectorI18n.class, value = "defaultWorkspaceNamePropertyLabel" )
151     @Category( i18n = JBossCacheConnectorI18n.class, value = "defaultWorkspaceNamePropertyCategory" )
152     private volatile String defaultWorkspace;
153 
154     @Description( i18n = JBossCacheConnectorI18n.class, value = "predefinedWorkspacesPropertyDescription" )
155     @Label( i18n = JBossCacheConnectorI18n.class, value = "predefinedWorkspacesPropertyLabel" )
156     @Category( i18n = JBossCacheConnectorI18n.class, value = "predefinedWorkspacesPropertyCategory" )
157     private volatile String[] predefinedWorkspaces = new String[] {};
158 
159     @Description( i18n = JBossCacheConnectorI18n.class, value = "updatesAllowedPropertyDescription" )
160     @Label( i18n = JBossCacheConnectorI18n.class, value = "updatesAllowedPropertyLabel" )
161     @Category( i18n = JBossCacheConnectorI18n.class, value = "updatesAllowedPropertyCategory" )
162     private volatile boolean updatesAllowed = DEFAULT_UPDATES_ALLOWED;
163 
164     private volatile CachePolicy defaultCachePolicy;
165     private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(true, true, false, true, false);
166     private transient JBossCacheRepository repository;
167     private transient Context jndiContext;
168     private transient RepositoryContext repositoryContext;
169     private final Set<String> repositoryNamesForConfigurationNameProblems = new HashSet<String>();
170 
171     /**
172      * Create a repository source instance.
173      */
174     public JBossCacheSource() {
175     }
176 
177     /**
178      * {@inheritDoc}
179      * 
180      * @see org.modeshape.graph.connector.RepositorySource#initialize(org.modeshape.graph.connector.RepositoryContext)
181      */
182     public void initialize( RepositoryContext context ) throws RepositorySourceException {
183         this.repositoryContext = context;
184     }
185 
186     /**
187      * {@inheritDoc}
188      */
189     public String getName() {
190         return this.name;
191     }
192 
193     /**
194      * {@inheritDoc}
195      * 
196      * @see org.modeshape.graph.connector.RepositorySource#getCapabilities()
197      */
198     public RepositorySourceCapabilities getCapabilities() {
199         return capabilities;
200     }
201 
202     /**
203      * {@inheritDoc}
204      * 
205      * @see org.modeshape.graph.connector.RepositorySource#getRetryLimit()
206      */
207     public int getRetryLimit() {
208         return retryLimit;
209     }
210 
211     /**
212      * {@inheritDoc}
213      * 
214      * @see org.modeshape.graph.connector.RepositorySource#setRetryLimit(int)
215      */
216     public synchronized void setRetryLimit( int limit ) {
217         retryLimit = limit < 0 ? 0 : limit;
218     }
219 
220     /**
221      * Set the name of this source
222      * 
223      * @param name the name for this source
224      */
225     public synchronized void setName( String name ) {
226         if (this.name == name || this.name != null && this.name.equals(name)) return; // unchanged
227         this.name = name;
228     }
229 
230     /**
231      * Get the default cache policy for this source, or null if the global default cache policy should be used
232      * 
233      * @return the default cache policy, or null if this source has no explicit default cache policy
234      */
235     public CachePolicy getDefaultCachePolicy() {
236         return defaultCachePolicy;
237     }
238 
239     /**
240      * @param defaultCachePolicy Sets defaultCachePolicy to the specified value.
241      */
242     public synchronized void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) {
243         if (this.defaultCachePolicy == defaultCachePolicy || this.defaultCachePolicy != null
244             && this.defaultCachePolicy.equals(defaultCachePolicy)) return; // unchanged
245         this.defaultCachePolicy = defaultCachePolicy;
246     }
247 
248     /**
249      * Get the name in JNDI of a {@link Cache} instance that should be used by this source.
250      * <p>
251      * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
252      * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
253      * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
254      * configuration name} if supplied or the default configuration if not set.
255      * </p>
256      * 
257      * @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
258      *         factory {@link #getCacheFactoryJndiName() found in JNDI} using the specified {@link #getCacheConfigurationName()
259      *         cache configuration name}.
260      * @see #setCacheJndiName(String)
261      * @see #getCacheConfigurationName()
262      * @see #getCacheFactoryJndiName()
263      */
264     public String getCacheJndiName() {
265         return cacheJndiName;
266     }
267 
268     /**
269      * Set the name in JNDI of a {@link Cache} instance that should be used by this source.
270      * <p>
271      * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
272      * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
273      * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
274      * configuration name} if supplied or the default configuration if not set.
275      * </p>
276      * 
277      * @param cacheJndiName the JNDI name of the {@link Cache} instance that should be used, or null if the cache is to be created
278      *        with a cache factory {@link #getCacheFactoryJndiName() found in JNDI} using the specified
279      *        {@link #getCacheConfigurationName() cache configuration name}.
280      * @see #getCacheJndiName()
281      * @see #getCacheConfigurationName()
282      * @see #getCacheFactoryJndiName()
283      */
284     public synchronized void setCacheJndiName( String cacheJndiName ) {
285         if (this.cacheJndiName == cacheJndiName || this.cacheJndiName != null && this.cacheJndiName.equals(cacheJndiName)) return; // unchanged
286         this.cacheJndiName = cacheJndiName;
287     }
288 
289     /**
290      * Get the name in JNDI of a {@link CacheFactory} instance that should be used to create the cache for this source.
291      * <p>
292      * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
293      * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
294      * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
295      * configuration name} if supplied or the default configuration if not set.
296      * </p>
297      * 
298      * @return the JNDI name of the {@link CacheFactory} instance that should be used, or null if the {@link DefaultCacheFactory}
299      *         should be used if a cache is to be created
300      * @see #setCacheFactoryJndiName(String)
301      * @see #getCacheConfigurationName()
302      * @see #getCacheJndiName()
303      */
304     public String getCacheFactoryJndiName() {
305         return cacheFactoryJndiName;
306     }
307 
308     /**
309      * Set the name in JNDI of a {@link CacheFactory} instance that should be used to obtain the {@link Cache} instance used by
310      * this source.
311      * <p>
312      * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
313      * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
314      * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
315      * configuration name} if supplied or the default configuration if not set.
316      * </p>
317      * 
318      * @param jndiName the JNDI name of the {@link CacheFactory} instance that should be used, or null if the
319      *        {@link DefaultCacheFactory} should be used if a cache is to be created
320      * @see #setCacheFactoryJndiName(String)
321      * @see #getCacheConfigurationName()
322      * @see #getCacheJndiName()
323      */
324     public synchronized void setCacheFactoryJndiName( String jndiName ) {
325         if (this.cacheFactoryJndiName == jndiName || this.cacheFactoryJndiName != null
326             && this.cacheFactoryJndiName.equals(jndiName)) return; // unchanged
327         this.cacheFactoryJndiName = jndiName;
328     }
329 
330     /**
331      * Get the name of the configuration that should be used if a {@link Cache cache} is to be created using the
332      * {@link CacheFactory} found in JNDI or the {@link DefaultCacheFactory} if needed.
333      * <p>
334      * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
335      * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
336      * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
337      * configuration name} if supplied or the default configuration if not set.
338      * </p>
339      * 
340      * @return the name of the configuration that should be passed to the {@link CacheFactory}, or null if the default
341      *         configuration should be used
342      * @see #setCacheConfigurationName(String)
343      * @see #getCacheFactoryJndiName()
344      * @see #getCacheJndiName()
345      */
346     public String getCacheConfigurationName() {
347         return cacheConfigurationName;
348     }
349 
350     /**
351      * Get the name of the configuration that should be used if a {@link Cache cache} is to be created using the
352      * {@link CacheFactory} found in JNDI or the {@link DefaultCacheFactory} if needed.
353      * <p>
354      * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
355      * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
356      * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
357      * configuration name} if supplied or the default configuration if not set.
358      * </p>
359      * 
360      * @param cacheConfigurationName the name of the configuration that should be passed to the {@link CacheFactory}, or null if
361      *        the default configuration should be used
362      * @see #getCacheConfigurationName()
363      * @see #getCacheFactoryJndiName()
364      * @see #getCacheJndiName()
365      */
366     public synchronized void setCacheConfigurationName( String cacheConfigurationName ) {
367         if (this.cacheConfigurationName == cacheConfigurationName || this.cacheConfigurationName != null
368             && this.cacheConfigurationName.equals(cacheConfigurationName)) return; // unchanged
369         this.cacheConfigurationName = cacheConfigurationName;
370     }
371 
372     /**
373      * 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
374      * the existing root node.
375      * 
376      * @return the UUID of the root node for the cache.
377      */
378     public String getRootNodeUuid() {
379         return this.rootNodeUuid.toString();
380     }
381 
382     /**
383      * 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
384      * the existing root node.
385      * 
386      * @return the UUID of the root node for the cache.
387      */
388     public UUID getRootNodeUuidObject() {
389         return this.rootNodeUuid;
390     }
391 
392     /**
393      * 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
394      * of the existing root node.
395      * 
396      * @param rootNodeUuid the UUID of the root node for the cache, or null if the UUID should be randomly generated
397      */
398     public synchronized void setRootNodeUuid( String rootNodeUuid ) {
399         UUID uuid = null;
400         if (rootNodeUuid == null) uuid = UUID.randomUUID();
401         else uuid = UUID.fromString(rootNodeUuid);
402         if (this.rootNodeUuid.equals(uuid)) return; // unchanged
403         this.rootNodeUuid = uuid;
404     }
405 
406     /**
407      * Get the name of the default workspace.
408      * 
409      * @return the name of the workspace that should be used by default; never null
410      */
411     public String getDefaultWorkspaceName() {
412         return defaultWorkspace;
413     }
414 
415     /**
416      * Set the name of the workspace that should be used when clients don't specify a workspace.
417      * 
418      * @param nameOfDefaultWorkspace the name of the workspace that should be used by default, or null if the
419      *        {@link #DEFAULT_NAME_OF_DEFAULT_WORKSPACE default name} should be used
420      */
421     public synchronized void setDefaultWorkspaceName( String nameOfDefaultWorkspace ) {
422         this.defaultWorkspace = nameOfDefaultWorkspace != null ? nameOfDefaultWorkspace : DEFAULT_NAME_OF_DEFAULT_WORKSPACE;
423     }
424 
425     /**
426      * Gets the names of the workspaces that are available when this source is created.
427      * 
428      * @return the names of the workspaces that this source starts with, or null if there are no such workspaces
429      * @see #setPredefinedWorkspaceNames(String[])
430      * @see #setCreatingWorkspacesAllowed(boolean)
431      */
432     public synchronized String[] getPredefinedWorkspaceNames() {
433         String[] copy = new String[predefinedWorkspaces.length];
434         System.arraycopy(predefinedWorkspaces, 0, copy, 0, predefinedWorkspaces.length);
435         return copy;
436     }
437 
438     /**
439      * Sets the names of the workspaces that are available when this source is created.
440      * 
441      * @param predefinedWorkspaceNames the names of the workspaces that this source should start with, or null if there are no
442      *        such workspaces
443      * @see #setCreatingWorkspacesAllowed(boolean)
444      * @see #getPredefinedWorkspaceNames()
445      */
446     public synchronized void setPredefinedWorkspaceNames( String[] predefinedWorkspaceNames ) {
447         this.predefinedWorkspaces = predefinedWorkspaceNames;
448     }
449 
450     /**
451      * Get whether this source allows workspaces to be created dynamically.
452      * 
453      * @return true if this source allows workspaces to be created by clients, or false if the
454      *         {@link #getPredefinedWorkspaceNames() set of workspaces} is fixed
455      * @see #setPredefinedWorkspaceNames(String[])
456      * @see #getPredefinedWorkspaceNames()
457      * @see #setCreatingWorkspacesAllowed(boolean)
458      */
459     @Description( i18n = JBossCacheConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyDescription" )
460     @Label( i18n = JBossCacheConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyLabel" )
461     @Category( i18n = JBossCacheConnectorI18n.class, value = "creatingWorkspacesAllowedPropertyCategory" )
462     public boolean isCreatingWorkspacesAllowed() {
463         return capabilities.supportsCreatingWorkspaces();
464     }
465 
466     /**
467      * Set whether this source allows workspaces to be created dynamically.
468      * 
469      * @param allowWorkspaceCreation true if this source allows workspaces to be created by clients, or false if the
470      *        {@link #getPredefinedWorkspaceNames() set of workspaces} is fixed
471      * @see #setPredefinedWorkspaceNames(String[])
472      * @see #getPredefinedWorkspaceNames()
473      * @see #isCreatingWorkspacesAllowed()
474      */
475     public synchronized void setCreatingWorkspacesAllowed( boolean allowWorkspaceCreation ) {
476         capabilities = new RepositorySourceCapabilities(true, capabilities.supportsUpdates(), false, allowWorkspaceCreation,
477                                                         capabilities.supportsReferences());
478     }
479 
480     /**
481      * {@inheritDoc}
482      * 
483      * @see org.modeshape.graph.connector.RepositorySource#getConnection()
484      */
485     @SuppressWarnings( "unchecked" )
486     public synchronized RepositoryConnection getConnection() throws RepositorySourceException {
487         if (getName() == null) {
488             I18n msg = JBossCacheConnectorI18n.propertyIsRequired;
489             throw new RepositorySourceException(getName(), msg.text("name"));
490         }
491         if (this.repository == null) {
492             Context context = getContext();
493             if (context == null) {
494                 try {
495                     context = new InitialContext();
496                 } catch (NamingException err) {
497                     throw new RepositorySourceException(name, err);
498                 }
499             }
500 
501             // Look for a cache manager in JNDI ...
502             CacheFactory<UUID, JBossCacheNode> cacheFactory = null;
503             String jndiName = getCacheFactoryJndiName();
504             if (jndiName != null && jndiName.trim().length() != 0) {
505                 Object object = null;
506                 try {
507                     object = context.lookup(jndiName);
508                     if (object != null) cacheFactory = (CacheFactory<UUID, JBossCacheNode>)object;
509                 } catch (ClassCastException err) {
510                     I18n msg = JBossCacheConnectorI18n.objectFoundInJndiWasNotCacheFactory;
511                     String className = object != null ? object.getClass().getName() : "null";
512                     throw new RepositorySourceException(getName(), msg.text(jndiName, this.getName(), className), err);
513                 } catch (Throwable err) {
514                     if (err instanceof RuntimeException) throw (RuntimeException)err;
515                     throw new RepositorySourceException(getName(), err);
516                 }
517             }
518             if (cacheFactory == null) cacheFactory = new DefaultCacheFactory<UUID, JBossCacheNode>();
519 
520             // Now create the repository ...
521             Cache<UUID, JBossCacheNode> cache = createNewCache(cacheFactory, getName());
522             this.repository = new JBossCacheRepository(this, cache);
523         }
524 
525         return new Connection<JBossCacheNode, JBossCacheWorkspace>(this, repository);
526     }
527 
528     /**
529      * {@inheritDoc}
530      * 
531      * @see org.modeshape.graph.connector.RepositorySource#close()
532      */
533     public synchronized void close() {
534         // Null the reference to the repository; open connections still reference it and can continue to work ...
535         this.repository = null;
536     }
537 
538     /**
539      * Method that is responsible for attempting to create a new cache given the supplied workspace name. Note that this is
540      * probably called at most once for each workspace name (except if this method fails to create a cache for a given workspace
541      * name).
542      * 
543      * @param cacheFactory the cache factory
544      * @param repositoryName the name of the repository
545      * @return the new cache that corresponds to the workspace name
546      */
547     @GuardedBy( "writeLock" )
548     protected Cache<UUID, JBossCacheNode> createNewCache( CacheFactory<UUID, JBossCacheNode> cacheFactory,
549                                                           String repositoryName ) {
550         assert repositoryName != null;
551         if (cacheFactory == null) return null;
552 
553         // Try to create the cache using the workspace name as the configuration ...
554         try {
555             return cacheFactory.createCache(repositoryName);
556         } catch (ConfigurationException error) {
557             // The workspace name is probably not the name of a configuration ...
558             I18n msg = JBossCacheConnectorI18n.workspaceNameWasNotValidConfiguration;
559             Logger.getLogger(getClass()).debug(msg.text(repositoryName, error.getMessage()));
560         }
561 
562         if (this.cacheConfigurationName != null) {
563             // Try to create the cache using the default configuration name ...
564             try {
565                 return cacheFactory.createCache(getCacheConfigurationName());
566             } catch (ConfigurationException error) {
567                 // The default configuration name is not valid ...
568                 if (this.repositoryNamesForConfigurationNameProblems.add(repositoryName)) {
569                     // Log this problem only the first time ...
570                     I18n msg = JBossCacheConnectorI18n.defaultCacheFactoryConfigurationNameWasNotValidConfiguration;
571                     Logger.getLogger(getClass()).debug(msg.text(repositoryName));
572                 }
573             }
574         }
575 
576         // Just create a new cache with the default configuration ...
577         return cacheFactory.createCache();
578     }
579 
580     /**
581      * @return repositoryContext
582      */
583     public RepositoryContext getRepositoryContext() {
584         return repositoryContext;
585     }
586 
587     protected Observer getObserver() {
588         return repositoryContext != null ? repositoryContext.getObserver() : null;
589     }
590 
591     protected Context getContext() {
592         return this.jndiContext;
593     }
594 
595     protected synchronized void setContext( Context context ) {
596         this.jndiContext = context;
597     }
598 
599     public boolean areUpdatesAllowed() {
600         return this.updatesAllowed;
601     }
602 
603     public void setUpdatesAllowed( boolean updatesAllowed ) {
604         this.updatesAllowed = updatesAllowed;
605     }
606 
607     /**
608      * {@inheritDoc}
609      */
610     @Override
611     public boolean equals( Object obj ) {
612         if (obj == this) return true;
613         if (obj instanceof JBossCacheSource) {
614             JBossCacheSource that = (JBossCacheSource)obj;
615             if (this.getName() == null) {
616                 if (that.getName() != null) return false;
617             } else {
618                 if (!this.getName().equals(that.getName())) return false;
619             }
620             return true;
621         }
622         return false;
623     }
624 
625     @Override
626     public int hashCode() {
627         return HashCode.compute(getName());
628     }
629 
630     /**
631      * {@inheritDoc}
632      */
633     public synchronized Reference getReference() {
634         String className = getClass().getName();
635         String factoryClassName = this.getClass().getName();
636         Reference ref = new Reference(className, factoryClassName, null);
637 
638         ref.add(new StringRefAddr(SOURCE_NAME, getName()));
639         ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid().toString()));
640         ref.add(new StringRefAddr(CACHE_JNDI_NAME, getCacheJndiName()));
641         ref.add(new StringRefAddr(CACHE_FACTORY_JNDI_NAME, getCacheFactoryJndiName()));
642         ref.add(new StringRefAddr(CACHE_CONFIGURATION_NAME, getCacheConfigurationName()));
643         ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
644         ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getDefaultWorkspaceName()));
645         ref.add(new StringRefAddr(UPDATES_ALLOWED, String.valueOf(areUpdatesAllowed())));
646         ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed())));
647         String[] workspaceNames = getPredefinedWorkspaceNames();
648         if (workspaceNames != null && workspaceNames.length != 0) {
649             ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES, StringUtil.combineLines(workspaceNames)));
650         }
651         if (getDefaultCachePolicy() != null) {
652             ByteArrayOutputStream baos = new ByteArrayOutputStream();
653             CachePolicy policy = getDefaultCachePolicy();
654             try {
655                 ObjectOutputStream oos = new ObjectOutputStream(baos);
656                 oos.writeObject(policy);
657                 ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray()));
658             } catch (IOException e) {
659                 I18n msg = JBossCacheConnectorI18n.errorSerializingCachePolicyInSource;
660                 throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e);
661             }
662         }
663         return ref;
664     }
665 
666     /**
667      * {@inheritDoc}
668      */
669     public Object getObjectInstance( Object obj,
670                                      javax.naming.Name name,
671                                      Context nameCtx,
672                                      Hashtable<?, ?> environment ) throws Exception {
673         if (obj instanceof Reference) {
674             Map<String, Object> values = new HashMap<String, Object>();
675             Reference ref = (Reference)obj;
676             Enumeration<?> en = ref.getAll();
677             while (en.hasMoreElements()) {
678                 RefAddr subref = (RefAddr)en.nextElement();
679                 if (subref instanceof StringRefAddr) {
680                     String key = subref.getType();
681                     Object value = subref.getContent();
682                     if (value != null) values.put(key, value.toString());
683                 } else if (subref instanceof BinaryRefAddr) {
684                     String key = subref.getType();
685                     Object value = subref.getContent();
686                     if (value instanceof byte[]) {
687                         // Deserialize ...
688                         ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value);
689                         ObjectInputStream ois = new ObjectInputStream(bais);
690                         value = ois.readObject();
691                         values.put(key, value);
692                     }
693                 }
694             }
695             String sourceName = (String)values.get(SOURCE_NAME);
696             String rootNodeUuidString = (String)values.get(ROOT_NODE_UUID);
697             String cacheJndiName = (String)values.get(CACHE_JNDI_NAME);
698             String cacheFactoryJndiName = (String)values.get(CACHE_FACTORY_JNDI_NAME);
699             String cacheConfigurationName = (String)values.get(CACHE_CONFIGURATION_NAME);
700             Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY);
701             String retryLimit = (String)values.get(RETRY_LIMIT);
702             String defaultWorkspace = (String)values.get(DEFAULT_WORKSPACE);
703             String createWorkspaces = (String)values.get(ALLOW_CREATING_WORKSPACES);
704             String updatesAllowed = (String)values.get(UPDATES_ALLOWED);
705 
706             String combinedWorkspaceNames = (String)values.get(PREDEFINED_WORKSPACE_NAMES);
707             String[] workspaceNames = null;
708             if (combinedWorkspaceNames != null) {
709                 List<String> paths = StringUtil.splitLines(combinedWorkspaceNames);
710                 workspaceNames = paths.toArray(new String[paths.size()]);
711             }
712 
713             // Create the source instance ...
714             JBossCacheSource source = new JBossCacheSource();
715             if (sourceName != null) source.setName(sourceName);
716             if (rootNodeUuidString != null) source.setRootNodeUuid(rootNodeUuidString);
717             if (cacheJndiName != null) source.setCacheJndiName(cacheJndiName);
718             if (cacheFactoryJndiName != null) source.setCacheFactoryJndiName(cacheFactoryJndiName);
719             if (cacheConfigurationName != null) source.setCacheConfigurationName(cacheConfigurationName);
720             if (defaultCachePolicy instanceof CachePolicy) {
721                 source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy);
722             }
723             if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
724             if (defaultWorkspace != null) source.setDefaultWorkspaceName(defaultWorkspace);
725             if (createWorkspaces != null) source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces));
726             if (workspaceNames != null && workspaceNames.length != 0) source.setPredefinedWorkspaceNames(workspaceNames);
727             if (updatesAllowed != null) source.setUpdatesAllowed(Boolean.valueOf(updatesAllowed));
728             return source;
729         }
730         return null;
731     }
732 }