Toutes les entreprises et les particuliers partagent un même besoin de conservation de leurs contacts à long terme.

Les entreprises confient souvent les leurs à un CRM ou à un ERP.

Dès lors comment y accéder depuis d’autres applications sans créer de nouveaux référentiels dont la multiplication ne fera que diminuer la qualité des données et la capacité future à changer de fournisseur de service ?

Une solution consiste à utiliser une norme largement utilisée : VCARD (https://fr.wikipedia.org/wiki/VCard) et CardDAV (https://fr.wikipedia.org/wiki/CardDAV)

VCARD

Utilisé depuis de longues années par Outlook, Google et tous vos Android, échangé régulièrement par email, ce format texte de description des informations d’un contact est relativement simple.

BEGIN:VCARD
VERSION:2.1
N;LANGUAGE=fr;CHARSET=utf-8:GILBART;Frédéric
FN;CHARSET=utf-8:Frédéric GILBART
ORG:CAPSIEL
TITLE:Directeur
TEL;WORK;VOICE:+33 (0) 972571595
NOTE:
END:VCARD

L’avantage : il est facilement lu par de nombreuses librairies dans tous les langages. Un simple fichier texte peut en contenir des milliers.

Voir la RFC complète : https://tools.ietf.org/html/rfc6350

CardDAV

CardDAV est un protocole de stockage et d’accès aux VCards : ce qui signifie que vos contacts sauvegardés dans un serveur CardDAV peuvent-être accédés depuis toutes les applications implémentant le protocole normalisé.

Vos contacts peuvent donc être dans un référentiel unique puis accédés à distances, synchronisés et partagés.

Voir la RFC complète : https://tools.ietf.org/html/rfc6352

Synology

Les NAS Synology proposent une application CardDAV Server que nous allons utiliser pour quelques essais.

iCal4j

Nous utiliserons iCal4j comme bibliothèque d’accès Java.

        <dependency>
            <groupId>org.mnode.ical4j</groupId>
            <artifactId>ical4j-connector</artifactId>
            <version>0.9.5-SNAPSHOT</version>
        </dependency>

Pour cela nous avons besoin d’un PathResolver adapté au Synology :

public class SynologyPathResolver extends PathResolver {
	public String getPrincipalPath(String username) {
		return "/principals/users/" + username + "/";
	}

	public String getUserPath(String username) {
		return "/addressbooks/users/" + username + "/";
	}
}

Connexion


	private CardDavStore store = null;

	@Before
	public void setUp() throws Exception {
		URL url = new URL("http://192.168.10.100:8008/");
		String prodId = "-//CAPSIEL//CalDAV Client//EN";
		store = new CardDavStore(prodId, url, new SynologyPathResolver());
		boolean connected = store.connect("login", password.toCharArray());
		assertTrue(connected);
	}

	@Test
	public void synologyConnect() throws Exception {
		assertTrue(store.isConnected());
	}	
	
	@After
	public void tearDown() {
		store.disconnect();
	}

Lister tous les contacts

Cela consiste à faire une addressbook-query qui malheureusement ne fonctionne pas, le Synology retournant une erreur “HTTP/1.1 400 Bad Request”. Il faut modifier iCal4j : net.fortuna.ical4j.connector.dav.CardDavCollection

	public VCard[] getComponents() throws ObjectStoreException {
		try {
			DavPropertyNameSet properties = new DavPropertyNameSet();
			properties.add(DavPropertyName.GETETAG);
			properties.add(CardDavPropertyName.ADDRESS_DATA);
			Document document = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder().newDocument();
			Element filter = DomUtil
					.createElement(document, CalDavConstants.PROPERTY_FILTER,
							CalDavConstants.CARDDAV_NAMESPACE);
			Element calFilter = DomUtil.createElement(document,
					CalDavConstants.PROPERTY_COMP_FILTER,
					CalDavConstants.CARDDAV_NAMESPACE);
			calFilter.setAttribute(CalDavConstants.ATTRIBUTE_NAME,
					CalDavConstants.PROPERTY_ADDRESS_DATA);
			ReportInfo info = new ReportInfo(ReportMethod.ADDRESSBOOK_QUERY, 1,
					properties);
			info.setContentElement(filter);
			ReportMethod method = new ReportMethod(getPath(), info);
			getStore().getClient().execute(method);
			if (method.getStatusCode() == DavServletResponse.SC_MULTI_STATUS) {
				return method.getVCards();
			} else if (method.getStatusCode()
					== DavServletResponse.SC_NOT_FOUND) {
				return new VCard[0];
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (DOMException e) {
			e.printStackTrace();
		} catch (DavException e) {
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} /* catch (ParserException e) {
			e.printStackTrace();
        } */
		return new VCard[0];
	}

On obtient alors :

	@Test
	public void getAllContacts() throws Exception {
		for (CardDavCollection o : store.getCollections()) {
			assertNotNull(o);
			for (VCard card : o.getComponents()) {
				System.err.println(card.toString());
			}
		}
	}
BEGIN:VCARD
VERSION:3.0
UID:460308fc-fe58-4a57-a2a5-a65457b61cdb
FN:nom2  
N:nom2;;;
NOTE:
END:VCARD

BEGIN:VCARD
VERSION:3.0
UID:fe7b0d35-9ddf-4d17-a6fe-11758768e32a
FN:gilb fred 
N:gilb;fred;;;
NOTE:
END:VCARD

Faire une recherche précise

Comment indiqué dans la RFC en utilisant à nouveau “addressbook-query” et en précisant le filter de recherche (ici par nom)

<?xml version="1.0" encoding="UTF-8"?>
<C:addressbook-query xmlns:C="urn:ietf:params:xml:ns:carddav">
<D:prop xmlns:D="DAV:">
    <D:getetag />
    <C:address-data>
        <C:prop name="VERSION" />
        <C:prop name="UID" />
        <C:prop name="FN" />
        <C:prop name="TEL" />
    </C:address-data>
</D:prop>
<C:filter test="anyof">
    <C:prop-filter name="TEL">
        <C:text-match collation="i;unicode-casemap" match-type="contains">972571595</C:text-match>
    </C:prop-filter>
</C:filter>
</C:addressbook-query>

Réponse : 1 seul contact comme prévu, trouvé via une partie de son téléphone :

BEGIN:VCARD
VERSION:3.0
UID:fe7b0d35-9ddf-4d17-a6fe-11758768e32a
FN:gilb fred 
N:gilb;fred;;;
TEL;TYPE=Default:+33 (0) 972571595
END:VCARD

Ajout d’un nouveau contact

	@Test
	public void addContact() throws Exception {
		VCard card = new VCard();
		card.getProperties().add(Version.VERSION_4_0);
		card.getProperties().add(new Uid(URI.create(UUID.randomUUID().toString())));
		card.getProperties().add(new Fn("fullname"));
		card.getProperties().add(new Email("email@domain"));
		card.getProperties().add(new Telephone("email@domain"));
		for (CardDavCollection o : store.getCollections()) {
			assertNotNull(o);
			o.addCard(card);
		}
	}

Liste des serveurs CardDAV

Liste plus complète : https://devguide-calconnect.rhcloud.com/CardDAV/Client-Implementations

Liste des libs VCARD

(650 mots)