| Adding URL Support

## URL Context Implementation

> A URL context implementation is a context that can handle arbitrary URL strings of the URL scheme supported by the context. It is a class that implements the `Context` interface or one of its subinterfaces. It differs from the (plain) context implementation described in the The Essential Components lesson in the way that its methods accept and process the name argument.

> #### Structured versus String Names

> The Ground Rules lesson suggests that you implement the context methods that accept strings in terms of their `Name` counterparts because string names should be treated like (JNDI) composite names. This rule does not apply to URL context implementations, because a URL string is not a JNDI composite name. A URL string should be processed according to the syntax defined by its URL scheme. In fact, the dependency is the other way around. That is, the overloads that accept `Name` should be implemented in terms of their string counterparts.

> If you do not want to support federation, then you can just throw an `InvalidNameException` when presented with a multicomponent `Name`:

> >     public void bind(Name name, Object obj) throws NamingException {
> >         if (name.size() == 1) {
> >             bind(name.get(0), obj);
> >         } else {
> >             throw new InvalidNameException(
> >     	    "Cannot have composite names with URLs");
> >         }
> >     }

> If you do support federation, then when presented with a `CompositeName` whose first component is a valid URL string, you should treat the first component as a URL and the rest as components for federation (that is, resolving the URL will tell you which naming system to use to resolve the remaining part). If presented with a `Name` that is not a `CompositeName`, you should treat this as an error case and throw an `InvalidNameException`. This is because a URL embedded within a compound name is nonsensical.

> This example doesn't check whether the input is a `CompositeName`. Here is the definition of `bind()` from the example.

> >     public void bind(Name name, Object obj) throws NamingException {
> >         if (name.size() == 1) {
> >             bind(name.get(0), obj);
> >         } else {
> >     	Context ctx = getContinuationContext(name);
> >     	try {
> >     	    ctx.bind(name.getSuffix(1), obj);
> >     	} finally {
> >     	    ctx.close();
> >             }
> >         }
> >     }

> All overloads that accept `Name` use a similar pattern. If the name contains a single component, then extract the first component and pass it to the overload that accepts a string name. This resembles the caller's using the overload that accepts a string name in the first place. Otherwise, use the utility method `getContinuationContext()` to resolve the first component (i.e., the URL string) and continue the operation in that context. Here is the definition of `getContinuationContext()`.

> >     protected Context getContinuationContext(Name n) throws NamingException {
> >         Object obj = lookup(n.get(0));
> >         CannotProceedException cpe = new CannotProceedException();
> >         cpe.setResolvedObj(obj);
> >         cpe.setEnvironment(myEnv);
> >         return NamingManager.getContinuationContext(cpe);
> >     }

> This utility method resolves the first component of the name and uses the result as the "resolved object" in a call to `NamingManager.getContinuationContext()` to get the target context in which to continue the operation on the remainder of the name.

> #### The Root URL Context

> Now that you have taken care of the overloads that accept `Name`, you can turn your attention to the overloads that accept `java.lang.String`. The implementations of these methods depend highly on their "non-URL" counterpart context implementation. For example, a URL context implementation for the LDAP URL is highly dependent on the context implementation for the LDAP service. A lookup of an LDAP URL in an LDAP URL context typically results in a lookup in an LDAP context (from the LDAP context implementation) using an LDAP DN. In this scenario, the URL context implementation is really just a front-end to the actual service's context implementation. Because of this close relationship, this example might not apply as well to some actual implementations.

> The example uses the notion of a root context that is derived from the input URL string. It defines a utility method, `getRootURLContext()`, that accepts a URL string. This method returns a tuple that consists of a context that is derived from information in the URL and the remaining name from the URL to be resolved relative to the root context. For example, in the LDAP example, suppose that the input URL string is `"ldap://favserver:289/o=jnditutorial"`. The root context might be the context at the root of the LDAP server at machine `favserver` and port `289`. In that case, the `"ldap://favserver:289/"` portion of the URL string will be consumed in producing the root context and the remaining name will be `"o=jnditutorial"`.

> In our `foo` URL example, the root context points to the root of the `HierCtx` static namespace, and `"foo:/"` is consumed in producing the root context. The remaining name is represented as a single component `CompositeName`.

