1.4 Understanding the Service Provider Interface Architecture
Our goal in this section is not to discuss the Service Provider Interface (SPI) architecture in such grand detail that you are prepared to run off and write your own provider package. The goal is to provide you with enough insight into the architecture that you as a developer have a rudimentary understanding of how the JCA architecture operates. More precisely, by having an understanding of the SPI architecture, and in particular the factory method design, you will have a higher degree of comfort with the factory methods and thus be able to fully exploit the power of the architecture exposed by the engine classes.
Factory design patterns are simple in concept and provide virtually unlimited extensibility, and it is the underpinnings of this design pattern that give the JCA architecture (and by extension the JCE) its true power. Each core JCA engine is located in the Java, security package, and it is represented by an abstract class (e.g., MessageDigest represents a message digest engine) that extends another abstract class with an identical name and an appended suffix of "Spi" (e.g., MessageDigestSpi). When a provider writes a cryptographic function, they must determine the engine through which their function will be exposed. For example, if we were to write a new and improved message digest, it would ultimately need to be accessible via one of the factory methods on the MessageDigest engine.
NOTE: Developers work exclusively through the engine and never directly access either an SPI class or a concrete implementation by name. In fact, the Java language helps ensure this because the SPI class and the engine class are declared as abstract, ensuring neither can be directly instantiated.
The factory methods on the engines are easy to identify. They use a well-known factory method naming pattern, getlnstance(String algorithm). When invoked, the method locates (and if necessary instantiates) a concrete class suitable for the request. Recall that the physical implementations extend the engine class (e.g., public class MyMessageDigest extends MessageDigest), and the invocation of the factory method casts the instance back to its ancestor class (e.g., return (MessageDigest) new MyMessageDigestO). Let's revisit one of the code examples from earlier:
MessageDigest md5 = MessageDigest.getlnstance("MD5");
The invocation of this one line of code provides us with a concrete implementation of a message digest that implements the MD5 algorithm. Fundamentally, we have no idea on the surface which provider the JCA is using to fulfill this request, nor do we really care. As we get into more detailed code examples later in the text, proper exception handling will be introduced to handle situations where the named algorithm is not available.
NOTE: Throughout the rest of this book we will use the phrase standard pair of engine factory methods to represent the two most prevalent factory methods found on nearly every engine, getlnstance(algorithm) and getlnstance(algorithm, provider). This keeps us from having to repeatedly redefine identical method signatures and method operation for each engine covered.