Feinspezifikation

From Syncphony (inactive) Wiki
Jump to: navigation, search

Contents

Einführung

Eine grundlegende Funktionalität bei der Nutzung mobiler Endgeräte ist die Verwendung von PIM Daten (Personal Information Management) wie beispielsweise Kontakten und Terminen. Da diese PIM-Objekte in Behörden und Unternehmen meist in Groupware-Systemen zentral gespeichert werden, ergibt sich der Bedarf für eine Synchronisationslösung zwischen Groupware-System und den mobilen Endgeräten. Diese dient als Vermittler zwischen dem Groupware-System und dem mobilen Endgerät. Bei einer solch zentralen Komponente werden hohe Anforderungen an Robustheit und Sicherheit gestellt.

Inhalt dieses Dokumentes ist die Spezifikation einer Synchronisationslösung, die einen Austausch von PIM-Objekten eines Kolab-Groupware Servers mit mobilen Endgeräten ermöglicht. Kolab ist eine freie Groupwarelösung die auf offenen Technologien basiert und eine Alternative zu den marktdominierenden Groupware-Systemen darstellt. Zur Unterstützung einer großen Anzahl mobiler Endgeräte wird als zentrale Komponente ein Funambol-Server eingesetzt, der über SyncML eine standardisierte Schnittstelle bietet. SyncML (Synchronization Markup Language) ist ein Plattform-unabhängiger Standard zur Datensynchronisation und wird von den meisten mobilen Endgeräten unterstützt. Die Architektur der Synchronisationslösung ist so offen konzipiert, dass die Anbindung anderer Protokoll-Adapter möglich ist. Insbesondere die spätere Anbindung eines ActiveSync Adapters zur Abdeckung von Windows Mobile basierten Endgeräten ist vorgesehen.

Die Grundlage für diese Spezifikation basiert auf dem Lastenheft sowie auf dem Pflichtenheft des Projektes 'Synchronisation Kolab mit mobilen Endgeräten'. Dieses Dokument dient als technische Feinspezifikation. Es adressiert Software-Entwickler und Software-Architekten, und setzt erweiterte Kenntnisse in den verwendeten Technologien und Fremdsystemen voraus. Es ist jedoch so gestaltet, dass insbesondere das erste Kapitel auch mit allgemeinen Kenntnissen der Materie nachvollzogen werden kann.

Die Gliederung sieht eine über die Kapitel eingeteilte tiefere Einsicht in das Projekt vor. Während die ersten Kapitel für Nicht-Techniker geschrieben wurden und vor allem den Kontext, sowie die Entwurfsentscheidungen darlegt, findet sich in den folgenden Kapiteln ein genaues technisches Umsetzungskonzept, welches von Softwareentwicklern zur Entwicklung der beschriebenen Software genutzt werden soll.


Rahmenbedingungen

Im folgenden werden der Kontext und die Rahmenbedingungen des Projektes dargestellt. Dies ist für das Verständnis der gewälten Architektur unabdingbar. Hierzu erfolgt zunächst eine exemplarische Darstellung der zentralen Anwendungsfälle.

Akteure

Die Akteure einer Synchronisation sind aus fachlicher Sicht der Anwender, sein Endgerät sowie der Groupware-Server mit der vorgelagerten Synchronisationslösung.

Akteure
Anwender Der Benutzer eines mobilen Endgerätes.
Endgerät Ein mobiles Endgerät welches die Möglichkeit bietet Kollaborationsdaten und Emails zu verwalten.
Groupware Ein Kolab Groupwareserver der die PIM Daten zentral vorhält.
Synchronisationslösung Die in diesem Dokument beschriebene und zu erstellende Software.

Anwendungsfälle

Denkbare Anwendungsfälle für eine Synchronisationslösung sehen wie folgt aus:

  • Initiale Synchronisation: Ein Anwender bekommt ein neues Mobiles Endgerät. Er authentisiert sich gegen die Synchronisationssoftware und synchronisiert die ihm zugeordneten PIM-Daten von der Groupware auf sein Endgerät.
  • Aktualisierung des Endgerätes: Die Pflege der PIM-Daten wird überwiegend vom Desktop PC durchgeführt. Dieser ändert die PIM-Daten auf dem Server. Der Anwender startet die Synchronisation erneut und erwartet, dass die geänderten Daten auf das mobile Endgerät übertragen werden.
  • Aktualisierung der Groupware: Auf dem mobilen Endgerät wurden neue PIM-Daten erfasst oder bestehende Daten geändert. Der Anwender startet die Synchronisation erneut und erwartet, dass die geänderten Daten in das Groupware-System übernommen werden werden.
  • Konfliktbehandlung: Der Anwender ändert bzw. löscht PIM-Daten auf dem mobilen Endgerät, welche in der Zwichenzeit auch in der Groupware-Lösung geändert wurden. Anschließend stößt er eine Synchronisation an und erwartet eine robuste Behandlung durch die Synchronisationslösung. Diese verwendet eine Server-Wins Strategie. Somit werden alle Datensätze, für die ein Konflikt existiert, auf den Stand des Servers gebracht.

Systemüberblick

Kapitel2.jpg

Entwurfsziele

Das oberste Entwurfsziel ist es, eine robuste und einsatztaugliche Synchronisationslösung zu schaffen, die einen Abgleich von SyncML-fähigen Endgeräten mit einem Kolab-Groupware-Server ermöglicht und ausschließlich auf Software-Komponenten basiert, die unter einer freien Lizenz verfügbar sind.

Von zentraler Bedeutung für die Lösung ist es, die bestehenden Anforderungen an Daten- und Zugriffssicherheit zu erfüllen.

Neben diesen zentralen Anforderung ist es das Ziel im Rahmen der Lösung einen WebService entstehen zu lassen, der auch außerhalb der direkten Lösung genutzt werden kann. Mögliche Nutzungsszenarien sind dabei die Anbindung weiterer Synchronisations-Adapter (z.B. für ActiveSync) oder die Integration in dritt-Anwendungen einer Service orientierten Architektur (SOA). Der WebService ist in seinem Funktionsumfang auf die Anforderungen aus diesem Projekt abgestimmt. Das Design wurde jedoch auf einen breiten Einsatz ausgerichtet.

Zentrale Anforderungen und Rahmenbedingungen

Die Anforderungen an die Synchronisationslösung sind dem Lastenheft und dem Pflichtenheft - Synchronisation Kolab mit mobilen Geräten zu entnehmen. Im folgenden erfolgt jedoch eine Wiederholung der zentralen Anforderungen und Rahmenbedingungen um den Kontext der Spezifikation zu bilden:

Es soll eine Synchronisation von Kolab mit mobilen Endgeräten ermöglicht werden, welche folgende PIM (Personal Information Manager) Objekte umfasst:

  • Synchronisation von Kontaktdaten in beide Richtungen
  • Synchronisation von Terminen in beide Richtungen
  • Synchronisation von Notizen in beide Richtungen
  • Synchronisation von Aufgaben in beide Richtungen
  • Zugriff auf Emails (IMAP)

Des weiteren sei darauf hingewiesen, dass diese Lösungen allen Beschränkungen unterliegt, die das SyncML-Protokoll und die Implementierungen auf den mobilen Endgeräten unterliegen. Dies ist insbesondere die Beschränkung auf ein statisches Synchronisieren, welches keine direkten Server-Anfragen (z.B. Suche in allen Adressbüchern) für den Benutzer ermöglicht. Die Umsetzung solcher Anforderungen ist einer Lösung wie beispielsweise einem nativen Kolab-Client für das mobile Endgerät vorbehalten.

Die Authentisierung und die Konfiguration (wo dies nötig ist) soll über einen vorhandenen LDAP-Dienst erfolgen und sich auf diese Weise einfach administrieren lassen.

Systemarchitektur

Im folgenden wird die grobe Systemarchitektur dargestellt. Um die gewählte Umsetzung nach zu vollziehen erfolgt vorab eine Auflistung und Begründung der getroffenen Entwurfsentscheidungen. Ihre Bedeutung wird teilweise erst zusammen mit den folgenden Erläuterungen klar werden.

Entwurfsentscheidungen

  • Zugriff auf Email: Da die verbreiteten Clients mobiler Endgeräte den Zugriff auf Emails über IMAP (und ggf. ActiceSync), nicht jedoch über SyncML realisieren, wurde die Email-Funktionalität aus der SyncML-Synchronisationslösung aus genommen. Die Verwirklichung von mobilem Mailing erfolgt mit Hilfe von mobilen Email-Clients über die Standard-IMAP Schnittstelle des Kolab Servers.
  • Funambol: Der Funambol-Server ist die einzige bekannte Synchronisations-Engine, die unter einer freien Lizenz verfügbar ist und eine weite Verbreitung und hohe Geräteabdeckung findet.
  • Java Plattform: Die Java Plattform bietet eine seit Jahren etablierte Infrastruktur für die Realisierung von WebServices. Neben der zunehmenden Standardisierung der APIs (z.B: JAX-WS) existiert eine breite Auswahl unterschiedlicher Container und Implementierungen dieser APIs. Es wurde entschieden im Rahmen der Java-Plattform die JAX-WS Schnittstelle zur Erstellung der WebServices zu verwenden, da dies die standardkonforme Realisierung darstellt.
  • JBoss: Als Ablauf-Container für den WebService wurde der JBoss Application Server in Version 5 gewählt, da dieser eine sehr einfache Umsetzung der JAX-WS konformen WebServices ermöglicht und eine ausgereifte Unterstützung des WebService Security Standards besitzt. Des weiteren bietet der JBoss über seine Clusteringmöglichkeiten die Unterstützung für die geforderte Skalierung. Ein Betrieb der Anwendung sollte ohne größere Anpassungen jedoch auch in anderen J2EE-Containern möglich sein.
  • Trennung von Funambol und WebService: Der Funambol-Server wird gebündelt mit einem Apache Tomcat Servlet-Container ausgeliefert und lässt sich nicht ohne weiteres in einem anderen Container betreiben. In einer späteren Erweiterung wäre es jedoch wünschenswert diesen ebenfalls im JBoss Server zu betreiben.
  • WebService API: Die gewählte WebService API orientiert sich an den Funktionen, die von einem Funambol-Connector benötigt werden. Weiterhin wurden die Methoden mit der ActiveSync-Implementierung des ZPush-Servers abgeglichen, um auch dessen Anforderungen zu berücksichtigen. Die Datentypen wurden eng an das von Kolab verwendete XML-Format angelehnt. Die einzige Abweichung besteht im Bereich der Serientermine (s.u.). Weiterhin orientiert sich die Schnittstelle an den WebService Policies des WS-I Basic Profile. Der WebService wurde so entworfen, dass ein Konsument über keine spezielles Kolab-Wissen und insbesondere über keine IMAP-Kenntnisse verfügen muss. Beim Design der Schnittstellen wurden die Schnittstellen des Akonadi Frameworks, sowie die Schnittstellen des Microsoft Exchange WebServices betrachtet und auf Eignung untersucht. Aus einer Reihe von Gründen konnten sie hier jedoch nicht direkt verwendet werden.
  • ID-Mapping im WebService: Ein Kolab-Server (IMAP Server mit Kolab-Objekten) bietet keine Möglichkeit des direkten Zugriffes auf ein Kolab-Objekt anhand der Kolab-ID. Um diesen Zugriff dennoch effizient realisieren zu können verfügt der WebService über eine einfache Mapping-Struktur, die über IMAP-Synchronisationen aktuell gehalten wird. Neben dem Mapping von Kolab-ID auf IMAP-GUID verfügt diese Struktur noch über durchsuchbare Meta-Daten (z.B. Datum der erkannten Änderung eines Objektes). Um einen effizienten Zugriff auf diese Mapping-Struktur zu ermöglichen wird sie anhand einer Tabelle einer relationalen Datenbank umgesetzt.
  • Datenhaltung: Das System wurde so designed, dass weder im Funambol, noch im WebService die Inhalte von PIM-Objekten gespeichert werden. Dies minimiert die Möglichkeit für Angreifer an diese Daten zu gelangen.
  • User Credentials: Die Daten zur Authentisierung werden von den Diensten nur durch gereicht und nicht zwischen gespeichert.
  • Konfliktbehandlung Sevrer-Wins: Konflikte werden durch gleichzeitige Modifikationen (auch Löschungen) eines Datensatzes auf Client und Server hervorgerufen. Da ein interaktives Auflösen der Konflikte durch den Benutzer von mobilen Endgeräten nicht unterstützt wird, musste die Entscheidung für eine interaktionslose Strategie getroffen werden. Hierbei wurde Server-Wins gewählt, da sie begünstigt, dass bei einem Konflikt zwischen mobile-Client und Desktop-Client der Desktop gewinnt.
  • LDAP-Authentisierung: Die Authentisierung gegen den LDAP-Server erfolgt in Form eines LDAP-bind mit den Benutzerdaten. Die ist das übliche Vorgehen zur LDAP-Authentisierung.
  • TODO: Dokumentation der Entscheidung für die Zeit in der WS-Schnittstelle

Grobarchitektur

Wie in der Grafik zu erkennen ist, wird der Synchronisationsserver in zwei Komponenten unterteilt: Einen Web-Service als Erweiterung des Kolab-Servers sowie den Funambol-Server. Die Kollaborationsdaten (Kontakte etc.) sind im Kolab-Server und auf den Endgeräten abgespeichert. Die übrigen Komponenten leiten diese Daten nur weiter und legen diese nicht auf dem Sekundärspeicher ab. Die Kommunikationskanäle können mit Hilfe eines VPNs oder des HTTPS Protokolls gesichert werden. D.h. das System ist darauf ausgelegt, dass alle Kanäle gesichert betrieben werden müssen. Zu jedem Protokoll einer entsprechenden Komponente ist auch die gesicherte Variante implementiert. Ob dies zum Einsatz kommt ist Bestandteil der Konfiguration im Betrieb. Ebenso können WebService Security und IMAPS, ldaps zur Sicherung der entsprechenden Kommunikationsstrecken konfigurativ verwendet werden.

Kapitel3.jpeg

Kolab WebService

Der Zugriff auf den Kolab-IMAP-Server wird durch einen WebService gekapselt. Auf diese Weise kann über eine explizit definierte Schnittstelle mit festgelegten Datentypen auf die im IMAP Server gespeicherten Kollaborationsdaten zugegriffen werden. Bei den Daten handelt es sich um Kontakte, Kalenderdaten, Notizen und Aufgaben. Der Kolab WebService wird von dem oben beschriebenen Funabmol Connector verwendet, soll aber eine allgemeingültige Schnittstelle besitzen, die von beliebigen Clients gut aufgerufen werden kann. Auf diese Weise entsteht eine Service-Komponente, die den Zugriff auf den Kolab-Server vereinfacht und somit eine Integration der Kolab-Daten in unterschiedliche Anwendungen vereinfacht.

Die Kollaborationsdaten sind im IMAP Server in Mailboxen (Ordnern) abgelegt, sodass in einem Mailboxverzeichnis nur Daten des selben Typs gespeichert werden (z.B. Kontakte). Datenobjekte sind in Form von Kolab-spezifischen XML-Files gespeichert. Es existiert ein Rechtesystem, das festlegt welche Verzeichnisse von einem Benutzer des Systems gelesen oder beschrieben werden können. Die Datentypen haben eine Kolab-spezifische ID, die innerhalb eines Verzeichnisses normalerweise eindeutig ist. Es ist jedoch nicht möglich Daten des gleichen Typs in unterschiedlichen Verzeichnissen anhand ihrer ID zu vergleichen.

