MAPIStore 10 Development Guide
Version 15 (Julien Kerihuel, 2011-10-05 03:01 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 | 10 | Julien Kerihuel | |
| 97 | 10 | Julien Kerihuel | h2. MAPIStore Backend |
| 98 | 10 | Julien Kerihuel | |
| 99 | 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: |
| 100 | 10 | Julien Kerihuel | <pre> |
| 101 | 10 | Julien Kerihuel | /usr/local/samba/lib/mapistore_backends/ |
| 102 | 10 | Julien Kerihuel | </pre> |
| 103 | 10 | Julien Kerihuel | |
| 104 | 10 | Julien Kerihuel | For mapistore to be able to load the backend, it is required to implement an entry point called *mapistore_init_backend*. |
| 105 | 10 | Julien Kerihuel | |
| 106 | 10 | Julien Kerihuel | This function fills a mapistore backend structure and registers it. This makes backends available for the initialized mapistore instance. |
| 107 | 11 | Julien Kerihuel | All the required parameters are grouped into sub structures, but the key one to proper backend registration is the backend one. |
| 108 | 1 | Julien Kerihuel | |
| 109 | 11 | Julien Kerihuel | A very preliminary backend would looks like: |
| 110 | 1 | Julien Kerihuel | <pre> |
| 111 | 11 | Julien Kerihuel | #include <stdbool.h> |
| 112 | 11 | Julien Kerihuel | #include <talloc.h> |
| 113 | 11 | Julien Kerihuel | #include <gen_ndr/exchange.h> |
| 114 | 11 | Julien Kerihuel | #include <mapistore/mapistore.h> |
| 115 | 11 | Julien Kerihuel | #include <mapistore/mapistore_errors.h> |
| 116 | 1 | Julien Kerihuel | |
| 117 | 11 | Julien Kerihuel | static int example_backend_init(void) |
| 118 | 11 | Julien Kerihuel | { |
| 119 | 11 | Julien Kerihuel | return MAPISTORE_SUCCESS; |
| 120 | 11 | Julien Kerihuel | } |
| 121 | 11 | Julien Kerihuel | |
| 122 | 11 | Julien Kerihuel | static int example_backend_create_context(TALLOC_CTX *mem_ctx, |
| 123 | 11 | Julien Kerihuel | struct mapistore_connection_info *conn_info, |
| 124 | 11 | Julien Kerihuel | struct tdb_wrap *indexing_tdb, |
| 125 | 11 | Julien Kerihuel | const char *uri, |
| 126 | 11 | Julien Kerihuel | void **context_object) |
| 127 | 11 | Julien Kerihuel | { |
| 128 | 11 | Julien Kerihuel | return MAPISTORE_SUCCESS; |
| 129 | 11 | Julien Kerihuel | } |
| 130 | 11 | Julien Kerihuel | |
| 131 | 10 | Julien Kerihuel | int mapistore_init_backend(void) |
| 132 | 1 | Julien Kerihuel | { |
| 133 | 1 | Julien Kerihuel | struct mapistore_backend backend; |
| 134 | 1 | Julien Kerihuel | int ret; |
| 135 | 14 | Julien Kerihuel | static bool registered = false; |
| 136 | 1 | Julien Kerihuel | |
| 137 | 14 | Julien Kerihuel | if (registered == true) return MAPISTORE_SUCCESS; |
| 138 | 14 | Julien Kerihuel | |
| 139 | 11 | Julien Kerihuel | backend.backend.name = "Example"; |
| 140 | 11 | Julien Kerihuel | backend.backend.description = "An example backend"; |
| 141 | 11 | Julien Kerihuel | backend.backend.namespace = "example://"; |
| 142 | 11 | Julien Kerihuel | backend.backend.init = example_backend_init; |
| 143 | 11 | Julien Kerihuel | backend.backend.create_context = example_backend_create_context; |
| 144 | 10 | Julien Kerihuel | |
| 145 | 10 | Julien Kerihuel | [...] |
| 146 | 10 | Julien Kerihuel | |
| 147 | 10 | Julien Kerihuel | ret = mapistore_backend_register(&backend); |
| 148 | 10 | Julien Kerihuel | if (ret != MAPISTORE_SUCCESS) { |
| 149 | 10 | Julien Kerihuel | DEBUG(0, ("Failed to register the '%s' mapistore backend!\n", backend.backend.name)); |
| 150 | 10 | Julien Kerihuel | } |
| 151 | 10 | Julien Kerihuel | |
| 152 | 14 | Julien Kerihuel | registered = true; |
| 153 | 14 | Julien Kerihuel | |
| 154 | 10 | Julien Kerihuel | return ret; |
| 155 | 10 | Julien Kerihuel | } |
| 156 | 10 | Julien Kerihuel | |
| 157 | 10 | Julien Kerihuel | </pre> |
| 158 | 10 | Julien Kerihuel | |
| 159 | 10 | Julien Kerihuel | h3. backend structure |
| 160 | 10 | Julien Kerihuel | |
| 161 | 10 | Julien Kerihuel | This structure is required to proper backend registration. It holds the following parameters: |
| 162 | 10 | Julien Kerihuel | |
| 163 | 10 | Julien Kerihuel | * *backend.name*: the name of the backend |
| 164 | 1 | Julien Kerihuel | * *backend.description*: A short description for the backend |
| 165 | 1 | Julien Kerihuel | * *backend.namespace*: the namespace used to reference this backend |
| 166 | 1 | Julien Kerihuel | * *backend.init*: a pointer on the function to call for initialization |
| 167 | 10 | Julien Kerihuel | * *backend.create_context*: a pointer on the function to call to create a mapistore context |
| 168 | 10 | Julien Kerihuel | |
| 169 | 12 | Julien Kerihuel | h4. Deleting context |
| 170 | 12 | Julien Kerihuel | |
| 171 | 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. |
| 172 | 1 | Julien Kerihuel | |
| 173 | 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.) |
| 174 | 12 | Julien Kerihuel | |
| 175 | 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. |
| 176 | 13 | Julien Kerihuel | |
| 177 | 13 | Julien Kerihuel | To create a talloc destructor, call the following function: |
| 178 | 13 | Julien Kerihuel | <pre> |
| 179 | 13 | Julien Kerihuel | talloc_set_destructor((void *)context, (int (*)(void *))destructor_fct); |
| 180 | 13 | Julien Kerihuel | </pre> |
| 181 | 13 | Julien Kerihuel | |
| 182 | 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: |
| 183 | 13 | Julien Kerihuel | <pre> |
| 184 | 13 | Julien Kerihuel | static int desctructor_fct(void *data) |
| 185 | 13 | Julien Kerihuel | { |
| 186 | 13 | Julien Kerihuel | /* perform operations to clean-up everything properly */ |
| 187 | 13 | Julien Kerihuel | |
| 188 | 13 | Julien Kerihuel | return 0; |
| 189 | 13 | Julien Kerihuel | } |
| 190 | 13 | Julien Kerihuel | </pre> |
| 191 | 14 | Julien Kerihuel | |
| 192 | 14 | Julien Kerihuel | h4. backend.init function |
| 193 | 14 | Julien Kerihuel | |
| 194 | 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. |
| 195 | 15 | Julien Kerihuel | |
| 196 | 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. |
| 197 | 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. |
| 198 | 15 | Julien Kerihuel | |
| 199 | 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: |
| 200 | 15 | Julien Kerihuel | * open a connection to a remote system |
| 201 | 15 | Julien Kerihuel | * load a virtual machine |
| 202 | 15 | Julien Kerihuel | |
| 203 | 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. |
| 204 | 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. |
| 205 | 15 | Julien Kerihuel | |
| 206 | 15 | Julien Kerihuel | h4. backend.create_context function |