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