001    /*
002     * JBoss, Home of Professional Open Source.
003     * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004     * as indicated by the @author tags. See the copyright.txt file in the
005     * distribution for a full listing of individual contributors. 
006     *
007     * This is free software; you can redistribute it and/or modify it
008     * under the terms of the GNU Lesser General Public License as
009     * published by the Free Software Foundation; either version 2.1 of
010     * the License, or (at your option) any later version.
011     *
012     * This software is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this software; if not, write to the Free
019     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021     */
022    package org.jboss.dna.connector.inmemory;
023    
024    import java.io.ByteArrayInputStream;
025    import java.io.ByteArrayOutputStream;
026    import java.io.IOException;
027    import java.io.ObjectInputStream;
028    import java.io.ObjectOutputStream;
029    import java.util.Enumeration;
030    import java.util.HashMap;
031    import java.util.Hashtable;
032    import java.util.Map;
033    import java.util.UUID;
034    import java.util.concurrent.atomic.AtomicInteger;
035    import javax.naming.BinaryRefAddr;
036    import javax.naming.Context;
037    import javax.naming.InitialContext;
038    import javax.naming.NamingException;
039    import javax.naming.RefAddr;
040    import javax.naming.Reference;
041    import javax.naming.StringRefAddr;
042    import javax.naming.spi.ObjectFactory;
043    import net.jcip.annotations.GuardedBy;
044    import org.jboss.dna.common.i18n.I18n;
045    import org.jboss.dna.common.util.CheckArg;
046    import org.jboss.dna.graph.cache.CachePolicy;
047    import org.jboss.dna.graph.connectors.RepositoryConnection;
048    import org.jboss.dna.graph.connectors.RepositoryContext;
049    import org.jboss.dna.graph.connectors.RepositorySource;
050    import org.jboss.dna.graph.connectors.RepositorySourceCapabilities;
051    import org.jboss.dna.graph.connectors.RepositorySourceException;
052    
053    /**
054     * @author Randall Hauch
055     */
056    public class InMemoryRepositorySource implements RepositorySource, ObjectFactory {
057    
058        /**
059         * The initial version is 1
060         */
061        private static final long serialVersionUID = 1L;
062    
063        /**
064         * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source.
065         */
066        public static final int DEFAULT_RETRY_LIMIT = 0;
067    
068        protected static final RepositorySourceCapabilities CAPABILITIES = new RepositorySourceCapabilities(true, true);
069    
070        protected static final String ROOT_NODE_UUID = "rootNodeUuid";
071        protected static final String SOURCE_NAME = "sourceName";
072        protected static final String DEFAULT_CACHE_POLICY = "defaultCachePolicy";
073        protected static final String JNDI_NAME = "jndiName";
074        protected static final String RETRY_LIMIT = "retryLimit";
075    
076        @GuardedBy( "sourcesLock" )
077        private String name;
078        @GuardedBy( "this" )
079        private String jndiName;
080        private UUID rootNodeUuid = UUID.randomUUID();
081        private CachePolicy defaultCachePolicy;
082        private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT);
083        private transient InMemoryRepository repository;
084        private transient RepositoryContext repositoryContext;
085    
086        /**
087         * Create a repository source instance.
088         */
089        public InMemoryRepositorySource() {
090            super();
091        }
092    
093        /**
094         * {@inheritDoc}
095         * 
096         * @see org.jboss.dna.graph.connectors.RepositorySource#initialize(org.jboss.dna.graph.connectors.RepositoryContext)
097         */
098        public void initialize( RepositoryContext context ) throws RepositorySourceException {
099            this.repositoryContext = context;
100        }
101    
102        /**
103         * @return repositoryContext
104         */
105        public RepositoryContext getRepositoryContext() {
106            return repositoryContext;
107        }
108    
109        /**
110         * {@inheritDoc}
111         * 
112         * @see org.jboss.dna.graph.connectors.RepositorySource#getRetryLimit()
113         */
114        public int getRetryLimit() {
115            return retryLimit.get();
116        }
117    
118        /**
119         * {@inheritDoc}
120         * 
121         * @see org.jboss.dna.graph.connectors.RepositorySource#setRetryLimit(int)
122         */
123        public void setRetryLimit( int limit ) {
124            retryLimit.set(limit < 0 ? 0 : limit);
125        }
126    
127        /**
128         * Get the default cache policy for this source, or null if the global default cache policy should be used
129         * 
130         * @return the default cache policy, or null if this source has no explicit default cache policy
131         */
132        public CachePolicy getDefaultCachePolicy() {
133            return defaultCachePolicy;
134        }
135    
136        /**
137         * @param defaultCachePolicy Sets defaultCachePolicy to the specified value.
138         */
139        public void setDefaultCachePolicy( CachePolicy defaultCachePolicy ) {
140            this.defaultCachePolicy = defaultCachePolicy;
141        }
142    
143        /**
144         * @return rootNodeUuid
145         */
146        public UUID getRootNodeUuid() {
147            return this.rootNodeUuid;
148        }
149    
150        /**
151         * @param rootNodeUuid Sets rootNodeUuid to the specified value.
152         */
153        public void setRootNodeUuid( UUID rootNodeUuid ) {
154            this.rootNodeUuid = rootNodeUuid != null ? rootNodeUuid : UUID.randomUUID();
155        }
156    
157        /**
158         * If you use this to set a JNDI name, this source will be bound to that name using the default {@link InitialContext}. You
159         * can also do this manually if you have additional requirements.
160         * 
161         * @param name the JNDI name
162         * @throws NamingException if there is a problem registering this object
163         * @see #getJndiName()
164         */
165        public void setJndiName( String name ) throws NamingException {
166            setJndiName(name, null);
167        }
168    
169        /**
170         * Register this source in JNDI under the supplied name using the supplied context. to set a JNDI name, this source will be
171         * bound to that name using the default {@link InitialContext}. You can also do this manually if you have additional
172         * requirements.
173         * 
174         * @param name the JNDI name, or null if this object is to no longer be registered
175         * @param context the JNDI context, or null if the {@link InitialContext} should be used
176         * @throws NamingException if there is a problem registering this object
177         * @see #getJndiName()
178         */
179        public synchronized void setJndiName( String name,
180                                              Context context ) throws NamingException {
181            CheckArg.isNotNull(name, "name");
182            if (context == null) context = new InitialContext();
183    
184            // First register in JNDI under the new name ...
185            if (name != null) {
186                context.bind(name, this);
187            }
188            // Second, unregister from JNDI if there is already a name ...
189            if (jndiName != null && !jndiName.equals(name)) {
190                context.unbind(jndiName);
191            }
192            // Record the new name ...
193            this.jndiName = name;
194        }
195    
196        /**
197         * Gets the JNDI name this source is bound to. Only valid if you used setJNDIName to bind it.
198         * 
199         * @return the JNDI name, or null if it is not bound in JNDI
200         * @see #setJndiName(String)
201         */
202        public synchronized String getJndiName() {
203            return jndiName;
204        }
205    
206        /**
207         * {@inheritDoc}
208         */
209        public String getName() {
210            return this.name;
211        }
212    
213        /**
214         * @param name Sets name to the specified value.
215         */
216        public void setName( String name ) {
217            this.name = name;
218        }
219    
220        /**
221         * {@inheritDoc}
222         * 
223         * @see org.jboss.dna.graph.connectors.RepositorySource#getConnection()
224         */
225        public RepositoryConnection getConnection() throws RepositorySourceException {
226            if (repository == null) {
227                repository = new InMemoryRepository(name, rootNodeUuid);
228            }
229            return new InMemoryRepositoryConnection(this, repository);
230        }
231    
232        /**
233         * {@inheritDoc}
234         */
235        public synchronized Reference getReference() {
236            String className = getClass().getName();
237            String factoryClassName = this.getClass().getName();
238            Reference ref = new Reference(className, factoryClassName, null);
239    
240            if (getName() != null) {
241                ref.add(new StringRefAddr(SOURCE_NAME, getName()));
242            }
243            if (getRootNodeUuid() != null) {
244                ref.add(new StringRefAddr(ROOT_NODE_UUID, getRootNodeUuid().toString()));
245            }
246            if (getJndiName() != null) {
247                ref.add(new StringRefAddr(JNDI_NAME, getJndiName()));
248            }
249            if (getDefaultCachePolicy() != null) {
250                ByteArrayOutputStream baos = new ByteArrayOutputStream();
251                CachePolicy policy = getDefaultCachePolicy();
252                try {
253                    ObjectOutputStream oos = new ObjectOutputStream(baos);
254                    oos.writeObject(policy);
255                    ref.add(new BinaryRefAddr(DEFAULT_CACHE_POLICY, baos.toByteArray()));
256                } catch (IOException e) {
257                    I18n msg = InMemoryConnectorI18n.errorSerializingCachePolicyInSource;
258                    throw new RepositorySourceException(getName(), msg.text(policy.getClass().getName(), getName()), e);
259                }
260            }
261            ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
262            return ref;
263        }
264    
265        /**
266         * {@inheritDoc}
267         */
268        public Object getObjectInstance( Object obj,
269                                         javax.naming.Name name,
270                                         Context nameCtx,
271                                         Hashtable<?, ?> environment ) throws Exception {
272            if (obj instanceof Reference) {
273                Map<String, Object> values = new HashMap<String, Object>();
274                Reference ref = (Reference)obj;
275                Enumeration<?> en = ref.getAll();
276                while (en.hasMoreElements()) {
277                    RefAddr subref = (RefAddr)en.nextElement();
278                    if (subref instanceof StringRefAddr) {
279                        String key = subref.getType();
280                        Object value = subref.getContent();
281                        if (value != null) values.put(key, value.toString());
282                    } else if (subref instanceof BinaryRefAddr) {
283                        String key = subref.getType();
284                        Object value = subref.getContent();
285                        if (value instanceof byte[]) {
286                            // Deserialize ...
287                            ByteArrayInputStream bais = new ByteArrayInputStream((byte[])value);
288                            ObjectInputStream ois = new ObjectInputStream(bais);
289                            value = ois.readObject();
290                            values.put(key, value);
291                        }
292                    }
293                }
294                String sourceName = (String)values.get(SOURCE_NAME);
295                String rootNodeUuidString = (String)values.get(ROOT_NODE_UUID);
296                String jndiName = (String)values.get(JNDI_NAME);
297                Object defaultCachePolicy = values.get(DEFAULT_CACHE_POLICY);
298                String retryLimit = (String)values.get(RETRY_LIMIT);
299    
300                // Create the source instance ...
301                InMemoryRepositorySource source = new InMemoryRepositorySource();
302                if (sourceName != null) source.setName(sourceName);
303                if (rootNodeUuidString != null) source.setRootNodeUuid(UUID.fromString(rootNodeUuidString));
304                if (jndiName != null) source.setJndiName(jndiName);
305                if (defaultCachePolicy instanceof CachePolicy) {
306                    source.setDefaultCachePolicy((CachePolicy)defaultCachePolicy);
307                }
308                if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
309                return source;
310            }
311            return null;
312        }
313    
314        /**
315         * {@inheritDoc}
316         * 
317         * @see org.jboss.dna.graph.connectors.RepositorySource#getCapabilities()
318         */
319        public RepositorySourceCapabilities getCapabilities() {
320            return CAPABILITIES;
321        }
322    }