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=jndidocs". 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=jndidocs".

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