Über den Kolab WebService ist es für einen Client möglich, alle für den angemeldeten Benutzer sichtbaren Kollaborationsdaten zu lesen, und wenn die Rechte es erlauben, neue Datensätze anzulegen. Der WebService bietet weiterhin eine Möglichkeit bestehende Daten auf dem Client effizient mit den Daten auf dem IMAP Server abzugleichen.

Funambol Connector

Funambol ist ein Open Source Synchronisationsserver für mobile Endgeräte. Das System ermöglicht die Anbindung vieler unterschiedlicher Endgeräte und reduziert so den Entwicklungsaufwand erheblich. Es unterstützt SyncML, als Plattform-unabhängigen Standard. Da wenige Endgeräte SyncML-fähige Email Clients bereitstellen, wird der Funambol Server in der beschriebenen Architektur nicht für die Synchronisation von Emaildaten verwendet. Das Konzept des Funambol-Servers kann durch Module erweitert werden, die jeweils die Verbindung zu einer oder mehreren Datenquellen regeln. Die Anbindung an den Kolab WebService erfolgt durch die Implementierung eines solchen Funambol-Connector-Moduls.


Benutzerverwaltung

Im Rahmen dieser Anwendung wird die Konfigurationsoberfläche des Kolab-Servers erweitert. Dort kann sich ein Benutzer mit seinen LDAP Daten anmelden. Er hat dann die Möglichkeit für alle für ihn lesbaren Folder festzulegen, ob diese über den WebService zur Synchronisation angeboten werden oder nicht. Außerdem hat er die Möglichkeit das Zeitintervall zu bestimmen, in dem Termine auf das Endgerät übertragen werden. Die Speicherung der Benutzerdaten erfolgt im LDAP sowie in Form von IMAP-Annotationen auf den entsprechenden Foldern. Die Details der Konfigurationsparameter werden weiter unten festgelegt.

Wenn möglich wird diese Information mit Hilfe von Imap Annotationen direkt in den betreffenden Ordnern abgespeichert.

Administrative Benutzerverwaltung

Die Verwaltung der Benutzer, die Zugriff auf die Synchronisation haben geschieht komplett durch das verwendete LDAP. Die Synchronisationslösung entscheidet anhand der Zugehörigkeit eines Benutzers zu der Gruppe sync ob eine Nutzung gewährt wird oder nicht. Die Prüfung wird sowohl vom Funambol-Server, als auch vom WebService durchgeführt. Zusätzlich zu der Verwaltung über das LDAP kann das Funambol-eigene Frontend zur Verwaltung von Endgeräten genutzt werden.

Authentifizierung

