Implementation Design - Grimoires
Overview
- Internally, the registry adopts a message-passing metaphore and a component based approach. Soap messages are transformed by Axis into Java objects. Messages are handled by components we refer to as handlers.
Handlers are typically designed to process messages of a same category, such as
the messages of the UDDI publish interface, of the UDDI inquiry interface, or
messages related to metadata. Such handlers in fact would correspond to the
business logic implementing the registry. This has a number of benefits:
- Ease of maintenance: code for a handler for a given interface will be well scoped.
- Separate compilation and flexible deployment: we aim at being able to compile separately components, and possibly deploy only a subset of them in some specific configuration. For instance: we should be able to deploy just the
UDDI interfaces, or just the UDDI and metadata interfaces. This obviously requires component code not to make reference to other component code!
- Some OO patterns have been adopted: specifically, the visitor pattern is used to structure all messages and their associated handlers. Each message of a given category will implement a given interface, e.g.
PublishMessage and PublishProcessable for messages of the UDDI publish interface. In particular, such messages will implement a method accept for the handler handling messages of such category. Handlers will be visitors offering a method process for each of messages in the interface.
- Some default handlers are provided for each interface. In particular, a handler "not implemented" which will return an error for each message it is passed. When developing a new handler, it is therefore natural to subclass such a "not implemented" handler, which will ensure consistent error messages for all non implemented methods. This is far better that returning a null value since it becomes very hard to know where this value was generated.
- We have adopted a message passing approach so that messages can be delegated to other components simply by passing them. Currently, our handling of messages is asymmetric. A message is passed to a handler using the visitor pattern approach, i.e. by calling
process on the handler, with the message as argument. However, the returned result (if any) is not passed as a message in the same way. Instead, results are stored using the setResult method (in AbstractMessage), and the input message is returned with its result field set.
- Given this approach, when an error situation is reached in the business
logic, say because an input is incorrect, we do not raise an exception in the
handler, instead we set the error field, using the
setError method (in AbstractMessage).
- A source of inefficiency is copying objects (and in particular deep copies). We aim to avoid these by making sure that once an object is created by the axis container, we do not copy it again. When methods have to be added to these objects' classes, we subclass the classes generated by axis, and we need to let axis know that the class to be used when deserialising an object is the new one. (This is done by defining the entry for the data type in the config/deploy.wsdd file).
The structure explained
| package | comment |
uk.ac.soton.ecs.grimoires | the grimoires distribution |
uk.ac.soton.ecs.grimoires.server | the server side |
uk.ac.soton.ecs.grimoires.server.impl | implementation of all interfaces |
uk.ac.soton.ecs.grimoires.server.impl.uddiv2 | implementation of uddiv2 interfaces |
uk.ac.soton.ecs.grimoires.server.impl.metadata | implementation of metadata interfaces |
uk.ac.soton.ecs.grimoires.server.impl.wsdl | implementation of wsdl-related interface |
uk.ac.soton.ecs.grimoires.server.impl.damls | implementation of damls interfaces |
uk.ac.soton.ecs.grimoires.server.impl.topic | implementation of topics interfaces |
uk.ac.soton.ecs.grimoires.server.store | backend related matters |
uk.ac.soton.ecs.grimoires.server.configuration | configuration classes |
uk.ac.soton.ecs.grimoires.server.test |
functionality and performance tests |
uk.ac.soton.ecs.grimoires.server.axis |
WSDL generator factory code |
uk.ac.soton.ecs.grimoires.server.jena.sesame |
Jena-Sesame-Model, through which Sesame is used as the triple store |
uk.ac.soton.ecs.grimoires.wstester | an XML client tester |
Each implementation
XXX of an interface attempts to follow a same structure:
In uk.ac.soton.ecs.grimoires.server.impl. XXX | comment |
messages | message definitions and associated visitors |
datamodel | data type definitions |
handlers | implementation of handlers |
api | implementation of APIs (currently server ties) |
Saver, Loader and RDQLGenerator visitors
Grimoires uses the visitor pattern to save data objects to the RDF store, load data objects back from the store and generating RDQL queries from query data objects. Using this pattern means that we do not have to alter the data objects themselves, which are generated by Axis. Additionally, savers, loaders and query generators can be chosen to match the backend store. Currently this pattern is implemented for the metadata API implementations but the UDDIv2 APIs still use the old views design (where the data objects contain the load, save and generate RDQL methods).
The interfaces for the savers, loaders and RDQL generators are Saver, Loader and RDQLGenerator respectively. Each has an base implementation in the same package called AbstractSaver, AbstractLoader and AbstractRDQLGenerator. These throw a StoreException (for save and load) or RDFException (for RDQL generation) reporting that the object given as parameter cannot be saved/loaded/used to generate RDQL.
The Abstract classes should be overridden in the handlers of each API. For example, there is a MetadataSaver, MetadataLoader and MetadataRDQLGenerator. These override the save, load and generate methods for each type object to be saved, loaded or used in generation.
Using the save and load methods is fairly straightforward. To save, call saveToStore on a saver passing the Model to store to and the object to store. To load, call loadFromStore with an empty (default constructed) object to be initialised, the Model to load from and the Resource identifying the stored object details.
Generating RDQL is only slightly more complex. Each method for generating RDQL queries, named generateRdqlQuery, takes as arguments the object to generate the query from and a QueryDetails object. QueryDetails stores the list of statements, constraints, namespaces etc. that make up the query and is manipulated by the generate method. The client would usually create a new QueryDetails using the default constructor when generating a query. The generate method returns a GenerationResults object that contains the QueryDetails and the name of the variable that is used to identify the object to be returned by the query. The GenerationResults object can be processed using uk.ac..grimoires.server.impl.Jena.processQuery passing the Model to query, the GenerationResults and the variable name that the client is interested in.
Security
Grimoires uses its deployment container to obtain necessary tokens for authentication, for example an X509 Distinguished Name. An authorization component then checks this against an internal access control list (specified prior to the deployment of Grimoires), to see whether the incoming request is valid for the authentication token supplied. If so, it is executed, otherwise an Axis fault is returned to the originating client. A potential client must be capable of creating and sending the authentication token type expected by Grimoires' deployment container.
Client side
Separate concern
The idea behind the design and implementation of GRIMOIRES client side support
is to
separate concern.
- Firstly, we need to separate the presentation layer and the business
layer at the client side.The presentation layer is the user interface, which
could be a command line utility (GShell), a GUI,
or an Eclipse plug-in. The business layer is in charge of calling GRIMOIRES
API with the given user input, which is performed by
GrimoiresProxy.
- Secondly, in GrimoiresProxy we further separate the protocol processing
and the message delivering. The protocol processing is in charge of
preparing request data, invoking the proper operation and processing the
response data. While the message delivering is usually an Axis-generated
stub that serializes Java objects to SOAP message, sends the SOAP message to
the service, and deserialzes the response SOAP message.
Abstract factory pattern
We use
abstract factory pattern to instantiate multiple GrimoiresProxy that are
used in different situations (i.e., with different message delivering
capability) but share the same protocol processing code. We have
- GrimoiresWebServiceProxyFactory: a GrimoiresProxyFactory to
instantiate GrimoiresProxy able to talk with Grimoires service deployed
in OMII/Axis.
- GrimoiresjUDDIWebServiceProxyFactory: a GrimoiresProxyFactory to
instantiate GrimoiresProxy able to talk with jUDDI service deployed in
OMII/Axis.
- GrimoiresGT4WebServiceProxyFactory: a GrimoiresProxyFactory to
instantiate GrimoiresProxy able to talk with Grimoires service deployed
in GT4.
- GrimoiresBusinessLogicProxyFactory: a GrimoiresProxyFactory to
instantiate GrimoiresProxy able to talk with Grimoires business logic.
GShell runtime configuration
To demonstrate the flexibility of Grimoires client side support,
here we show how to configure GShell to talk with Grimoires deployed in
different containers without modifying GShell's source code or doing
recompilation.
Prepare a gshell.properties in the GShell directory, which has the
following content:
GrimoiresProxyFactory = uk.ac.soton.ecs.grimoires.proxy.ws.GrimoiresWebServiceProxyFactory
This will tell GShell to instantiate a GrimoiresProxy able to talk
with Grimoires service deployed in OMII or Axis.
GrimoiresProxyFactory = uk.ac.soton.ecs.grimoires.proxy.gt4.GrimoiresGT4WebServiceProxyFactory
This will tell GShell to instantiate a GrimoiresProxy able to talk
with Grimoires service deployed in GT4.
Of course the corresponding stub should be on the classpath.