4.17 Repository Implementation
This section contains some information on internals of the repository server. It is mostly only relevant for people who want to hack on Daisy itself.
4.17.1 Repository Implementation
We have mentioned before that there are two implementations of the repository API: one we call local (the one in the repository server) and one we call remote. In this document we're going to look into how the repository objects are implemented to support both local and remote implementations.
4.17.1.1 Repository server implementation
The implementation of the repository can be found in the source tree in the repository directory. This is the structure of the repository directory:
+ repository
+ api
+ client
+ common
+ server
+ server-spi
+ spi
+ test
+ xmlschema-bindings
The api directory contains the repository API definition, these are mainly interfaces. Repository client applications only need to depend on the api jar and the xmlschema-bindings jar (thus when compiling a client program, only these jars need to be in the classpath).
The client directory contains the remote implementation, the server directory contains the local implementation. The common directory contains classes common to both implementations (this is discussed further on). The spi directory contains "Service Provider Interfaces", these are interfaces towards extension components. The server-spi directory is similar to the spi directory, but contains interfaces that are only relevant for the server implementation. The test directory contains functional tests. These tests automate the process of constructing an empty database, starting an embedded repository server, and then execute a JUnit test. The xmlschema-bindings directory contains XML schemas for the XML formats used by the repository, these are compiled into corresponding Java classes by use of XMLBeans. Logically speaking these classes are a part of the repository API.
Next to the repository directory, the services directory also contains a good amount of functionality that is used by the repository client or server. The subprojects in the service directory are however separated from the main repository code because either they are completely independent from it (and reusable and testable outside of the Daisy repository code), or it are repository extensions.
4.17.1.2 The local, remote and common implementations
If we consider an entity such as a Document, a User or even an Acl, there's a lot of the implementation of these interfaces that will be equal in the local and remote interface: in both cases they need instance variables to hold the data, and implementations of the various methods. In fact, for most of these entities, the only difference is the implementation of the save method. In the local implementation, the save method should update the database, while in the remote implementation, the save method should use an appropriate HTTP call to perform the operation.
Therefore, the basic implementation of these objects is separated out in the "common-impl" subproject, which delegates things that are specific for local or remote implementation to a certain Strategy interface. The Strategy interface is then implemented differently for the local and remote implementations.
The diagram below depicts this basic organisation of the code.

Not all operations of the repository are of course loading and saving entities, there are also items such as querying and ACL-checking, and these are also delegated by the common-impl to the appropriate strategy instance.
So practically speaking, we could say that most of the real work happens in the implementations of the Strategy interfaces. You can find the Strategy interfaces by going to the repository/common directory and searching for all *Strategy.java files.
4.17.1.3 User-specific and common objects
Lets do a quick review of the Daisy API, for example:
// Getting a repository instance
Repository repository = repositoryManager.getRepository(new Credentials("user", "password"));
// Retrieving a document
Document document = repository.getDocument(55, false);
// Creating a collection
CollectionManager collectionManager = repository.getCollectionManager();
DocumentCollection newCollection = collectionManager.createCollection("abc");
newCollection.save();
The RepositoryManager.getRepository() method returns a caller-specific Repository object. With "caller-specific", I mean a custom instance for the thread that called the getRepository() method. The Repository object remembers then the user it belongs too, so further methods don't need a credentials parameter.
If later on we do a call to repository.getCollectionManager(), the returned collectionManager instance is again caller-specific, thus it knows it represents the authenticated user and we don't need to specify credentials when calling further methods.
The implementations of interfaces like Repository (RepositoryImpl) and CollectionManager (CollectionManagerImpl) delegate internally the calls simply to a CommonRepository instance and a CommonCollectionManager instance, calling similar methods on them but with an additional user parameter. Thus RepositoryImpl and CollectionManagerImpl in a sense exist only to remember the authenticated user.
A diagram which illustrates all this is available
As you'll see in the diagram, a call for getDocument on the repository delegates this call to the CommonRepository instance, which then in itself delegates the call to a LoadStoreStrategy class. If the CommonRepository class simply forwards the call to the LoadStoreStrategy class, you could wonder why this extra layer of delegation still exists and thus why the CommonRepository and its corresponding LoadStoreStrategy are not merged into one. Or in general, why this is not done for all *Manager classes (CollectionManager, AccessManager, UserManager, etc.) and their corresponding Common* classes. For a big part, this is because this is how it historically evolved, though the Common* classes still perform some functions such as caching which are (sometimes) shared between the local and remote implementations.
Previous