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.naming.context;
018
019 import javax.naming.CompositeName;
020 import javax.naming.Context;
021 import javax.naming.InvalidNameException;
022 import javax.naming.Name;
023 import javax.naming.NameAlreadyBoundException;
024 import javax.naming.NameParser;
025 import javax.naming.NamingEnumeration;
026 import javax.naming.NamingException;
027 import javax.naming.NotContextException;
028 import javax.naming.LinkRef;
029 import javax.naming.NameNotFoundException;
030 import javax.naming.InitialContext;
031 import javax.naming.OperationNotSupportedException;
032 import java.io.Serializable;
033 import java.util.Collections;
034 import java.util.Hashtable;
035 import java.util.Map;
036
037 public abstract class AbstractContext implements Context, NestedContextFactory, Serializable {
038 private static final long serialVersionUID = 6481918425692261483L;
039 private final String nameInNamespace;
040 private final Name parsedNameInNamespace;
041 private final ContextAccess contextAccess;
042 private final boolean modifiable;
043 private final ThreadLocal inCall = new ThreadLocal();
044
045 protected AbstractContext(String nameInNamespace) {
046 this(nameInNamespace, ContextAccess.MODIFIABLE);
047 }
048
049 public AbstractContext(String nameInNamespace, ContextAccess contextAccess) {
050 this.nameInNamespace = nameInNamespace;
051 try {
052 this.parsedNameInNamespace = getNameParser().parse(nameInNamespace);
053 } catch (NamingException e) {
054 throw new RuntimeException(e);
055 }
056 this.contextAccess = contextAccess;
057 this.modifiable = contextAccess.isModifiable(getParsedNameInNamespace());
058 }
059
060 public void close() throws NamingException {
061 //Ignore. Explicitly do not close the context
062 }
063
064 protected ContextAccess getContextAccess() {
065 return contextAccess;
066 }
067
068 //
069 // Lookup Binding
070 //
071
072 /**
073 * Gets the object bound to the name. The name may contain slashes.
074 * @param name the name
075 * @return the object bound to the name, or null if not found
076 */
077 protected Object getDeepBinding(String name) {
078 return null;
079 }
080
081 /**
082 * Gets the object bound to the name. The name will not contain slashes.
083 * @param name the name
084 * @return the object bound to the name, or null if not found
085 */
086 protected Object getBinding(String name) throws NamingException {
087 Map bindings = getBindings();
088 return bindings.get(name);
089 }
090
091 /**
092 * Finds the specified entry. Normally there is no need to override this method; instead you should
093 * simply implement the getDeepBindings(String) and getBindings(String) method.
094 *
095 * This method will follow links except for the final element which is always just returned without
096 * inspection. This means this method can be used to implement lookupLink.
097 *
098 * @param stringName the string version of the name; maybe null
099 * @param parsedName the parsed name; may be null
100 * @return the value bound to the name
101 * @throws NamingException if no value is bound to that name or if a problem occurs during the lookup
102 */
103 protected Object lookup(String stringName, Name parsedName) throws NamingException {
104 if (stringName == null && parsedName == null) {
105 throw new IllegalArgumentException("Both stringName and parsedName are null");
106 }
107 if (stringName == null) stringName = parsedName.toString();
108
109 // If the name starts with our name in namespace strip it off
110 // This works because the name in namespace is assumed to be absolute
111 if (stringName.startsWith(nameInNamespace)) {
112 stringName = stringName.substring(nameInNamespace.length());
113 }
114
115 // try to look up the name directly (this is the fastest path)
116 Object directLookup = getDeepBinding(stringName);
117 if (directLookup != null) {
118 return ContextUtil.resolve(stringName, directLookup);
119 }
120
121 // if the parsed name has no parts, they are asking for the current context
122 if (parsedName == null) parsedName = getNameParser().parse(stringName);
123 if (parsedName.isEmpty()) {
124 return this;
125 }
126
127 // we didn't find an entry, pop the first element off the parsed name and attempt to
128 // get a context from the bindings and delegate to that context
129 Object localValue;
130 String firstNameElement = parsedName.get(0);
131 if (firstNameElement.length() == 0) {
132 // the element is null... this is normally caused by looking up with a trailing '/' character
133 localValue = this;
134 } else {
135 localValue = getBinding(firstNameElement);
136 }
137
138 if (localValue != null) {
139
140 // if the name only had one part, we've looked up everything
141 if (parsedName.size() == 1) {
142 localValue = ContextUtil.resolve(stringName, localValue);
143 return localValue;
144 }
145
146 // if we have a link ref, follow it
147 if (localValue instanceof LinkRef) {
148 LinkRef linkRef = (LinkRef) localValue;
149 localValue = lookup(linkRef.getLinkName());
150 }
151
152 // we have more to lookup so we better have a context object
153 if (!(localValue instanceof Context)) {
154 throw new NameNotFoundException(stringName);
155 }
156
157 // delegate to the sub-context
158 return ((Context) localValue).lookup(parsedName.getSuffix(1));
159 }
160
161 // if we didn't find an entry, it may be an absolute name
162 Object value = faultLookup(stringName, parsedName);
163 if (value != null) {
164 return value;
165 }
166 if (parsedName.size() > 1) {
167 throw new NotContextException(stringName);
168 } else {
169 throw new NameNotFoundException(stringName);
170 }
171 }
172
173 /**
174 * When a value can not be found within this context, this method is called as a last ditch effort befrore
175 * thowing a null pointer exception.
176 * @param stringName the string version of the name; will not be null
177 * @param parsedName the parsed name; will not be null
178 * @return the value or null if no fault value could be found
179 */
180 protected Object faultLookup(String stringName, Name parsedName) {
181 if (!stringName.startsWith(nameInNamespace) && stringName.indexOf(':') > 0 && inCall.get() == null) {
182 inCall.set(parsedName);
183 try {
184 Context ctx = new InitialContext();
185 return ctx.lookup(parsedName);
186 } catch (NamingException ignored) {
187 // thrown below
188 } finally {
189 inCall.set(null);
190 }
191 }
192 return null;
193 }
194
195 protected Context lookupFinalContext(Name name) throws NamingException {
196 Object value = null;
197 try {
198 value = lookup(name.getPrefix(name.size() - 1));
199 } catch (NamingException e) {
200 throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does not exist");
201 }
202
203 if (value == null) {
204 throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does not exist");
205 } else if (!(value instanceof Context)) {
206 throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does is not a context");
207 } else {
208 return (Context) value;
209 }
210 }
211
212 //
213 // List Bindings
214 //
215
216 /**
217 * Gets a map of the bindings for the current node (i.e., no names with slashes).
218 * This method must not return null.
219 *
220 * @return a Map from binding name to binding value
221 * @throws NamingException if a problem occurs while getting the bindigns
222 */
223 protected abstract Map getBindings() throws NamingException;
224
225 //
226 // Add Binding
227 //
228
229 protected void addDeepBinding(Name name, Object value, boolean rebind, boolean createIntermediateContexts) throws NamingException {
230 if (name == null) throw new NullPointerException("name is null");
231 if (value == null) throw new NullPointerException("value is null");
232
233 if (name.isEmpty()) {
234 throw new InvalidNameException("Name is empty");
235 }
236
237 if (name.size() == 1) {
238 addBinding(name.get(0), value, rebind);
239 return;
240 }
241
242 if (!createIntermediateContexts) {
243 Context context = lookupFinalContext(name);
244
245 String lastSegment = name.get(name.size() - 1);
246 addBinding(context, lastSegment, value, rebind);
247 } else {
248 Context currentContext = this;
249 for (int i = 0; i < name.size(); i++) {
250 String part = name.get(i);
251
252 // empty path parts are not allowed
253 if (part.length() == 0) {
254 // this could be supported but it would be tricky
255 throw new InvalidNameException("Name part " + i + " is empty: " + name);
256 }
257
258 // Is this the last element in the name?
259 if (i == name.size() - 1) {
260 // we're at the end... (re)bind the value into the parent context
261 addBinding(currentContext, part, value, rebind);
262
263 // all done... this is redundant but makes the code more readable
264 break;
265 } else {
266 Object currentValue = getBinding(currentContext, part);
267 if (currentValue == null) {
268 // the next step in the tree is not present, so create everything down
269 // and add it to the current bindings
270 Context subcontext = createSubcontextTree(name.getPrefix(i).toString(), name.getSuffix(i), value);
271 addBinding(currentContext, part, subcontext, rebind);
272
273 // all done
274 break;
275 } else {
276 // the current value must be a nested subcontext
277 if (!(currentContext instanceof Context)) {
278 throw new NotContextException("Expected an instance of context to be bound at " +
279 part + " but found an instance of " + currentValue.getClass().getName());
280 }
281 currentContext = (Context) currentValue;
282 // now we recurse into the current context
283 }
284 }
285 }
286 }
287 }
288
289 /**
290 * Gets the value bound to the specified name within the specified context. If the specified context is an
291 * AbstractContext this method will call the faster getBinding method, otherwise it will call lookup.
292 *
293 * @param context the context to get the binding from
294 * @param name the binding name
295 * @return the bound value or null if no value was bound
296 */
297 private static Object getBinding(Context context, String name) {
298 try {
299 if (context instanceof AbstractContext) {
300 AbstractContext abstractContext = (AbstractContext) context;
301 Object value = abstractContext.getBinding(name);
302 return value;
303 } else {
304 Object value = context.lookup(name);
305 return value;
306 }
307 } catch (NamingException e) {
308 return null;
309 }
310 }
311
312 /**
313 * Binds the specified value to the specified name within the specified context. If the specified context is an
314 * AbstractContext and is a nested subcontext, this method will call the direct addBinding method, otherwise it
315 * will call public (re)bind method.
316 *
317 * @param context the context to add the binding to
318 * @param name the binding name
319 * @param value the value to bind
320 * @param rebind if true, this method will replace any exsiting binding, otherwise a NamingException will be thrown
321 * @throws NamingException if a problem occurs while (re)binding
322 */
323 protected void addBinding(Context context, String name, Object value, boolean rebind) throws NamingException {
324 if (context == this || (context instanceof AbstractContext && isNestedSubcontext(context))) {
325 AbstractContext abstractContext = (AbstractContext) context;
326 abstractContext.addBinding(name, value, rebind);
327 } else {
328 if (rebind) {
329 context.rebind(name, value);
330 } else {
331 context.bind(name, value);
332 }
333 }
334 }
335
336 protected abstract boolean addBinding(String name, Object value, boolean rebind) throws NamingException;
337
338 /**
339 * Creates a context tree which will be rooted at the specified path and contain a single entry located down
340 * a path specified by the name. All necessary intermediate contexts will be created using the createContext method.
341 * @param path the path to the context that will contains this context
342 * @param name the name under which the value should be bound
343 * @param value the vale
344 * @return a context with the value bound at the specified name
345 * @throws NamingException if a problem occurs while creating the subcontext tree
346 */
347 protected Context createSubcontextTree(String path, Name name, Object value) throws NamingException {
348 if (path == null) throw new NullPointerException("path is null");
349 if (name == null) throw new NullPointerException("name is null");
350 if (name.size() < 2) throw new InvalidNameException("name must have at least 2 parts " + name);
351
352 if (!path.endsWith("/")) path += "/";
353
354 for (int i = name.size() - 2; i >= 0; i--) {
355 String fullPath = path + name.getSuffix(i);
356 String key = name.get(i + 1);
357 value = createNestedSubcontext(fullPath, Collections.singletonMap(key, value));
358 }
359 return (Context) value;
360 }
361
362
363 //
364 // Remove Binding
365 //
366
367 /**
368 * Removes the binding from the context. The name will not contain a path and the value will not
369 * be a nested context although it may be a foreign context.
370 * @param name name under which the value should be bound
371 * @param removeNotEmptyContext
372 * @throws NamingException if a problem occurs during the bind such as a value already being bound
373 */
374 protected abstract boolean removeBinding(String name, boolean removeNotEmptyContext) throws NamingException;
375
376 protected void removeDeepBinding(Name name, boolean pruneEmptyContexts) throws NamingException {
377 removeDeepBinding(name, pruneEmptyContexts, false);
378 }
379
380 protected void removeDeepBinding(Name name, boolean pruneEmptyContexts, boolean removeNotEmptyContext) throws NamingException {
381 if (name == null) throw new NullPointerException("name is null");
382 if (name.isEmpty()) {
383 throw new InvalidNameException("Name is empty");
384 }
385
386 if (name.size() == 1) {
387 removeBinding(name.get(0), removeNotEmptyContext);
388 return;
389 }
390
391 if (!pruneEmptyContexts) {
392 Context context = lookupFinalContext(name);
393 context.unbind(name.getSuffix(name.size() - 1));
394 } else {
395 // we serch the tree for a target context and name to remove
396 // this is normally the last context in the tree and the final name part, but
397 // it may be farther up the path if the intervening nodes are empty
398 Context targetContext = this;
399 String targetName = name.get(0);
400
401 Context currentContext = this;
402 for (int i = 0; i < name.size(); i++) {
403 String part = name.get(i);
404
405 // empty path parts are not allowed
406 if (part.length() == 0) {
407 throw new InvalidNameException("Name part " + i + " is empty: " + name);
408 }
409
410 // update targets
411 if (getSize(currentContext) > 1) {
412 targetContext = currentContext;
413 targetName = part;
414 }
415
416
417 // Is this the last element in the name?
418 if (i == name.size() - 1) {
419 // we're at the end... unbind value
420 unbind(targetContext, targetName, true);
421
422 // all done... this is redundant but makes the code more readable
423 break;
424 } else {
425 Object currentValue = getBinding(currentContext, part);
426 if (currentValue == null) {
427 // path not found we are done, but first prune the empty contexts
428 if (targetContext != currentContext) {
429 unbind(targetContext, targetName, false);
430 }
431 break;
432 } else {
433 // the current value must be a context
434 if (!(currentValue instanceof Context)) {
435 throw new NotContextException("Expected an instance of context to be bound at " +
436 part + " but found an instance of " + currentValue.getClass().getName());
437 }
438 currentContext = (Context) currentValue;
439 // now we recurse into the current context
440 }
441 }
442 }
443 }
444 }
445
446 protected static boolean isEmpty(Context context) throws NamingException {
447 if (context instanceof AbstractContext) {
448 AbstractContext abstractContext = (AbstractContext) context;
449 Map currentBindings = abstractContext.getBindings();
450 return currentBindings.isEmpty();
451 } else {
452 NamingEnumeration namingEnumeration = context.list("");
453 return namingEnumeration.hasMore();
454 }
455 }
456
457 protected static int getSize(Context context) throws NamingException {
458 if (context instanceof AbstractContext) {
459 AbstractContext abstractContext = (AbstractContext) context;
460 Map currentBindings = abstractContext.getBindings();
461 return currentBindings.size();
462 } else {
463 NamingEnumeration namingEnumeration = context.list("");
464 int size = 0;
465 while (namingEnumeration.hasMore()) size++;
466 return size;
467 }
468 }
469
470 /**
471 * Unbinds any value bound to the specified name within the specified context. If the specified context is an
472 * AbstractContext and is a nested context, this method will call the direct removeBinding method, otherwise it
473 * will call public unbind.
474 *
475 * @param context the context to remove the binding from
476 * @param name the binding name
477 * @param removeNotEmptyContext
478 * @throws NamingException if a problem occurs while unbinding
479 */
480 private void unbind(Context context, String name, boolean removeNotEmptyContext) throws NamingException {
481 if (context == this || (context instanceof AbstractContext && isNestedSubcontext(context))) {
482 AbstractContext abstractContext = (AbstractContext) context;
483 abstractContext.removeBinding(name, removeNotEmptyContext);
484 } else {
485 context.unbind(name);
486 }
487 }
488
489 //
490 // Environment
491 //
492
493 /**
494 * Always returns a new (empty) Hashtable.
495 * @return a new (empty) Hashtable
496 */
497 public Hashtable getEnvironment() {
498 return new Hashtable();
499 }
500
501 public Object addToEnvironment(String propName, Object propVal) throws NamingException {
502 if (propName == null) throw new NullPointerException("propName is null");
503 if (propVal == null) throw new NullPointerException("propVal is null");
504
505 Map env = getEnvironment();
506 return env.put(propName, propVal);
507 }
508
509 public Object removeFromEnvironment(String propName) throws NamingException {
510 if (propName == null) throw new NullPointerException("propName is null");
511
512 Map env = getEnvironment();
513 return env.remove(propName);
514 }
515
516 //
517 // Name handling
518 //
519
520 /**
521 * Gets the name of this context withing the global namespace. This method may return null
522 * if the location of the node in the global namespace is not known
523 * @return the name of this context within the global namespace or null if unknown.
524 */
525 public String getNameInNamespace() {
526 return nameInNamespace;
527 }
528
529 /**
530 * Gets the name of this context withing the global namespace. This method may return null
531 * if the location of the node in the global namespace is not known
532 * @return the name of this context within the global namespace or null if unknown.
533 */
534 protected Name getParsedNameInNamespace() {
535 return parsedNameInNamespace;
536 }
537
538 /**
539 * Gets the name of a path withing the global namespace context.
540 */
541 protected String getNameInNamespace(String path) {
542 String nameInNamespace = getNameInNamespace();
543 if (nameInNamespace == null || nameInNamespace.length() == 0) {
544 return path;
545 } else {
546 return nameInNamespace + "/" + path;
547 }
548 }
549
550 /**
551 * Gets the name of a path withing the global namespace context.
552 */
553 protected Name getNameInNamespace(Name path) throws NamingException {
554 Name nameInNamespace = getParsedNameInNamespace();
555 if (nameInNamespace == null || nameInNamespace.size() == 0) {
556 return path;
557 } else {
558 return composeName(nameInNamespace, path);
559 }
560 }
561
562 /**
563 * A parser that can turn Strings into javax.naming.Name objects.
564 * @return ContextUtil.NAME_PARSER
565 */
566 protected NameParser getNameParser() {
567 return ContextUtil.NAME_PARSER;
568 }
569
570 public NameParser getNameParser(Name name) {
571 return getNameParser();
572 }
573
574 public NameParser getNameParser(String name) {
575 return getNameParser();
576 }
577
578 public Name composeName(Name name, Name prefix) throws NamingException {
579 if (name == null) throw new NullPointerException("name is null");
580 if (prefix == null) throw new NullPointerException("prefix is null");
581
582 Name result = (Name) prefix.clone();
583 result.addAll(name);
584 return result;
585 }
586
587 public String composeName(String name, String prefix) throws NamingException {
588 if (name == null) throw new NullPointerException("name is null");
589 if (prefix == null) throw new NullPointerException("prefix is null");
590
591 CompositeName result = new CompositeName(prefix);
592 result.addAll(new CompositeName(name));
593 return result.toString();
594 }
595
596 //
597 // Lookup
598 //
599
600 public Object lookup(String name) throws NamingException {
601 if (name == null) throw new NullPointerException("name is null");
602
603 Object value = lookup(name, null);
604
605 // if we got a link back we need to resolve it
606 if (value instanceof LinkRef) {
607 LinkRef linkRef = (LinkRef) value;
608 value = lookup(linkRef.getLinkName());
609 }
610
611 return value;
612 }
613
614 public Object lookup(Name name) throws NamingException {
615 if (name == null) throw new NullPointerException("name is null");
616
617 Object value = lookup(null, name);
618
619
620 // if we got a link back we need to resolve it
621 if (value instanceof LinkRef) {
622 LinkRef linkRef = (LinkRef) value;
623 value = lookup(linkRef.getLinkName());
624 }
625
626 return value;
627 }
628
629 public Object lookupLink(String name) throws NamingException {
630 if (name == null) throw new NullPointerException("name is null");
631 return lookup(name, null);
632 }
633
634 public Object lookupLink(Name name) throws NamingException {
635 if (name == null) throw new NullPointerException("name is null");
636 return lookup(null, name);
637 }
638
639 //
640 // Bind, rebind, rename and unbind
641 //
642
643 public void bind(String name, Object obj) throws NamingException {
644 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
645 if (name == null) throw new NullPointerException("name is null");
646 if (name.length() == 0) {
647 throw new NameAlreadyBoundException("Cannot bind to an empty name (this context)");
648 }
649 bind(new CompositeName(name), obj);
650 }
651
652 public void bind(Name name, Object obj) throws NamingException {
653 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
654 if (name == null) throw new NullPointerException("name is null");
655 if (name.isEmpty()) {
656 throw new NameAlreadyBoundException("Cannot bind to an empty name (this context)");
657 }
658 addDeepBinding(name, obj, false, false);
659 }
660
661 public void rebind(String name, Object obj) throws NamingException {
662 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
663 if (name == null) throw new NullPointerException("name is null");
664 rebind(new CompositeName(name), obj);
665 }
666
667 public void rebind(Name name, Object obj) throws NamingException {
668 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
669 if (name == null) throw new NullPointerException("name is null");
670 if (name.isEmpty()) {
671 throw new NameAlreadyBoundException("Cannot rebind an empty name (this context)");
672 }
673 addDeepBinding(name, obj, true, false);
674 }
675
676 public void rename(String oldName, String newName) throws NamingException {
677 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
678 if (oldName == null) throw new NullPointerException("oldName is null");
679 if (newName == null) throw new NullPointerException("newName is null");
680 rename(new CompositeName(oldName), new CompositeName(newName));
681 }
682
683 public void rename(Name oldName, Name newName) throws NamingException {
684 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
685 if (oldName == null || newName == null) {
686 throw new NullPointerException("name is null");
687 } else if (oldName.isEmpty() || newName.isEmpty()) {
688 throw new NameAlreadyBoundException("Name cannot be empty");
689 }
690 this.bind(newName, this.lookup(oldName));
691 this.unbind(oldName);
692 }
693
694 public void unbind(String name) throws NamingException {
695 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
696 if (name == null) throw new NullPointerException("name is null");
697 unbind(new CompositeName(name));
698 }
699
700 public void unbind(Name name) throws NamingException {
701 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
702 if (name == null) throw new NullPointerException("name is null");
703 if (name.isEmpty()) {
704 throw new InvalidNameException("Cannot unbind empty name");
705 }
706 removeDeepBinding(name, false);
707 }
708
709 //
710 // List
711 //
712
713 protected NamingEnumeration list() throws NamingException {
714 Map bindings = getBindings();
715 return new ContextUtil.ListEnumeration(bindings);
716 }
717
718 protected NamingEnumeration listBindings() throws NamingException {
719 Map bindings = getBindings();
720 return new ContextUtil.ListBindingEnumeration(bindings);
721 }
722
723 public NamingEnumeration list(String name) throws NamingException {
724 if (name == null) throw new NullPointerException("name is null");
725
726 // if the name is empty, list the current context
727 if (name.length() == 0) {
728 return list();
729 }
730
731 // lookup the target context
732 Object target = null;
733 try {
734 target = lookup(name);
735 } catch (NamingException e) {
736 throw new NotContextException(name);
737 }
738
739 if (target == this) {
740 return list();
741 } else if (target instanceof Context) {
742 return ((Context) target).list("");
743 } else {
744 throw new NotContextException("The name " + name + " cannot be listed");
745 }
746 }
747
748 public NamingEnumeration list(Name name) throws NamingException {
749 if (name == null) throw new NullPointerException("name is null");
750
751 // if the name is empty, list the current context
752 if (name.isEmpty()) {
753 return list();
754 }
755
756 // lookup the target context
757 Object target = null;
758 try {
759 target = lookup(name);
760 } catch (NamingException e) {
761 throw new NotContextException(name.toString());
762 }
763
764 if (target == this) {
765 return list();
766 } else if (target instanceof Context) {
767 return ((Context) target).list("");
768 } else {
769 throw new NotContextException("The name " + name + " cannot be listed");
770 }
771 }
772
773 public NamingEnumeration listBindings(String name) throws NamingException {
774 if (name == null) throw new NullPointerException("name is null");
775
776 // if the name is empty, list the current context
777 if (name.length() == 0) {
778 return listBindings();
779 }
780
781 // lookup the target context
782 Object target = null;
783 try {
784 target = lookup(name);
785 } catch (NamingException e) {
786 throw new NotContextException(name.toString());
787 }
788
789 if (target == this) {
790 return listBindings();
791 } else if (target instanceof Context) {
792 return ((Context) target).listBindings("");
793 } else {
794 throw new NotContextException("The name " + name + " cannot be listed");
795 }
796 }
797
798 public NamingEnumeration listBindings(Name name) throws NamingException {
799 if (name == null) throw new NullPointerException("name is null");
800
801 // if the name is empty, list the current context
802 if (name.isEmpty()) {
803 return listBindings();
804 }
805
806 // lookup the target context
807 Object target = null;
808 try {
809 target = lookup(name);
810 } catch (NamingException e) {
811 throw new NotContextException(name.toString());
812 }
813
814 if (target == this) {
815 return listBindings();
816 } else if (target instanceof Context) {
817 return ((Context) target).listBindings("");
818 } else {
819 throw new NotContextException("The name " + name + " cannot be listed");
820 }
821 }
822
823 //
824 // Subcontexts
825 //
826
827 public Context createSubcontext(String name) throws NamingException {
828 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
829 if (name == null) throw new NullPointerException("name is null");
830 return createSubcontext(new CompositeName(name));
831 }
832
833 public Context createSubcontext(Name name) throws NamingException {
834 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
835 if (name == null) throw new NullPointerException("name is null");
836 if (name.isEmpty()) {
837 throw new NameAlreadyBoundException("Cannot create a subcontext if the name is empty");
838 }
839 Context abstractContext = createNestedSubcontext(name.toString(), Collections.EMPTY_MAP);
840 addDeepBinding(name, abstractContext, false, false);
841 return abstractContext;
842 }
843
844 public void destroySubcontext(String name) throws NamingException {
845 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
846 if (name == null) throw new NullPointerException("name is null");
847 destroySubcontext(new CompositeName(name));
848 }
849
850 public void destroySubcontext(Name name) throws NamingException {
851 if (!modifiable) throw new OperationNotSupportedException("Context is read only");
852 if (name == null) throw new NullPointerException("name is null");
853 if (name.isEmpty()) {
854 throw new InvalidNameException("Cannot destroy subcontext with empty name");
855 }
856 unbind(name);
857 }
858 }