Die Authentifizierung erfolgt über den Benutzernamen und das Passwort aus dem LDAP. Diese Credentials müssen auf dem mobilen Endgerät entsprechend hinterlegt werden. Die Übertragung vom mobilen Endgerät zum Funambol-Server erfolgt entsprechend der Spezifikation SyncML HTTP Binding, Version 1.1 [8]. Hierbei wird ein HTTP-Basic-Auth verwendet. Die Übergabe der Credentials vom Funambol-Server zum WebService erfolgt anhand der Spezifikation Web Services Security UsernameToken Profile 1.0 [9]. Hierbei wird die Default-Variante (Password-Type=#PasswordText) verwendet. Die Prüfung der Benutzer-Credential gegen das LDAP erfolgt über Verwendung eines LDAP-Bind.

Feinentwurf der Module

Funambol Connector

Sync source uml.png


Das Zentrale Server-Element für die Synchronisation ist der Funambol-Server. Er nimmt die Client-Verbindung entgegen und koordiniert die Synchronisation. Die Anbindung an den Kolab-Server erfolgt über die Erstellung eines Kolab-Connectors, der den Kolab WebService verwendet. Konkret wird ein Funambol Connector durch die Implementierung einer SyncSource realisert. Durch die Methoden des Funambol-Connectors (siehe Grafik) wird klar vorgegeben, wie sich ein Funambol-Connector verhalten muss um dem Server die entsprechenden Daten bereitzustellen. Folgende Sync Sources werden implementiert: KolabContactSyncSource für Kontakt Einträge, KolabCalendarSyncSource für Termine und Aufgaben sowie KolabNotesSyncSource für Notizen.

Der Funambol Connector wird anhand der Richtlinien des Funambol Developers Guide [1] entwickelt. Er wird als Apache Maven [5] Projekt realisiert, dessen Ausgabe eine s4j-Datei ist, die in einem Funambol Server installiert werden kann. Basis für den Zugriff auf den Kolab WebService ist die WSDL Beschreibung der Kolab WebService Schnittstelle. Die WSDL Datei ist Bestandteil des Maven Projektes. Auf diesem Weg werden Zugriffsklassen für den WebService automatisch während des Build-Vorganges generiert, was einem Contract First Modell Programmiermodell entspricht. Der Zugriff auf den Kolab WebService wird mit Hilfe des WebService Frameworks Apache CXF [4] durchgeführt, welches die JAX-WS Spezifikation umsetzt.

Initiale Synchronisation

Wird das mobile Endgerät zum ersten Mal mit dem zentralen Datenbestand synchronisiert, werden alle Datensätze in entsprechend konfigurierten Foldern des Servers auf den Client übertragen. Der Funambol Connector wird dazu vom Funambol Server aufgerufen um die Verbindung mit dem Datenbestand durchzuführen. Zu diesem Zweck implementiert der Connector die abstrakte Klasse com.funambol.framework.engine.source.AbstractSyncSource (siehe Anhang A1). Diese wird, wie im folgenden UML Sequenzdiagramm dargestellt, aufgerufen.

FunambolInitSyncSeq.png

Zu Beginn der Synchronisation wird die Operation beginSync aufgerufen, der ein Anwendungskontext übergeben wird. Mit Hilfe dieses Kontextes werden globale Attribute des Connectors, wie z.B. Authentifizierungsdaten, Geräte-ID und Startzeit der Synchronisation ausgelesen. Die vom Funambol Server gemessene Startzeit wird mit der aktuellen Zeit des Kolab WebService verglichen. Liegt die Zeit des Funambol Servers abzüglich eines Toleranzintervalls von 5 Sekunden über der Systemzeit des Kolab WebService wird ein Fehler erzeugt und die Synchronisation abgebrochen.

Für die Implementierung der Operation getAllSyncItemKeys werden die Operationen get[...]Folders und get[...]Ids des WebServices aufgerufen. Sie liefern die IDs aller Einträge in einem Folder. Um eine übergreifend eindeutige ID für die mobilen Endgeräte zu erzeugen, wird der Imap Ordner und die ID des Datensatzes zu einer neuen ID zusammengefasst. Diese Änderung der ID muss eindeutig umkehrbar sein um eine Funambol ID im Funambol Connector wieder in ihre Bestandteile zerlegen zu können. Hierzu wird folgende Syntax verwendet: <Foldername> ':' <Kolab-id>

Der Kolab Connector aggregiert die Inhalte aus allen Kolab Foldern in eine Menge, die mit dem Endgerät abgeglichen wird. Hierdurch werden Einträge aus mehreren Foldern auf dem Endgerät 'nebeneinander' angezeigt. Technisch ist dies durch die Verwendung obiger ID-Modifikation einfach möglich. Um auch auf fachlicher Ebene unterscheiden zu können, aus welchem Kontext die Elemente kommen, wird den Titeln der Termine der Folder-Name voran gestellt. Wenn neue Daten-Objekte vom mobilen Endgerät zum Server übertragen werden, werden diese im Standard-Folder des Benutzers für diesen Datentyp abgelegt. Ein neuer Termin landet damit beispielsweise im Kolab-Standard-Ordner des Benutzers. Dieser Standardordner ist im Kolab-Server eindeutig gegeben.

Weiterhin wird die Operation getSyncItemFromId implementiert, die mit Hilfe der WebService Operationen getContact, getNote, getEvent und getTask alle Kollaborationsdaten einsammelt. Der Typ des Datensatzes wird dabei anhand des Imap-Ordners ermittelt, der in der Eingabe-ID kodiert ist.

Inkrementelle Synchronisation

Wurde zwischen einem Client und dem zentralen Datenspeicher schon ein initialer Datenabgleich durchgeführt, dann ist es bei den nächsten Synchronisationsvorgängen ausreichend nur die Änderungen zwischen dem Client- und Serversystem auszutauschen. Funambol bietet dafür den "Fast Sync" Modus. Dabei werden zuerst die IDs von allen auf dem Server geänderten Datensätzen an den Client übertragen. Dieser bereinigt eventuell mögliche Konflikte und sendet dann alle Änderungen des Clients an den Server. Im Anschluss werden die auf dem Server geänderten Daten an den Client übertragen. Das folgende Sequenzdiagramm veranschaulicht den Ablauf des inkrementellen Synchronisationsvorgangs.

FunambolIncSyncSeq.png

Sowohl für die Initiale, als auch für die Inkrementelle Synchronisation wird die selbe Connector-Implementierung (selbe Klasse) verwendet. Die Operationen beginSync und getAllSyncItemKeys wurden schon im vorangehenden Abschnitt beschrieben und können bestehen bleiben. Die Operationen getNewSyncItemKeys, getUpdatedSyncItemKeys und getDeletedSyncItemKeys rufen zuerst die Operationen get[..]Folders am WebService auf um die verwendeten Imap-Ordner zu erfragen. Im Anschluss werden die Operationen getNew[..]Ids, getUpdated[..]Ids und getRemoved[..]Ids für jeden Ordner aufgerufen. Der Parameter since im Funambol Connector legt den Zeitpunkt der letzten Synchronisation fest und ist für den gesamten Synchronisationsvorgang konstant. Der Zeitwert wird abzüglich eines Toleranzintervalls von 5 Sekunden als Eingabeparameter für den WebService verwendet. Auf diese Weise ist sicher gestellt, dass auch bei minimalen Abweichungen beider Server keine Änderungen übersehen werden. Die Änderungen am Datenbestand des Clients werden mit Hilfe der Methoden updateSyncItem, removeSyncItem und addSyncItem dem Funambol Connector mitgeteilt. Diese werden mit den Operationen update[..], remove[..] und add[..] an den WebService weitergegeben.

Konfliktbehandlung

Als Konfliktbehandlung wurde beschlossen ein Server Wins Verfahren einzusetzen. Dies hat den Vorteil, dass bei konkurrierenden Änderungen vom mobilen Endgerät und dem Desktop-Client die Version des Desktop-Clients verwendet wird. Die Auswahl einer Strategie zur Konfliktbehandlung im Funambol erfolgt konfigurativ.

Typumwandlung

Innerhalb des Connectors muss eine Umwandlung zwischen verschiedenen Kollaborationsdatentypen erfolgen. Es muss eine umkehrbare Abbildung zwischen den Kolab-Datentypen [2] und den von Endgeräten unterstützten Formaten definiert werden. Für Kontaktdaten wird bei den Endgeräten das Format vCard [6] eingesetzt. Für Kalenderdaten wird das iCalender [7] Format verwendet. Die Konvertierung zwischen den Datenformaten erfolgt über Hilfsklassen, die vom Funambolserver bereitgestellt werden (com.funambol.common.pim.contact.Contact, com.funambol.common.pim.calendar.Event und com.funambol.common.pim.calendar.Task). Hierbei können eine Reihe von Umwandlungskonflikten entstehen, die wie folgt zu lösen sind:

  • Feld existiert nur in einem Datenschema: Das Feld wird nicht unterstützt. Bei einer Änderung auf dem Client (also beim schreiben des Datenobjektes in den Server) muss sicher gestellt werden, dass diese Felder nicht mit Null-Werten überschrieben werden. Hierzu muss das Ursprungsobjekt vom Server geholt und durch die Daten des Client-Objektes ergänzt werden.
  • Die Felder sind unterschiedlich benannt: Mapping des Feldes
  • Die Felder haben einen unterschiedlichen Datentyp: Konvertierung des Eintrages
  • Abbildung mehrerer Felder auf Eines: Wahl einer möglichst sauber umkehrbaren Abbildung
  • Unterschiedliche Formatierungen (z.B. unterschiedliche Trennzeichen einer Telefonnummer): Nicht allgemein lösbar. Hierbei wird die Formatierung durch den Client bestimmt, der das Datenobjekt zuletzt schreibt.

Es ist zu bemerken, dass die hier angesprochene Typumwandlung unter Umständen nicht komplett gelöst werden kann. Je nach verwendetem Endgerät und eingesetztem Kolab-Client können Unterschiede im Datenschema und in der Interpretation des Datenschemas dazu führen, dass z.B. Formatierungen verloren gehen, wenn ein Datenobjekt mit dem jeweils anderen Client geändert wurde. Diese Artefakte möglichst zu minimieren ist Bestandteil einer frühen Testphase während der Implementierung.

Authentifizierung

Die Authentifizierung bei Funambol wird mittels eines Benutzernamens und eines Passwortes durchgeführt. Diese Daten müssen von dem mobilen Endgerät bei einem Synchronisationsvorgang mitgesendet werden (HTTP Basic Auth). Die Funambol Anwendung prüft dann mit Hilfe eines LDAP Servers die Gültigkeit der Daten (ldab bind). Es handelt sich dabei um den selben LDAP Server der auch vom Kolab/IMAP-Server zur Authentifizierung verwendet wird. Die Implementierung der Authentifizierungslogik wird mit Hilfe der Officer API [1] von Funambol durchgeführt. Die Passwörter werden nicht auf dem Sekundärspeicher abgelegt.

Kolab WebService

Der Kolab Zugriff wird über einen WebService gekapselt. Hauptgrund hierfür ist, den Kolab-spezifischen Zugriff auf Daten-Objekte in einem IMAP-Server durch einen einfachen direkten WebService-Aufruf zu ersetzen. Dabei kann der WebService-Aufruf direkt anhand der Kolab Objekt-ID erfolgen, anstatt der IMAP spezifischen Mails IDs. Der WebService-Client benötigt keine aufwändige IMAP-Synchronisation mit dem gesamten Folder, bei der jedes Daten-Objekt aus dem IMAP bezogen werden und geparst werden muss. Diese Maßnahmen erhöhen die Sicherheit des Systems, reduzieren die Fehleranfälligkeit und vermindern redundanten Code.

Des weiteren erhält man eine Schnittstelle mit Operationen und festgelegten Typen. Durch die explizite Typisierung und die Möglichkeit ein Datenschema zu forcieren wird das System weniger anfällig für Fehler und unsachgemäße Verwendung: Es ist möglich Instanzen eines Typs auf einfache standardisierte Art zu validieren.

Um eine saubere Schnittstelle zu erhalten bietet es sich an, die in der Kolab Format Beschreibung [2] definierten XML-Datentypen auch als Datentypen für den Kolab WebService im groben zu übernehmen. Hiervon liegt aus dem Kolab Projekt keine Definition der XML-Schemata vor. Im Rahmen der Web-Service Definition wird dies jedoch erstellt. In der Kolab Dokumentation fehlt eine Beschreibung des Synchronisationsalgorithmus und des ID-Generierungsalgorithmus. Diese wurden daher von der tarent GmbH abgeleitet.

Der WebService greift lesend und schreibend auf den einen IMAP-Server zu, der Kollaborationsdaten wie z.B. Kontakte und Termine verwaltet. Der Zugriff auf den IMAP Server ist nicht exklusiv: Auch andere Applikationen haben schreibenden zum gleichen Zeitpunkt Zugriff auf den IMAP-Server. Konzepte wie beispielsweise Transaktionen werden vom Kolab Server nicht unterstützt.


Verwendete Software
Software Version Verwendung
Java 1.6 Laufzeitumgebung
Maven 2.0.x Build System
EJB 3.0 Komponenten Plattform (geringe Verwendung)
JPA 1.0.x Persistenzschicht
JAX-WS 2.0 WebService Standard
JAXB 2.0 XML-Binding Standard
JavaMail 1.4.2 IMAP Implementierung
JBoss 5.0.x GA J2EE Container

Der WebService wird auf der Java 1.6 Plattform als Maven 2 Projekt realisiert. Hierbei werden Enterprise Java Beans und JAX-WS verwendet. Der Service soll in einem aktuellen JBoss Appliaction Server [3] betrieben werden. Dieses Vorgehen ermöglicht hohe Skalierbarkeit und Performance. Hauptgrund für diese Plattformwahl sind jedoch die sehr ausgereiften Standards für die Arbeit mit WebServices. Mit Hilfe von EJB, JAX-WS und JAXB Technologien kann das Projekt sehr kompakt realisiert werden. Die Schnittstelle des WebService mit seinen Datentypen wird mit Hilfe einer WSDL-Datei, die konform mit dem WS-I Basic Profile ist, definiert. Die Datei wird im WebService Projekt und im Funambol-Connector Projekt für einen Contract-First Ansatz zur Codegenerierung verwendet werden.

Für den Zugriff auf den IMAP-Server kommt die Bibliothek JavaMail von Sun zum Einsatz. Da diese Bibliothek unter einer Open Source Lizenz steht, ist sie beliebig erweiterbar und gibt somit eine Investitionssicherheit für dieses Projekt. Außerdem stellt sie eine im Enterprise Umfeld weit etablierte Implementierung dar, bei der nicht mit Instabilitäten bei der Umsetzung des IMAP-Protokolls zu rechnen ist. Eine Erweiterung der JavaMail-API für die Unterstützung von IMAP-Annotationen ist vorgesehen.

Datentypen

Die Datentypen des Kolab Servers [2] sind in einem XML-Format definiert. Es existiert bisher kein XML Schema dazu. Die Datentypen können in ähnlicher Form im WebService verwendet werden. Als einzige neue Datenstruktur kommt die Klasse Folder hinzu, die einen IMAP-Ordner repräsentiert. Hier im Text erfolgt lediglich eine beschreibende Darstellung der Datentypen und Operationen. Dabei wird nicht zwischen den Java-Typen und der WebService-Typen differenziert. Eine genaue Festlegung ist jedoch dem WSDL aus Anhang 2 zu entnehmen.

In folgendem UML Klassen Diagramm ist die vom WebService verwendete Typ-Architektur dargestellt. Attribute die mit einem Minus markiert sind, werden nicht über die Schnittstelle herausgegeben und sind nur intern sichtbar. Die Kolab-Typen "Distribution List" und "Journal" werden vom WebService nicht verwendet und fehlen in dem Diagramm, da sie nicht Bestandteil der Projektanforderungen waren.

Ein besondere Bedeutung kommt dem Attribut uid in der obersten Klasse CollaborationItem zu. Es wird auf dem Endgerät benötigt um Kollaborationsdaten den Daten im Kolab Server zuordnen zu können. Der Wert dieses Attributs wird vom Kolab WebService erzeugt und gesetzt.

Die Werte der Attribute creationDate und lastModificationDate in der Klasse CollaborationItem liegen auch in der Verantwortung des WebServices. Sie werden vom Endgerät nicht benötigt und werden nur intern verwendet. Das Attribut productId wird beim Schreiben eines Datensatzes auf den Wert "Kolab WebService <Version>" gesetzt und ist nur für Debugging Zwecke vorgesehen.

KolabInternalTypesClass.png

Der Recurrence Datentyp wurde in Bezug zum originalen Kolab-Element leicht verändert um die Semantik verständlicher zu machen. Eine spätere Aufnahme dieser Änderung in das offizielle Kolab Format wird angestrebt. Dazu wurden hauptsächlich Attribute mit Mehrfachbedeutung auf mehrere Attribute aufgeteilt. Das cycle Attribut darf die Werte "daily", "weekly", "monthly" oder "yearly" enthalten. Das Attribut interval bezieht sich dann auf diesen Multiplikator. Das Attribut range wurde in die Attribute rangeNumber und rangeDate aufgeteilt. Das Erste legt fest wie oft der Termin wiederholt werden soll (0 = unendlich oft), und das Zweite bis zu welchem Zeitpunkt der Termin wiederholt werden soll. Es muss immer genau eines dieser Attribute gesetzt sein. Die Attributliste day wird hier durch das Attribut weekDays als Bitfeld kodiert. Der Montag wird als 2^0=1 kodiert und Sonntag als 2^6=64. Somit sind Werte von 1-127 erlaubt um beliebige nicht leere Mengen von Wochentagen darzustellen. Das Attribut month bleibt bestehen, die Monate werden lediglich als Ganzzahlen statt als Strings kodiert (Januar = 1, Dezember = 12). Das Attribut daynumber wird durch die Attribute week, yearDay und monthDay dargestellt, um die unterschiedliche Semantik der Feldbelegungen hervorzuheben. Das Attribut week bezeichnet die Woche im Monat, wobei die erste Woche mit dem ersten Tag eines Monats beginnt. Mit dieser Bezeichnung macht auch die Kombination von mehreren Wochentagen und einem Wochenzähler Sinn. Es ist allerdings nicht klar ob andere Kolab Clients dies ebenso interpretieren, da in der Dokumentation [2] nur der Fall mit einem festen Wochentag beschrieben wird.

In der folgenden Tabelle sind die gültigen Belegungen für die komplizierteren Felder aufgelistet. Oben ist der Wert des Attributs cycle aufgelistet und darunter zeigen Markierungen welche Attribute belegt sind. Eine Spalte repräsentiert eine erlaubte Belegung der Attribute. Dabei ist zu erwähnen, dass daily und weekly nur eine erlaubte Belegung haben, während weekly und monthly zwei bzw. drei mögliche Belegungen besitzen.

cycle daily weekly monthly     yearly       
month x x
week x x
yearDay x
monthDay x x
weekDays x x x

Eine Abbildung zwischen dem Recurrence Typ im WebService und dem im Kolab Server ist sehr einfach durchführbar da keine Umrechnungen vorgenommen werden müssen.

Operationen

Die beschriebenen Operationen dienen zur Synchronisation der Kolab Daten mit einem Endgerät. Dies kann wie beschrieben über eine Zwischenstation wie Funambol gehen. Die Schnittstelle soll allgemein verwendbar sein, sich jedoch besonders gut für die vorgesehene Verwendung in einem Funambol Connector eignen. Neben dem Funambol Connector wurden die Anforderungen des Zarafa z-push-Frameworks analysiert und in die Überlegungen zum Schnittstellen-Design einbezogen. Das z-push Frameworks bietet eine ActiveSync Schnittstelle und sollte ebenfalls an den WebService anbindbar sein.

Beim Schnittstellendesign wurden ebenfalls das Akonadi-Framework und die Schnittstelle der Microsoft Exchange WebServices [10] betrachtet. Beide Schnittstellen verfolgen einen etwas anderen Einsatz und konnten nicht als Vorlage verwendet werden. Bei letzterer ist dies vor allem Aufgrund ihrer Mächtigkeit der Fall.

Emaildaten werden nicht über den WebService abgefragt. Wenn dies zu einem späteren Zeitpunkt gewünscht ist, können der WebService Schnittstelle entsprechende Operationen hinzugefügt werden. Da dort keine Kolab spezifischen IDs und Typen vorkommen, gibt es wenig Überschneidungen mit den schon vorhandenen Operationen. Eine Implementierung der Methoden für den Email-Zugriff kann analog zu den bestehenden Methoden für die PIM-Objekte erfolgen.

TODO: Berücksichtigung einer createFolder-Methode, getDistributionList-Methode (in der Spezifikation für eine spätere Erweiterung)

getCurrentTime
Beschreibung Gibt die aktuelle Systemzeit zurück. Diese Operation kann verwendet werden, um vor einem Synchronisationsvorgang die aktuelle Systemzeit abzuspeichern und diese bei einer inkrementellen erneuten Synchronisation als Startdatum zu verwenden. Ist es nicht möglich die Systemzeit für die Synchronisation auf dem Clientrechner zu speichern, muss die Zeit auf dem Client- und Server-System synchronisiert sein. Diese Operation kann dann verwendet werden um diese Vorgabe zu überprüfen.
Ausgabe Date Die aktuelle Systemzeit.
Fehler -
getContactFolders, getNoteFolders, getEventFolders, getTaskFolders
Beschreibung Gibt eine Liste der für den Benutzer lesbaren Ordner für den jeweiligen Datentyp zurück.
Ausgabe List<Folder> Die Liste der für den Benutzer lesbaren Ordner für den jeweiligen Datentyp. Das erste Element der Liste ist der Standardordner für den jeweiligen Datentyp. Die Liste kann leer sein.
Fehler
  • Es wurde kein Standardordner für den Benutzer und den Datentyp gefunden.
getContactSyncFolders, getNoteSyncFolders, getEventSyncFolders, getTaskSyncFolders
Beschreibung Gibt eine Liste der für den Benutzer lesbaren Sync-Ordner für den jeweiligen Datentyp zurück. Sync-Ordner sind die jenigen, die mit der ensprechenden IMAP Annotation für die Synchronisation auf ein mobiles Endgerät markiert sind.
Ausgabe List<Folder> Die Liste der für den Benutzer lesbaren Sync-Ordner für den jeweiligen Datentyp.
Fehler
  • Es wurde kein Standardordner für den Benutzer und den Datentyp gefunden.
getContactIDs, getNoteIDs, getEventIDs, getTaskIDs
Beschreibung Gibt die IDs aller Datensätze in dem angegebenen Ordner zurück.
Eingabe Folder folder Der Ordner, auf den zugegriffen werden soll.
Ausgabe List<String> Menge von IDs von Datensätzen.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den jeweiligen Datentyp
  • Benutzer hat keine Leserechte für den Ordner
getNewContactIDs, getNewNoteIDs, getNewEventIDs, getNewTaskIDs
Beschreibung Gibt die IDs aller seit dem angegebenen Zeitpunkt in dem angegebenen Ordner hinzugefügten Datensätze zurück. Datensätze die von dem selben Benutzer von dem selben Endgerät hinzugefügt wurden, werden aus der Rückgabe ausgeschlossen.
Eingabe Folder folder Der Ordner, auf den zugegriffen werden soll.
String deviceID Die ID des Endgerätes.
Date lastUpdateTime Das Datum wann das letzte Update durchgeführt wurde. Es werden nur IDs für Datensätze zurückgegeben, die nach diesem Datum hinzugefügt wurden.
Ausgabe List<String> Menge von IDs von Datensätzen.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den jeweiligen Datentyp
  • Benutzer hat keine Leserechte für den Ordner
getUpdatedContactIDs, getUpdatedNoteIDs, getUpdatedEventIDs, getUpdatedTaskIDs
Beschreibung Gibt die IDs aller seit dem angegebenen Zeitpunkt in dem angegebenen Ordner geänderten Datensätze zurück. Datensätze die von dem selben Benutzer von dem selben Endgerät geändert wurden, werden aus der Rückgabe ausgeschlossen.
Eingabe Folder folder Der Ordner, auf den zugegriffen werden soll.
String deviceID Die ID des Endgeräts.
Date lastUpdateTime Das Datum wann das letzte Update durchgeführt wurde. Es werden nur IDs für Datensätze zurückgegeben, die nach diesem Datum geändert wurden.
Ausgabe List<String> Menge von IDs von Datensätzen.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den jeweiligen Datentyp
  • Benutzer hat keine Leserechte für den Ordner
getRemovedContactIDs, getRemovedNoteIDs, getRemovedEventIDs, getRemovedTaskIDs
Beschreibung Gibt die IDs aller seit dem angegebenen Zeitpunkt in dem angegebenen Ordner gelöschten Datensätze zurück. Datensätze die von dem selben Benutzer von dem selben Endgerät gelöscht wurden, werden aus der Rückgabe ausgeschlossen.
Eingabe Folder folder Der Ordner, auf den zugegriffen werden soll.
String deviceID Die ID des Endgeräts.
Date lastUpdateTime Das Datum wann das letzte Update durchgeführt wurde. Es werden nur IDs für Datensätze zurückgegeben, die nach diesem Datum gelöscht wurden.
Ausgabe List<String> Menge von IDs von Datensätzen.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den jeweiligen Datentyp
  • Benutzer hat keine Leserechte für den Ordner
getContact
Beschreibung Gibt einen Kontakt zurück.
Eingabe Folder folder Der Ordner des Kontaktes.
String ID ID des gewünschten Kontaktes.
Ausgabe Contact Der angefragte Kontakt oder null wenn ein Kontakt mit der angegebenen ID in dem Ordner nicht existiert.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Kontakt-Datentyp
  • Benutzer hat keine Leserechte für den Ordner
getNote
Beschreibung Gibt eine Notiz zurück.
Eingabe Folder folder Der Ordner der Notiz.
String ID ID der gewünschten Notiz.
Ausgabe Contact Die angefragte Notiz oder null wenn eine Notiz mit der angegebenen ID in dem Ordner nicht existiert.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Notiz-Datentyp
  • Benutzer hat keine Leserechte für den Ordner
getEvent
Beschreibung Gibt einen Termin zurück.
Eingabe Folder folder Der Ordner des Termins.
String ID ID des gewünschten Termins.
Ausgabe Contact Der angefragte Termin oder null wenn ein Termin mit der angegebenen ID in dem Ordner nicht existiert.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Termin-Datentyp
  • Benutzer hat keine Leserechte für den Ordner
getTask
Beschreibung Gibt eine Aufgabe zurück.
Eingabe Folder folder Der Ordner der Aufgabe.
String ID ID der gewünschten Aufgabe.
Ausgabe Contact Die angefragte Aufgabe oder null wenn eine Aufgabe mit der angegebenen ID in dem Ordner nicht existiert.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Aufgaben-Datentyp
  • Benutzer hat keine Leserechte für den Ordner
addContact
Beschreibung Speichert einen neuen Kontakt.
Eingabe Folder folder Der Ordner, in dem der Kontakt gespeichert werden soll.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
Contact contact Der zu speichernde Kontakt.
Ausgabe String Die für diesen Kontakt vom Server generierte ID. Diese muss vom Client in Zukunft verwendet werden.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Kontakt-Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
  • Der Kontakt enthält einen ungültigen Wert
addNote
Beschreibung Speichert eine neue Notiz.
Eingabe Folder folder Der Ordner, in dem die Notiz gespeichert werden soll.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
Note note Die zu speichernde Notiz.
Ausgabe String Die für diese Notiz vom Server generierte ID. Diese muss vom Client in Zukunft verwendet werden.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Notiz-Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
  • Die Notiz enthält einen ungültigen Wert
addEvent
Beschreibung Speichert einen neuen Termin.
Eingabe Folder folder Der Ordner, in dem der Termin gespeichert werden soll.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
Event event Der zu speichernde Termine.
Ausgabe String Die für diesen Termin vom Server generierte ID. Diese muss vom Client in Zukunft verwendet werden.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Termine-Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
  • Der Termin enthält einen ungültigen Wert
addTask
Beschreibung Speichert eine neue Aufgabe.
Eingabe Folder folder Der Ordner, in dem die Aufgabe gespeichert werden soll.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
Task task Die zu speichernde Aufgabe.
Ausgabe String Die für diese Aufgabe vom Server generierte ID. Diese muss vom Client in Zukunft verwendet werden.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Aufgaben-Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
  • Die Aufgabe enthält einen ungültigen Wert
removeContact, removeNote, removeEvent, removeTask
Beschreibung Löscht einen Datensatz.
Eingabe Folder folder Der Ordner, in dem sich der Datensatz befindet.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
String ID Die ID des zu löschenden Datensatzes.
Date lastUpdateTime Der Zeitpunkt des letzten Synchronisationsvorgangs. Wurde der Datensatz nach diesem Zeitpunkt auf dem Server geändert, wird die Löschung nicht durchgeführt.
Ausgabe boolean Gibt true zurück wenn der Datensatz gelöscht wurde. Andernfalls wurde der Datensatz nach dem angegebenen Zeitpunkt von einer anderen Partei geändert und wird nicht gelöscht.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
updateContact
Beschreibung Ändert einen Kontakt.
Eingabe Folder folder Der Ordner, in dem sich der Kontakt befindet.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
Contact contact Der neue Wert des Kontakts.
Date lastUpdateTime Der Zeitpunkt des letzten Synchronisationsvorgangs. Wurde der Kontakt nach diesem Zeitpunkt auf dem Server geändert, wird die Änderung nicht durchgeführt.
Ausgabe boolean Gibt true zurück wenn der Kontakt geändert wurde. Andernfalls wurde der Kontakt nach dem angegebenen Zeitpunkt von einer anderen Partei geändert oder gelöscht und wird nicht geändert.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
  • Der Kontakt enthält einen ungültigen Wert
updateNote
Beschreibung Ändert eine Notiz.
Eingabe Folder folder Der Ordner, in dem sich die Notiz befindet.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
Note note Der neue Wert der Notiz.
Date lastUpdateTime Der Zeitpunkt des letzten Synchronisationsvorgangs. Wurde die Notiz nach diesem Zeitpunkt auf dem Server geändert, wird die Änderung nicht durchgeführt.
Ausgabe boolean Gibt true zurück wenn die Notiz geändert wurde. Andernfalls wurde die Notiz nach dem angegebenen Zeitpunkt von einer anderen Partei geändert oder gelöscht und wird nicht geändert.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
  • Die Notiz enthält einen ungültigen Wert
updateEvent
Beschreibung Ändert einen Termin.
Eingabe Folder folder Der Ordner, in dem sich der Termin befindet.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
Event event Der neue Wert des Termins.
Date lastUpdateTime Der Zeitpunkt des letzten Synchronisationsvorgangs. Wurde der Termine nach diesem Zeitpunkt auf dem Server geändert, wird die Änderung nicht durchgeführt.
Ausgabe boolean Gibt true zurück wenn der Termin geändert wurde. Andernfalls wurde der Termin nach dem angegebenen Zeitpunkt von einer anderen Partei geändert oder gelöscht und wird nicht geändert.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
  • Der Termin enthält einen ungültigen Wert
updateTask
Beschreibung Ändert eine Aufgabe.
Eingabe Folder folder Der Ordner, in dem sich die Aufgabe befindet.
String deviceID Die ID des Endgeräts. Im WebService wird dieser Wert gespeichert, um den Datensatz bei der nächsten Synchronisation mit dem Gerät nicht zu übertragen.
Task task Der neue Wert der Aufgabe.
Date lastUpdateTime Der Zeitpunkt des letzten Synchronisationsvorgangs. Wurde die Aufgabe nach diesem Zeitpunkt auf dem Server geändert, wird die Änderung nicht durchgeführt.
Ausgabe boolean Gibt true zurück wenn die Aufgabe geändert wurde. Andernfalls wurde die Aufgabe nach dem angegebenen Zeitpunkt von einer anderen Partei geändert oder gelöscht und wird nicht geändert.
Fehler
  • Ordner existiert nicht
  • Ordner besitzt nicht die richtige Kolab-Annotation für den Datentyp
  • Benutzer hat keine Schreibrechte für den Ordner
  • Die Aufgabe enthält einen ungültigen Wert

Authentifizierung

Die Authentifizierung am Kolab WebService wird mittels eines Benutzernamens und eines Passwortes durchgeführt. Diese Daten müssen von dem Funambol Connector bei einem Synchronisationsvorgang mitgesendet werden (WebServices Security Username Token). Der WebService prüft dann mit Hilfe eines LDAP Servers die Gültigkeit der Daten (LDAP bind und Prüfung der Gruppe). Es handelt sich um den selben LDAP Server der auch vom Kolab/IMAP-Server zur Authentifizierung verwendet wird. Die Passwörter werden nicht auf dem Sekundärspeicher abgelegt.

Sicherstellung der Konsistenz

Bedingt durch die Architektur von Kolab können Konflikte beim Synchronisieren von Kollaborationsdaten auftreten. Diese entstehen z.B. dadurch, dass kein Transaktionskonzept existiert. Es ist somit nicht sichergestellt, dass mehrstufige Operationen atomar ausgeführt werden und es wird keine Konsistenzprüfung des Datenbestandes durchgeführt. Des Weiteren werden Primärschlüssel nicht auf Eindeutigkeit überprüft. Im Folgenden werden mögliche Konflikte diskutiert und festgelegt wie der Kolab WebService diese behandelt.

Nicht eindeutige Kolab-ID

Da die Generierung einer Kolab-ID in der Verantwortung eines Kolab-Clients liegt und in der Kolab Dokumentation zu diesem Vorgang keine Vorgaben gemacht werden, können keine Annahmen über die Art der ID getroffen werden. Ein Kolab-Client hat weiterhin, je nach den Rechten des angemeldeten Benutzers, und auch je nach Konfiguration des Benutzers, keinen Zugriff auf alle Kolab-Datensätze. Es ist einem Kolab-Client somit allgemein nicht möglich eindeutige Kolab-IDs zu garantieren. Lediglich innerhalb einer Mailbox (eines Ordners) kann ein Client versuchen die Kolab-IDs eindeutig zu halten. Aber auch dies kann nicht allgemein garantiert werden. Folgende Ursachen für eine mehrfach vorkommende Kolab-ID sind denkbar:

  • Ein anderer Kolab-Client ändert einen Datensatz. Da diese Operation nicht atomar auf dem IMAP Server durchgeführt werden kann, existieren kurzzeitig zwei Datensätze mit der selben Kolab-ID in dem selben Ordner. Tritt nun zu diesem Zeitpunkt ein Fehler im Client auf oder greift ein anderer Client zu diesem Zeitpunkt auf den Ordner zu, liegen zwei Datensätze mit der selben Kolab-ID vor.
  • Ein anderer Kolab-Client arbeitet nicht auf dem Ordner mit Datensatz A und kennt somit nicht dessen Kolab-ID. Er legt zufällig in einem anderen Ordner einen neuen Datensatz des selben Typ und mit der selben ID wie Datensatz A an.
  • Da der IMAP Server keine Historie hat kann es vorkommen, dass ein Kolab-Client einen neuen Datensatz mit einer ID, die vorher schon einmal vergeben war anlegt.
  • Zwei Kolab-Clients legen gleichzeitig einen Datensatz mit der selben ID an.

Ein Kolab-Client hat, falls er nicht ab dem Installationszeitpunkt von Kolab alle Änderungen protokolliert, keine Möglichkeit die Ursache dieses Konflikts zu erkennen. Insbesondere kann der Konflikttyp nicht identifiziert werden. Ein entsprechendes Reagieren ist somit nicht möglich.

Der Kolab WebService wird als Primärschlüssel eines Datensatzes die Kombination aus Kolab-ID und IMAP-Ordner verwenden. Hierdurch wird die Anzahl der möglichen Konflikte minimiert. Da ein IMAP-Ordner immer nur Daten eines Typs enthält, sind somit auch Datensätze verschiedenen Typs mit der selben ID erlaubt. Für jeden über den Service gelesenen und geschriebenen Datensatz wird dessen Primärschlüssel in einem DBMS protokolliert. Wird ein neuer Datensatz mit einem schon bekannten Primärschlüssel gefunden, wird der vorher bekannte Datensatz genauso behandelt als wäre er gelöscht worden.

Nicht konsistenter Datensatz

Da keine Überprüfung der von einem Kolab-Client angelegten Datensätze durchgeführt wird, kann es vorkommen, dass ein ungültiger Datensatz abgespeichert wird. So kann beim Lesen des Datensatzes, z.B. während dem Parsen des Xml-Teils, ein Fehler auftreten.

Der Kolab WebService wird im Falle eines nicht fehlerfrei interpretierbaren Datensatzes diesen genauso behandeln, als wäre er gelöscht worden.

Implementierung

Folgendes Klassendiagramm zeigt den internen Aufbau des Kolab WebServices.

KolabServiceClass.png

Das Projekt ist so angelegt, dass das Build-Ergebnis einerseits direkt als WebService in einem JBoss Server deployt und andererseits auch als Bibliothek für eine Java Applikation verwendet werden kann. Die Klasse KolabAccessor enthält dabei die zentrale Implementierung der Synchronisationsoperationen. Diese Klasse würde auch von einer Java Applikation verwendet werden, die einen Kolab Client implementieren möchte. An die selbe Klasse werden auch die Aufrufe des WebServices, die in der Klasse KolabService behandelt werden, delegiert.

Die Java Klassen für die Kollaborationstypen und ein Interface, das die oben beschriebenen Operationen des WebService enthält, werden automatisch aus der im Projekt enthaltenen WSDL-Datei generiert. Die Klasse KolabSerializer implementiert statische Operationen um das Kolab-XML-Format zu lesen und zu schreiben. Zu diesem Zweck wird die effiziente Java StaX Implementierung verwendet. Die (De)serialisierung der Java Typen für die SOAP-Anfragen und Antworten werden automatisch durch eine JAXB Implementierung durchgeführt.

Um eine Verbesserung der Performance zu erreichen, kann später die Klasse KolabCache erweitert werden, die mehrfache gleiche Zugriffe auf den IMAP-Server einsparen kann.

Datenbankschema

Der WebService verwendet eine Datenbank um Daten dauerhaft abzuspeichern. Eine Datenhaltung im WebService ist zum einen wichtig, um die Anfragen effizient beantworten zu können, ohne jedes mal die gesamgten IMAP-Folder durchsuchen zu müssen. Zum anderen ist die Datenhaltung nötig, um zu erkennen welche Datensätze gelöscht wurden, da der IMAP Server keine Historie besitzt. In der Datenbank wird die Zuordnung von einer Kolab-ID zu der Mail-ID abgespeichert. So kann bei einem Zugriff auf ein Kollaborationsobjekt der Inhalt direkt über die Mail-ID aus dem Imap Server bezogen werden. Vor jedem Zugriff auf Kollaborationsdaten muss der WebService eine Synchronisation der IDs mit dem Imap-Server durchführen, um Änderungen durch andere Kolab-Clients zu erkennen. Allerdings wird bei zu dicht aufeinander folgenden Anfragen keine erneute Synchronisation vorgenommen. Dazu wird ein Intervall konfiguriert das die minimale Zeit zwischen zwei Synchronisationen festlegt. Der Standardwert für dieses Intervall liegt bei 10 Sekunden.

Eine Beschreibung des Synchronisation vom Kolab mit der Datenbank ist dem unten stehenden Prozess Aktualisierung WebService Cache zu entnehmen.

Anforderungen an das Datenbankschema

Die Anforderungen an das Datenbankschema leiten sich direkt aus den unten beschriebenen Prozessen ab. Im wesentlichen sind dies:

  • Zugriff auf das Mapping von Folder/Kolab-ID auf die Folder/Mail-ID
  • Finden der gelöschten Objekte in einem gegebenen Zeitraum
  • Finden der Termine und Aufgaben mit Gültigkeit im konfigurierten Zeitintervall (ohne Wiederholungstermine)

Folgendes Datenbankschema wird verwendet:

KolabWsDatamodel.png

Die Tabelle kolab_item besitzt den Primärschlüssel pk und weiterhin die Schlüssel fk_folder zusammen mit kolab_id sowie fk_folder zusammen mit mail_id. Bei jedem neuen Eintrag mit einer neuen Kombination für den Schlüssel fk_folder und kolab_id wird das Attribut creation_date auf die aktuelle Systemzeit gesetzt. Bei jeder Neuanlegung, Änderung oder Löschung eines Kollaborationsdatums wird das Attribut change_date auf die aktuelle Systemzeit gesetzt. Existieren zwei E-Mails mit der selben Kolab-ID in dem selben Ordner wird die alte Zuordnung in der Tabelle überschrieben. Tritt bei der Deserialisierung der E-Mail ein Fehler auf, wird das Feld valid auf false gesetzt. Dieses Feld dient dazu ungültige Einträge leicht finden zu können. Logisch wird ein Eintrag als gelöscht angesehen, wenn die Bedingung deleted OR NOT valid erfüllt ist.

Die Attribute relevanz_date_start und relevanz_date_end sind bei Terminen und Aufgaben gefüllt. Sie werden heran gezogen um schnell entscheiden zu können wann die Daten-Objekte relevant sind (beginnen und enden). Damit können sie dann abhängig vom konfigurierten Zeitintervall synchronisiert werden oder von der Synchronisation ausgeschlossen werden.

Weiterhin existieren die Attribute change_user_id und change_device_id. Sie werden benötigt, um zu vermeiden, dass Änderungen, die ein Endgerät auf dem Server veranlasst bei der nächsten Synchronisation wieder an das Endgerät gesendet werden. Ohne diese Parameter wäre es nicht möglich zu erkennen, dass eine Änderung dem Anfragenden Endgerät schon bekannt ist.

Konfiguration

Der Kolab WebService verwendet folgende Konfigurationen die den Synchronisationsvorgang beeinflussen:

Synchronisations- Zeitraum: Für die datumsbezogenen Datensätze Termine und Aufgaben kann mittels zweier Parameter ein Zeitfenster um den aktuellen Zeitpunkt festgelegt werden. Ist das Datum eines Termins oder einer Aufgabe außerhalb dieses Intervalls, wird der Datensatz von dem WebService behandelt als wäre er nicht existent. Durch dieses Vorgehen werden veraltete Termine und Aufgaben auf den Endgeräten wieder entfernt, so dass ihre Anzahl nicht beständig ansteigt. Die Parameter heißen syncWindowPastDays und syncWindowFutureDays. Das Ablaufintervall wird benutzerspezifisch im LDAP Server gespeichert. Wenn dort keine Werte angegeben sind, wird eine Voreinstellung von 60 für beide Werte verwendet.

Termine oder Aufgaben die ein Wiederholungsfeld besitzen (recurrence) müssen dann theoretisch als nicht existent betrachtet werden, wenn das Intervall zwischen dem ersten und dem letzten Auftreten des Ereignisses mit dem definierten Intervall disjunkt ist. In der Praxis kann die dazu nötige Datumsberechnung aber zu aufwendig sein, so dass entschieden wurde, alle existierenden Wiederholungsereignisse auf dem Client zu belassen.

Ordnerauswahl zur Synchronisation: Welche Folder ein Benutzer zur Synchronisation ausgewählt hat wird durch eine private IMAP-Annotation an den entsprechenden Foldern markiert. Die Annotation hat folgenden Bezeichner: /vendor/kolab/sync-folder. Besitzt die Annotation einen Wert der den String mobile enthält, so ist der Folder in die Synchronisation einzubeziehen. Die Wahl des Wertevergleiches über einen Teilstring ist so gewählt um eine spätere Erweiterung zu ermöglichen, bei der als Wert eine Liste von Gerätetypen eingetragen werden kann.

Prozesse

Im Folgenden erfolgt eine detailliertere und technische Darstellung der wesentlichen Prozesse. Hierzu werden zunächst die Akteure für die weiter unten beschriebenen Prozesse aufgelistet.

Akteure

Akteure
Anwender Der Benutzer eines mobilen Endgeräts.
Endgerät Ein mobiles Endgerät welches die Möglichkeit bietet Kollaborationsdaten und Emails zu verwalten.
Funambol Server Der Funambol-Server wird vom Endgerät angesprochen und bearbeitet Synchronisationsanfragen.
Funambol Connector Ein Plugin im Funambol-Server, welches dazu dient mit dem Kolab WebService zu kommunizieren. Diese Komponente wird von der tarent GmbH realisiert und ist Teil dieser Spezifikation.
Kolab Benutzerverwaltung Eine Komponente die dem Benutzer eines mobilen Endgeräts ein GUI zur Konfiguration des Kolab Servers anbietet. Diese Komponente wird von der tarent GmbH realisiert und ist Teil dieser Spezifikation.
Kolab WebService Ein WebService, der dem Funambol-Connector Operationen zum Zugriff auf den Kolab-Server bietet. Diese Komponente wird von der tarent GmbH realisiert und ist Teil dieser Spezifikation.
Kolab Server Ein IMAP-Server, der als Datenspeicher für Kollaborationsdaten und Emails dient.
LDAP Server Ein LDAP-Server, der vom Funambol-Server, vom Kolab WebService, von der Kolab Benutzerverwaltung und vom Kolab Server zur Authentifizierung verwendet wird.


Prozess Authentifizierung

Authentifizierung
Beschreibung Der Prozess der Authentifizierung erfolgt bei jeder Anfrage eines Clients an die Synchronisationslösung. Da er bei allen Anfragen gleich ist, wird er hier einmal generisch beschrieben.
Beteiligte Akteure
  • Anwender
  • Endgerät
  • Funambol-Server
  • Funambol-Connector
  • Kolab WebService
  • Kolab Server
  • LDAP Server
Verwendete Prozesse keine
Auslöser Ein Anwender initiiert über sein mobiles Endgerät eine Synchronisationsanfrage.
Vorbedingungen
  • Der Anwender besitzt einen LDAP Account und hat die Benutzerdaten zur Authentifizierung in seinem Endgerät hinterlegt.
  • Im Endgerät ist die URL des Funambol-Servers konfiguriert.
Invarianten keine
Nachbedingung / Ergebnis Der Vorgang ist mit Erfolg oder mit einer Fehlermeldung abgeschlossen.
Standardablauf
  1. Der Anwender startet einen Synchronisationsvorgang auf einem Endgerät.
  2. Das Endgerät verbindet sich mit dem Funambol-Server (SyncML über HTTP/S) und sendet Benutzername und Passwort mit (Http Basic Auth).
  3. Der Funambol Server (Login Komponente) überprüft die Benutzerdaten mit Hilfe des LDAP Servers (LDAP-bind).
  4. Der Funambol Server (Login Komponente) überprüft die Gruppenzugehörigkeit des Benutzers zu der Gruppe sync.
  5. Der Funambol Server ruft den Funambol Connector auf.
  6. Der Kolab-Connector ruft mehrfach den Kolab WebService auf. Dabei werden jedesmal die folgenden Schritte durchlaufen.
    1. Der Kolab-Connector ruft den WebService auf und überträgt die Benutzerdaten in der SOAP Nachricht (Username-Token nach dem WebService Security Standard [9])
    2. Der WebService überprüft die Benutzerdaten mit Hilfe des LDAP Servers (LDAP-bind).
    3. Der WebService überprüft die Gruppenzugehörigkeit des Benutzers zu der Gruppe sync.
    4. Bei erfolgreicher Prüfung gewärt er den Zugriff
    5. Er öffnet eine IMAP-Verbindung zum Kolab Server. Dabei werden der Benutzername und das Passwort für den Verbindungsaufbau verwendet.
    6. Der Kolab Server überprüft die Benutzerdaten mit Hilfe des LDAP Servers.
Alternative Ablaufschritte
  • Schlägt die Authentifizierung im Funambol Server fehl, wird ein Fehler über den entsprechenden HTTP-Statuscode (401) an das Endgerät gesendet.
  • Tritt bei der Synchronisation ein Fehler auf (z.B. HTTP Status 50x bei serverseitigem Fehler), zeigt das Endgerät einen Fehler an.
  • Tritt im Kolab WebService ein Fehler auf, so wird dieser über eine SOAP-Exception an den Funambol-Server singnalisiert und anschließend über einen entsprechenden HTTP-Statuscode an das Endgerät signalisiert.
Hinweise


Prozess Initiale Synchronisation

Sequenzdiagramm initialer sync.png

Initiale Synchronisation
Beschreibung Erstmalige Synchronisation der Kollaborationsdaten eines Endgerätes mit den Daten des Kolab Servers.
Beteiligte Akteure
  • Anwender
  • Endgerät
  • Funambol-Server
  • Funambol-Connector
  • Kolab WebService
  • Kolab Server
  • LDAP Server
Verwendete Prozesse Authentifizierung, Aktualisierung WebService Cache
Auslöser Ein Anwender startet erstmalig einen Synchronisationsvorgang seiner Kollaborationsdaten auf einem Endgerät.
Vorbedingungen
  • Der Anwender besitzt einen LDAP Account und hat die Benutzerdaten zur Authentifizierung in seinem Endgerät hinterlegt.
  • Im Endgerät ist die URL des Funambol-Servers konfiguriert.
Invarianten
  • Emaildaten
  • Konfigurationsdaten
Nachbedingung / Ergebnis Der Datenbestand des Endgerätes und des Kolab Servers sind angeglichen.
Standardablauf
  1. Der Anwender startet einen Synchronisationsvorgang für einen Typ von Kollaborations Objekten (z.B. Kontakte) auf einem Endgerät.
  2. Das Endgerät verbindet sich mit dem Funambol-Server und addressiert die entsprechende Datenquelle.
  3. Der Funambol Server initialisiert den Kolab-Connector.
  4. Ermittlung der Einträge des Servers
    1. Der Funambol Server erfragt vom Kolab-Connector die Liste aller IDs der Daten-Objekte des angefragten Typs.
    2. Der Koalb-Connector erfragt vom WebService die Synchronisations-Folder des angefragten Typs.
    3. Der WebService ermittelt die Folder des Benutzers und liefert davon jene zurück, die für den entsprechenden Typ vorgesehenen und für die Synchronisation markiert sind.
    4. Für jeden zurück gelieferten Folder erfragt der Koalb-Connector beim WebService die IDs der Daten-Objekte des Folders.
    5. Der WebService aktualisiert bei Bedarf (abhängig von der vergangenen Zeit zur letzten Aktualisierung) die Caching-Struktur in seiner Datenbank (Prozess: Aktualisierung WebService Cache)
    6. Anhand der Cache-Struktur liefert der WebService die Liste aller Keys in dem angefragten Folder zurück. Dies geschieht anhand einer Datenbank-Anfrage wie beispielsweise SELECT kolab_id FROM TABLE koalb_item WHERE fk_folder = [übergebene Folder ID] AND valid = true AND deleted = false;.
    7. Der Kolab-Connector bildet aus den zurück gelieferten Keys und der Folder-ID eine Folder-übergreifend eindeutige ID für das Daten-Objekt und sammelt alle zurück gelieferten IDs in einer Liste, die er anschließend an den Funambol Server zurück liefert.
  5. Bezug der Datenobjekte des Servers
    1. Der Funambol Server fragt nun für jede ID in der Liste das entsprechende Daten-Objekt (als VCard-Java Objekt) vom Kolab-Connector an.
    2. Für jedes angefragte Daten-Objekt fragt der Kolab-Connector den WebService an. Hierbei löst er die zusammengesetzte ID wieder in den Foldernamen und die Kolab-ID auf.
    3. Der WebService löst die Kolab-ID anhand seiner Caching Struktur auf, um die IMAP-Id des Datenobjektes zu bekommen. Dies geschieht anhand einer Datenbank-Anfrage wie beispielsweise SELECT mail_id FROM TABLE koalb_item WHERE fk_folder = [übergebene Folder ID] AND kolab_id = [übergebene Kolab ID];.
    4. Der WebService bezieht das Kolab-Daten-Objekt über die IMAP Schnittstelle des Kolab-Servers, prüft das Schema, und liefert es zurück.
    5. Der Kolab-Connector parst das zurück gelieferte Kolab-Daten-Objekt und erstellt ein entsprechendes VCard-Java Objekt daraus, welches er an den Funambol Server zurück liefert.
    6. Der Funambol Server sammelt die Daten-Objekte und überträgt sie an das Endgerät.
  6. Speicherung der Datenobjekte des Clients
    1. Für alle Daten-Objekte, die auf dem Endgerät vorhanden sind veranlasst der Funambol Server den Kolab-Connector diese zu speichern.
    2. Der Kolab-Connector erstellt hierzu aus dem übergebenen VCard-Java Objekt ein Kolab-Daten-Objekt und sendet dieses zum Speichern (im Standardordner des entsprechenden Datentyps) an den WebService.
    3. Der WebService prüft das Schema des übergbenen Daten-Objektes, generiert eine Kolab-ID für das Daten-Objekt und speichert dieses über die IMAP Schnittstelle im Kolab Server.
    4. Der WebService aktualisiert seine Caching-Struktur anhand der resultierenden IDs.
    5. Der WebService liefert die Kolab-ID an den Kolab-Connector zurück.
    6. Der Kolab-Connector vervollständigt das VCard-Java Objekt mit der ID (bestehend aus Folder-ID und Kolab-ID) und liefert es an den Funambol-Connector zurück.
  7. Der Funambol Server speichert den Zeitpunkt der Synchronisation für dieses Endgerät
  8. Abschließend signalisiert der Funambol Server dem Kolab-Connector das Ende der Synchronisation
Alternative Ablaufschritte
  • Tritt bei der Synchronisation ein Fehler auf (z.B. HTTP Status 50x bei serverseitigem Fehler), zeigt das Endgerät einen Fehler an.
  • Tritt im Kolab WebService ein Fehler auf, so wird dieser über eine SOAP-Exception an den Funambol-Server signalisiert und anschließend über einen entsprechenden HTTP-Statuscode an das Endgerät signalisiert.
Hinweise
  • Kommt es zu einem Konflikt, weil ein Datensatz mit der selben ID auf dem Endgerät und auf dem Kolab-Server existiert und beide unterschiedlichen Inhalt haben, enthält der Datensatz auf dem Endgerät und dem Kolab Server nach der Synchronisation den Stand des Kolab Servers.



Prozess Inkrementelle Synchronisation

Sequenzdiagramm inkrementeller sync.png

Inkrementelle Synchronisation
Beschreibung Synchronisation der Kollaborationsdaten eines Endgerätes mit den Daten des Kolab-Servers nach einer vormaligen (initialen oder inkrementellen) Synchronisation.
Beteiligte Akteure
  • Anwender
  • Endgerät
  • Funambol-Server
  • Funambol-Connector
  • Kolab WebService
  • Kolab Server
  • LDAP Server
Verwendete Prozesse Authentifizierung, Aktualisierung WebService Cache
Auslöser Ein Anwender startet einen Synchronisationsvorgang seiner Kollaborationsdaten auf einem Endgerät.
Vorbedingungen
  • Der Anwender besitzt einen LDAP Account und hat die Benutzerdaten zur Authentifizierung in seinem Endgerät hinterlegt.
  • Im Endgerät ist die URL des Funambol-Servers abgespeichert.
  • Der Anwender hat schon mindestens eine Synchronisation des Endgerätes mit dem Kolab Server durchgeführt.
Invarianten
  • Emaildaten
  • Konfigurationsdaten
Nachbedingung / Ergebnis Der Datenbestand des Endgerätes und des Kolab Servers sind angeglichen.
Standardablauf
  1. Der Anwender startet einen Synchronisationsvorgang für einen Typ von Kollaborations Objekten (z.B. Kontakte) auf einem Endgerät.
  2. Das Endgerät verbindet sich mit dem Funambol-Server und addressiert die entsprechende Datenquelle.
  3. Der Funambol Server initialisiert den Kolab-Connector.
    1. Der Koalb-Connector erfragt vom WebService die Synchronisations-Folder des angefragten Typs.
    2. Der WebService ermittelt die Folder des Benutzers und liefert davon jene zurück, die für den entsprechenden Typ vorgesehen und für die Synchronisation markiert sind.
  4. Ermittlung der neuen Einträge des Servers
    1. Der Funambol Server erfragt vom Kolab-Connector die Liste aller IDs der neuen Daten-Objekte seit der letzten Synchronisation des angefragten Typs.
    2. Für jeden Synchronisations-Folder erfragt der Kolab-Connector beim WebService die IDs der neuen Daten-Objekte des Folders und übergibt dabei den Zeitpunkt der letzten Synchronisation.
    3. Der WebService aktualisiert bei Bedarf (abhängig von der vergangenen Zeit zur letzten Aktualisierung) die Caching-Struktur in seiner Datenbank (Prozess: Aktualisierung WebService Cache)
    4. Anhand der Cache-Struktur liefert der WebService die entsprechende Liste der Keys in dem angefragten Folder zurück. Dies geschieht anhand einer Datenbank-Anfrage wie beispielsweise SELECT kolab_id FROM TABLE koalb_item WHERE fk_folder = [übergebene Folder ID] AND valid = true AND deleted = false AND creation_date >= [übergebenes Datum];.
    5. Der Kolab-Connector bildet aus den zurück gelieferten Keys und der Folder-ID eine Folder-übergreifend eindeutige ID für das Daten-Objekt und sammelt alle zurück gelieferten IDs in einer Liste, die er anschließend an den Funambol Server zurück liefert.
  5. Ermittlung der geänderten Einträge des Servers
    1. Der Funambol Server erfragt vom Kolab-Connector die Liste aller IDs der geänderten Daten-Objekte seit der letzten Synchronisation des angefragten Typs.
    2. Für jeden Synchronisations-Folder erfragt der Kolab-Connector beim WebService die IDs der geänderten Daten-Objekte des Folders und übergibt dabei den Zeitpunkt der letzten Synchronisation.
    3. Der WebService aktualisiert bei Bedarf (abhängig von der vergangenen Zeit zur letzten Aktualisierung) die Caching-Struktur in seiner Datenbank (Prozess: Aktualisierung WebService Cache)
    4. Anhand der Cache-Struktur liefert der WebService die entsprechende Liste der Keys in dem angefragten Folder zurück. Dies geschieht anhand einer Datenbank-Anfrage wie beispielsweise SELECT kolab_id FROM TABLE koalb_item WHERE fk_folder = [übergebene Folder ID] AND valid = true AND deleted = false AND change_date >= [übergebenes Datum] AND NOT (creation = date).
    5. Der Kolab-Connector bildet aus den zurück gelieferten Keys und der Folder-ID eine Folder-übergreifend eindeutige ID für das Daten-Objekt und sammelt alle zurück gelieferten IDs in einer Liste, die er anschließend an den Funambol Server zurück liefert.
  6. Ermittlung der gelöschten Einträge des Servers
    1. Der Funambol Server erfragt vom Kolab-Connector die Liste aller IDs der gelöschten Daten-Objekte seit der letzten Synchronisation des angefragten Typs.
    2. Für jeden Synchronisations-Folder erfragt der Kolab-Connector beim WebService die IDs der gelöschten Daten-Objekte des Folders und übergibt dabei den Zeitpunkt der letzten Synchronisation.
    3. Der WebService aktualisiert bei Bedarf (abhängig von der vergangenen Zeit zur letzten Aktualisierung) die Caching-Struktur in seiner Datenbank (Prozess: Aktualisierung WebService Cache)
    4. Anhand der Cache-Struktur liefert der WebService die entsprechende Liste der Keys in dem angefragten Folder zurück. Dies geschieht anhand einer Datenbank-Anfrage wie beispielsweise SELECT kolab_id FROM TABLE koalb_item WHERE fk_folder = [übergebene Folder ID] AND (valid = false OR deleted = true) AND change_date >= [übergebenes Datum].
    5. Der Kolab-Connector bildet aus den zurück gelieferten Keys und der Folder-ID eine Folder-übergreifend eindeutige ID für das Daten-Objekt und sammelt alle zurück gelieferten IDs in einer Liste, die er anschließend an den Funambol Server zurück liefert.
  7. Bezug der Datenobjekte des Servers
    1. Der Funambol Server ermittelt nun aus den vorher erstellten Listen die relevanten Datenobjekte für den Client und fragt für jede ID in der Liste das entsprechende Daten-Objekt (als VCard-Java Objekt) vom Kolab-Connector an.
    2. Für jedes angefragte Daten-Objekt fragt der Kolab-Connector den WebService an. Hierbei löst er die zusammengesetzte ID wieder in den Foldernamen und die Kolab-ID auf.
    3. Der WebService löst die Kolab-ID anhand seiner Caching Struktur auf um die IMAP-ID des Datenobjektes zu bekommen. Dies geschieht anhand einer Datenbank-Anfrage wie beispielsweise SELECT mail_id FROM TABLE koalb_item WHERE fk_folder = <übergebene Folder ID> AND kolab_id = <übergebene Kolab ID>;.
    4. Der WebService bezieht das Kolab-Daten-Objekt über die IMAP Schnittstelle des Kolab-Servers, prüft das Schema, und liefert es zurück.
    5. Der Kolab-Connector parst das zurück gelieferte Kolab-Daten-Objekt und erstellt ein entsprechendes VCard-Java Objekt daraus, welches er an den Funambol Server zurück liefert.
    6. Der Funambol Server sammelt die Daten-Objekte und überträgt sie an das Endgerät.
  8. Speicherung der neuen Datenobjekte des Clients
    1. Für alle Daten-Objekte, die auf dem Endgerät neu angelegt wurden veranlasst der Funambol Server den Kolab-Connector diese zu speichern.
    2. Der Kolab-Connector erstellt hierzu aus dem übergebenen VCard-Java Objekt ein Kolab-Daten-Objekt und sendet dieses zum Speichern (im Standardordner des entsprechenden Datentyps) an den WebService.
    3. Der WebService prüft das Schema des übergbenen Daten-Objektes, generiert eine Kolab-ID für das Daten-Objekt und speichert dieses über die IMAP Schnittstelle im Kolab Server.
    4. Der WebService aktualisiert seine Caching-Struktur anhand der resultierenden IDs.
    5. Der WebService liefert die Kolab-ID an den Kolab-Connector zurück.
    6. Der Kolab-Connector vervollständigt das VCard-Java Objekt mit der ID (bestehend aus Folder-ID und Kolab-ID) und liefert es an den Funambol-Connector zurück.
  9. Speicherung der geänderten Datenobjekte des Clients
    1. Für alle Daten-Objekte, die auf dem Endgerät geändert wurden veranlasst der Funambol Server den Kolab-Connector diese zu speichern.
    2. Der Kolab-Connector fordert nun das Kolab-Daten-Objekt vom WebService an und ergänzt die entsprechenden Einträge anhand des übergebenen VCard-Java Objektes.
    3. Das ergänzte Kolab-Daten-Objekt sendet er der Kolab-Connector zum Speichern in dem Ursprungsordner an den WebService.
    4. Der WebService prüft das Schema des übergebenen Daten-Objektes und speichert dieses über die IMAP Schnittstelle im Kolab Server.
    5. Der WebService aktualisiert seine Caching-Struktur.
  10. Löschung der gelöschten Datenobjekte des Clients
    1. Für alle Daten-Objekte, die auf dem Endgerät gelöscht wurden veranlasst der Funambol Server den Kolab-Connector diese ebenfalls zu löschen.
    2. Der Kolab-Connector veranlasst den WebService das entsprechende Objekt zu löschen. Hierbei löst er die zusammengesetze ID wieder in den Foldernamen und die Kolab-ID auf.
    3. Der WebService löst die Kolab-ID anhand seiner Caching Struktur auf um die IMAP-ID des Datenobjektes zu bekommen. Dies geschieht anhand einer Datenbank-Anfrage wie beispielsweise SELECT mail_id FROM TABLE koalb_item WHERE fk_folder = <übergebene Folder ID> AND kolab_id = <übergebene Kolab ID>;.
    4. Der WebService löscht das Daten-Objekt über die IMAP-Schnittstelle
    5. Der WebService aktualisiert seine Caching-Struktur.
  11. Abschließend sendet der Funambol Server die Änderungen an den Client zurück und signalisiert dem Kolab-Connector das Ende der Synchronisation
Alternative Ablaufschritte
  • Schlägt die Authentifizierung im Funambol Server fehl, wird ein Fehler über den entsprechenden HTTP-Statuscode (401) an das Endgerät gesendet.
  • Tritt bei der Synchronisation ein Fehler auf (z.B. HTTP Status 50x bei serverseitigem Fehler), zeigt das Endgerät einen Fehler an.
  • Tritt im Kolab WebService ein Fehler auf, so wird dieser über eine SOAP-Exception an den Funambol-Server singnalisiert und anschließend über einen entsprechenden HTTP-Statuscode an das Endgerät signalisiert.
Hinweise
  • Kommt es zu einem Konflikt, weil ein Datensatz mit der selben ID auf dem Endgerät und auf dem Kolab-Server existiert und beide unterschiedlichen Inhalt haben oder einer von Beiden gelöscht und der andere geändert wurde, so enthält der Datensatz auf dem Endgerät und dem Kolab Server nach der Synchronisation den Stand des Kolab Servers.


Prozess Aktualisierung WebService Cache

Aktualisierung WebService Cache
Beschreibung Aktualisierung der Cache-Struktur des WebServices in Form einer relationalen Datenbank.
Beteiligte Akteure
  • Kolab WebService
  • Kolab Server
Verwendete Prozesse keine
Auslöser Im WebService wird eine Methode aufgerufen, die auf den Cache zugreift und die letzte aktualisierung liegt zu lange zurück.
Vorbedingungen
  • Die Folder-ID ist bekannt
Invarianten
Nachbedingung / Ergebnis Die Einträge der Tabelle kolab_item für den entsprechenden Folder entsprechen den Einträgen die im IMAP-Folder zum Zeitpunkt des Starts der Aktualisierung vorhanden sind.
Standardablauf
  1. Der WebServices bezieht die Menge aller IMAP-IDs im gegebenen Folder vom Kolab-Server (server-ids).
  2. Der WebServices ermittelt die Menge aller IMAP-IDs die in der Tabelle kolab_item, nicht jedoch in den Server-IDs enthalten sind und markiert die korrespondierenden Einträge als gelöscht.
  3. Der WebService bezieht die Menge aller IMAP-IDs mit einem Invalid-Flag und markiert die korrespondierenden Einträge der Tabelle kolab_item als invalid.
  4. Der WebService ermittelt die Menge aller IDs aus den Server-IDs, die größer sind als die höchste IMAP-ID in der Tabelle kolab_item (für den entsprechenden Folder) (new-ids).
  5. Für jede ID aus new-ids bezieht der WebService das Kolab-Daten-Objekt vom IMAP-Server und parst dieses, um die Kolab-ID zu ermitteln.
  6. Wenn die Kolab-ID für den entsprechenden Folder neu ist, legt er einen neuen Eintrag in der Tabelle kolab_item für dieses Datenobjekt an. creation_date und change_date werden hierbei auf den aktuellen Zeitpunkt gesetzt.
  7. Wenn die Kolab-ID bereits für den entsprechenden Folder existiert aktualisiert der WebService das Änderungsdatum change_date in der Tabelle kolab_item indem er es auf das aktuelle Datum setzt. Außerdem aktualisiert er die IMAP-id in dem Feld mail_id
Alternative Ablaufschritte
Hinweise Die Implementierung des Prozesses muss SingleThreadded pro Imap-Folder ausgeführt werden.


Anhang

A1 - Funambol Connector Schnittstelle

package org.evolvis.bsi.funambol; 

import java.sql.Timestamp;

import com.funambol.framework.engine.SyncItem;
import com.funambol.framework.engine.SyncItemKey;
import com.funambol.framework.engine.source.AbstractSyncSource;
import com.funambol.framework.engine.source.SyncContext;
import com.funambol.framework.engine.source.SyncSource;
import com.funambol.framework.engine.source.SyncSourceException;
import com.funambol.framework.tools.beans.BeanInitializationException;
import com.funambol.framework.tools.beans.LazyInitBean;

public class KolabSyncSource extends AbstractSyncSource
		implements SyncSource, LazyInitBean {

	public SyncItem addSyncItem(SyncItem item) throws SyncSourceException {}

	public SyncItemKey[] getAllSyncItemKeys() throws SyncSourceException {}

	public SyncItemKey[] getDeletedSyncItemKeys(Timestamp sinceTs,
			Timestamp untilTs) throws SyncSourceException {}

	public SyncItemKey[] getNewSyncItemKeys(Timestamp sinceTs,
			Timestamp untilTs) throws SyncSourceException {}

	public SyncItem getSyncItemFromId(SyncItemKey key)
			throws SyncSourceException {}

	public SyncItemKey[] getSyncItemKeysFromTwin(SyncItem item)
			throws SyncSourceException {}

	public void removeSyncItem(SyncItemKey key, Timestamp time,
			boolean softDelete) throws SyncSourceException {}

	public void setOperationStatus(String operationName, int status,
			SyncItemKey[] keys) {}

	public SyncItem updateSyncItem(SyncItem item) throws SyncSourceException {}

	public SyncItemKey[] getUpdatedSyncItemKeys(Timestamp sinceTs,
			Timestamp untilTs) throws SyncSourceException {}

	public void init() throws BeanInitializationException {}

	// not abstract operations

	public void beginSync(SyncContext syncContext) throws SyncSourceException {
		super.beginSync(syncContext);
	}

	public void commitSync() throws SyncSourceException {
		super.commitSync();
	}

	public void endSync() throws SyncSourceException {
		super.endSync();
	}
 
}

A2 - Kolab WebService WSDL Schnittstelle

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="KolabService" targetNamespace="http://service.kolab.bsi.evolvis.org/"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://service.kolab.bsi.evolvis.org/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
 <wsdl:types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://service.kolab.bsi.evolvis.org/"
    attributeFormDefault="unqualified" elementFormDefault="unqualified" targetNamespace="http://service.kolab.bsi.evolvis.org/">
   <xs:complexType name="folder">
       <xs:sequence>
           <xs:element minOccurs="0" name="name" type="xs:string"/>
           <xs:element minOccurs="0" name="parent" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:complexType name="collaborationItem">
       <xs:sequence>
           <xs:element minOccurs="0" name="uid" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:complexType name="incidence">
       <xs:complexContent>
           <xs:extension base="tns:collaborationItem">
               <xs:sequence>
                   <xs:element minOccurs="0" name="summary" type="xs:string"/>
                   <xs:element minOccurs="0" name="location" type="xs:string"/>
                   <xs:element minOccurs="0" name="organizerDisplayName" type="xs:string"/>
                   <xs:element minOccurs="0" name="organizerSmtpAddress" type="xs:string"/>
                   <xs:element minOccurs="0" name="startDate" type="xs:dateTime"/>
                   <xs:element minOccurs="0" name="alarm" type="xs:int"/>
                   <xs:element minOccurs="0" name="recurrence" type="tns:recurrence"/>
                   <xs:element maxOccurs="unbounded" minOccurs="0" name="attendeeList" nillable="true" type="tns:attendee"/>
               </xs:sequence>
           </xs:extension>
       </xs:complexContent>
   </xs:complexType>
   <xs:complexType name="recurrence">
       <xs:sequence>
           <xs:element minOccurs="0" name="cycle" type="xs:string"/>
           <xs:element minOccurs="0" name="interval" type="xs:int"/>
           <xs:element minOccurs="0" name="month" type="xs:int"/>
           <xs:element minOccurs="0" name="week" type="xs:int"/>
           <xs:element minOccurs="0" name="yearDay" type="xs:int"/>
           <xs:element minOccurs="0" name="monthDay" type="xs:int"/>
           <xs:element minOccurs="0" name="weekDays" type="xs:int"/>
           <xs:element minOccurs="0" name="rangeNumber" type="xs:int"/>
           <xs:element minOccurs="0" name="rangeDate" type="xs:dateTime"/>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="exclusionList" nillable="true" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:complexType name="attendee">
       <xs:sequence>
           <xs:element minOccurs="0" name="displayName" type="xs:string"/>
           <xs:element minOccurs="0" name="smtpAddress" type="xs:string"/>
           <xs:element minOccurs="0" name="status" type="xs:string"/>
           <xs:element minOccurs="0" name="requestResponse" type="xs:boolean"/>
           <xs:element minOccurs="0" name="role" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:complexType name="event">
       <xs:complexContent>
           <xs:extension base="tns:incidence">
               <xs:sequence>
                   <xs:element minOccurs="0" name="showTimeAs" type="xs:string"/>
                   <xs:element minOccurs="0" name="colorLabel" type="xs:string"/>
                   <xs:element minOccurs="0" name="endDate" type="xs:dateTime"/>
               </xs:sequence>
           </xs:extension>
       </xs:complexContent>
   </xs:complexType>
   <xs:complexType name="task">
       <xs:complexContent>
           <xs:extension base="tns:incidence">
               <xs:sequence>
                   <xs:element minOccurs="0" name="priority" type="xs:int"/>
                   <xs:element minOccurs="0" name="completed" type="xs:int"/>
                   <xs:element minOccurs="0" name="status" type="xs:string"/>
                   <xs:element minOccurs="0" name="dueDate" type="xs:dateTime"/>
                   <xs:element minOccurs="0" name="parent" type="xs:string"/>
               </xs:sequence>
           </xs:extension>
       </xs:complexContent>
   </xs:complexType>
   <xs:complexType name="contact">
       <xs:complexContent>
           <xs:extension base="tns:collaborationItem">
               <xs:sequence>
                   <xs:element minOccurs="0" name="givenName" type="xs:string"/>
                   <xs:element minOccurs="0" name="middleNames" type="xs:string"/>
                   <xs:element minOccurs="0" name="lastName" type="xs:string"/>
                   <xs:element minOccurs="0" name="fullName" type="xs:string"/>
                   <xs:element minOccurs="0" name="initials" type="xs:string"/>
                   <xs:element minOccurs="0" name="prefix" type="xs:string"/>
                   <xs:element minOccurs="0" name="suffix" type="xs:string"/>
                   <xs:element minOccurs="0" name="freeBusyUrl" type="xs:string"/>
                   <xs:element minOccurs="0" name="organization" type="xs:string"/>
                   <xs:element minOccurs="0" name="webPage" type="xs:string"/>
                   <xs:element minOccurs="0" name="imAddress" type="xs:string"/>
                   <xs:element minOccurs="0" name="department" type="xs:string"/>
                   <xs:element minOccurs="0" name="officeLocation" type="xs:string"/>
                   <xs:element minOccurs="0" name="profession" type="xs:string"/>
                   <xs:element minOccurs="0" name="jobTitle" type="xs:string"/>
                   <xs:element minOccurs="0" name="managerName" type="xs:string"/>
                   <xs:element minOccurs="0" name="assistant" type="xs:string"/>
                   <xs:element minOccurs="0" name="nickName" type="xs:string"/>
                   <xs:element minOccurs="0" name="spouseName" type="xs:string"/>
                   <xs:element minOccurs="0" name="birthday" type="xs:dateTime"/>
                   <xs:element minOccurs="0" name="anniversary" type="xs:dateTime"/>
                   <xs:element minOccurs="0" name="picture" type="xs:base64Binary"/>
                   <xs:element minOccurs="0" name="children" type="xs:string"/>
                   <xs:element minOccurs="0" name="gender" type="xs:string"/>
                   <xs:element minOccurs="0" name="language" type="xs:string"/>
                   <xs:element maxOccurs="unbounded" minOccurs="0" name="phoneList" nillable="true" type="tns:phone"/>
                   <xs:element maxOccurs="unbounded" minOccurs="0" name="emailList" nillable="true" type="tns:email"/>
                   <xs:element maxOccurs="unbounded" minOccurs="0" name="addressList" nillable="true" type="tns:address"/>
                   <xs:element minOccurs="0" name="preferredAddress" type="xs:string"/>
                   <xs:element minOccurs="0" name="latitude" type="xs:double"/>
                   <xs:element minOccurs="0" name="longitude" type="xs:double"/>
               </xs:sequence>
           </xs:extension>
       </xs:complexContent>
   </xs:complexType>
   <xs:complexType name="phone">
       <xs:sequence>
           <xs:element minOccurs="0" name="type" type="xs:string"/>
           <xs:element minOccurs="0" name="number" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:complexType name="email">
       <xs:sequence>
           <xs:element minOccurs="0" name="displayName" type="xs:string"/>
           <xs:element minOccurs="0" name="smtpAddress" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:complexType name="address">
       <xs:sequence>
           <xs:element minOccurs="0" name="type" type="xs:string"/>
           <xs:element minOccurs="0" name="street" type="xs:string"/>
           <xs:element minOccurs="0" name="locality" type="xs:string"/>
           <xs:element minOccurs="0" name="region" type="xs:string"/>
           <xs:element minOccurs="0" name="postalCode" type="xs:string"/>
           <xs:element minOccurs="0" name="country" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:complexType name="note">
       <xs:complexContent>
           <xs:extension base="tns:collaborationItem">
               <xs:sequence>
                   <xs:element minOccurs="0" name="backgroundColor" type="xs:string"/>
                   <xs:element minOccurs="0" name="foregroundColor" type="xs:string"/>
                   <xs:element minOccurs="0" name="summary" type="xs:string"/>
               </xs:sequence>
           </xs:extension>
       </xs:complexContent>
   </xs:complexType>
   <xs:element name="getNewTaskIds" type="tns:getNewTaskIds"/>
   <xs:complexType name="getNewTaskIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNewTaskIdsResponse" type="tns:getNewTaskIdsResponse"/>
   <xs:complexType name="getNewTaskIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getEvent" type="tns:getEvent"/>
   <xs:complexType name="getEvent">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="id" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getEventResponse" type="tns:getEventResponse"/>
   <xs:complexType name="getEventResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="tns:event"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="updateTask" type="tns:updateTask"/>
   <xs:complexType name="updateTask">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="task" type="tns:task"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="updateTaskResponse" type="tns:updateTaskResponse"/>
   <xs:complexType name="updateTaskResponse">
       <xs:sequence>
           <xs:element name="return" type="xs:boolean"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getEventFolders" type="tns:getEventFolders"/>
   <xs:complexType name="getEventFolders">
       <xs:sequence/>
   </xs:complexType>
   <xs:element name="getEventFoldersResponse" type="tns:getEventFoldersResponse"/>
   <xs:complexType name="getEventFoldersResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="addEvent" type="tns:addEvent"/>
   <xs:complexType name="addEvent">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="event" type="tns:event"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="addEventResponse" type="tns:addEventResponse"/>
   <xs:complexType name="addEventResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getRemovedNoteIds" type="tns:getRemovedNoteIds"/>
   <xs:complexType name="getRemovedNoteIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getRemovedNoteIdsResponse" type="tns:getRemovedNoteIdsResponse"/>
   <xs:complexType name="getRemovedNoteIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="updateEvent" type="tns:updateEvent"/>
   <xs:complexType name="updateEvent">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="event" type="tns:event"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="updateEventResponse" type="tns:updateEventResponse"/>
   <xs:complexType name="updateEventResponse">
       <xs:sequence>
           <xs:element name="return" type="xs:boolean"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getTask" type="tns:getTask"/>
   <xs:complexType name="getTask">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="id" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getTaskResponse" type="tns:getTaskResponse"/>
   <xs:complexType name="getTaskResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="tns:task"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getEventIds" type="tns:getEventIds"/>
   <xs:complexType name="getEventIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getEventIdsResponse" type="tns:getEventIdsResponse"/>
   <xs:complexType name="getEventIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getContactFolders" type="tns:getContactFolders"/>
   <xs:complexType name="getContactFolders">
       <xs:sequence/>
   </xs:complexType>
   <xs:element name="getContactFoldersResponse" type="tns:getContactFoldersResponse"/>
   <xs:complexType name="getContactFoldersResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="removeContact" type="tns:removeContact"/>
   <xs:complexType name="removeContact">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="id" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="removeContactResponse" type="tns:removeContactResponse"/>
   <xs:complexType name="removeContactResponse">
       <xs:sequence>
           <xs:element name="return" type="xs:boolean"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getRemovedEventIds" type="tns:getRemovedEventIds"/>
   <xs:complexType name="getRemovedEventIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getRemovedEventIdsResponse" type="tns:getRemovedEventIdsResponse"/>
   <xs:complexType name="getRemovedEventIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getCurrentTime" type="tns:getCurrentTime"/>
   <xs:complexType name="getCurrentTime">
       <xs:sequence/>
   </xs:complexType>
   <xs:element name="getCurrentTimeResponse" type="tns:getCurrentTimeResponse"/>
   <xs:complexType name="getCurrentTimeResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="addContact" type="tns:addContact"/>
   <xs:complexType name="addContact">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="contact" type="tns:contact"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="addContactResponse" type="tns:addContactResponse"/>
   <xs:complexType name="addContactResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getRemovedTaskIds" type="tns:getRemovedTaskIds"/>
   <xs:complexType name="getRemovedTaskIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getRemovedTaskIdsResponse" type="tns:getRemovedTaskIdsResponse"/>
   <xs:complexType name="getRemovedTaskIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getContact" type="tns:getContact"/>
   <xs:complexType name="getContact">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="id" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getContactResponse" type="tns:getContactResponse"/>
   <xs:complexType name="getContactResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="tns:contact"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getTaskIds" type="tns:getTaskIds"/>
   <xs:complexType name="getTaskIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getTaskIdsResponse" type="tns:getTaskIdsResponse"/>
   <xs:complexType name="getTaskIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getUpdatedEventIds" type="tns:getUpdatedEventIds"/>
   <xs:complexType name="getUpdatedEventIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getUpdatedEventIdsResponse" type="tns:getUpdatedEventIdsResponse"/>
   <xs:complexType name="getUpdatedEventIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="addNote" type="tns:addNote"/>
   <xs:complexType name="addNote">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="note" type="tns:note"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="addNoteResponse" type="tns:addNoteResponse"/>
   <xs:complexType name="addNoteResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNoteIds" type="tns:getNoteIds"/>
   <xs:complexType name="getNoteIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNoteIdsResponse" type="tns:getNoteIdsResponse"/>
   <xs:complexType name="getNoteIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getRemovedContactIds" type="tns:getRemovedContactIds"/>
   <xs:complexType name="getRemovedContactIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getRemovedContactIdsResponse" type="tns:getRemovedContactIdsResponse"/>
   <xs:complexType name="getRemovedContactIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNoteFolders" type="tns:getNoteFolders"/>
   <xs:complexType name="getNoteFolders">
       <xs:sequence/>
   </xs:complexType>
   <xs:element name="getNoteFoldersResponse" type="tns:getNoteFoldersResponse"/>
   <xs:complexType name="getNoteFoldersResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getContactIds" type="tns:getContactIds"/>
   <xs:complexType name="getContactIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getContactIdsResponse" type="tns:getContactIdsResponse"/>
   <xs:complexType name="getContactIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="removeEvent" type="tns:removeEvent"/>
   <xs:complexType name="removeEvent">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="id" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="removeEventResponse" type="tns:removeEventResponse"/>
   <xs:complexType name="removeEventResponse">
       <xs:sequence>
           <xs:element name="return" type="xs:boolean"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getUpdatedTaskIds" type="tns:getUpdatedTaskIds"/>
   <xs:complexType name="getUpdatedTaskIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getUpdatedTaskIdsResponse" type="tns:getUpdatedTaskIdsResponse"/>
   <xs:complexType name="getUpdatedTaskIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getUpdatedNoteIds" type="tns:getUpdatedNoteIds"/>
   <xs:complexType name="getUpdatedNoteIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getUpdatedNoteIdsResponse" type="tns:getUpdatedNoteIdsResponse"/>
   <xs:complexType name="getUpdatedNoteIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="removeNote" type="tns:removeNote"/>
   <xs:complexType name="removeNote">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="id" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="removeNoteResponse" type="tns:removeNoteResponse"/>
   <xs:complexType name="removeNoteResponse">
       <xs:sequence>
           <xs:element name="return" type="xs:boolean"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="removeTask" type="tns:removeTask"/>
   <xs:complexType name="removeTask">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="id" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="removeTaskResponse" type="tns:removeTaskResponse"/>
   <xs:complexType name="removeTaskResponse">
       <xs:sequence>
           <xs:element name="return" type="xs:boolean"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNote" type="tns:getNote"/>
   <xs:complexType name="getNote">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="id" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNoteResponse" type="tns:getNoteResponse"/>
   <xs:complexType name="getNoteResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="tns:note"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="updateNote" type="tns:updateNote"/>
   <xs:complexType name="updateNote">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="note" type="tns:note"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="updateNoteResponse" type="tns:updateNoteResponse"/>
   <xs:complexType name="updateNoteResponse">
       <xs:sequence>
           <xs:element name="return" type="xs:boolean"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getTaskFolders" type="tns:getTaskFolders"/>
   <xs:complexType name="getTaskFolders">
       <xs:sequence/>
   </xs:complexType>
   <xs:element name="getTaskFoldersResponse" type="tns:getTaskFoldersResponse"/>
   <xs:complexType name="getTaskFoldersResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="tns:folder"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getUpdatedContactIds" type="tns:getUpdatedContactIds"/>
   <xs:complexType name="getUpdatedContactIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getUpdatedContactIdsResponse" type="tns:getUpdatedContactIdsResponse"/>
   <xs:complexType name="getUpdatedContactIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="updateContact" type="tns:updateContact"/>
   <xs:complexType name="updateContact">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="contact" type="tns:contact"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="updateContactResponse" type="tns:updateContactResponse"/>
   <xs:complexType name="updateContactResponse">
       <xs:sequence>
           <xs:element name="return" type="xs:boolean"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="addTask" type="tns:addTask"/>
   <xs:complexType name="addTask">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="task" type="tns:task"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="addTaskResponse" type="tns:addTaskResponse"/>
   <xs:complexType name="addTaskResponse">
       <xs:sequence>
           <xs:element minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNewEventIds" type="tns:getNewEventIds"/>
   <xs:complexType name="getNewEventIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNewEventIdsResponse" type="tns:getNewEventIdsResponse"/>
   <xs:complexType name="getNewEventIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNewNoteIds" type="tns:getNewNoteIds"/>
   <xs:complexType name="getNewNoteIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNewNoteIdsResponse" type="tns:getNewNoteIdsResponse"/>
   <xs:complexType name="getNewNoteIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNewContactIds" type="tns:getNewContactIds"/>
   <xs:complexType name="getNewContactIds">
       <xs:sequence>
           <xs:element minOccurs="0" name="folder" type="tns:folder"/>
           <xs:element minOccurs="0" name="deviceId" type="xs:string"/>
           <xs:element minOccurs="0" name="lastUpdateTime" type="xs:dateTime"/>
       </xs:sequence>
   </xs:complexType>
   <xs:element name="getNewContactIdsResponse" type="tns:getNewContactIdsResponse"/>
   <xs:complexType name="getNewContactIdsResponse">
       <xs:sequence>
           <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="xs:string"/>
       </xs:sequence>
   </xs:complexType>
</xs:schema>
 </wsdl:types>
 <wsdl:message name="getNewContactIds">
   <wsdl:part name="parameters" element="tns:getNewContactIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="addEventResponse">
   <wsdl:part name="parameters" element="tns:addEventResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getEvent">
   <wsdl:part name="parameters" element="tns:getEvent">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getRemovedTaskIds">
   <wsdl:part name="parameters" element="tns:getRemovedTaskIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNote">
   <wsdl:part name="parameters" element="tns:getNote">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="removeNote">
   <wsdl:part name="parameters" element="tns:removeNote">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="removeContactResponse">
   <wsdl:part name="parameters" element="tns:removeContactResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="addNoteResponse">
   <wsdl:part name="parameters" element="tns:addNoteResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="removeTaskResponse">
   <wsdl:part name="parameters" element="tns:removeTaskResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getEventIdsResponse">
   <wsdl:part name="parameters" element="tns:getEventIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="addContactResponse">
   <wsdl:part name="parameters" element="tns:addContactResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getRemovedContactIds">
   <wsdl:part name="parameters" element="tns:getRemovedContactIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNoteIds">
   <wsdl:part name="parameters" element="tns:getNoteIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getTaskResponse">
   <wsdl:part name="parameters" element="tns:getTaskResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="updateTask">
   <wsdl:part name="parameters" element="tns:updateTask">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="removeContact">
   <wsdl:part name="parameters" element="tns:removeContact">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getContactResponse">
   <wsdl:part name="parameters" element="tns:getContactResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getContactIds">
   <wsdl:part name="parameters" element="tns:getContactIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getEventFoldersResponse">
   <wsdl:part name="parameters" element="tns:getEventFoldersResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getTaskIdsResponse">
   <wsdl:part name="parameters" element="tns:getTaskIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getUpdatedEventIdsResponse">
   <wsdl:part name="parameters" element="tns:getUpdatedEventIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getTask">
   <wsdl:part name="parameters" element="tns:getTask">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getUpdatedEventIds">
   <wsdl:part name="parameters" element="tns:getUpdatedEventIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNewEventIdsResponse">
   <wsdl:part name="parameters" element="tns:getNewEventIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="removeEvent">
   <wsdl:part name="parameters" element="tns:removeEvent">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getUpdatedTaskIdsResponse">
   <wsdl:part name="parameters" element="tns:getUpdatedTaskIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="updateEventResponse">
   <wsdl:part name="parameters" element="tns:updateEventResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="removeNoteResponse">
   <wsdl:part name="parameters" element="tns:removeNoteResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="updateNote">
   <wsdl:part name="parameters" element="tns:updateNote">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="addContact">
   <wsdl:part name="parameters" element="tns:addContact">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="updateContactResponse">
   <wsdl:part name="parameters" element="tns:updateContactResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getRemovedTaskIdsResponse">
   <wsdl:part name="parameters" element="tns:getRemovedTaskIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getUpdatedContactIds">
   <wsdl:part name="parameters" element="tns:getUpdatedContactIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getRemovedNoteIds">
   <wsdl:part name="parameters" element="tns:getRemovedNoteIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="addTask">
   <wsdl:part name="parameters" element="tns:addTask">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getUpdatedTaskIds">
   <wsdl:part name="parameters" element="tns:getUpdatedTaskIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getContactFolders">
   <wsdl:part name="parameters" element="tns:getContactFolders">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getCurrentTime">
   <wsdl:part name="parameters" element="tns:getCurrentTime">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="removeTask">
   <wsdl:part name="parameters" element="tns:removeTask">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getContactFoldersResponse">
   <wsdl:part name="parameters" element="tns:getContactFoldersResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNoteIdsResponse">
   <wsdl:part name="parameters" element="tns:getNoteIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="addTaskResponse">
   <wsdl:part name="parameters" element="tns:addTaskResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getUpdatedNoteIds">
   <wsdl:part name="parameters" element="tns:getUpdatedNoteIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getRemovedEventIds">
   <wsdl:part name="parameters" element="tns:getRemovedEventIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getRemovedNoteIdsResponse">
   <wsdl:part name="parameters" element="tns:getRemovedNoteIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getCurrentTimeResponse">
   <wsdl:part name="parameters" element="tns:getCurrentTimeResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNoteFolders">
   <wsdl:part name="parameters" element="tns:getNoteFolders">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getUpdatedNoteIdsResponse">
   <wsdl:part name="parameters" element="tns:getUpdatedNoteIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="addEvent">
   <wsdl:part name="parameters" element="tns:addEvent">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getContactIdsResponse">
   <wsdl:part name="parameters" element="tns:getContactIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="updateNoteResponse">
   <wsdl:part name="parameters" element="tns:updateNoteResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getTaskFolders">
   <wsdl:part name="parameters" element="tns:getTaskFolders">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNewEventIds">
   <wsdl:part name="parameters" element="tns:getNewEventIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNoteFoldersResponse">
   <wsdl:part name="parameters" element="tns:getNoteFoldersResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="updateTaskResponse">
   <wsdl:part name="parameters" element="tns:updateTaskResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getTaskIds">
   <wsdl:part name="parameters" element="tns:getTaskIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getRemovedContactIdsResponse">
   <wsdl:part name="parameters" element="tns:getRemovedContactIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="updateContact">
   <wsdl:part name="parameters" element="tns:updateContact">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNewNoteIdsResponse">
   <wsdl:part name="parameters" element="tns:getNewNoteIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNewNoteIds">
   <wsdl:part name="parameters" element="tns:getNewNoteIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNewTaskIdsResponse">
   <wsdl:part name="parameters" element="tns:getNewTaskIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="addNote">
   <wsdl:part name="parameters" element="tns:addNote">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getTaskFoldersResponse">
   <wsdl:part name="parameters" element="tns:getTaskFoldersResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="removeEventResponse">
   <wsdl:part name="parameters" element="tns:removeEventResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getContact">
   <wsdl:part name="parameters" element="tns:getContact">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNewTaskIds">
   <wsdl:part name="parameters" element="tns:getNewTaskIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getRemovedEventIdsResponse">
   <wsdl:part name="parameters" element="tns:getRemovedEventIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNewContactIdsResponse">
   <wsdl:part name="parameters" element="tns:getNewContactIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getEventFolders">
   <wsdl:part name="parameters" element="tns:getEventFolders">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getNoteResponse">
   <wsdl:part name="parameters" element="tns:getNoteResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getUpdatedContactIdsResponse">
   <wsdl:part name="parameters" element="tns:getUpdatedContactIdsResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="updateEvent">
   <wsdl:part name="parameters" element="tns:updateEvent">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getEventIds">
   <wsdl:part name="parameters" element="tns:getEventIds">
   </wsdl:part>
 </wsdl:message>
 <wsdl:message name="getEventResponse">
   <wsdl:part name="parameters" element="tns:getEventResponse">
   </wsdl:part>
 </wsdl:message>
 <wsdl:portType name="KolabServicePortType">
   <wsdl:operation name="getNewTaskIds">
     <wsdl:input name="getNewTaskIds" message="tns:getNewTaskIds">
   </wsdl:input>
     <wsdl:output name="getNewTaskIdsResponse" message="tns:getNewTaskIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getEvent">
     <wsdl:input name="getEvent" message="tns:getEvent">
   </wsdl:input>
     <wsdl:output name="getEventResponse" message="tns:getEventResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="updateTask">
     <wsdl:input name="updateTask" message="tns:updateTask">
   </wsdl:input>
     <wsdl:output name="updateTaskResponse" message="tns:updateTaskResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getEventFolders">
     <wsdl:input name="getEventFolders" message="tns:getEventFolders">
   </wsdl:input>
     <wsdl:output name="getEventFoldersResponse" message="tns:getEventFoldersResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="addEvent">
     <wsdl:input name="addEvent" message="tns:addEvent">
   </wsdl:input>
     <wsdl:output name="addEventResponse" message="tns:addEventResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getRemovedNoteIds">
     <wsdl:input name="getRemovedNoteIds" message="tns:getRemovedNoteIds">
   </wsdl:input>
     <wsdl:output name="getRemovedNoteIdsResponse" message="tns:getRemovedNoteIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="updateEvent">
     <wsdl:input name="updateEvent" message="tns:updateEvent">
   </wsdl:input>
     <wsdl:output name="updateEventResponse" message="tns:updateEventResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getTask">
     <wsdl:input name="getTask" message="tns:getTask">
   </wsdl:input>
     <wsdl:output name="getTaskResponse" message="tns:getTaskResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getEventIds">
     <wsdl:input name="getEventIds" message="tns:getEventIds">
   </wsdl:input>
     <wsdl:output name="getEventIdsResponse" message="tns:getEventIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getContactFolders">
     <wsdl:input name="getContactFolders" message="tns:getContactFolders">
   </wsdl:input>
     <wsdl:output name="getContactFoldersResponse" message="tns:getContactFoldersResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="removeContact">
     <wsdl:input name="removeContact" message="tns:removeContact">
   </wsdl:input>
     <wsdl:output name="removeContactResponse" message="tns:removeContactResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getRemovedEventIds">
     <wsdl:input name="getRemovedEventIds" message="tns:getRemovedEventIds">
   </wsdl:input>
     <wsdl:output name="getRemovedEventIdsResponse" message="tns:getRemovedEventIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getCurrentTime">
     <wsdl:input name="getCurrentTime" message="tns:getCurrentTime">
   </wsdl:input>
     <wsdl:output name="getCurrentTimeResponse" message="tns:getCurrentTimeResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="addContact">
     <wsdl:input name="addContact" message="tns:addContact">
   </wsdl:input>
     <wsdl:output name="addContactResponse" message="tns:addContactResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getRemovedTaskIds">
     <wsdl:input name="getRemovedTaskIds" message="tns:getRemovedTaskIds">
   </wsdl:input>
     <wsdl:output name="getRemovedTaskIdsResponse" message="tns:getRemovedTaskIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getContact">
     <wsdl:input name="getContact" message="tns:getContact">
   </wsdl:input>
     <wsdl:output name="getContactResponse" message="tns:getContactResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getTaskIds">
     <wsdl:input name="getTaskIds" message="tns:getTaskIds">
   </wsdl:input>
     <wsdl:output name="getTaskIdsResponse" message="tns:getTaskIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getUpdatedEventIds">
     <wsdl:input name="getUpdatedEventIds" message="tns:getUpdatedEventIds">
   </wsdl:input>
     <wsdl:output name="getUpdatedEventIdsResponse" message="tns:getUpdatedEventIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="addNote">
     <wsdl:input name="addNote" message="tns:addNote">
   </wsdl:input>
     <wsdl:output name="addNoteResponse" message="tns:addNoteResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNoteIds">
     <wsdl:input name="getNoteIds" message="tns:getNoteIds">
   </wsdl:input>
     <wsdl:output name="getNoteIdsResponse" message="tns:getNoteIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getRemovedContactIds">
     <wsdl:input name="getRemovedContactIds" message="tns:getRemovedContactIds">
   </wsdl:input>
     <wsdl:output name="getRemovedContactIdsResponse" message="tns:getRemovedContactIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNoteFolders">
     <wsdl:input name="getNoteFolders" message="tns:getNoteFolders">
   </wsdl:input>
     <wsdl:output name="getNoteFoldersResponse" message="tns:getNoteFoldersResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getContactIds">
     <wsdl:input name="getContactIds" message="tns:getContactIds">
   </wsdl:input>
     <wsdl:output name="getContactIdsResponse" message="tns:getContactIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="removeEvent">
     <wsdl:input name="removeEvent" message="tns:removeEvent">
   </wsdl:input>
     <wsdl:output name="removeEventResponse" message="tns:removeEventResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getUpdatedTaskIds">
     <wsdl:input name="getUpdatedTaskIds" message="tns:getUpdatedTaskIds">
   </wsdl:input>
     <wsdl:output name="getUpdatedTaskIdsResponse" message="tns:getUpdatedTaskIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getUpdatedNoteIds">
     <wsdl:input name="getUpdatedNoteIds" message="tns:getUpdatedNoteIds">
   </wsdl:input>
     <wsdl:output name="getUpdatedNoteIdsResponse" message="tns:getUpdatedNoteIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="removeNote">
     <wsdl:input name="removeNote" message="tns:removeNote">
   </wsdl:input>
     <wsdl:output name="removeNoteResponse" message="tns:removeNoteResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="removeTask">
     <wsdl:input name="removeTask" message="tns:removeTask">
   </wsdl:input>
     <wsdl:output name="removeTaskResponse" message="tns:removeTaskResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNote">
     <wsdl:input name="getNote" message="tns:getNote">
   </wsdl:input>
     <wsdl:output name="getNoteResponse" message="tns:getNoteResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="updateNote">
     <wsdl:input name="updateNote" message="tns:updateNote">
   </wsdl:input>
     <wsdl:output name="updateNoteResponse" message="tns:updateNoteResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getTaskFolders">
     <wsdl:input name="getTaskFolders" message="tns:getTaskFolders">
   </wsdl:input>
     <wsdl:output name="getTaskFoldersResponse" message="tns:getTaskFoldersResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getUpdatedContactIds">
     <wsdl:input name="getUpdatedContactIds" message="tns:getUpdatedContactIds">
   </wsdl:input>
     <wsdl:output name="getUpdatedContactIdsResponse" message="tns:getUpdatedContactIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="updateContact">
     <wsdl:input name="updateContact" message="tns:updateContact">
   </wsdl:input>
     <wsdl:output name="updateContactResponse" message="tns:updateContactResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="addTask">
     <wsdl:input name="addTask" message="tns:addTask">
   </wsdl:input>
     <wsdl:output name="addTaskResponse" message="tns:addTaskResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNewEventIds">
     <wsdl:input name="getNewEventIds" message="tns:getNewEventIds">
   </wsdl:input>
     <wsdl:output name="getNewEventIdsResponse" message="tns:getNewEventIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNewNoteIds">
     <wsdl:input name="getNewNoteIds" message="tns:getNewNoteIds">
   </wsdl:input>
     <wsdl:output name="getNewNoteIdsResponse" message="tns:getNewNoteIdsResponse">
   </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNewContactIds">
     <wsdl:input name="getNewContactIds" message="tns:getNewContactIds">
   </wsdl:input>
     <wsdl:output name="getNewContactIdsResponse" message="tns:getNewContactIdsResponse">
   </wsdl:output>
   </wsdl:operation>
 </wsdl:portType>
 <wsdl:binding name="KolabServiceSoapBinding" type="tns:KolabServicePortType">
   <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
   <wsdl:operation name="getNewTaskIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getNewTaskIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getNewTaskIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getEvent">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getEvent">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getEventResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="updateTask">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="updateTask">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="updateTaskResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="addEvent">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="addEvent">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="addEventResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getEventFolders">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getEventFolders">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getEventFoldersResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getRemovedNoteIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getRemovedNoteIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getRemovedNoteIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="updateEvent">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="updateEvent">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="updateEventResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getTask">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getTask">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getTaskResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getEventIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getEventIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getEventIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getContactFolders">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getContactFolders">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getContactFoldersResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="removeContact">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="removeContact">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="removeContactResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getRemovedEventIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getRemovedEventIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getRemovedEventIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getCurrentTime">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getCurrentTime">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getCurrentTimeResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="addContact">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="addContact">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="addContactResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getRemovedTaskIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getRemovedTaskIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getRemovedTaskIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getContact">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getContact">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getContactResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getTaskIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getTaskIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getTaskIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getUpdatedEventIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getUpdatedEventIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getUpdatedEventIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="addNote">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="addNote">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="addNoteResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNoteIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getNoteIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getNoteIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getRemovedContactIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getRemovedContactIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getRemovedContactIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getContactIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getContactIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getContactIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNoteFolders">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getNoteFolders">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getNoteFoldersResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="removeEvent">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="removeEvent">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="removeEventResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getUpdatedNoteIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getUpdatedNoteIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getUpdatedNoteIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getUpdatedTaskIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getUpdatedTaskIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getUpdatedTaskIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="removeNote">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="removeNote">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="removeNoteResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="removeTask">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="removeTask">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="removeTaskResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNote">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getNote">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getNoteResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getTaskFolders">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getTaskFolders">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getTaskFoldersResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="updateNote">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="updateNote">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="updateNoteResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getUpdatedContactIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getUpdatedContactIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getUpdatedContactIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="updateContact">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="updateContact">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="updateContactResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="addTask">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="addTask">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="addTaskResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNewEventIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getNewEventIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getNewEventIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNewContactIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getNewContactIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getNewContactIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
   <wsdl:operation name="getNewNoteIds">
     <soap:operation soapAction="" style="document"/>
     <wsdl:input name="getNewNoteIds">
       <soap:body use="literal"/>
     </wsdl:input>
     <wsdl:output name="getNewNoteIdsResponse">
       <soap:body use="literal"/>
     </wsdl:output>
   </wsdl:operation>
 </wsdl:binding>
 <wsdl:service name="KolabService">
   <wsdl:port name="KolabServicePort" binding="tns:KolabServiceSoapBinding">
     <soap:address location="http://localhost:9090/KolabServicePort"/>
   </wsdl:port>
 </wsdl:service>
</wsdl:definitions>

Literatur und Links

[1] Funambol Developers Guide

[2] Kolab Format Beschreibung

[3] JBoss 5.0.1.GA-jdk6

[4] Apache CXF

[5] Apache Maven 2

[6] RFC 2426

[7] RFC 2445

[8] SyncML HTTP Binding, version 1.1 [1]

[9] Web Services Security UsernameToken Profile 1.0 [2]

[10] Microsoft Exchange WebServices [3]