MAPIStore 10 Development Guide

Version 20 (Julien Kerihuel, 2011-10-05 05:12 pm)

1 1 Julien Kerihuel
h1. MAPIStore 1.0 Development Guide
2 1 Julien Kerihuel
3 3 Julien Kerihuel
{{>toc}}
4 3 Julien Kerihuel
5 1 Julien Kerihuel
h2. What is MAPIStore?
6 1 Julien Kerihuel
7 2 Julien Kerihuel
MAPIStore is the SAL component of OpenChange server. SAL stands for Storage Abstraction Layer. It is the component used by OpenChange Server to push/get information (messages, folders) to/from storage backends. It is designed as a library called within OpenChange Server and it accesses backends compiled as dynamic shared object (DSO) and loaded when mapistore is initialized.
8 1 Julien Kerihuel
9 1 Julien Kerihuel
The main objective of mapistore is to provide an interface layer with a common set of atomic functions (operations) used to trigger and dispatch data and commands to the appropriate backend. MAPIStore relies on a backend mechanism specifically designed to transparently handle some of the MAPI semantics required by any Exchange compatible server.
10 1 Julien Kerihuel
11 1 Julien Kerihuel
The initial idea was to provide to OpenChange a highly customizable storage backend mechanism which would fit in any situation and any environments. One of the greatest limitation we have found with existing groupware is the storage layer which is generally limited to a single solution, service or format and is neither scalable nor modifiable when user requirements evolve upon time.
12 1 Julien Kerihuel
13 1 Julien Kerihuel
MAPIStore solves this problem and go beyond classical limitations. It is not a revolutionary concept, but the way openchange uses it makes the whole difference and offer administrators an innovative way to customize storage.
14 1 Julien Kerihuel
15 1 Julien Kerihuel
MAPIStore allows you to:
16 1 Julien Kerihuel
* use a different backend for any top-folder
17 1 Julien Kerihuel
* transparently move/copy data across backends
18 1 Julien Kerihuel
* develop new backends quickly
19 1 Julien Kerihuel
* access all the different backends through an unique API
20 1 Julien Kerihuel
21 1 Julien Kerihuel
For example (assuming all associated backends were developed) a user could have the following storage organization for his mailbox:
22 1 Julien Kerihuel
* Mails stored using an IMAP backend (Cyrus-IMAP or dovecot)
23 1 Julien Kerihuel
* Calendar items stored in CalDAV or pushed in Google calendar
24 1 Julien Kerihuel
* Sent emails and archives/backup stored in a compression backend
25 1 Julien Kerihuel
* Tasks stored in a MySQL database
26 1 Julien Kerihuel
* Notes stored on the filesystem
27 1 Julien Kerihuel
28 1 Julien Kerihuel
If the user is not satisfied with one of the backend's performance, they would just have to use an administration tool, change the backend, wait for the replication, synchronization to finish and there data will be available from the new backend.
29 1 Julien Kerihuel
30 1 Julien Kerihuel
Information can be completely decentralized, stored on one of several servers and still be accessible transparently from OpenChange server.
31 3 Julien Kerihuel
32 3 Julien Kerihuel
h2. Getting started
33 3 Julien Kerihuel
34 4 Julien Kerihuel
h3. Getting Source code
35 4 Julien Kerihuel
36 3 Julien Kerihuel
Using mapistore v1 requires to use a different development tree than trunk. You will need to checkout the code from our current mapistore v1 development branches:
37 3 Julien Kerihuel
<pre>
38 3 Julien Kerihuel
svn co http://svnmirror.openchange.org/openchange/branches/sogo-good
39 3 Julien Kerihuel
</pre>
40 1 Julien Kerihuel
41 4 Julien Kerihuel
Refer to [[HowTo Install OpenChange From Source]] (but use svn command above instead of trunk) for instructions on requirements and how to build openchange. 
42 4 Julien Kerihuel
43 4 Julien Kerihuel
This branch also relies on the sogo backend developed by inverse.ca which provides the most up to date backend's implementation using mapistore v1. 
44 4 Julien Kerihuel
For instructions on how to download and build the backend, refers to [[HowTo Setup SOGo with OpenChange Server]].
45 3 Julien Kerihuel
46 3 Julien Kerihuel
h3. Why isn't mapistore v1 available in trunk?
47 3 Julien Kerihuel
48 3 Julien Kerihuel
Back in February 2010, we have decided to move from our existing mapistore v1 interface to a brand new and completely redesigned mapistore v2 interface. This new interface was introducing new concepts, fixing limitation of v1 implementation. This was a massive work and while it started very well, the effort was too big for the small number of people working on it and became abandoned, while v1 implementation kept progressing.
49 3 Julien Kerihuel
50 3 Julien Kerihuel
It means that mapistore v2 in trunk is not usable in current state and will progressively be replaced step by step with an updated version of mapistore v1 covered in this documentation, introducing new concepts progressively.
51 5 Julien Kerihuel
52 5 Julien Kerihuel
53 5 Julien Kerihuel
h2. MAPIStore architecture overview
54 5 Julien Kerihuel
55 5 Julien Kerihuel
h3. URI and scope
56 5 Julien Kerihuel
57 5 Julien Kerihuel
A URI is the key (and only) element mapistore and calling applications rely on in order to access and perform operation on a specific backend.
58 5 Julien Kerihuel
59 5 Julien Kerihuel
It is made of a prefixing namespace followed by data specific to the backend:
60 6 Julien Kerihuel
* *namespace*: This is the unique identifier associated to your backend. It is made of your backend's name followed by *://*
61 5 Julien Kerihuel
* *backend's specific data*: This is a set of data that doesn't have any meaning for the calling application but is relevant for the backend. While the format of this data is completely specific to your backend, backends generally set information such as username, password or folder name within the destination system the backend manages.
62 5 Julien Kerihuel
63 5 Julien Kerihuel
<pre>
64 5 Julien Kerihuel
{namespace}{backend's specific data}
65 5 Julien Kerihuel
66 1 Julien Kerihuel
sogo://user:password@folder/
67 6 Julien Kerihuel
java://user##folder/
68 5 Julien Kerihuel
</pre>
69 5 Julien Kerihuel
70 5 Julien Kerihuel
h3. Contexts
71 5 Julien Kerihuel
72 7 Julien Kerihuel
An Exchange user mailbox is (roughly) represented as a top folder (The Mailbox name) holding root (sub) folders (Inbox, Outbox, Calendar, Contacts, Notes, Tasks etc.). In MAPIStore, we consider that we shouldn't have to use the same backend for everything. We should be able to use a backend for Inbox, another for Calendar and so on.
73 7 Julien Kerihuel
74 7 Julien Kerihuel
This is where the notion of mapistore context steps in. A context is a sandbox created by a backend when you access root folder. For every operation you perform on this root folder or any data within this folder, the same created context will be used. For example, if we want to open the message _Rates_ in /Inbox/Holidays/, we will:
75 7 Julien Kerihuel
# Create a context on Inbox (it opens the folder)
76 7 Julien Kerihuel
# Open the Holidays folder using the Inbox context
77 7 Julien Kerihuel
# Access the message using the Inbox context
78 8 Julien Kerihuel
79 8 Julien Kerihuel
If the backend attached to my Inbox points to an IMAP server, all the operation performed on Inbox and subfolders will be propagated to this IMAP server. In this case, creating a context would mean:
80 8 Julien Kerihuel
# Open a connection on the IMAP server
81 8 Julien Kerihuel
# Open the Inbox folder
82 8 Julien Kerihuel
# Keep the connection opened and save parameters this IMAP backend needs to access/interact with physical (folder, message) or virtual (tables) elements stored within this Inbox folder.
83 9 Julien Kerihuel
84 9 Julien Kerihuel
h3. openchange.ldb URI wrapper
85 9 Julien Kerihuel
86 9 Julien Kerihuel
We have just seen that a context has to be created when we access a root folder and the only key we need to call the proper backend and create the context is the URI.
87 9 Julien Kerihuel
88 9 Julien Kerihuel
It means we need a mapping to associate the Inbox folder of a user to a specific URI (e.g.: imap://user:pass@Inbox/). This is done through the openchange.ldb database which maintains the mapping between Exchange mailbox root folders and URI used to access them.
89 9 Julien Kerihuel
90 9 Julien Kerihuel
This file does not (yet) belongs to mapistore, but is widely used by OpenChange Server when serving users mailboxes. It is created within _/usr/local/samba/private/openchange.ldb_ when OpenChange server is provisioned and can be read/edited using ldbedit installed in _/usr/local/samba/bin/ldbedit_.
91 9 Julien Kerihuel
92 9 Julien Kerihuel
<pre>
93 9 Julien Kerihuel
/usr/local/samba/bin/ldbedit -H /usr/local/samba/private/openchange.ldb
94 9 Julien Kerihuel
</pre> 
95 10 Julien Kerihuel
96 18 Julien Kerihuel
h3. indexing.tdb URI/FMID wrapper
97 10 Julien Kerihuel
98 18 Julien Kerihuel
What we call FMID is a shortcut combination of FID (Folder Identifier) and MID (Message Identifier). It means either Folder or Message identifier.
99 18 Julien Kerihuel
100 18 Julien Kerihuel
While _openchange.ldb_ handles the mapping between Mailbox root folders and mapistore URI, *indexing.tdb handles the mapping for all the other elements (folder, message) of a user that are NOT root folders*.
101 18 Julien Kerihuel
102 18 Julien Kerihuel
Each time a user creates a folder within a root folder (Inbox, Calendar etc.) or create a message (Appointment, Drafts, Task etc.), the FID or MID is associated to a mapistore URI within the indexing.tdb database of the user. It also means that indexing.tdb is user specific and each user of the system has its own indexing.tdb. This database is similar to a hash table: it stores the FID/MID as key and the mapistore URI as value.
103 18 Julien Kerihuel
104 18 Julien Kerihuel
To make things clear:
105 18 Julien Kerihuel
* openchange.ldb indexes and map root folders of user mailboxes and is common to all users
106 18 Julien Kerihuel
* indexing.tdb indexes everything else but one database is created for each user
107 18 Julien Kerihuel
108 18 Julien Kerihuel
109 10 Julien Kerihuel
h2. MAPIStore Backend
110 10 Julien Kerihuel
111 10 Julien Kerihuel
A backend has to be developed as a dynamic shared library and installed in the folder mapistore uses to look up and load backends:
112 10 Julien Kerihuel
<pre>
113 10 Julien Kerihuel
/usr/local/samba/lib/mapistore_backends/
114 10 Julien Kerihuel
</pre>
115 10 Julien Kerihuel
116 10 Julien Kerihuel
For mapistore to be able to load the backend, it is required to implement an entry point called *mapistore_init_backend*.
117 10 Julien Kerihuel
118 10 Julien Kerihuel
This function fills a mapistore backend structure and registers it. This makes backends available for the initialized mapistore instance.
119 11 Julien Kerihuel
All the required parameters are grouped into sub structures, but the key one to proper backend registration is the backend one. 
120 1 Julien Kerihuel
121 11 Julien Kerihuel
A very preliminary backend would looks like:
122 1 Julien Kerihuel
<pre>
123 11 Julien Kerihuel
#include <stdbool.h>
124 11 Julien Kerihuel
#include <talloc.h>
125 11 Julien Kerihuel
#include <gen_ndr/exchange.h>
126 11 Julien Kerihuel
#include <mapistore/mapistore.h>
127 11 Julien Kerihuel
#include <mapistore/mapistore_errors.h>
128 1 Julien Kerihuel
129 11 Julien Kerihuel
static int example_backend_init(void)
130 11 Julien Kerihuel
{
131 11 Julien Kerihuel
     return MAPISTORE_SUCCESS;
132 11 Julien Kerihuel
}
133 11 Julien Kerihuel
134 11 Julien Kerihuel
static int example_backend_create_context(TALLOC_CTX *mem_ctx,
135 11 Julien Kerihuel
                                          struct mapistore_connection_info *conn_info,
136 11 Julien Kerihuel
                                          struct tdb_wrap *indexing_tdb,
137 11 Julien Kerihuel
                                          const char *uri,
138 11 Julien Kerihuel
                                          void **context_object)
139 11 Julien Kerihuel
{
140 11 Julien Kerihuel
    return MAPISTORE_SUCCESS;
141 11 Julien Kerihuel
}
142 11 Julien Kerihuel
143 10 Julien Kerihuel
int mapistore_init_backend(void)
144 1 Julien Kerihuel
{
145 1 Julien Kerihuel
     struct mapistore_backend backend;
146 1 Julien Kerihuel
     int                      ret;
147 14 Julien Kerihuel
     static bool              registered = false;
148 1 Julien Kerihuel
149 14 Julien Kerihuel
     if (registered == true) return MAPISTORE_SUCCESS;
150 14 Julien Kerihuel
151 11 Julien Kerihuel
     backend.backend.name = "Example";
152 11 Julien Kerihuel
     backend.backend.description = "An example backend";
153 11 Julien Kerihuel
     backend.backend.namespace = "example://";
154 11 Julien Kerihuel
     backend.backend.init = example_backend_init;
155 11 Julien Kerihuel
     backend.backend.create_context = example_backend_create_context;
156 10 Julien Kerihuel
157 10 Julien Kerihuel
     [...]
158 10 Julien Kerihuel
159 10 Julien Kerihuel
     ret = mapistore_backend_register(&backend);
160 10 Julien Kerihuel
     if (ret != MAPISTORE_SUCCESS) {
161 10 Julien Kerihuel
        DEBUG(0, ("Failed to register the '%s' mapistore backend!\n", backend.backend.name));
162 10 Julien Kerihuel
     }
163 10 Julien Kerihuel
164 14 Julien Kerihuel
     registered = true;
165 14 Julien Kerihuel
166 10 Julien Kerihuel
     return ret;
167 10 Julien Kerihuel
}
168 10 Julien Kerihuel
169 10 Julien Kerihuel
</pre>
170 10 Julien Kerihuel
171 17 Julien Kerihuel
h3. backend.backend structure
172 10 Julien Kerihuel
173 10 Julien Kerihuel
This structure is required to proper backend registration. It holds the following parameters:
174 10 Julien Kerihuel
175 10 Julien Kerihuel
* *backend.name*: the name of the backend
176 1 Julien Kerihuel
* *backend.description*: A short description for the backend
177 1 Julien Kerihuel
* *backend.namespace*: the namespace used to reference this backend
178 1 Julien Kerihuel
* *backend.init*: a pointer on the function to call for initialization
179 10 Julien Kerihuel
* *backend.create_context*: a pointer on the function to call to create a mapistore context
180 10 Julien Kerihuel
181 12 Julien Kerihuel
h4. Deleting context
182 12 Julien Kerihuel
183 11 Julien Kerihuel
You will notice there is no function to delete a context explicitly within the backend. This is because *the mapistore interface deletes a backend on its own* by free'ing the memory context used by this backend.
184 1 Julien Kerihuel
185 12 Julien Kerihuel
However, backend's developers are required to *allocate their internal data with the memory context passed during create_context and associate a talloc destructor function*. If additional operations need to be performed such as close a file descriptor, socket or call the language specific memory release system (garbage collector, pool etc.) 
186 12 Julien Kerihuel
187 12 Julien Kerihuel
When mapistore will delete the context (free up memory), the hierarchy will automatically be processed and the destructor function for the backend will be called. This is in this internal function that you need to implement actions such as close (fd, socket) etc.
188 13 Julien Kerihuel
189 13 Julien Kerihuel
To create a talloc destructor, call the following function:
190 13 Julien Kerihuel
<pre>
191 13 Julien Kerihuel
talloc_set_destructor((void *)context, (int (*)(void *))destructor_fct);
192 13 Julien Kerihuel
</pre>
193 13 Julien Kerihuel
194 13 Julien Kerihuel
In this example context is the structure allocated with the memory context passed in parameter of the create_context function. An example of the destructor function would looks like:
195 13 Julien Kerihuel
<pre>
196 13 Julien Kerihuel
static int desctructor_fct(void *data)
197 13 Julien Kerihuel
{
198 13 Julien Kerihuel
    /* perform operations to clean-up everything properly */
199 13 Julien Kerihuel
200 13 Julien Kerihuel
   return 0;
201 13 Julien Kerihuel
}
202 13 Julien Kerihuel
</pre>
203 14 Julien Kerihuel
204 14 Julien Kerihuel
h4. backend.init function
205 14 Julien Kerihuel
206 15 Julien Kerihuel
This is the function called right after the backend is registered (mapistore_backend_register). This function will be executed each time mapistore_init function is called from the calling application. 
207 15 Julien Kerihuel
208 15 Julien Kerihuel
In the context of OpenChange server and Samba forked model, it means the backend would be called each time a new instance of the server is created. 
209 15 Julien Kerihuel
To keep init only called once in the server's lifetime, it is required to use a static variable within mapistore_init_backend which value changes when backend gets initialized the first time.
210 15 Julien Kerihuel
211 15 Julien Kerihuel
The init function is used to initialize your backend with data (or environment) it will need all along mapistore's lifetime. It is a one-time operation and is generally used to:
212 15 Julien Kerihuel
* open a connection to a remote system
213 15 Julien Kerihuel
* load a virtual machine
214 15 Julien Kerihuel
215 15 Julien Kerihuel
It results in having a static structure, local to the backend's file which is used along calls to access an environment or file descriptor.
216 15 Julien Kerihuel
It prevents from having to open or load a virtual machine or open a connection to a system each time a context is created.
217 15 Julien Kerihuel
218 15 Julien Kerihuel
h4. backend.create_context function
219 16 Julien Kerihuel
220 16 Julien Kerihuel
This is the function called when we are creating a context (see Contexts subsection). Its prototype uses the following parameters:
221 16 Julien Kerihuel
* *TALLOC_CTX ** Pointer to the memory context used for this backend. This is the memory context you need to use to allocate memory for your specific backend's context structure and for which you may want to set a destructor.
222 16 Julien Kerihuel
* *struct mapistore_connection_info ** Pointer to a data structure holding information about the incoming user and wrapping structures such as the mapistore_context structure pointer. It is needed for a backend for example to register messages on its own.
223 16 Julien Kerihuel
* *struct tdb_wrap ** Pointer to a wrapped TDB database (sort of hash table) which associates for all folder/messages an Exchange specific folder and message identifier to a mapistore URI.
224 16 Julien Kerihuel
* *const char *uri* Pointer to the URI for this context
225 1 Julien Kerihuel
* *void **context_object* Pointer to a context object to return and which the backend uses to associate backend's specific information on the context.
226 17 Julien Kerihuel
227 17 Julien Kerihuel
h3. backend.context structure
228 17 Julien Kerihuel
229 17 Julien Kerihuel
This structure holds couple of pointer on functions which perform operations specific to mapistore contexts:
230 17 Julien Kerihuel
* backend.context.get_path
231 17 Julien Kerihuel
* backend.context.get_root_folder
232 18 Julien Kerihuel
233 18 Julien Kerihuel
h4. context.get_path
234 18 Julien Kerihuel
235 18 Julien Kerihuel
*In MAPIStore, backends can't register FMID to mapistore URI themselves. This is the role of mapistore middleware.*
236 18 Julien Kerihuel
237 18 Julien Kerihuel
To understand this choice, an overview of message creation provides a good example. When a MAPI client creates a message, it doesn't do it through a single operation. It may even not do it through a single network transaction. This is different from an IMAP message where the email is composed and pushed at once. In the case of MAPI, a client will successively call (for a draft email): CreateMessage, SetProps, ModifyRecipients, SaveChangesMessage. If a client releases the message before it reaches the SaveChangesMessage, the message is destroyed and won't be anymore available on the system. However, during the message creation process, the message virtually exists and has a MID associated. It is just that nobody except the client creating the message can access it.
238 18 Julien Kerihuel
239 18 Julien Kerihuel
From an openchange perspective, it means that the MID exists but is not yet stored within the indexing.tdb database of the user and is not permanently associated to a mapistore URI:
240 18 Julien Kerihuel
* When the createmessage's backend function is called, it receives a MID.
241 18 Julien Kerihuel
* It generates a mapistore URI for this MID
242 18 Julien Kerihuel
* It stores internally this MID to mapistore URI mapping
243 18 Julien Kerihuel
* It goes this way through the entire message creation
244 18 Julien Kerihuel
245 18 Julien Kerihuel
When the message is finally save, it is time to expose the message publicly and reference it. This is where the context.get_path functions matters:
246 18 Julien Kerihuel
* The mapistore middleware will register the MID (or FID) within the indexing.tdb of the user
247 18 Julien Kerihuel
* However it doesn't know the mapistore URI which was associated
248 18 Julien Kerihuel
* It queries the backend using context.get_path to retrieve the URI associated to the FMID
249 18 Julien Kerihuel
* It registers the message within the indexing.tdb database
250 18 Julien Kerihuel
251 18 Julien Kerihuel
You have also figured out that such behavior doesn't only apply to messages but also to folders. When a CreateFolder is called, the FID is passed to CreateFolder, the backend generates a URI and internally associates it to this FID. When mapistore middleware registers the folder, it queries context.get_path with the FID and retrieve the URI.
252 18 Julien Kerihuel
253 18 Julien Kerihuel
This is an indirect way to retrieve data, but it is also useful when a call returns an error etc.
254 18 Julien Kerihuel
255 18 Julien Kerihuel
h4. context.get_root_folder
256 19 Julien Kerihuel
257 19 Julien Kerihuel
mapistore v1 is using object concepts. For example you may have noticed that we have divided backend functions from context functions. Forthcoming documentation also describe folder, message, table or property functions. Each of these subsets of functions represent an objet. So we have:
258 19 Julien Kerihuel
* a backend object
259 19 Julien Kerihuel
* a context object
260 19 Julien Kerihuel
* a fodler object
261 19 Julien Kerihuel
* a message object
262 19 Julien Kerihuel
* etc.
263 19 Julien Kerihuel
264 20 Julien Kerihuel
When we create a context, we are in fact opening a folder, except that this folder is a root folder. For this very special case, we are only creating a context but the folder is however opened. It means that a context is also a folder. The get_root_folder functions returns a folder representation of the context object created. It lets backends directly call folder operations on contexts rather than having to open (again) the folder to call its operations.