Simple caldav4j example in java and clojure

I was trying to access a caldav calendar through java and clojure to build a small application. But it wasn’t too easy.

caldav4j seemed to be very confusing and not so well maintained. There is no quick start guide and you are directed to have a look at the unit tests. But they are a compilation of interconnected classes and their hierarchies including a lot of deprecated classes being used – pretty confusing for a newcomer.

I had also problems using the alternative ical4j and it’s caldav connector. After some debugging it seemed incompatible with zimbra because of some strange filter constraints.

This might all be due to the fact that caldav is a complex protocol and I accepted to dig deeper into caldav4j.

Enough of the yacking, I traced down the necessary wire to build a basic example and hope it’ll help others looking for a quick start.

Java: https://github.com/dedeibel/list-events-caldav4j-example

Clojure: https://github.com/dedeibel/list-events-caldav4j-example-clojure

I tested it with zimbra. For google calendar a little more work might be required. The key might be in the GoogleCalDavDialect.java class.

 
Java:
 

    HttpClient httpClient = new HttpClient();
    // I tried it with zimbra - but I had no luck using google calendar
    httpClient.getHostConfiguration().setHost("CALDAVHOST", 443, "https");
    String username = "username";
    UsernamePasswordCredentials httpCredentials = new UsernamePasswordCredentials(username, "secret");
    httpClient.getState().setCredentials(AuthScope.ANY, httpCredentials);
    httpClient.getParams().setAuthenticationPreemptive(true);
    // Need a proxy?
    //httpClient.getHostConfiguration().setProxy("phost", 8080);

    CalDAVCollection collection = new CalDAVCollection(
        "/dav/"+ username +"/Calendar",
        (HostConfiguration) httpClient.getHostConfiguration().clone(),
        new CalDAV4JMethodFactory(),
        CalDAVConstants.PROC_ID_DEFAULT
        );

    GenerateQuery gq=new GenerateQuery();
    // TODO you might want to adjust the date
    gq.setFilter("VEVENT [20131001T000000Z;20131010T000000Z] : STATUS!=CANCELLED");
    // Get the raw caldav query
    // System.out.println("Query: "+ gq.prettyPrint());
    CalendarQuery calendarQuery = gq.generate();
    List<Calendar>calendars = collection.queryCalendars(httpClient, calendarQuery);

    for (Calendar calendar : calendars) {
      ComponentList componentList = calendar.getComponents().getComponents(Component.VEVENT);
      Iterator<VEvent> eventIterator = componentList.iterator();
      while (eventIterator.hasNext()) {
        VEvent ve = eventIterator.next();
        System.out.println("Event: "+ ve.toString());
        System.out.println("\n\n");
      }
    }

 
Clojure:
 

defn- create-query []
  (let [gq (GenerateQuery.)]
    ; Date Format yyyyMMdd
    (.setFilter gq "VEVENT [20131001T000000Z;20131010T000000Z] : STATUS!=CANCELLED")
    (.generate gq)))

(defn -main []
  (let [http-client (initialize-http-client)
        collection-path (:collection-path configuration)
        host-configuration (.clone (.getHostConfiguration http-client))
        method-factory (CalDAV4JMethodFactory.)
        collection (CalDAVCollection. collection-path host-configuration method-factory CalDAVConstants/PROC_ID_DEFAULT)
        calendars (.queryCalendars collection http-client (create-query))
        ]
    (doseq [cal calendars
            event (-> cal (.getComponents) (.getComponents Component/VEVENT))]
      (println "Event: " event)
      (println "\n\n"))))

Apache Ant Beispiel

Apache ant ist ein build Tool, hauptsächlich für Java, was XML Dateien als Konfiguration verwendet.

Ant war früher sehr populär und ist nun z.B. durch maven erweitert oder ivy ersetzt, da diese über automatische Abhängigkeitsauflösung verfügen. Beim Umgang mit maven können einige Ant Grundlagen nicht schaden.

Das folgende Beispiel baut zwei Klassen und packt diese dann mit den Resource files in ein gemeinsames Jar. Die Resource Dateien werden zuvor noch mit einem Zeitstempel versehen und zusammengeführt um die Möglichkeiten von ant zu demonstrieren.

In einem zweiten Beitrag zeiche ich die Umsetzung des gleichen Projektes mit maven. Apache Maven Beispiel

Code

Dateien

./build.xml
./resources
./resources/inputA
./resources/inputB
./resources/merge
./src
./src/LoadFile.java
./src/Print.java

Java Source

import java.io.*;
public class LoadFile {
  public String load() throws FileNotFoundException, UnsupportedEncodingException, IOException {
    char[] buff = new char[10240];
    // File file = new File("resources/file");
    // FileInputStream fis = new FileInputStream(file);
    // Datei ist nun im jar, also muss sie anders geladen werden
    ClassLoader cl  = this.getClass().getClassLoader();
    InputStream fis = cl.getResourceAsStream("resources/file");
    InputStreamReader in = new InputStreamReader(fis, "UTF-8"); 
    in.read(buff, 0, 10240);
    return new String(buff);
  }
}

