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.graph.connector.inmemory;
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.Hashtable;
034    import java.util.Map;
035    import java.util.UUID;
036    import java.util.concurrent.atomic.AtomicInteger;
037    import javax.naming.BinaryRefAddr;
038    import javax.naming.Context;
039    import javax.naming.InitialContext;
040    import javax.naming.NamingException;
041    import javax.naming.RefAddr;
042    import javax.naming.Reference;
043    import javax.naming.StringRefAddr;
044    import javax.naming.spi.ObjectFactory;
045    import net.jcip.annotations.GuardedBy;
046    import org.jboss.dna.common.i18n.I18n;
047    import org.jboss.dna.common.util.CheckArg;
048    import org.jboss.dna.graph.GraphI18n;
049    import org.jboss.dna.graph.cache.CachePolicy;
050    import org.jboss.dna.graph.connector.RepositoryConnection;
051    import org.jboss.dna.graph.connector.RepositoryContext;
052    import org.jboss.dna.graph.connector.RepositorySource;
053    import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
054    import org.jboss.dna.graph.connector.RepositorySourceException;
055    
056    /**
057     * A {@link RepositorySource} for an in-memory repository. Each {@link InMemoryRepositorySource} instance contains its own
058     * repository, and the lifetime of the source dictates the lifetime of the repository and its content.
059     * 
060     * @author Randall Hauch
061     */
062    public class InMemoryRepositorySource implements RepositorySource, ObjectFactory {
063    
064        /**
065         * The initial version is 1
066         */
067        private static final long serialVersionUID = 1L;
068    
069        /**
070         * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source.
071         */
072        public static final int DEFAULT_RETRY_LIMIT = 0;
073    
074        /**
075         * The default name for the workspace used by this source, which is a blank string.
076         */
077        public static final String DEFAULT_WORKSPACE_NAME = "";
078    
079        protected static final RepositorySourceCapabilities CAPABILITIES = new RepositorySourceCapabilities(true, true, false, true,
080                                                                                                            true);
081    
082        protected static final String ROOT_NODE_UUID_ATTR = "rootNodeUuid";
083        protected static final String SOURCE_NAME_ATTR = "sourceName";
084        protected static final String DEFAULT_WORKSPACE_NAME_ATTR = "defaultWorkspaceName";
085        protected static final String DEFAULT_CACHE_POLICY_ATTR = "defaultCachePolicy";
086        protected static final String JNDI_NAME_ATTR = "jndiName";
087        protected static final String RETRY_LIMIT_ATTR = "retryLimit";
088    
089        @GuardedBy( "sourcesLock" )
090        private String name;
091        @GuardedBy( "this" )
092        private String jndiName;
093        private String defaultWorkspaceName = DEFAULT_WORKSPACE_NAME;
094        private UUID rootNodeUuid = UUID.randomUUID();
095        private CachePolicy defaultCachePolicy;
096        private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT);
097        private transient InMemoryRepository repository;
098        private transient RepositoryContext repositoryContext;
099    
100        /**
101         * Create a repository source instance.
102         */
103        public InMemoryRepositorySource() {
104            super();
105        }
106    
107        /**
108         * {@inheritDoc}
109         * 
110         * @see org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext)
111         */
112        public void initialize( RepositoryContext context ) throws RepositorySourceException {
113            this.repositoryContext = context;
114        }
115    
116        /**
117         * @return repositoryContext
118         */
119        public RepositoryContext getRepositoryContext() {
120            return repositoryContext;
121        }
122    
123        /**
124         * {@inheritDoc}
125         * 
126         * @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit()
127         */
128        public int getRetryLimit() {
129            return retryLimit.get();
130        }
131    
132        /**
133         * {@inheritDoc}
134         * 
135         * @see org.jboss.dna.graph.connector.RepositorySource#setRetryLimit(int)
136         */
137        public void setRetryLimit( int limit ) {
138            retryLimit.set(limit < 0 ? 0 : limit);
139        }
140    
141        /**
142         * Get the default cache policy for this source, or null if the global default cache policy should be used
143         * 
144         * @return the default cache policy, or null if this source has no explicit default cache policy
145         */
146        public CachePolicy getDefaultCachePolicy() {
147            return defaultCachePolicy;
148        }
149    
150        /**
151         * @param defaultCachePolicy Sets defaultCachePolicy to the specified value.
152         */
153        public void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) {
154            this.defaultCachePolicy = defaultCachePolicy;
155        }
156    
157        /**
158         * Get the name of the workspace that should be used by default.
159         * 
160         * @return the name of the default workspace
161         */
162        public String getDefaultWorkspaceName() {
163            return defaultWorkspaceName;
164        }
165    
166        /**
167         * Set the default workspace name.
168         * 
169         * @param defaultWorkspaceName the name of the workspace that should be used by default, or null if "" should be used
170         */
171        public void setDefaultWorkspaceName( String defaultWorkspaceName ) {
172            this.defaultWorkspaceName = defaultWorkspaceName != null ? defaultWorkspaceName : DEFAULT_WORKSPACE_NAME;
173        }
174    
175        /**
176         * @return rootNodeUuid
177         */
178        public UUID getRootNodeUuid() {
179            return this.rootNodeUuid;
180        }
181    
182        /**
183         * @param rootNodeUuid Sets rootNodeUuid to the specified value.
184         */
185        public void setRootNodeUuid( UUID rootNodeUuid ) {
186            this.rootNodeUuid = rootNodeUuid != null ? rootNodeUuid : UUID.randomUUID();
187        }
188    
189        /**
190         * If you use this to set a JNDI name, this source will be bound to that name using the default {@link InitialContext}. You
191         * can also do this manually if you have additional requirements.
192         * 
193         * @param name the JNDI name
194         * @throws NamingException if there is a problem registering this object
195         * @see #getJndiName()
196         */
197        public void setJndiName( String name ) throws NamingException {
198            setJndiName(name, null);
199        }
200    
201        /**
202         * Register this source in JNDI under the supplied name using the supplied context. to set a JNDI name, this source will be
203         * bound to that name using the default {@link InitialContext}. You can also do this manually if you have additional
204         * requirements.
205         * 
206         * @param name the JNDI name, or null if this object is to no longer be registered
207         * @param context the JNDI context, or null if the {@link InitialContext} should be used
208         * @throws NamingException if there is a problem registering this object
209         * @see #getJndiName()
210         */
211        public synchronized void setJndiName( String name,
212                                              Context context ) throws NamingException {
213            CheckArg.isNotNull(name, "name");
214            if (context == null) context = new InitialContext();
215    
216            // First register in JNDI under the new name ...
217            if (name != null) {
218                context.bind(name, this);
219            }
220            // Second, unregister from JNDI if there is already a name ...
221            if (jndiName != null && !jndiName.equals(name)) {
222                context.unbind(jndiName);
223            }
224            // Record the new name ...
225            this.jndiName = name;
226        }
227    
228        /**
229         * Gets the JNDI name this source is bound to. Only valid if you used setJNDIName to bind it.
230         * 
231         * @return the JNDI name, or null if it is not bound in JNDI
232         * @see #setJndiName(String)
233         */
234        public synchronized String getJndiName() {
235            return jndiName;
236        }
237    
238        /**
239         * {@inheritDoc}
240         */
241        public String getName() {
242            return this.name;
243        }
244    
245        /**
246         * @param name Sets name to the specified value.
247         */
248        public void setName( String name ) {
249            this.name = name;
250        }
251    
252        /**
253         * {@inheritDoc}
254         * 
255         * @see org.jboss.dna.graph.connector.RepositorySource#getConnection()
256         */
257        public RepositoryConnection getConnection() throws RepositorySourceException {
258            if (repository == null) {
259                repository = new InMemoryRepository(name, rootNodeUuid, defaultWorkspaceName);
260            }
261            return new InMemoryRepositoryConnection(this, repository);
262        }
263    
264        /**
265         * {@inheritDoc}
266         */
267        public synchronized Reference getReference() {
268            String className = getClass().getName();
269            String factoryClassName = this.getClass().getName();
270            Reference ref = new Reference(className, factoryClassName, null);
271    
272            if (getName() != null) {
273                ref.add(new StringRefAddr(SOURCE_NAME_ATTR, getName()));
274            }
275            if (getRootNodeUuid() != null) {
276                ref.add(new StringRefAddr(ROOT_NODE_UUID_ATTR, getRootNodeUuid().toString()));
277            }
278            if (getJndiName() != null) {
279                ref.add(new StringRefAddr(JNDI_NAME_ATTR, getJndiName()));
280            }
281            if (getDefaultWorkspaceName() != null) {
282                ref.add(new StringRefAddr(DEFAULT_WORKSPACE_NAME_ATTR, getDefaultWorkspaceName()));
283            }
284            if (getDefaultCachePolicy() != null) {
285                ByteArrayOutputStream baos = new ByteArrayOutputStream();
286                CachePolicy policy = getDefaultCachePolicy();
287                try {
288                    ObjectOutputStream oos = new ObjectOutputStream(baos);
289                    oos.writeObject(policy);
290                    ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY_ATTR, baos.toByteArray()));
291                } catch (IOException e) {
292                    I18n msg = GraphI18n.errorSerializingInMemoryCachePolicyInSource;
293                    throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e);
294                }
295            }
296            ref.add(new StringRefAddr(RETRY_LIMIT_ATTR, Integer.toString(getRetryLimit())));
297            return ref;
298        }
299    
300        /**
301         * {@inheritDoc}
302         */
303        public Object getObjectInstance( Object obj,
304                                         javax.naming.Name name,
305                                         Context nameCtx,
306                                         Hashtable<?, ?> environment ) throws Exception {
307            if (obj instanceof Reference) {
308                Map<String, Object> values = new HashMap<String, Object>();
309                Reference ref = (Reference)obj;
310                Enumeration<?> en = ref.getAll();
311                while (en.hasMoreElements()) {
312                    RefAddr subref = (RefAddr)en.nextElement();
313                    if (subref instanceof StringRefAddr) {
314                        String key = subref.getType();
315                        Object value = subref.getContent();
316                        if (value != null) values.put(key, value.toString());
317                    } else if (subref instanceof BinaryRefAddr) {
318                        String key = subref.getType();
319                        Object value = subref.getContent();
320                        if (value instanceof byte[]) {
321                            // Deserialize ...
322                            ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value);
323                            ObjectInputStream ois = new ObjectInputStream(bais);
324                            value = ois.readObject();
325                            values.put(key, value);
326                        }
327                    }
328                }
329                String sourceName = (String)values.get(SOURCE_NAME_ATTR);
330                String rootNodeUuidString = (String)values.get(ROOT_NODE_UUID_ATTR);
331                String jndiName = (String)values.get(JNDI_NAME_ATTR);
332                String defaultWorkspaceName = (String)values.get(DEFAULT_WORKSPACE_NAME_ATTR);
333                Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY_ATTR);
334                String retryLimit = (String)values.get(RETRY_LIMIT_ATTR);
335    
336                // Create the source instance ...
337                InMemoryRepositorySource source = new InMemoryRepositorySource();
338                if (sourceName != null) source.setName(sourceName);
339                if (rootNodeUuidString != null) source.setRootNodeUuid(UUID.fromString(rootNodeUuidString));
340                if (defaultWorkspaceName != null) source.setDefaultWorkspaceName(defaultWorkspaceName);
341                if (jndiName != null) source.setJndiName(jndiName);
342                if (defaultCachePolicy instanceof CachePolicy) {
343                    source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy);
344                }
345                if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
346                return source;
347            }
348            return null;
349        }
350    
351        /**
352         * {@inheritDoc}
353         * 
354         * @see org.jboss.dna.graph.connector.RepositorySource#getCapabilities()
355         */
356        public RepositorySourceCapabilities getCapabilities() {
357            return CAPABILITIES;
358        }
359    
360        /**
361         * {@inheritDoc}
362         * 
363         * @see java.lang.Object#toString()
364         */
365        @Override
366        public String toString() {
367            return "The \"" + name + "\" in-memory repository";
368        }
369    }