 |
Dealing with Optionally Present APIs
Introduction
The HD Cookbook introduces the topic of dealing with optionally present APIs on page 16-5. In Blu-ray Java, most platform signatures are required to be present. For example. the class java.net.Socket is present on all Blu-ray players, even though a socket can only be created on a BD-Live player. However, there are some APIs that are only guaranteed to be present on some player profiles. For example, org.dvb.net.rc is only present in BD-Live players. Probably the only useful API that is only present on some players are the JSSE APIs in javax.net.ssl, which are only present on BD-Live players.
Please read the cookbook pages 16-5 through 16-6 for a more thorough introduction to this topic, including the brief introduction to GEM 1.0.3 annex W.3. As mentioned in the book, the best way to deal with optionally present APIs is to only launch an xlet that depends on them on players that include the APIs in question. For example, if you need to use the javax.net.ssl APIs directly, rather than just using java.net.URL with an https: URL, then you could put the functionality that requires those APIs in its own Xlet that only gets launched on BD-Live players. However, in certain circumstances this may not be possible. This article explains more about why this issue is present in Java, and how you can safely code around it.
What's the problem, anyway?
When a class is loaded into the Java Virtual Machine and code is executed, symbolic references to other classes need to be resolved by the VM. The Virtual Machine specification allows some latitude as to when these references are resolved, and when any linkage errors are reported to the application. Some VM implementations are called "eager linking," and attempt to resolve symbolic references early, and some eager linking implementations also report those errors to the application early. Further, the class verifier will sometimes resolve symbolic references fairly early, at times you might not expect. This can lead to unexpected and counter-intuitive failures when code is run on a platform where referenced APIs are not present.
For example, consider this code example, using desktop Java:
billf@~/tmp/tmp$ cat foo.java
public class foo {
public java.awt.Point myBar;
public void checkEager() {
myBar = new bar();
}
public static void main(String[] args) {
System.out.println("made it");
}
}
billf@~/tmp/tmp$ cat bar.java
public class bar extends java.awt.Point {
}
billf@~/tmp/tmp$ javac *.java ; rm bar.class
What happens when you run this code? Intuitively, you might think it will print the string "made it." However, in this case the verifier is required to load the class bar, in order to ensure that the assignment of an instance of bar to a variable of type java.awt.Point is legal. As a result, the class foo fails to load, so the main() method never even starts executing:
billf@~/tmp/tmp$ java foo
Exception in thread "main" java.lang.NoClassDefFoundError: bar
As you can see, linkage errors can lead to counter-intuitive errors. This one is repeatable, because it involves the verifier. Linkage errors resulting from "eager linking" behavior might happen at different points in an xlet, depending on the the player's implementation.
OK, I'm convinced. How can I deal with this safely?
As mentioned earlier, the simplest solution is to put any code that needs optional APIs in a separate xlet that's only launched on players that support the API. Since the only useful optional API we know of is JSSE, we'll use javax.net.ssl as an example. If your main xlet doesn't depend on javax.net.ssl, just compile it against a platform definition that doesn't include this package. This might be called the "enhanced profile platform stubs," because GEM's name for a device without a network connection is the "enhanced profile."
But what can you do if you really need to access javax.net.ssl from within an xlet that needs to run on all players? There are two safe ways to do this. You can use a tested library within your xlet that serves as a proxy to the API, or you can put the code that needs to access javax.net.ssl behind a singleton "gateway" class, as described below.
Using a Proxy Library in your Xlet
In this option, somebody writes a library that gives access to the JSSE functionality. This library might be somewhat large, since it needs to expose all of the underlying functionality. The implementation of this library can use reflection or other safe techniques to access JSSE on BD-Live players, and can be written to fail gracefully on players without a network connection.
If you're looking for a proxy library that does this, you're in luck! Maxim happens to have written one, and it's freely available in the HD cookbook open-source project in the package contrib/jsse_encapsulation. You can
browse the source code here.
When using this option, you should compile your xlet against the platform definition that does /not/ contain the JSSE APIs. That way, if you unintentionally refer to a JSSE API, javac will flag the error for you, and your xlet will not compile.
Using a Singleton Gateway Class
This design pattern is 100% safe. It was introduced in MHP in annex W.3, which is adapted and reproduced in GEM annex W.3. In the J2ME/CLDC world of mobile phones, it is sometimes called "The Nokia Idiom," though we believe that MHP's annex W.3 came first, and annex W.3 was a Sun contribution.
With the gateway pattern, you make a singleton class with a method for each operation in the xlet that requires JSSE. Usually the networking part of a well-structured xlet will be in fairly well separated from other parts of the code, so encapsulating such code behind an abstract class shouldn't be too hard. In this class, you use a tiny bit of reflection to safely load the concrete class that serves as the "gateway" to the functionality that requires JSSE. This bit of reflection consists of a call to Class.forName(String).newInstance(), called with the name of a subclass that implements the networked parts of the xlet. A convenient and elegant place to put this initialization code is in the gateway class's static initializer.
For example, consider an xlet that uses the javax.net.ssl APIs directly for a t-commerce transaction, which we'll call buyHDCookbook(). Further, assume that the main xlet needs to enable or disable a menu button depending on whether or not the player is capable of connecting to the Internet. This can be accomplished with a class like the following:
public class NetworkStuff {
private static NetworkStuff theInstance;
static {
try {
if (Class.forName("javax.net.ssl.SSLSocket") != null) {
// throws ClassNotFoundException if not found
theInstance = (NetworkStuff) Class.forName(
"NetworkStuffImpl").newInstance();
}
} catch (Throwable ignored) {
theInstance = new NetworkStuff();
}
}
// package-private constructor
NetworkStuff() {
}
public static NetworkStuff getInstance() {
return theInstance;
}
public boolean canBuyHDCookbookUsingJSSE() {
return false;
}
public void buyHDCookbook() {
}
}
Within the main xlet, you access this class like any singleton, e.g. with calls like NetworkStuff.getInstance().buyHDCookbook().
On a player without JSSE, the static initializer of the class will encounter a ClassNotFoundException when it attempts to load the SSLSocket class, so it will assign a direct instance of NetworkStuff to theInstance. Note that there are no references to any class that uses one of the JSSE APIs directly. This code is safe because all such accesses go through the method Class.forName(). On a BD-Live player, theInstance will be assigned an instance of NetworkStuffImpl.
The class NetworkStuffImpl will be a subclass of NetworkStuff that overrides the methods canBuyHDCookbookUsingJSSE() and buyHDCookbook(). NetworkStuffImpl can freely use any of the javax.net.ssl APIs in any way. The implementation of canBuyHDCookbookUsingJSSE() might attempt to make a network connection, to ensure that a network is actually connected to the player. The implementation of buyHDCookbook() might present a t-commerce screen, and eventually use the JSSE APIs to complete a purchase transaction.
In order to ensure that the main xlet doesn't inadvertently refer to JSSE APIs, it should be compiled against a platform definition that does not contain these classes. This is the same as the "enhanced profile platform stubs" discussed above. This main xlet, which does not contain the networking functionality, can be tested on any player, whether or not that player supports networking. If the main xlet's classes inadvertently refer to a JSSE API, javac will catch the bug for you, and the xlet will not compile. This xlet will contain NetworkStuff, but not NetworkStuffImpl.
To make the full xlet that contains networking functionality, you can compile the networking functionality classes separately. This compilation would, of course, be against a platform definition that contains the JSSE APIs. This might be called the "interactive profile platform definition", because the GEM profile that includes networking is called the "interactive profile." During this compilation, you can compile against the .class files of the main xlet from the previous step, and you can combine the full set of .class files into one xlet JAR file.
Setting up your compilation environment to compile different parts of the xlet against the correct platform definition might sound tedious, but it's well worth it. Doing this, you'll catch any possible bugs resulting from a reference to an optional API very early in your debugging process, because an xlet that gets this wrong will fail to compile. This is important in establishing a productive workflow, because bugs are much cheaper to correct if detected early. This is particularly important in an environment like Blu-ray, where debugging runtime errors is time-consuming.
-- Main.billf - 21 Apr 2008
|