001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.xbean.spring.jndi;
018    
019    import javax.naming.Binding;
020    import javax.naming.CompositeName;
021    import javax.naming.Context;
022    import javax.naming.LinkRef;
023    import javax.naming.Name;
024    import javax.naming.NameClassPair;
025    import javax.naming.NameNotFoundException;
026    import javax.naming.NameParser;
027    import javax.naming.NamingEnumeration;
028    import javax.naming.NamingException;
029    import javax.naming.NotContextException;
030    import javax.naming.OperationNotSupportedException;
031    import javax.naming.Reference;
032    import javax.naming.spi.NamingManager;
033    
034    import java.io.Serializable;
035    import java.util.HashMap;
036    import java.util.Hashtable;
037    import java.util.Iterator;
038    import java.util.Map;
039    
040    /**
041     * A simple spring based JNDI context which is mutable
042     *
043     * @version $Revision: 657 $
044     */
045    public class DefaultContext implements Context, Serializable {
046    
047        private static final long serialVersionUID = -5754338187296859149L;
048        protected static final NameParser nameParser = new NameParserImpl();
049    
050        private boolean freeze = false;
051    
052        protected final Hashtable environment;        // environment for this context
053        protected final Map bindings;         // bindings at my level
054        protected final Map treeBindings;     // all bindings under me
055    
056        private boolean frozen = false;
057        private String nameInNamespace = "";
058        public static final String SEPARATOR = "/";
059    
060        public DefaultContext() {
061            environment = new Hashtable();
062            bindings = new HashMap();
063            treeBindings = new HashMap();
064        }
065    
066        public DefaultContext(Hashtable env) {
067            if (env == null) {
068                this.environment = new Hashtable();
069            }
070            else {
071                this.environment = new Hashtable(env);
072            }
073            this.bindings = new HashMap();
074            this.treeBindings = new HashMap();
075        }
076    
077        public DefaultContext(Hashtable environment, Map bindings) {
078            if (environment == null) {
079                this.environment = new Hashtable();
080            }
081            else {
082                this.environment = new Hashtable(environment);
083            }
084            this.bindings = bindings;
085            treeBindings = new HashMap();
086            frozen = true;
087        }
088    
089        public DefaultContext(Hashtable environment, Map bindings, String nameInNamespace) {
090            this(environment, bindings);
091            this.nameInNamespace = nameInNamespace;
092        }
093    
094        protected DefaultContext(DefaultContext clone, Hashtable env) {
095            this.bindings = clone.bindings;
096            this.treeBindings = clone.treeBindings;
097            this.environment = new Hashtable(env);
098        }
099    
100        protected DefaultContext(DefaultContext clone, Hashtable env, String nameInNamespace) {
101            this(clone, env);
102            this.nameInNamespace = nameInNamespace;
103        }
104    
105        public Object addToEnvironment(String propName, Object propVal) throws NamingException {
106            return environment.put(propName, propVal);
107        }
108    
109        public Hashtable getEnvironment() throws NamingException {
110            return (Hashtable) environment.clone();
111        }
112    
113        public Object removeFromEnvironment(String propName) throws NamingException {
114            return environment.remove(propName);
115        }
116    
117        public Object lookup(String name) throws NamingException {
118            if (name.length() == 0) {
119                return this;
120            }
121            Object result = treeBindings.get(name);
122            if (result == null) {
123                result = bindings.get(name);
124            }
125            if (result == null) {
126                int pos = name.indexOf(':');
127                if (pos > 0) {
128                    String scheme = name.substring(0, pos);
129                    Context ctx = NamingManager.getURLContext(scheme, environment);
130                    if (ctx == null) {
131                        throw new NamingException("scheme " + scheme + " not recognized");
132                    }
133                    return ctx.lookup(name);
134                }
135                else {
136                    // Split out the first name of the path
137                    // and look for it in the bindings map.
138                    CompositeName path = new CompositeName(name);
139    
140                    if (path.size() == 0) {
141                        return this;
142                    }
143                    else {
144                        String first = path.get(0);
145                        Object obj = bindings.get(first);
146                        if (obj == null) {
147                            throw new NameNotFoundException(name);
148                        }
149                        else if (obj instanceof Context && path.size() > 1) {
150                            Context subContext = (Context) obj;
151                            obj = subContext.lookup(path.getSuffix(1));
152                        }
153                        return obj;
154                    }
155                }
156            }
157            if (result instanceof LinkRef) {
158                LinkRef ref = (LinkRef) result;
159                result = lookup(ref.getLinkName());
160            }
161            if (result instanceof Reference) {
162                try {
163                    result = NamingManager.getObjectInstance(result, null, null, this.environment);
164                }
165                catch (NamingException e) {
166                    throw e;
167                }
168                catch (Exception e) {
169                    throw (NamingException) new NamingException("could not look up : " + name).initCause(e);
170                }
171            }
172            if (result instanceof DefaultContext) {
173                String prefix = getNameInNamespace();
174                if (prefix.length() > 0) {
175                    prefix = prefix + SEPARATOR;
176                }
177                result = new DefaultContext((DefaultContext) result, environment, prefix + name);
178            }
179            return result;
180        }
181    
182        public Object lookup(Name name) throws NamingException {
183            return lookup(name.toString());
184        }
185    
186        public Object lookupLink(String name) throws NamingException {
187            return lookup(name);
188        }
189    
190        public Name composeName(Name name, Name prefix) throws NamingException {
191            Name result = (Name) prefix.clone();
192            result.addAll(name);
193            return result;
194        }
195    
196        public String composeName(String name, String prefix) throws NamingException {
197            CompositeName result = new CompositeName(prefix);
198            result.addAll(new CompositeName(name));
199            return result.toString();
200        }
201    
202        public NamingEnumeration list(String name) throws NamingException {
203            Object o = lookup(name);
204            if (o == this) {
205                return new DefaultContext.ListEnumeration();
206            }
207            else if (o instanceof Context) {
208                return ((Context) o).list("");
209            }
210            else {
211                throw new NotContextException();
212            }
213        }
214    
215        public NamingEnumeration listBindings(String name) throws NamingException {
216            Object o = lookup(name);
217            if (o == this) {
218                return new DefaultContext.ListBindingEnumeration();
219            }
220            else if (o instanceof Context) {
221                return ((Context) o).listBindings("");
222            }
223            else {
224                throw new NotContextException();
225            }
226        }
227    
228        public Object lookupLink(Name name) throws NamingException {
229            return lookupLink(name.toString());
230        }
231    
232        public NamingEnumeration list(Name name) throws NamingException {
233            return list(name.toString());
234        }
235    
236        public NamingEnumeration listBindings(Name name) throws NamingException {
237            return listBindings(name.toString());
238        }
239    
240        public void bind(Name name, Object value) throws NamingException {
241            bind(name.toString(), value);
242        }
243    
244        public void bind(String name, Object value) throws NamingException {
245            checkFrozen();
246            internalBind(name, value);
247        }
248    
249        public void close() throws NamingException {
250            // ignore
251        }
252    
253        public Context createSubcontext(Name name) throws NamingException {
254            throw new OperationNotSupportedException();
255        }
256    
257        public Context createSubcontext(String name) throws NamingException {
258            throw new OperationNotSupportedException();
259        }
260    
261        public void destroySubcontext(Name name) throws NamingException {
262            throw new OperationNotSupportedException();
263        }
264    
265        public void destroySubcontext(String name) throws NamingException {
266            throw new OperationNotSupportedException();
267        }
268    
269        public String getNameInNamespace() throws NamingException {
270            return nameInNamespace;
271        }
272    
273        public NameParser getNameParser(Name name) throws NamingException {
274            return nameParser;
275        }
276    
277        public NameParser getNameParser(String name) throws NamingException {
278            return nameParser;
279        }
280    
281        public void rebind(Name name, Object value) throws NamingException {
282            rebind(name.toString(), value);
283        }
284    
285        public void rebind(String name, Object value) throws NamingException {
286            checkFrozen();
287            internalBind(name, value, true);
288        }
289    
290        public void rename(Name oldName, Name newName) throws NamingException {
291            checkFrozen();
292            Object value = lookup(oldName);
293            unbind(oldName);
294            bind(newName, value);
295        }
296    
297        public void rename(String oldName, String newName) throws NamingException {
298            Object value = lookup(oldName);
299            unbind(oldName);
300            bind(newName, value);
301        }
302    
303        public void unbind(Name name) throws NamingException {
304            unbind(name.toString());
305        }
306    
307        public void unbind(String name) throws NamingException {
308            checkFrozen();
309            internalBind(name, null, true);
310        }
311    
312        private abstract class LocalNamingEnumeration implements NamingEnumeration {
313            private Iterator i = bindings.entrySet().iterator();
314    
315            public boolean hasMore() throws NamingException {
316                return i.hasNext();
317            }
318    
319            public boolean hasMoreElements() {
320                return i.hasNext();
321            }
322    
323            protected Map.Entry getNext() {
324                return (Map.Entry) i.next();
325            }
326    
327            public void close() throws NamingException {
328            }
329        }
330    
331        private class ListEnumeration extends DefaultContext.LocalNamingEnumeration {
332            public Object next() throws NamingException {
333                return nextElement();
334            }
335    
336            public Object nextElement() {
337                Map.Entry entry = getNext();
338                return new NameClassPair((String) entry.getKey(), entry.getValue().getClass().getName());
339            }
340        }
341    
342        private class ListBindingEnumeration extends DefaultContext.LocalNamingEnumeration {
343            public Object next() throws NamingException {
344                return nextElement();
345            }
346    
347            public Object nextElement() {
348                Map.Entry entry = getNext();
349                return new Binding((String) entry.getKey(), entry.getValue());
350            }
351        }
352    
353        public Map getEntries() {
354            return new HashMap(bindings);
355        }
356    
357        public void setEntries(Map entries) throws NamingException {
358            if (entries != null) {
359                for (Iterator iter = entries.entrySet().iterator(); iter.hasNext();) {
360                    Map.Entry entry = (Map.Entry) iter.next();
361                    String name = (String) entry.getKey();
362                    Object value = entry.getValue();
363                    internalBind(name, value);
364                }
365            }
366        }
367    
368        public boolean isFreeze() {
369            return freeze;
370        }
371    
372        public void setFreeze(boolean freeze) {
373            this.freeze = freeze;
374        }
375    
376        /**
377         * internalBind is intended for use only during setup or possibly by suitably synchronized superclasses.
378         * It binds every possible lookup into a map in each context.  To do this, each context
379         * strips off one name segment and if necessary creates a new context for it. Then it asks that context
380         * to bind the remaining name.  It returns a map containing all the bindings from the next context, plus
381         * the context it just created (if it in fact created it). (the names are suitably extended by the segment
382         * originally lopped off).
383         *
384         * @param name
385         * @param value
386         * @return
387         * @throws javax.naming.NamingException
388         */
389        protected Map internalBind(String name, Object value) throws NamingException {
390            return internalBind(name, value, false);
391            
392        }
393        protected Map internalBind(String name, Object value, boolean allowRebind) throws NamingException {
394            
395            if (name == null || name.length() == 0){
396                throw new NamingException("Invalid Name " + name);
397            }
398            if (frozen){
399                throw new NamingException("Read only");
400            }
401    
402            Map newBindings = new HashMap();
403            int pos = name.indexOf('/');
404            if (pos == -1) {
405                Object oldValue = treeBindings.put(name, value);
406                if (!allowRebind && oldValue != null) {
407                    throw new NamingException("Something already bound at " + name);
408                }
409                bindings.put(name, value);
410                newBindings.put(name, value);
411            }
412            else {
413                String segment = name.substring(0, pos);
414              
415                if (segment == null || segment.length()==0){
416                    throw new NamingException("Invalid segment " + segment);
417                }
418                Object o = treeBindings.get(segment);
419                if (o == null) {
420                    o = newContext();
421                    treeBindings.put(segment, o);
422                    bindings.put(segment, o);
423                    newBindings.put(segment, o);
424                }
425                else if (!(o instanceof DefaultContext)) {
426                    throw new NamingException("Something already bound where a subcontext should go");
427                }
428                DefaultContext defaultContext = (DefaultContext) o;
429                String remainder = name.substring(pos + 1);
430                Map subBindings = defaultContext.internalBind(remainder, value, allowRebind);
431                for (Iterator iterator = subBindings.entrySet().iterator(); iterator.hasNext();) {
432                    Map.Entry entry = (Map.Entry) iterator.next();
433                    String subName = segment + "/" + (String) entry.getKey();
434                    Object bound = entry.getValue();
435                    treeBindings.put(subName, bound);
436                    newBindings.put(subName, bound);
437                }
438            }
439            return newBindings;
440        }
441    
442        protected void checkFrozen() throws OperationNotSupportedException {
443            if (isFreeze()) {
444                throw new OperationNotSupportedException("JNDI context is frozen!");
445            }
446        }
447    
448        protected DefaultContext newContext() {
449            return new DefaultContext();
450        }
451    
452    }