> >     protected ResolveResult getRootURLContext(String url, Hashtable env)
> >         throws NamingException {
> >         if (!url.startsWith("foo:/")) {
> >     	throw new IllegalArgumentException(url + " is not a foo URL");
> >         }

> >         String objName = url.length() > 5 ? url.substring(5) : null;

> >         // Represent the object name as empty or a single-component composite name.
> >         CompositeName remaining = new CompositeName();
> >         if (objName != null) {
> >     	remaining.add(objName);
> >         }

> >         // Get the handle to the static namespace to use for testing
> >         // In an actual implementation, this might be the root
> >         // namespace on a particular server
> >         Context ctx = tut.HierCtx.getStaticNamespace(env);

> >         return (new ResolveResult(ctx, remaining));
> >     }

> The overloads that accept string names use this utility method to process the URL string and then complete the operation. Here is the definition of `bind()` from the example.

> >     public void bind(String name, Object obj) throws NamingException {
> >         ResolveResult res = getRootURLContext(name, myEnv);
> >         Context ctx = (Context)res.getResolvedObj();
> >         try {
> >             ctx.bind(res.getRemainingName(), obj);
> >         } finally {
> >             ctx.close();
> >         }
> >     }

> Notice that before the method returns, it closes the root context. In this example, this step is not really necessary because `fooURLContext` doesn't maintain any connections or resources. However, doing this is good practice so as to ensure that implementations that do maintain connections or resources are cleaned up properly. This also means that stateful methods such as `list()` must ensure that the enumeration results that they return remain usable even after the context has been closed.

> #### Special Considerations for rename()

> `rename()` differs from the other context methods in that it accepts two names instead of one. With one name, you can use `getRootURLContext()` to get a workable context to complete the operation. With two names, you cannot call `getRootURLContext()` twice, once for each name, because each call might return a different context. `rename()` can have only one context in which to do the renaming.

> To solve this for the example here, you first extract from both names a common prefix (by using the internal utility `getURLPrefix()`). Then you use `getRootURLContext()` to get the root context and remaining name of the original name. To get the remaining name of the new name, you use another internal utility, `getURLSuffix()`. Note that it is very important that all three methods--`getRootURLContext()`, `getURLPrefix()`, and `getURLSuffix()`--are in complete agreement regarding how a URL string is parsed and which parts are designated the prefix and suffix.

> In particular, `getRootURLContext()` should consume the portion that is returned by `getURLPrefix()` to create its root context and return as remaining name the portion that `getURLSuffix()` will return. You also should take into account the restrictions on a context implementation's ability to perform renames across different servers or namespaces.

> Here is the example of `rename()`.

> >     public void rename(String oldName, String newName)
> >         throws NamingException {
> >         String oldPrefix = getURLPrefix(oldName);
> >         String newPrefix = getURLPrefix(newName);
> >         if (!urlEquals(oldPrefix, newPrefix)) {
> >     	throw new OperationNotSupportedException(
> >     	    "Renaming using different URL prefixes not supported : " +
> >     		oldName + " " + newName);
> >         }

> >         ResolveResult res = getRootURLContext(oldName, myEnv);
> >         Context ctx = (Context)res.getResolvedObj();
> >         try {
> >             ctx.rename(res.getRemainingName(),
> >     	    getURLSuffix(newPrefix, newName));
> >         } finally {
> >     	ctx.close();
> >         }
> >     }

> Here are the implementations of `getURLPrefix()` and `getURLSuffix()`.

> >     protected String getURLPrefix(String url) throws NamingException {
> >         int start = url.indexOf(":");

> >         if (start < 0) {
> >             throw new OperationNotSupportedException("Invalid URL: " + url);
> >         }
> >         ++start; // Skip ":"

> >         if (url.startsWith("//", start)) {
> >     	start += 2;  // Skip the double forward slash

> >     	// Find the last forward slash
> >     	int posn = url.indexOf("/", start);
> >     	if (posn >= 0) {
> >     	    start = posn;
> >     	} else {
> >     	    start = url.length();  // Rest of the URL
> >     	}
> >         }

> >         // Else 0 or 1 initial slashes; the start is unchanged
> >         return url.substring(0, start);
> >     }

> >     protected Name getURLSuffix(String prefix, String url)
> >         throws NamingException {
> >         String suffix = url.substring(prefix.length());
> >         if (suffix.length() == 0) {
> >             return new CompositeName();
> >         }

> >         if (suffix.charAt(0) == '/') {
> >     	suffix = suffix.substring(1); // Skip the leading forward slash
> >         }

> >         // Note: This is a simplified implementation;
> >         // a real implementation should
> >         // transform any URL-encoded characters into their Unicode char
> >         // representations
> >         return new CompositeName().add(suffix);
> >     }

| Adding URL Support