001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.connector.jbosscache;
025    
026    import java.io.ByteArrayInputStream;
027    import java.io.ByteArrayOutputStream;
028    import java.io.IOException;
029    import java.io.ObjectInputStream;
030    import java.io.ObjectOutputStream;
031    import java.util.Enumeration;
032    import java.util.HashMap;
033    import java.util.HashSet;
034    import java.util.Hashtable;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Set;
038    import java.util.UUID;
039    import javax.naming.BinaryRefAddr;
040    import javax.naming.Context;
041    import javax.naming.InitialContext;
042    import javax.naming.NamingException;
043    import javax.naming.RefAddr;
044    import javax.naming.Reference;
045    import javax.naming.Referenceable;
046    import javax.naming.StringRefAddr;
047    import javax.naming.spi.ObjectFactory;
048    import net.jcip.annotations.ThreadSafe;
049    import org.jboss.cache.Cache;
050    import org.jboss.cache.CacheFactory;
051    import org.jboss.cache.DefaultCacheFactory;
052    import org.jboss.dna.common.i18n.I18n;
053    import org.jboss.dna.common.util.StringUtil;
054    import org.jboss.dna.graph.DnaLexicon;
055    import org.jboss.dna.graph.cache.CachePolicy;
056    import org.jboss.dna.graph.connector.RepositoryConnection;
057    import org.jboss.dna.graph.connector.RepositoryContext;
058    import org.jboss.dna.graph.connector.RepositorySource;
059    import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
060    import org.jboss.dna.graph.connector.RepositorySourceException;
061    import org.jboss.dna.graph.observe.Observer;
062    import org.jboss.dna.graph.property.Name;
063    
064    /**
065     * A repository source that uses a JBoss Cache instance to manage the content. This source is capable of using an existing
066     * {@link Cache} instance or creating a new instance. This process is controlled entirely by the JavaBean properties of the
067     * JBossCacheSource instance.
068     * <p>
069     * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it attempts to
070     * create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
071     * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache configuration
072     * name} if supplied or the default configuration if not set.
073     * </p>
074     * <p>
075     * Like other {@link RepositorySource} classes, instances of JBossCacheSource can be placed into JNDI and do support the creation
076     * of {@link Referenceable JNDI referenceable} objects and resolution of references into JBossCacheSource.
077     * </p>
078     * 
079     * @author Randall Hauch
080     */
081    @ThreadSafe
082    public class JBossCacheSource implements RepositorySource, ObjectFactory {
083    
084        private static final long serialVersionUID = 2L;
085        /**
086         * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source.
087         */
088        public static final int DEFAULT_RETRY_LIMIT = 0;
089        public static final String DEFAULT_UUID_PROPERTY_NAME = DnaLexicon.UUID.getString();
090    
091        /**
092         * The initial {@link #getNameOfDefaultWorkspace() name of the default workspace} is "{@value} ", unless otherwise specified.
093         */
094        public static final String DEFAULT_NAME_OF_DEFAULT_WORKSPACE = "default";
095    
096        protected static final String ROOT_NODE_UUID = "rootNodeUuid";
097        protected static final String SOURCE_NAME = "sourceName";
098        protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy";
099        protected static final String CACHE_CONFIGURATION_NAME = "cacheConfigurationName";
100        protected static final String CACHE_FACTORY_JNDI_NAME = "cacheFactoryJndiName";
101        protected static final String CACHE_JNDI_NAME = "cacheJndiName";
102        protected static final String RETRY_LIMIT = "retryLimit";
103        protected static final String DEFAULT_WORKSPACE = "defaultWorkspace";
104        protected static final String PREDEFINED_WORKSPACE_NAMES = "predefinedWorkspaceNames";
105        protected static final String ALLOW_CREATING_WORKSPACES = "allowCreatingWorkspaces";
106    
107        private volatile String name;
108        private volatile UUID rootNodeUuid = UUID.randomUUID();
109        private volatile CachePolicy defaultCachePolicy;
110        private volatile String cacheConfigurationName;
111        private volatile String cacheFactoryJndiName;
112        private volatile String cacheJndiName;
113        private volatile int retryLimit = DEFAULT_RETRY_LIMIT;
114        private volatile String defaultWorkspace;
115        private volatile String[] predefinedWorkspaces = new String[] {};
116        private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(true, true, false, true, false);
117        private transient JBossCacheWorkspaces workspaces;
118        private transient Context jndiContext;
119        private transient RepositoryContext repositoryContext;
120    
121        /**
122         * Create a repository source instance.
123         */
124        public JBossCacheSource() {
125        }
126    
127        /**
128         * {@inheritDoc}
129         * 
130         * @see org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext)
131         */
132        public void initialize( RepositoryContext context ) throws RepositorySourceException {
133            this.repositoryContext = context;
134        }
135    
136        /**
137         * {@inheritDoc}
138         */
139        public String getName() {
140            return this.name;
141        }
142    
143        /**
144         * {@inheritDoc}
145         * 
146         * @see org.jboss.dna.graph.connector.RepositorySource#getCapabilities()
147         */
148        public RepositorySourceCapabilities getCapabilities() {
149            return capabilities;
150        }
151    
152        /**
153         * {@inheritDoc}
154         * 
155         * @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit()
156         */
157        public int getRetryLimit() {
158            return retryLimit;
159        }
160    
161        /**
162         * {@inheritDoc}
163         * 
164         * @see org.jboss.dna.graph.connector.RepositorySource#setRetryLimit(int)
165         */
166        public synchronized void setRetryLimit( int limit ) {
167            retryLimit = limit < 0 ? 0 : limit;
168        }
169    
170        /**
171         * Set the name of this source
172         * 
173         * @param name the name for this source
174         */
175        public synchronized void setName( String name ) {
176            if (this.name == name || this.name != null && this.name.equals(name)) return; // unchanged
177            this.name = name;
178        }
179    
180        /**
181         * Get the default cache policy for this source, or null if the global default cache policy should be used
182         * 
183         * @return the default cache policy, or null if this source has no explicit default cache policy
184         */
185        public CachePolicy getDefaultCachePolicy() {
186            return defaultCachePolicy;
187        }
188    
189        /**
190         * @param defaultCachePolicy Sets defaultCachePolicy to the specified value.
191         */
192        public synchronized void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) {
193            if (this.defaultCachePolicy == defaultCachePolicy || this.defaultCachePolicy != null
194                && this.defaultCachePolicy.equals(defaultCachePolicy)) return; // unchanged
195            this.defaultCachePolicy = defaultCachePolicy;
196        }
197    
198        /**
199         * Get the name in JNDI of a {@link Cache} instance that should be used by this source.
200         * <p>
201         * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
202         * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
203         * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
204         * configuration name} if supplied or the default configuration if not set.
205         * </p>
206         * 
207         * @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
208         *         factory {@link #getCacheFactoryJndiName() found in JNDI} using the specified {@link #getCacheConfigurationName()
209         *         cache configuration name}.
210         * @see #setCacheJndiName(String)
211         * @see #getCacheConfigurationName()
212         * @see #getCacheFactoryJndiName()
213         */
214        public String getCacheJndiName() {
215            return cacheJndiName;
216        }
217    
218        /**
219         * Set the name in JNDI of a {@link Cache} instance that should be used by this source.
220         * <p>
221         * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
222         * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
223         * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
224         * configuration name} if supplied or the default configuration if not set.
225         * </p>
226         * 
227         * @param cacheJndiName the JNDI name of the {@link Cache} instance that should be used, or null if the cache is to be created
228         *        with a cache factory {@link #getCacheFactoryJndiName() found in JNDI} using the specified
229         *        {@link #getCacheConfigurationName() cache configuration name}.
230         * @see #getCacheJndiName()
231         * @see #getCacheConfigurationName()
232         * @see #getCacheFactoryJndiName()
233         */
234        public synchronized void setCacheJndiName( String cacheJndiName ) {
235            if (this.cacheJndiName == cacheJndiName || this.cacheJndiName != null && this.cacheJndiName.equals(cacheJndiName)) return; // unchanged
236            this.cacheJndiName = cacheJndiName;
237        }
238    
239        /**
240         * Get the name in JNDI of a {@link CacheFactory} instance that should be used to create the cache for this source.
241         * <p>
242         * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
243         * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
244         * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
245         * configuration name} if supplied or the default configuration if not set.
246         * </p>
247         * 
248         * @return the JNDI name of the {@link CacheFactory} instance that should be used, or null if the {@link DefaultCacheFactory}
249         *         should be used if a cache is to be created
250         * @see #setCacheFactoryJndiName(String)
251         * @see #getCacheConfigurationName()
252         * @see #getCacheJndiName()
253         */
254        public String getCacheFactoryJndiName() {
255            return cacheFactoryJndiName;
256        }
257    
258        /**
259         * Set the name in JNDI of a {@link CacheFactory} instance that should be used to obtain the {@link Cache} instance used by
260         * this source.
261         * <p>
262         * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
263         * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
264         * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
265         * configuration name} if supplied or the default configuration if not set.
266         * </p>
267         * 
268         * @param jndiName the JNDI name of the {@link CacheFactory} instance that should be used, or null if the
269         *        {@link DefaultCacheFactory} should be used if a cache is to be created
270         * @see #setCacheFactoryJndiName(String)
271         * @see #getCacheConfigurationName()
272         * @see #getCacheJndiName()
273         */
274        public synchronized void setCacheFactoryJndiName( String jndiName ) {
275            if (this.cacheFactoryJndiName == jndiName || this.cacheFactoryJndiName != null
276                && this.cacheFactoryJndiName.equals(jndiName)) return; // unchanged
277            this.cacheFactoryJndiName = jndiName;
278        }
279    
280        /**
281         * Get the name of the configuration that should be used if a {@link Cache cache} is to be created using the
282         * {@link CacheFactory} found in JNDI or the {@link DefaultCacheFactory} if needed.
283         * <p>
284         * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
285         * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
286         * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
287         * configuration name} if supplied or the default configuration if not set.
288         * </p>
289         * 
290         * @return the name of the configuration that should be passed to the {@link CacheFactory}, or null if the default
291         *         configuration should be used
292         * @see #setCacheConfigurationName(String)
293         * @see #getCacheFactoryJndiName()
294         * @see #getCacheJndiName()
295         */
296        public String getCacheConfigurationName() {
297            return cacheConfigurationName;
298        }
299    
300        /**
301         * Get the name of the configuration that should be used if a {@link Cache cache} is to be created using the
302         * {@link CacheFactory} found in JNDI or the {@link DefaultCacheFactory} if needed.
303         * <p>
304         * This source first attempts to find an existing cache in {@link #getCacheJndiName() JNDI}. If none is found, then it
305         * attempts to create a cache instance using the {@link CacheFactory} found in {@link #getCacheFactoryJndiName() JNDI} (or the
306         * {@link DefaultCacheFactory} if no such factory is available) and the {@link #getCacheConfigurationName() cache
307         * configuration name} if supplied or the default configuration if not set.
308         * </p>
309         * 
310         * @param cacheConfigurationName the name of the configuration that should be passed to the {@link CacheFactory}, or null if
311         *        the default configuration should be used
312         * @see #getCacheConfigurationName()
313         * @see #getCacheFactoryJndiName()
314         * @see #getCacheJndiName()
315         */
316        public synchronized void setCacheConfigurationName( String cacheConfigurationName ) {
317            if (this.cacheConfigurationName == cacheConfigurationName || this.cacheConfigurationName != null
318                && this.cacheConfigurationName.equals(cacheConfigurationName)) return; // unchanged
319            this.cacheConfigurationName = cacheConfigurationName;
320        }
321    
322        /**
323         * 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
324         * the existing root node.
325         * 
326         * @return the UUID of the root node for the cache.
327         */
328        public String getRootNodeUuid() {
329            return this.rootNodeUuid.toString();
330        }
331    
332        /**
333         * 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
334         * the existing root node.
335         * 
336         * @return the UUID of the root node for the cache.
337         */
338        public UUID getRootNodeUuidObject() {
339            return this.rootNodeUuid;
340        }
341    
342        /**
343         * 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
344         * of the existing root node.
345         * 
346         * @param rootNodeUuid the UUID of the root node for the cache, or null if the UUID should be randomly generated
347         */
348        public synchronized void setRootNodeUuid( String rootNodeUuid ) {
349            UUID uuid = null;
350            if (rootNodeUuid == null) uuid = UUID.randomUUID();
351            else uuid = UUID.fromString(rootNodeUuid);
352            if (this.rootNodeUuid.equals(uuid)) return; // unchanged
353            this.rootNodeUuid = uuid;
354        }
355    
356        /**
357         * Get the name of the default workspace.
358         * 
359         * @return the name of the workspace that should be used by default; never null
360         */
361        public String getNameOfDefaultWorkspace() {
362            return defaultWorkspace;
363        }
364    
365        /**
366         * Set the name of the workspace that should be used when clients don't specify a workspace.
367         * 
368         * @param nameOfDefaultWorkspace the name of the workspace that should be used by default, or null if the
369         *        {@link #DEFAULT_NAME_OF_DEFAULT_WORKSPACE default name} should be used
370         */
371        public synchronized void setNameOfDefaultWorkspace( String nameOfDefaultWorkspace ) {
372            this.defaultWorkspace = nameOfDefaultWorkspace != null ? nameOfDefaultWorkspace : DEFAULT_NAME_OF_DEFAULT_WORKSPACE;
373        }
374    
375        /**
376         * Gets the names of the workspaces that are available when this source is created.
377         * 
378         * @return the names of the workspaces that this source starts with, or null if there are no such workspaces
379         * @see #setPredefinedWorkspaceNames(String[])
380         * @see #setCreatingWorkspacesAllowed(boolean)
381         */
382        public synchronized String[] getPredefinedWorkspaceNames() {
383            String[] copy = new String[predefinedWorkspaces.length];
384            System.arraycopy(predefinedWorkspaces, 0, copy, 0, predefinedWorkspaces.length);
385            return copy;
386        }
387    
388        /**
389         * Sets the names of the workspaces that are available when this source is created.
390         * 
391         * @param predefinedWorkspaceNames the names of the workspaces that this source should start with, or null if there are no
392         *        such workspaces
393         * @see #setCreatingWorkspacesAllowed(boolean)
394         * @see #getPredefinedWorkspaceNames()
395         */
396        public synchronized void setPredefinedWorkspaceNames( String[] predefinedWorkspaceNames ) {
397            this.predefinedWorkspaces = predefinedWorkspaceNames;
398        }
399    
400        /**
401         * Get whether this source allows workspaces to be created dynamically.
402         * 
403         * @return true if this source allows workspaces to be created by clients, or false if the
404         *         {@link #getPredefinedWorkspaceNames() set of workspaces} is fixed
405         * @see #setPredefinedWorkspaceNames(String[])
406         * @see #getPredefinedWorkspaceNames()
407         * @see #setCreatingWorkspacesAllowed(boolean)
408         */
409        public boolean isCreatingWorkspacesAllowed() {
410            return capabilities.supportsCreatingWorkspaces();
411        }
412    
413        /**
414         * Set whether this source allows workspaces to be created dynamically.
415         * 
416         * @param allowWorkspaceCreation 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 #isCreatingWorkspacesAllowed()
421         */
422        public synchronized void setCreatingWorkspacesAllowed( boolean allowWorkspaceCreation ) {
423            capabilities = new RepositorySourceCapabilities(true, capabilities.supportsUpdates(), false, allowWorkspaceCreation,
424                                                            capabilities.supportsReferences());
425        }
426    
427        /**
428         * {@inheritDoc}
429         * 
430         * @see org.jboss.dna.graph.connector.RepositorySource#getConnection()
431         */
432        @SuppressWarnings( "unchecked" )
433        public synchronized RepositoryConnection getConnection() throws RepositorySourceException {
434            if (getName() == null) {
435                I18n msg = JBossCacheConnectorI18n.propertyIsRequired;
436                throw new RepositorySourceException(getName(), msg.text("name"));
437            }
438            if (this.workspaces == null) {
439                Context context = getContext();
440                if (context == null) {
441                    try {
442                        context = new InitialContext();
443                    } catch (NamingException err) {
444                        throw new RepositorySourceException(name, err);
445                    }
446                }
447    
448                // Look for a cache factory in JNDI ...
449                CacheFactory<Name, Object> cacheFactory = null;
450                String jndiName = getCacheFactoryJndiName();
451                if (jndiName != null && jndiName.trim().length() != 0) {
452                    Object object = null;
453                    try {
454                        object = context.lookup(jndiName);
455                        if (object != null) cacheFactory = (CacheFactory<Name, Object>)object;
456                    } catch (ClassCastException err) {
457                        I18n msg = JBossCacheConnectorI18n.objectFoundInJndiWasNotCacheFactory;
458                        String className = object != null ? object.getClass().getName() : "null";
459                        throw new RepositorySourceException(getName(), msg.text(jndiName, this.getName(), className), err);
460                    } catch (Throwable err) {
461                        if (err instanceof RuntimeException) throw (RuntimeException)err;
462                        throw new RepositorySourceException(getName(), err);
463                    }
464                }
465                if (cacheFactory == null) cacheFactory = new DefaultCacheFactory<Name, Object>();
466    
467                // Get the default cache configuration name
468                String configName = this.getCacheConfigurationName();
469    
470                // Create the set of initial names ...
471                Set<String> initialNames = new HashSet<String>();
472                for (String initialName : getPredefinedWorkspaceNames())
473                    initialNames.add(initialName);
474    
475                // Now create the workspace manager ...
476                this.workspaces = new JBossCacheWorkspaces(getName(), cacheFactory, configName, initialNames, context);
477            }
478    
479            return new JBossCacheConnection(this, this.workspaces);
480        }
481    
482        /**
483         * @return repositoryContext
484         */
485        protected RepositoryContext getRepositoryContext() {
486            return repositoryContext;
487        }
488    
489        protected Observer getObserver() {
490            return repositoryContext != null ? repositoryContext.getObserver() : null;
491        }
492    
493        protected Context getContext() {
494            return this.jndiContext;
495        }
496    
497        protected synchronized void setContext( Context context ) {
498            this.jndiContext = context;
499        }
500    
501        /**
502         * {@inheritDoc}
503         */
504        @Override
505        public boolean equals( Object obj ) {
506            if (obj == this) return true;
507            if (obj instanceof JBossCacheSource) {
508                JBossCacheSource that = (JBossCacheSource)obj;
509                if (this.getName() == null) {
510                    if (that.getName() != null) return false;
511                } else {
512                    if (!this.getName().equals(that.getName())) return false;
513                }
514                return true;
515            }
516            return false;
517        }
518    
519        /**
520         * {@inheritDoc}
521         */
522        public synchronized Reference getReference() {
523            String className = getClass().getName();
524            String factoryClassName = this.getClass().getName();
525            Reference ref = new Reference(className, factoryClassName, null);
526    
527            ref.add(new StringRefAddr(SOURCE_NAME, getName()));
528            ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid().toString()));
529            ref.add(new StringRefAddr(CACHE_JNDI_NAME, getCacheJndiName()));
530            ref.add(new StringRefAddr(CACHE_FACTORY_JNDI_NAME, getCacheFactoryJndiName()));
531            ref.add(new StringRefAddr(CACHE_CONFIGURATION_NAME, getCacheConfigurationName()));
532            ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
533            ref.add(new StringRefAddr(DEFAULT_WORKSPACE, getNameOfDefaultWorkspace()));
534            ref.add(new StringRefAddr(ALLOW_CREATING_WORKSPACES, Boolean.toString(isCreatingWorkspacesAllowed())));
535            String[] workspaceNames = getPredefinedWorkspaceNames();
536            if (workspaceNames != null && workspaceNames.length != 0) {
537                ref.add(new StringRefAddr(PREDEFINED_WORKSPACE_NAMES, StringUtil.combineLines(workspaceNames)));
538            }
539            if (getDefaultCachePolicy() != null) {
540                ByteArrayOutputStream baos = new ByteArrayOutputStream();
541                CachePolicy policy = getDefaultCachePolicy();
542                try {
543                    ObjectOutputStream oos = new ObjectOutputStream(baos);
544                    oos.writeObject(policy);
545                    ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray()));
546                } catch (IOException e) {
547                    I18n msg = JBossCacheConnectorI18n.errorSerializingCachePolicyInSource;
548                    throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e);
549                }
550            }
551            return ref;
552        }
553    
554        /**
555         * {@inheritDoc}
556         */
557        public Object getObjectInstance( Object obj,
558                                         javax.naming.Name name,
559                                         Context nameCtx,
560                                         Hashtable<?, ?> environment ) throws Exception {
561            if (obj instanceof Reference) {
562                Map<String, Object> values = new HashMap<String, Object>();
563                Reference ref = (Reference)obj;
564                Enumeration<?> en = ref.getAll();
565                while (en.hasMoreElements()) {
566                    RefAddr subref = (RefAddr)en.nextElement();
567                    if (subref instanceof StringRefAddr) {
568                        String key = subref.getType();
569                        Object value = subref.getContent();
570                        if (value != null) values.put(key, value.toString());
571                    } else if (subref instanceof BinaryRefAddr) {
572                        String key = subref.getType();
573                        Object value = subref.getContent();
574                        if (value instanceof byte[]) {
575                            // Deserialize ...
576                            ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value);
577                            ObjectInputStream ois = new ObjectInputStream(bais);
578                            value = ois.readObject();
579                            values.put(key, value);
580                        }
581                    }
582                }
583                String sourceName = (String)values.get(SOURCE_NAME);
584                String rootNodeUuidString = (String)values.get(ROOT_NODE_UUID);
585                String cacheJndiName = (String)values.get(CACHE_JNDI_NAME);
586                String cacheFactoryJndiName = (String)values.get(CACHE_FACTORY_JNDI_NAME);
587                String cacheConfigurationName = (String)values.get(CACHE_CONFIGURATION_NAME);
588                Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY);
589                String retryLimit = (String)values.get(RETRY_LIMIT);
590                String defaultWorkspace = (String)values.get(DEFAULT_WORKSPACE);
591                String createWorkspaces = (String)values.get(ALLOW_CREATING_WORKSPACES);
592    
593                String combinedWorkspaceNames = (String)values.get(PREDEFINED_WORKSPACE_NAMES);
594                String[] workspaceNames = null;
595                if (combinedWorkspaceNames != null) {
596                    List<String> paths = StringUtil.splitLines(combinedWorkspaceNames);
597                    workspaceNames = paths.toArray(new String[paths.size()]);
598                }
599    
600                // Create the source instance ...
601                JBossCacheSource source = new JBossCacheSource();
602                if (sourceName != null) source.setName(sourceName);
603                if (rootNodeUuidString != null) source.setRootNodeUuid(rootNodeUuidString);
604                if (cacheJndiName != null) source.setCacheJndiName(cacheJndiName);
605                if (cacheFactoryJndiName != null) source.setCacheFactoryJndiName(cacheFactoryJndiName);
606                if (cacheConfigurationName != null) source.setCacheConfigurationName(cacheConfigurationName);
607                if (defaultCachePolicy instanceof CachePolicy) {
608                    source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy);
609                }
610                if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
611                if (defaultWorkspace != null) source.setNameOfDefaultWorkspace(defaultWorkspace);
612                if (createWorkspaces != null) source.setCreatingWorkspacesAllowed(Boolean.parseBoolean(createWorkspaces));
613                if (workspaceNames != null && workspaceNames.length != 0) source.setPredefinedWorkspaceNames(workspaceNames);
614                return source;
615            }
616            return null;
617        }
618    }