com.googlecode.noweco.calendar.CaldavServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.noweco.calendar.CaldavServlet.java

Source

/*
 * Copyright 2011 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.noweco.calendar;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.parameter.Value;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.noweco.calendar.caldav.Calendar;
import com.googlecode.noweco.calendar.caldav.CalendarHomeSet;
import com.googlecode.noweco.calendar.caldav.CalendarMultiget;
import com.googlecode.noweco.calendar.caldav.Collection;
import com.googlecode.noweco.calendar.caldav.Comp;
import com.googlecode.noweco.calendar.caldav.CurrentUserPrincipal;
import com.googlecode.noweco.calendar.caldav.Displayname;
import com.googlecode.noweco.calendar.caldav.Getcontenttype;
import com.googlecode.noweco.calendar.caldav.Getetag;
import com.googlecode.noweco.calendar.caldav.Multistatus;
import com.googlecode.noweco.calendar.caldav.ObjectFactory;
import com.googlecode.noweco.calendar.caldav.Owner;
import com.googlecode.noweco.calendar.caldav.PrincipalCollectionSet;
import com.googlecode.noweco.calendar.caldav.PrincipalSearchPropertySet;
import com.googlecode.noweco.calendar.caldav.Prop;
import com.googlecode.noweco.calendar.caldav.Propfind;
import com.googlecode.noweco.calendar.caldav.Propstat;
import com.googlecode.noweco.calendar.caldav.Report;
import com.googlecode.noweco.calendar.caldav.Resourcetype;
import com.googlecode.noweco.calendar.caldav.Response;
import com.googlecode.noweco.calendar.caldav.SupportedCalendarComponentSet;
import com.googlecode.noweco.calendar.caldav.SupportedReport;
import com.googlecode.noweco.calendar.caldav.SupportedReportSet;
import com.googlecode.noweco.calendar.caldav.SyncCollection;
import com.googlecode.noweco.calendar.caldav.SyncToken;

/**
 * @author Gael Lalire
 */
public class CaldavServlet extends HttpServlet {

    private static final long serialVersionUID = -4166352339673292359L;

    private static final Logger LOGGER = LoggerFactory.getLogger(CaldavServlet.class);

    /**
     * <tt>207 Multi-Status</tt>. (WebDAV - RFC 2518) or <tt>207 Partial Update
     * OK</tt> (HTTP/1.1 - draft-ietf-http-v11-spec-rev-01?)
     */
    public static final int SC_MULTI_STATUS = 207;

    private static final String METHOD_PROPFIND = "PROPFIND";

    private static final String METHOD_REPORT = "REPORT";

    private static final JAXBContext JAXB_CONTEXT;

    private static final MemoryFile ROOT;

    public void init2() {
        java.util.Calendar cal = java.util.Calendar.getInstance();
        cal.set(java.util.Calendar.MONTH, java.util.Calendar.DECEMBER);
        cal.set(java.util.Calendar.DAY_OF_MONTH, 25);

        VEvent christmas = new VEvent(new Date(cal.getTime()), "Christmas Day");
        // initialise as an all-day event..
        christmas.getProperties().getProperty(Property.DTSTART).getParameters().add(Value.DATE);

    }