import java.lang.*;

public class Print {
  public static void main(String[] args) {
    System.out.println("Hello.");
    try {
      System.out.println("Content:\n"+ new LoadFile().load());
    }
    catch (Exception e) {
      System.err.println("Error: "+ e.getMessage());
    }
  }
}

Ressourcen

File: resources/inputA
A: Build @date@
If the writeList method doesn't
catch the checked exceptions
that can occur within it, the
writeList method must specify
that it can throw these
exceptions. Let's modify the

File: resources/inputB
B:
original writeList method to
specify the exceptions it can
throw instead of catching
them. To remind you, here's
the original version of the
writeList method that won't
compile. 

File: resources/merge
#!/bin/sh
cat $1 $2 &gt; $3

Ant build.xml

<project name="Print" default="build" basedir=".">
    <description>
        Print example build file
    </description>
  <!-- set global properties for this build -->
  <property name="src" location="src"/>
  <property name="build" location="build"/>
  <property name="dist"  location="."/>
  <property name="resources" location="resources"/>

  <!-- Vorbereitung der Resourcen -->
  <target name="prep" depends="init">
    <!-- Aufruf des Merge Befehls mit den Dateipfaden als Argumenten -->
    <exec executable="${resources}/merge">
      <arg value="${resources}/inputA"/>
      <arg value="${resources}/inputB"/>
      <arg value="${build}/resources/file.in"/>
    </exec>
    <!-- Setze einen Filter, dieser kann das Vorkommen von @token@
         in Dateien während Kopierbefehlen durch einen Wert ersetzen.
         Dazu muss bei copy zum Beispiel filterting="true" angegeben werden.
         
         Hier wird ein Vorkommen von @date@ durch einen Timestamp ersetzt.
         Die property DSTAMP kommt dabei vom Befehl mkstamp, welcher im
         target init ausgefuehrt wurde.  -->
    <filter token="date" value="${DSTAMP}"/>
    <!-- Kopiere die Datei um und wende dabei die Filter an -->
    <copy tofile="${build}/resources/file" file="${build}/resources/file.in" filtering="true"/>
    <delete file="${build}/resources/file.in"/>
  </target>

  <target name="init">
    <!-- Create the time stamp, verfügbar als ${DSTAMP} -->
    <tstamp/>
    <property name="outjar" value="${dist}/${ant.project.name}-${DSTAMP}.jar"/>
    <!-- Create the build directory structure used by compile -->
    <mkdir dir="${build}"/>
    <mkdir dir="${build}/resources"/>
  </target>

  <target name="compile" depends="init"
      description="compile the source " >
    <!-- Compile the java code from ${src} into ${build} -->
    <javac srcdir="${src}" destdir="${build}"/>
  </target>

  <target name="build" depends="compile, prep"
      description="generate the distribution" >
    <jar jarfile="${outjar}" basedir="${build}">
      <manifest>
        <!-- Muss angegeben werden damit java die Hauptklasse
             beim Ausfuehren des jars finden kann -->
        <attribute name="Main-Class" value="Print"/>
      </manifest>
    </jar>
  </target>

  <target name="clean"
        description="clean up" >
    <!-- Delete the ${build} and ${dist} directory trees -->
    <delete dir="${build}"/>
    <delete>
      <fileset dir="." includes="Print*.jar"/>
    </delete>
  </target>
</project>

Ausführung

$ ant
Buildfile: build.xml

init:
    [mkdir] Created dir: /home/dedeibel/proggn/java/test/ant/build
    [mkdir] Created dir: /home/dedeibel/proggn/java/test/ant/build/resources

compile:
    [javac] Compiling 2 source files to /home/dedeibel/proggn/java/test/ant/build

prep:
     [copy] Copying 1 file to /home/dedeibel/proggn/java/test/ant/build/resources
   [delete] Deleting: /home/dedeibel/proggn/java/test/ant/build/resources/file.in

build:
      [jar] Building jar: /home/dedeibel/proggn/java/test/ant/Print-20091030.jar

BUILD SUCCESSFUL
Total time: 2 seconds

Anwendung:

$ java -jar Print-20091030.jar 
Hello.
Content:
A: Build 20091030
If the writeList method doesn't
catch the checked exceptions
that can occur within it, the
writeList method must specify
that it can throw these
exceptions. Let's modify the
B:
original writeList method to
specify the exceptions it can
throw instead of catching
them. To remind you, here's
the original version of the
writeList method that won't
compile.

Quellen

Apache ant homepage
Writing a simple ant file

Die Vernachlässigung von tollen Namespaces für meine Klassen sie mir verziehen.