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 */
017package org.apache.xbean.spring.jndi;
018
019import javax.naming.Binding;
020import javax.naming.CompositeName;
021import javax.naming.Context;
022import javax.naming.LinkRef;
023import javax.naming.Name;
024import javax.naming.NameClassPair;
025import javax.naming.NameNotFoundException;
026import javax.naming.NameParser;
027import javax.naming.NamingEnumeration;
028import javax.naming.NamingException;
029import javax.naming.NotContextException;
030import javax.naming.OperationNotSupportedException;
031import javax.naming.Reference;
032import javax.naming.spi.NamingManager;
033
034import java.io.Serializable;
035import java.util.HashMap;
036import java.util.Hashtable;
037import java.util.Iterator;
038import java.util.Map;
039
040/**
041 * A simple spring based JNDI context which is mutable
042 *
043 * @version $Revision: 657 $
044 */
045public 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}