    static {
        try {
            JAXB_CONTEXT = JAXBContext.newInstance(ObjectFactory.class.getPackage().getName());
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
        String userName = "admin";
        Prop prop;
        PrincipalCollectionSet principalCollectionSet;
        CurrentUserPrincipal currentUserPrincipal;
        CalendarHomeSet calendarHomeSet;
        Resourcetype resourcetype;
        Owner owner;
        Displayname displayname;

        ROOT = new MemoryFile(null, "", true);
        MemoryFile principals = new MemoryFile(ROOT, "principals", true);
        MemoryFile principalsUsers = new MemoryFile(principals, "users", true);
        MemoryFile principalsUsersCurrent = new MemoryFile(principalsUsers, userName, true);

        MemoryFile calendar = new MemoryFile(ROOT, "calendars", true);
        MemoryFile calendarUsers = new MemoryFile(calendar, "users", true);
        MemoryFile calendarUsersCurrent = new MemoryFile(calendarUsers, userName, true);
        MemoryFile calendarUsersCurrentCalendar = new MemoryFile(calendarUsersCurrent, "calendar", true);

        prop = ROOT.getProp();
        principalCollectionSet = new PrincipalCollectionSet();
        principalCollectionSet.setHref(principals.getURI());
        prop.setPrincipalCollectionSet(principalCollectionSet);

        prop = principals.getProp();
        currentUserPrincipal = new CurrentUserPrincipal();
        currentUserPrincipal.setHref(principalsUsersCurrent.getURI());
        prop.setCurrentUserPrincipal(currentUserPrincipal);

        prop = principalsUsersCurrent.getProp();
        principalCollectionSet = new PrincipalCollectionSet();
        principalCollectionSet.setHref(principals.getURI());
        prop.setPrincipalCollectionSet(principalCollectionSet);
        calendarHomeSet = new CalendarHomeSet();
        calendarHomeSet.setHref(calendarUsersCurrent.getURI());
        prop.setCalendarHomeSet(calendarHomeSet);
        displayname = new Displayname();
        displayname.getContent().add(userName);
        prop.setDisplayname(displayname);

        prop = calendarUsersCurrentCalendar.getProp();
        displayname = new Displayname();
        displayname.getContent().add("nowecoCalendar");
        prop.setDisplayname(displayname);
        resourcetype = new Resourcetype();
        resourcetype.setCollection(new Collection());
        resourcetype.setCalendar(new Calendar());
        prop.setResourcetype(resourcetype);
        owner = new Owner();
        owner.setHref(principalsUsersCurrent.getURI());
        prop.setOwner(owner);
    }

    public Marshaller createMarshaller() throws IOException {
        try {
            Marshaller marshaller = JAXB_CONTEXT.createMarshaller();
            marshaller.setProperty("jaxb.formatted.output", true);
            return marshaller;
        } catch (JAXBException e) {
            throw new CalendarException("Unable to create marshaller", e);
        }
    }

    public Unmarshaller createUnmarshaller() throws IOException {
        try {
            return JAXB_CONTEXT.createUnmarshaller();
        } catch (JAXBException e) {
            throw new CalendarException("Unable to create marshaller", e);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void service(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        String method = req.getMethod();
        if (LOGGER.isDebugEnabled()) {
            List<String> headers = Collections.list((Enumeration<String>) req.getHeaderNames());
            LOGGER.debug("Command : {}, Appel : {}, headers {}",
                    new Object[] { method, req.getRequestURI(), headers });
        }

        if (!authent(req)) {
            resp.addHeader("WWW-Authenticate", "BASIC realm=\"Noweco CalDAV\"");
            resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        String requestURI = req.getRequestURI();
        if (requestURI.length() != 0 && requestURI.charAt(0) != '/') {
            // unknown relative URI
            resp.sendError(HttpServletResponse.SC_FORBIDDEN);
            return;
        }

        try {
            if (METHOD_PROPFIND.equals(method)) {
                doPropfind(req, resp);
            } else if (METHOD_REPORT.equals(method)) {
                doReport(req, resp);
            } else {
                super.service(req, resp);
            }
        } catch (Throwable e) {
            LOGGER.error("Unexpected throwable", e);
        }
    }

    private static final String DEPTH_INFINITY = "infinity";

    public void doReport(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        Unmarshaller unMarshaller = createUnmarshaller();
        Marshaller marshaller = createMarshaller();
        Object xmlRequest = null;
        try {
            xmlRequest = unMarshaller.unmarshal(req.getReader());
        } catch (JAXBException e) {
            throw new CalendarException("Unable to parse request", e);
        }
        if (LOGGER.isTraceEnabled()) {
            try {
                StringWriter writer = new StringWriter();
                marshaller.marshal(xmlRequest, writer);
                LOGGER.trace("receive :\n{}", writer.toString());
            } catch (JAXBException e) {
                // ignore
            }
        }

        Multistatus multistatus = new Multistatus();

        if (xmlRequest instanceof CalendarMultiget) {
            CalendarMultiget calendarMultiget = (CalendarMultiget) xmlRequest;
            Prop reqProp = calendarMultiget.getProp();

            int status = propFind(multistatus, reqProp, req.getHeader("Depth"), calendarMultiget.getHref());
            if (status != HttpServletResponse.SC_OK) {
                resp.sendError(status);
                return;
            }
        } else if (xmlRequest instanceof SyncCollection) {
            SyncCollection syncCollection = (SyncCollection) xmlRequest;
            Prop reqProp = syncCollection.getProp();
            String requestURI = req.getRequestURI();
            MemoryFile locate = MemoryFileUtils.locate(ROOT, requestURI);
            for (MemoryFile memoryFile : locate.getChildren()) {
                int status = propFind(multistatus, reqProp, "0", memoryFile.getURI());
                if (status != HttpServletResponse.SC_OK) {
                    resp.sendError(status);
                    return;
                }
            }
            SyncToken syncToken = new SyncToken();
            syncToken.getContent().add("<string mal formee>");
            multistatus.setSyncToken(syncToken);
        } else if (xmlRequest instanceof PrincipalSearchPropertySet) {
            // PrincipalSearchPropertySet principalSearchPropertySet = (PrincipalSearchPropertySet) xmlRequest;
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
            return;
        } else {
            LOGGER.error("doReport not supported request " + xmlRequest.getClass());
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
            return;
        }

        resp.setStatus(SC_MULTI_STATUS);
        resp.setContentType("text/xml;charset=\"UTF-8\"");
        PrintWriter httpWriter = resp.getWriter();
        try {
            Writer writer;
            if (LOGGER.isTraceEnabled()) {
                writer = new StringWriter();
            } else {
                writer = httpWriter;
            }
            marshaller.marshal(multistatus, writer);
            if (LOGGER.isTraceEnabled()) {
                String string = writer.toString();
                LOGGER.trace("send :\n{}", string);
                httpWriter.write(string);
            }
        } catch (JAXBException e) {
            throw new CalendarException("Unable to format response", e);
        }
        httpWriter.close();
    }

    public boolean authent(final HttpServletRequest req) throws ServletException, IOException {
        String authorization = req.getHeader("authorization");
        if (authorization == null) {
            return false;
        }
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
                new ByteArrayInputStream(Base64.decodeBase64(authorization.substring("Basic ".length()))),
                "US-ASCII"));
        String[] credentials = bufferedReader.readLine().split(":");
        bufferedReader.close();
        if (credentials.length == 2) {
            String user = credentials[0];
            String password = credentials[1];
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("connect with user '{}' and password '{}'", user, password);
            }
            // TODO call spi to connect
            return true;
        }
        return false;
    }

    public int propFind(final Multistatus multistatus, final Prop reqProp, final String depth,
            final String requestURI) {
        MemoryFile requestMemoryFile = MemoryFileUtils.locate(ROOT, requestURI);
        if (requestMemoryFile == null) {
            return HttpServletResponse.SC_NOT_FOUND;
        }

        List<MemoryFile> memoryFiles;
        if ("0".equals(depth)) {
            memoryFiles = Collections.singletonList(requestMemoryFile);
        } else if ("1".equals(depth)) {
            memoryFiles = new ArrayList<MemoryFile>();
            memoryFiles.add(requestMemoryFile);
            if (requestMemoryFile.isDir()) {
                memoryFiles.addAll(requestMemoryFile.getChildren());
            }
        } else if (depth == null || DEPTH_INFINITY.equals(depth)) {
            memoryFiles = MemoryFileUtils.recursiveList(requestMemoryFile);
        } else {
            return HttpServletResponse.SC_BAD_REQUEST;
        }

        for (MemoryFile memoryFile : memoryFiles) {
            Response response = new Response();
            response.getHref().add(memoryFile.getURI());
            Propstat propstatOK = new Propstat();
            Propstat propstatNotFound = new Propstat();
            Prop propOK = new Prop();
            Prop propNotFound = new Prop();
            propstatOK.setProp(propOK);
            propstatNotFound.setProp(propNotFound);

            simplePropFind(memoryFile, reqProp, propOK, propNotFound);
            //            if (reqProp.getCalendarData() != null) {
            //                // FIXME how ?
            //               // propOK.setCalendarData(memoryFile.getContent());
            //            }
            if (reqProp.getSupportedReportSet() != null) {
                SupportedReportSet supportedReportSet = new SupportedReportSet();
                SupportedReport supportedReport;
                Report report;

                supportedReport = new SupportedReport();
                report = new Report();
                report.setCalendarMultiget(new CalendarMultiget());
                supportedReport.setReport(report);
                supportedReportSet.getSupportedReport().add(supportedReport);

                supportedReport = new SupportedReport();
                report = new Report();
                report.setSyncCollection(new SyncCollection());
                supportedReport.setReport(report);
                supportedReportSet.getSupportedReport().add(supportedReport);

                propOK.setSupportedReportSet(supportedReportSet);
            }
            if (reqProp.getSupportedCalendarComponentSet() != null) {
                SupportedCalendarComponentSet supportedCalendarComponentSet = new SupportedCalendarComponentSet();
                Comp comp = new Comp();
                comp.setName("VEVENT");
                supportedCalendarComponentSet.getComp().add(comp);
                propOK.setSupportedCalendarComponentSet(supportedCalendarComponentSet);
            }
            propstatOK.setStatus("HTTP/1.1 200 OK");
            response.getPropstat().add(propstatOK);

            propstatNotFound.setStatus("HTTP/1.1 404 Not Found");
            response.getPropstat().add(propstatNotFound);

            multistatus.getResponse().add(response);
        }
        return HttpServletResponse.SC_OK;
    }

    public void simplePropFind(final MemoryFile memoryFile, final Prop reqProp, final Prop propOK,
            final Prop propNotFound) {
        if (reqProp.getPrincipalCollectionSet() != null
                && memoryFile.getProp().getPrincipalCollectionSet() != null) {
            propOK.setPrincipalCollectionSet(memoryFile.getProp().getPrincipalCollectionSet());
        } else {
            propNotFound.setPrincipalCollectionSet(reqProp.getPrincipalCollectionSet());
        }
        if (reqProp.getCurrentUserPrincipal() != null && memoryFile.getProp().getCurrentUserPrincipal() != null) {
            propOK.setCurrentUserPrincipal(memoryFile.getProp().getCurrentUserPrincipal());
        } else {
            propNotFound.setCurrentUserPrincipal(reqProp.getCurrentUserPrincipal());
        }
        if (reqProp.getDisplayname() != null && memoryFile.getProp().getDisplayname() != null) {
            propOK.setDisplayname(memoryFile.getProp().getDisplayname());
        } else {
            propNotFound.setDisplayname(reqProp.getDisplayname());
        }
        if (reqProp.getResourcetype() != null && memoryFile.getProp().getResourcetype() != null) {
            propOK.setResourcetype(memoryFile.getProp().getResourcetype());
        } else {
            propNotFound.setResourcetype(reqProp.getResourcetype());
        }
        if (reqProp.getCalendarHomeSet() != null && memoryFile.getProp().getCalendarHomeSet() != null) {
            propOK.setCalendarHomeSet(memoryFile.getProp().getCalendarHomeSet());
        } else {
            propNotFound.setCalendarHomeSet(reqProp.getCalendarHomeSet());
        }
        if (reqProp.getGetetag() != null && memoryFile.getProp().getGetetag() != null) {
            propOK.setGetetag(memoryFile.getProp().getGetetag());
        } else {
            propNotFound.setGetetag(reqProp.getGetetag());
        }
        if (reqProp.getOwner() != null && memoryFile.getProp().getOwner() != null) {
            propOK.setOwner(memoryFile.getProp().getOwner());
        } else {
            propNotFound.setOwner(reqProp.getOwner());
        }
        if (reqProp.getGetcontenttype() != null && memoryFile.getProp().getGetcontenttype() != null) {
            propOK.setGetcontenttype(memoryFile.getProp().getGetcontenttype());
        } else {
            propNotFound.setGetcontenttype(reqProp.getGetcontenttype());
        }
        propNotFound.getAny().addAll(reqProp.getAny());
    }

    public void doPropfind(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        Unmarshaller unMarshaller = createUnmarshaller();
        Marshaller marshaller = createMarshaller();

        Propfind propfind = null;
        try {
            propfind = (Propfind) unMarshaller.unmarshal(req.getReader());
        } catch (JAXBException e) {
            throw new CalendarException("Unable to parse request", e);
        }
        if (LOGGER.isTraceEnabled()) {
            try {
                StringWriter writer = new StringWriter();
                marshaller.marshal(propfind, writer);
                LOGGER.trace("receive :\n{}", writer.toString());
            } catch (JAXBException e) {
                // ignore
            }
        }

        Prop reqProp = propfind.getProp();

        Multistatus multistatus = new Multistatus();
        int status = propFind(multistatus, reqProp, req.getHeader("Depth"), req.getRequestURI());
        if (status != HttpServletResponse.SC_OK) {
            resp.sendError(status);
            return;
        }

        resp.setStatus(SC_MULTI_STATUS);
        resp.setContentType("text/xml;charset=\"UTF-8\"");

        PrintWriter httpWriter = resp.getWriter();

        try {
            Writer writer;
            if (LOGGER.isTraceEnabled()) {
                writer = new StringWriter();
            } else {
                writer = httpWriter;
            }
            marshaller.marshal(multistatus, writer);
            if (LOGGER.isTraceEnabled()) {
                String string = writer.toString();
                LOGGER.trace("send :\n{}", string);
                httpWriter.write(string);
            }
        } catch (JAXBException e) {
            throw new CalendarException("Unable to format response", e);
        }
        httpWriter.close();
    }

    @Override
    public void doDelete(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        MemoryFile locate = MemoryFileUtils.locate(ROOT, req.getRequestURI());
        if (locate == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        locate.getParent().getChildren().remove(locate);
    }

    @Override
    public void doGet(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        MemoryFile locate = MemoryFileUtils.locate(ROOT, req.getRequestURI());
        if (locate == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        Getcontenttype getcontenttype = locate.getProp().getGetcontenttype();
        if (getcontenttype != null) {
            resp.setContentType(getcontenttype.getContent().get(0));
        }
        ServletOutputStream outputStream = resp.getOutputStream();
        outputStream.write(locate.getContent());
        outputStream.close();
    }

    @Override
    public void doPut(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        String requestURI = req.getRequestURI();
        String[] split = requestURI.split("/");
        MemoryFile memoryFile = ROOT;
        for (int i = 0; i < split.length - 1; i++) {
            String name = split[i];
            MemoryFile locate = MemoryFileUtils.locate(memoryFile, name);
            if (locate == null) {
                memoryFile = new MemoryFile(memoryFile, name, true);
            } else {
                memoryFile = locate;
            }
        }
        String name = split[split.length - 1];
        MemoryFile locate = MemoryFileUtils.locate(memoryFile, name);
        if (locate == null) {
            memoryFile = new MemoryFile(memoryFile, name, false);
        } else {
            memoryFile = locate;
        }
        CalendarBuilder builder = new CalendarBuilder();
        net.fortuna.ical4j.model.Calendar calendar;
        try {
            calendar = builder.build(req.getInputStream());
        } catch (ParserException e) {
            throw new RuntimeException(e);
        }
        for (Iterator i = calendar.getComponents().iterator(); i.hasNext();) {
            Component component = (Component) i.next();
            System.out.println("Component [" + component.getName() + "]");

            if (component instanceof VEvent) {
                VEvent event = (VEvent) component;
                LOGGER.info("Create event " + event.getSummary().getValue() + " in "
                        + event.getLocation().getValue() + " from " + event.getStartDate().getDate() + " to "
                        + event.getEndDate().getDate());

            }
        }
        CalendarOutputter outputter = new CalendarOutputter();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            outputter.output(calendar, out);
        } catch (ValidationException e) {
            throw new RuntimeException(e);
        }
        memoryFile.setContent(out.toByteArray());
        Getcontenttype getcontenttype = new Getcontenttype();
        getcontenttype.getContent().add(req.getHeader("Content-Type"));
        memoryFile.getProp().setGetcontenttype(getcontenttype);
        Prop prop = memoryFile.getProp();
        prop.setResourcetype(new Resourcetype());
        Getetag getetag = new Getetag();
        getetag.getContent().add("\"" + System.currentTimeMillis() + "\"");
        prop.setGetetag(getetag);
        resp.setHeader("ETag", prop.getGetetag().getContent().get(0));
        resp.setStatus(HttpServletResponse.SC_CREATED);
    }

    @Override
    public void doOptions(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setHeader("DAV", "1");
        resp.setHeader("Allow", "DELETE, GET, OPTIONS, POST, PROPFIND, PUT, REPORT");
    }
}