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)