/*
 * $Id: WebDavServlet.java,v 1.124 2008/04/17 10:01:00 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2010 
 *           (c) Universität Paderborn 2010 
 * Licensed under EUPL 1.1. 
 *
 * $Log: WebDavServlet.java,v $
 * Revision 1.124  2008/04/17 10:01:00  marbobo
 * + Velocity-Templates angepasst
 *
 * Revision 1.123  2007/10/30 12:12:27  marbobo
 * + removed unnecessary statements
 *
 * Revision 1.122  2007/08/01 14:56:49  marbobo
 * + WebDav-Logging implementiert
 * - Sessionbegrenzung wird nicht mehr auf WebDav angewendet
 *
 * Revision 1.121  2007/07/30 09:38:51  marbobo
 * + Bugfix: Zeichensatzkodierung wird nun korrekt geparst
 *
 * Revision 1.120  2007/07/20 14:18:05  marbobo
 * + Max. Anzahl von Benutzern die im System gleichzeitig angemeldet sein dürfen ist jetzt konfiguriebar
 *
 * Revision 1.119  2007/06/29 13:40:32  marbobo
 * + Bugfix (Vermeiden von doppel Benachrichtigungen)
 *
 * Revision 1.118  2007/06/15 11:48:44  korowa
 * Falls noch keine Änderungen an dem gefragten Ressource vorgenommen wurden, wird CreationDate als LastModifiedDate presentiert
 */
package vkc.webdav;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.xml.sax.InputSource;

import vkc.db.VKCDatabase;

import vkc.db.common.ConversionStatus;

import vkc.db.entities.VKCAclType;
import vkc.db.entities.VKCCategory;
import vkc.db.entities.VKCDocument;
import vkc.db.entities.VKCDocumentType;
import vkc.db.entities.VKCSubCategory;
import vkc.db.entities.VKCUser;
import vkc.db.helper.UserHelper;

import vkc.db.serializer.VKCAclSerializer;
import vkc.db.serializer.VKCCategorySerializer;
import vkc.db.serializer.VKCDocumentSerializer;
import vkc.db.serializer.VKCDocumentTypeSerializer;

import vkc.exception.DBException;

import vkc.misc.VKCLogManager;

import vkc.search.Search;

import vkc.security.PermissionManager;
import vkc.security.VKCAcl;

import vkc.server.archive.DataStoreException;

import vkc.util.CategoryUtils;
import vkc.util.DocumentUtils;
import vkc.util.ServletUtils;
import vkc.util.VKCStringUtils;

import vkc.view.WebShare;

import vkc.view.form.SysLinkForm;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import java.net.URL;
import java.net.URLDecoder;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import java.sql.Timestamp;

import java.text.SimpleDateFormat;

import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.Vector;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;


public final class WebDavServlet extends HttpServlet {
    private static Object wrongAuthenthication = new Object();

    //Dokumenttypen definieren
    public static int HTML_DOC_TYPE = 4;
    public static int NOTE_DOC_TYPE = 11;
    public static int LINK_DOC_TYPE = 12;
    public static int HTM_DOC_TYPE = 22;
    private static boolean debug = false;
    private static int requestCounter = 0;
    private static String[] categoryProperties = {
            "displayname", "resourcetype", "getcontentlength", "creationdate",
            "getlastmodified", "getlastaccessed", "supportedlock",
            "iscollection", "lastaccessed", "isstructureddocument", "isroot"
        };
    private static String[] documentProperties = {
            "creationdate", "displayname", "getcontentlanguage",
            "getcontentlength", "getcontenttype", "getlastmodified",
            "getlastaccessed", "resourcetype", "supportedlock", "iscollection",
            "lastaccessed"
        };
    private static String rootUrl;
    private static TimeZone timeZone = TimeZone.getDefault();
    private static final String METHOD_GET = "GET";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_PROPFIND = "PROPFIND";
    private static final String METHOD_PROPPATCH = "PROPPATCH";
    private static final String METHOD_MKCOL = "MKCOL";
    private static final String METHOD_COPY = "COPY";
    private static final String METHOD_MOVE = "MOVE";
    private static final String METHOD_LOCK = "LOCK";
    private static final String METHOD_UNLOCK = "UNLOCK";
    private static final int INFINITY = 3; // To limit tree browsing a bit
    private static final int LOCK_CREATION = 0;
    private static final int LOCK_REFRESH = 1;
    private static final int DEFAULT_TIMEOUT = 3600;
    private static final int MAX_TIMEOUT = 604800;
    private static final String DEFAULT_NAMESPACE = "DAV:";
    private static final SimpleDateFormat[] formats = {
            new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
            new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
            new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
        };
    private final static TimeZone gmtZone = TimeZone.getTimeZone("GMT");

    static {
        formats[0].setTimeZone(gmtZone);
        formats[1].setTimeZone(gmtZone);
        formats[2].setTimeZone(gmtZone);
    }

    private static final char[] hexadecimal = {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
            'e', 'f'
        };

    /**
      * JNDI resources name.
      */
    protected static final String RESOURCES_JNDI_NAME = "java:/comp/Resources";

    /**
      * MD5 message digest provider.
      */
    protected static MessageDigest md5Helper;

    /**
     * PROPFIND - Specify a property mask.
     */
    private static final int FIND_BY_PROPERTY = 0;

    /**
     * PROPFIND - Display all properties.
     */
    private static final int FIND_ALL_PROP = 1;

    /**
     * PROPFIND - Return property names.
     */
    private static final int FIND_PROPERTY_NAMES = 2;

    /** Simple date format for the creation date ISO representation (partial). */
    private static final SimpleDateFormat creationDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",
            Locale.US);
    private static final SimpleDateFormat lastModifiedDateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'",
            Locale.US);
    private static final SimpleDateFormat lastAccessedDateFormat = new SimpleDateFormat("M/d/yyy h:mm:ss a",
            Locale.US);

    static {
        creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    private static Hashtable pathIds = new Hashtable();
    private static long pathIdsCreated = System.currentTimeMillis();

    /**
     * Repository of the lock-null resources.
     *
     * <p>
     * Key : path of the collection containing the lock-null resource<br>
     * Value : Vector of lock-null resource which are members of the
     * collection. Each element of the Vector is the path associated with the
     * lock-null resource.
     * </p>
     */

    /** Size of file transfer buffer in bytes. */
    static final String realm = "VKC";

    /** The set of SimpleDateFormat formats to use in getDateHeader(). */

    //private String RESOURCES_ATTR = "org.apache.catalina.resources";
    private static Vector collectionLocks = new Vector();

    /**
     * Repository of the locks put on single resources.
     *
     * <p>
     * Key : path <br> Value : LockInfo
     * </p>
     */
    private static Hashtable resourceLocks = new Hashtable();
    private static Hashtable lockNullResources = new Hashtable();
    private static int ISO88591 = 1;
    private static int UTF8 = 2;
    private static int uriEncoding = -1;
    private Vector knownCatProperties = new Vector();
    private Vector knownDocProperties = new Vector();

    //Codierung des aktuellen Requests und Response-Kodierung
    private String reqEncoding = "UTF-8";
    private String resEncoding = "UTF-8";
    private int pathStart = -1;

    public void init() throws ServletException {
        super.init();

        //RootUrl setzen
        try {
            rootUrl = WebShare.getRootURL() + WebShare.getContextPath();
        } catch (Exception e) {
            VKCLogManager.errorLog(this, "13", "Can't retrieve value for RootURL!", e);
        }

        //Property-Vektoren initialisieren
        for (int i = 0; i < categoryProperties.length; i++) {
            knownCatProperties.addElement(categoryProperties[i]);
        }

        for (int i = 0; i < documentProperties.length; i++) {
            knownDocProperties.addElement(documentProperties[i]);
        }

        //Load the MD5 helper used to calculate signatures.
        try {
            md5Helper = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            VKCLogManager.errorLog(this, "", e.getMessage(), e);
            throw new IllegalStateException();
        }
    }

    /**
     * Service Service Routine, die alle ankommenden Requests vom Client
     * bearbeitet und je nach Client-Anforderung in die entsprechende Methode
     * (doGet_HTTP, doPut, ..) weiterleitet, die die Anfrage dann bearbeitet.
     *
     * @param req Anfrage vom Client die verarbeitet wird.
     * @param res Antwort vom Server die generiert wird.
     *
     * @throws IOException
     * @throws ServletException
     */
    public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        res.setHeader("Pragma", "no-cache");
        res.setHeader("Cache-Control", "no-cache");
        res.setHeader("Connection", "close");
        res.setDateHeader("Expires", 0);

        //Kodierung der URI einmalig bestimmen
        if (uriEncoding == -1) {
            String uri = req.getRequestURI();

            if (uri.indexOf("_ENCODING_TEST_") > 0) {
                if (req.getRequestURI().indexOf('%') > 0) {
                    uriEncoding = ISO88591;

                    if (debug) {
                        System.out.println("URIEncoding: ISO-8859-1");
                    }
                } else {
                    uriEncoding = UTF8;

                    if (debug) {
                        System.out.println("URIEncoding: UTF-8");
                    }
                }

                return;
            }
        }

        //rootUrl = req.getContextPath();
        if (debug) {
            System.out.println("rootUrl:" + rootUrl);
            System.out.println(dumpHeader(req));
        }

        HttpSession session = req.getSession();
        VKCUser user = (VKCUser) session.getAttribute(WebShare.USER_KEY);

        if (user == null) {
            // Authentifizierung Start
            if (req.getHeader("Authorization") == null) {
                res.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
                sendWebDavError(res, HttpServletResponse.SC_UNAUTHORIZED);

                if (debug) {
                    System.out.println("No authorization header!");
                }

                return;
            }

            String userpassEncoded = req.getHeader("Authorization").substring(6);
            String userpassDecoded = Base64Decoder.decode(userpassEncoded);

            int x = userpassDecoded.indexOf(":");
            String authuser = userpassDecoded.substring(0, x);
            String authpass = userpassDecoded.substring(x + 1, userpassDecoded.length());

            
            try {
                user = UserHelper.authenticate(VKCStringUtils.filterUnicodeChars(authuser), authpass, req);
            } catch (Exception e) {
                if (authuser.length() > 0) {
                    //Falscheingaben 5 sec verzögern, wegen Ausspähung von Benutzer-Accounts
                    synchronized (wrongAuthenthication) {
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException iex) {
                        }
                    }
                }

                res.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
                sendWebDavError(res, HttpServletResponse.SC_UNAUTHORIZED);

                return;
            }
             
            // Session-Timeout setzen
            session.setMaxInactiveInterval(300);
            
            // Eingeloggten User in der Session speichern
            session.setAttribute(WebShare.USER_KEY, user);
            //Authentifizierung Ende
        }

        if (debug) {
            System.out.println("User: " + user);
        }

        if (pathStart < 0) {
            String uri = req.getRequestURI();
            String servletPath = req.getServletPath();
            pathStart = uri.indexOf(servletPath) + servletPath.length();
        }

        //Codierung des Requests bestimmen
        reqEncoding = req.getContentType();

        String agentString = req.getHeader("user-agent");
        boolean agent11 = false;

        if (agentString.compareTo("Microsoft Data Access Internet Publishing Provider DAV 1.1") == 0) {
            agent11 = true;
        }

        if (reqEncoding != null) {
            reqEncoding = reqEncoding.toUpperCase();

            int charsetPos = reqEncoding.indexOf("CHARSET=");

            if (charsetPos >= 0) {
                reqEncoding = reqEncoding.substring(charsetPos + "CHARSET=".length(), reqEncoding.length());

                int sepPos = reqEncoding.indexOf(';');

                if (sepPos >= 0) {
                    reqEncoding = reqEncoding.substring(0, sepPos);
                }

                //Anführungszeichen entfernen
                reqEncoding = reqEncoding.replace('"', ' ');
                reqEncoding = reqEncoding.replace('\'', ' ').trim();
            } else {
                //Keine Kodierung gefunden
                if (agent11) {
                    reqEncoding = "iso-8859-1";
                } else {
                    reqEncoding = "UTF-8";
                }
            }
        } else {
            //Keine Kodierung gefunden
            if (agent11) {
                reqEncoding = "iso-8859-1";
            } else {
                reqEncoding = "UTF-8";
            }
        }

        //Laut RFC müsste der Response nach UTF-8 konvertiert werden, der M$-Explorer
        //versteht es aber leider nicht, deshalb so:
        //resEncoding = reqEncoding;
        if (debug) {
            System.out.println("Encoding: " + reqEncoding);
        }

        String method = req.getMethod();

        // Verteilen des ankommenden Requests. Die Variable method speichert die Request-Methode
        if (method.equals(METHOD_GET)) {
            doGet_http(req, res);
        } else if (method.equals(METHOD_HEAD)) {
            doHead_http(req, res);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, res);
        } else if (method.equals(METHOD_PUT)) {
            doPut_http(req, res);
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req, res);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete_http(req, res);
        } else if (method.equals(METHOD_PROPFIND)) {
            doPropfind(req, res);
        } else if (method.equals(METHOD_PROPPATCH)) {
            doProppatch(req, res);
        } else if (method.equals(METHOD_MKCOL)) {
            doMkcol(req, res);
        } else if (method.equals(METHOD_COPY)) {
            doCopy(req, res);
        } else if (method.equals(METHOD_MOVE)) {
            doMove(req, res);
        } else if (method.equals(METHOD_LOCK)) {
            doLock(req, res, user);
        } else if (method.equals(METHOD_UNLOCK)) {
            doUnlock(req, res);
        } else {
            // DefaultServlet processing
            super.service(req, res);
        }
    }

    /**
      * Convert ISO8859-1 format string (which is the default sent by IE
      * to the UTF-8 format that the database is in.
      * http://marc.theaimsgroup.com/?l=tomcat-user&m=105524426515137&w=2
      */
    private static String toUTF8(String isoString) {
        String utf8String = null;

        if ((null != isoString) && !isoString.equals("")) {
            try {
                byte[] stringBytesISO = isoString.getBytes("ISO-8859-1");
                utf8String = new String(stringBytesISO, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                // As we can't translate just send back the best guess.
                VKCLogManager.errorLog("WebDavServlet", "14", "UnsupportedEncodingException is: ", e);
                utf8String = isoString;
            }
        } else {
            utf8String = isoString;
        }

        return utf8String;
    }

    /**
     *  Dump the incoming HttpServletRequest object
     *
     * @param  req the HttpServletRequest object to dump
     * @return  Description of the Return Value
     * @exception  ServletException if any error occurs
     * @exception  IOException      if any error occurs
     */
    private String dumpHeader(HttpServletRequest req) {
        String queryString = null;

        // this routine simple dumps out HTTP variables and headers.
        StringBuffer sb = new StringBuffer();
        String s = null;
        sb.append((requestCounter++) + ">>>>>>>>>>>>>>>>>>>>>>>>>>\n");
        sb.append("Request URL:\n");
        sb.append(" http://" + req.getServerName() + ":" + req.getServerPort() + req.getRequestURI() + "\n");

        sb.append("Request Info:\n");
        sb.append(" Request Method: " + req.getMethod()).append("\n");
        sb.append(" Request URI: " + req.getRequestURI()).append("\n");
        sb.append(" Request URL: " + req.getRequestURL()).append("\n");
        sb.append(" Request Protocol: " + req.getProtocol()).append("\n");
        sb.append(" Servlet Path: " + req.getServletPath()).append("\n");
        sb.append(" Path Info: " + req.getPathInfo()).append(" hex:");

        if (req.getPathInfo() != null) {
            char[] pathInfo = req.getPathInfo().toCharArray();

            for (int i = 0; i < pathInfo.length; i++) {
                sb.append(Integer.toHexString(pathInfo[i]) + " ");
            }

            sb.append("UTF8:" + toUTF8(req.getPathInfo()));
        }

        sb.append("\n");

        sb.append(" Path Translated: " + req.getPathTranslated()).append("\n");
        sb.append(" Context Path: " + req.getContextPath()).append("\n");
        sb.append(" Content Length: " + req.getContentLength()).append("\n");
        sb.append(" Content Type: " + req.getContentType()).append("\n");

        queryString = req.getQueryString();

        sb.append(" QueryString: " + queryString).append("\n");
        sb.append(" Server Name: " + req.getServerName()).append("\n");
        sb.append(" Server Port: " + req.getServerPort()).append("\n");
        sb.append(" Remote User: " + req.getRemoteUser()).append("\n");
        sb.append(" Remote Host: " + req.getRemoteHost()).append("\n");
        sb.append(" Remote Address: " + req.getRemoteAddr()).append("\n");
        sb.append(" Authentication Scheme: " + req.getAuthType()).append("\n\n");

        // return parameters (name/value pairs)
        Enumeration Params = req.getParameterNames();

        if (Params.hasMoreElements()) {
            sb.append("Parameters:").append("\n");

            while (Params.hasMoreElements()) {
                String name = (String) Params.nextElement();
                sb.append(name + ": ");

                String[] values = req.getParameterValues(name);

                for (int x = 0; x < values.length; x++) {
                    if (x > 0) {
                        sb.append(", ");
                    }

                    sb.append(values[x]);
                }

                sb.append("\n");
            }

            sb.append("\n");
        }

        // return HTTP Headers
        Enumeration e = req.getHeaderNames();

        if (e.hasMoreElements()) {
            sb.append("Request Headers:\n");

            while (e.hasMoreElements()) {
                String name = (String) e.nextElement();
                sb.append("  " + name + ": " + req.getHeader(name)).append("\n");
            }

            sb.append("\n");
        }

        sb.append("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<END OF DUMP\n");

        s = sb.toString();

        return s;
    }

    /**
     * displayDocument Liest den übergebenen InputStream ein und schickt ihn
     * (gepuffert) wieder über einen OutputStream auf einen Writer. Der wird
     * durch ein assoziertes Programm repräsentiert. Bei .doc Dateien wird
     * beispielsweise Word aufgerufen.
     *
     * @param response Antwort vom Server, die generiert wird
     * @param stream Ein Outputstream
     * @param mimType Typ der Datei
     *
     * @throws IOException
     */
    private void displayDocument(InputStream stream, String mimeType,
        HttpServletResponse response) throws IOException {
        response.setContentType(mimeType);
        response.setBufferSize(8192);

        OutputStream out = response.getOutputStream();

        int bytesRead = 0;
        byte[] buffer = new byte[8192];

        while ((bytesRead = stream.read(buffer, 0, 8192)) != -1) {
            out.write(buffer, 0, bytesRead);
        }

        stream.close();
        out.close();
    }

    private VKCDocumentType mimeTypeCatcher(HttpServletRequest req) {
        String path = getPath(req);
        int dotIndex = path.lastIndexOf('.');
        VKCDocumentType docType = null;

        if ((dotIndex >= 0) && ((dotIndex + 1) < path.length())) {
            String suf = path.substring(dotIndex + 1);
            suf = suf.toUpperCase().trim();

            docType = VKCDocumentTypeSerializer.getDocumentTypeByExtension(suf);
        }

        if (docType != null) {
            return docType;
        }

        return new VKCDocumentType();
    }

    /**
     * Liefert die Kategorie-ID auf den sich der übergebene Pfad bezieht.
     *
     * @param path Pfad der gesucht werden soll
     * @return ID der Kategorie
     */
    private Integer retrieveCategoryId(String path) {
        if (debug) {
            System.out.println("retrieveCategoryId: " + path);
        }

        Integer catId = null;

        if ((path.length() == 0) || (path.equals("/"))) {
            if (debug) {
                System.out.println("root catId: " + VKCCategory.ROOT_CATEGORY_ID);
            }

            return new Integer(VKCCategory.ROOT_CATEGORY_ID);
        }

        //Testen ob die ID aus dem Cache ermittelt werden kann
        if (WebShare.database.getStructureChangedAt() < pathIdsCreated) {
            catId = (Integer) pathIds.get(path);

            if (catId != null) {
                if (debug) {
                    System.out.println("catId from cache: " + catId);
                }

                return catId;
            }
        } else {
            //Hastable löschen, weil sie veraltet ist.
            pathIds.clear();
            pathIdsCreated = System.currentTimeMillis();
        }

        //Pfad in der Datenbank suchen
        try {
            catId = CategoryUtils.retrieveCategoryIdByPath(path);

            if (catId != null) {
                pathIds.put(path, catId);

                if (debug) {
                    System.out.println("catId from database: " + catId);
                }

                return catId;
            }
        } catch (DBException e) {
        }

        String catName = path.substring(path.lastIndexOf('/') + 1);

        //Ist die Kategorie evtl. umbenannt worden?
        try {
            if (catName.indexOf("(CID ") == 0) {
                int idEnd = catName.indexOf(')');
                String id = catName.substring(5, idEnd);
                catId = new Integer(id);

                //nachsehen ob die ID auch gültig ist
                Integer parentCatId = retrieveParentCategoryId(path);
                VKCCategory cat = null;

                cat = VKCCategorySerializer.retrieveCategory(catId);

                //Gültigkeit prüfen
                if ((cat == null) || (!cat.getParentCategoryId().equals(parentCatId))) {
                    catId = null;
                }
            }
        } catch (Exception e) {
            return null;
        }

        if (debug) {
            System.out.println("catId from CID: " + catId);
        }

        return catId;
    }

    /**
     * Liefert die Parent-Kategorie-ID des übergebenen Pfades.
     *
     * @param path Pfad dessen Parent gesucht werden soll
     * @return ID der Kategorie
     */
    private Integer retrieveParentCategoryId(String path) {
        if (debug) {
            System.out.println("retrieveParentCategoryId: " + path);
        }

        String parentPath = new File(path).getParent().replace('\\', '/');

        return retrieveCategoryId(parentPath);
    }

    /**
     * Liefert die Dokument-ID auf den sich der übergebene Pfad bezieht.
     *
     * @param path Pfad der gesucht werden soll
     * @return ID des Dokuments
     */
    private Integer retrieveDocumentId(String path) {
        String docName;
        String catPath;
        Integer docId;

        if (debug) {
            System.out.println("docPath: " + path);
        }

        int lastSlash = path.lastIndexOf('/');
        catPath = path.substring(0, lastSlash);
        docName = path.substring(lastSlash + 1);

        //Datei in der Datenbank suchen
        Integer catId = retrieveCategoryId(catPath);

        if (debug) {
            System.out.println("catId: " + catId);
        }

        try {
            Collection docIds = DocumentUtils.retrieveDocumentIdsByCategoryIdAndName(catId, docName);
            docId = (Integer) docIds.iterator().next();
        } catch (Exception e) {
            docId = null;
        }

        //Ist das Dokument evtl. umbenannt worden?
        try {
            if ((docId == null) && (docName.indexOf("(DID ") == 0)) {
                int idEnd = docName.indexOf(')');
                String id = docName.substring(5, idEnd);
                docId = new Integer(id);

                //nachsehen ob die ID auch gültig ist
                VKCDocument doc = null;

                doc = VKCDocumentSerializer.retrieveDocument(docId);

                //Gültigkeit prüfen
                if ((doc != null) && catId.equals(doc.getCategoryId())) {
                } else {
                    docId = null;
                }
            }
        } catch (Exception e) {
            return null;
        }

        if (debug) {
            System.out.println("docId: " + docId);
        }

        return docId;
    }

    /**
     * Liefert die Dokument-ID auf den sich der übergebene Pfad bezieht.
     *
     * @param path Pfad der gesucht werden soll
     * @return ID des Dokuments
     */
    private Integer retrieveFirstDocumentId(String path) {
        String docName;
        String catPath;
        Integer docId;

        if (debug) {
            System.out.println("docPath: " + path);
        }

        int lastSlash = path.lastIndexOf('/');
        catPath = path.substring(0, lastSlash);
        docName = path.substring(lastSlash + 1);

        //Datei in der Datenbank suchen
        Integer catId = retrieveCategoryId(catPath);

        if (debug) {
            System.out.println("catId: " + catId);
        }

        try {
            Collection docIds = DocumentUtils.retrieveDocumentIdsByCategoryIdAndName(catId, docName);
            docId = (Integer) docIds.iterator().next();
            docId = VKCDocumentSerializer.getFirstVersionDocId(docId);
        } catch (Exception e) {
            docId = null;
        }

        //Ist das Dokument evtl. umbenannt worden?
        if ((docId == null) && (docName.indexOf("(DID ") == 0)) {
            int idEnd = docName.indexOf(')');
            String id = docName.substring(5, idEnd);
            docId = new Integer(id);

            //nachsehen ob die ID auch gültig ist
            VKCDocument doc = null;

            try {
                doc = VKCDocumentSerializer.retrieveDocument(VKCDocumentSerializer.getTopVersionDocId(docId));
            } catch (DBException e1) {
            }

            //Gültigkeit prüfen
            if ((doc != null) && catId.equals(doc.getCategoryId())) {
                try {
                    docId = doc.getFirstVersionDocumentId();
                } catch (DBException e) {
                    docId = null;
                }
            } else {
                docId = null;
            }
        }

        if (debug) {
            System.out.println("docId: " + docId);
        }

        return docId;
    }

    /**
     * Aktualisiert den Zugriffszähler, Leser, Zugriffsdatum
     *
     * @param doc für dieses Dokument
     * @param request mit diesem Request
     *
     * @throws DBException falls beim DB update etwas schief läuft
     * @throws IOException 
     */
    private void updateDocumentAccess(VKCDocument doc, HttpServletRequest req)
        throws DBException, IOException {
        doc.setAccessCount(new Integer(doc.getAccessCount().intValue() + 1));
        doc.setLastAccessed(new Date(System.currentTimeMillis()));
        doc.setLastAccessorId(ServletUtils.getUser(req).getUserId());

        VKCDocumentSerializer.updateAccessData(doc);
        Search.updateDocument(doc, false);
    }

    /**
      * Aktualisiert den Zugriffszähler, Leser, Zugriffsdatum
      *
      * @param doc für dieses Dokument
      * @param request mit diesem Request
      *
      * @throws DBException falls beim DB update etwas schief läuft
      */
    private void updateCategoryAccess(VKCCategory cat, HttpServletRequest req)
        throws DBException {
        Integer accessCount = cat.getAccessCount();

        if (accessCount == null) {
            accessCount = new Integer(0);
        }

        cat.setAccessCount(new Integer(accessCount.intValue() + 1));
        cat.setLastAccessed(new Date(System.currentTimeMillis()));
        cat.setLastAccessorId(ServletUtils.getUser(req).getUserId());

        VKCCategorySerializer.updateAccessInformation(cat);
    }

    /**
     * doGet Öffnet eine Datei
     *
     * @param req Anfrage vom Client, die verarbeitet wird
     * @param res Antwort vom Server, die generiert wird
     *
     * @throws IOException
     * @throws DBException
     */
    protected void doGet_http(HttpServletRequest req, HttpServletResponse res) {
        VKCLogManager.webdavAccessLog(req, METHOD_GET + " called on path:" + getPath(req));

        InputStream stream = null;

        String path = getPath(req);

        //DocID aus dem Request holen
        Integer catId = retrieveCategoryId(path);

        if (debug) {
            System.out.println("catId: " + catId);
        }

        if (catId == null) {
            try {
                Integer docId = retrieveDocumentId(path);

                Integer parentId = CategoryUtils.retrieveCategoryIdForDocument(docId);

                if (hasPermission(VKCAclSerializer.getAclByCategoryId(parentId), req, 'r')) {
                    //GET bezog sich auf ein Dokument
                    VKCDocument vkcDoc = VKCDocumentSerializer.retrieveDocument(docId);

                    //ETag wird wegen eines Bugs in Office2000 gebraucht
                    res.addHeader("ETag", vkcDoc.getDocumentId().toString());

                    int docTypeId = vkcDoc.getDocTypeId().intValue();

                    if (docTypeId != LINK_DOC_TYPE) {
                        // Stream aus Archiv holen
                        stream = WebShare.archive.getInputStream(vkcDoc.getArchiveId().toString());
                    } else {
                        if (debug) {
                            System.out.println("URL: " + vkcDoc.getName());
                        }

                        stream = new URL(vkcDoc.getName()).openConnection().getInputStream();
                    }

                    //WebCopy?
                    boolean webCopy = false;

                    if (((docTypeId == HTML_DOC_TYPE) || (docTypeId == HTM_DOC_TYPE)) && (new Integer(1).equals(vkcDoc.getZipped()))) {
                        webCopy = true;
                    }

                    // Die Stream-Variable wird weitergegeben.
                    if (webCopy) {
                        displayDocument(stream, "application/zip", res);
                    } else {
                        displayDocument(stream, vkcDoc.getDocumentType().getMimeType(), res);
                    }

                    //Statistik aktualisieren
                    updateDocumentAccess(vkcDoc, req);
                } else {
                    //keine ausreichenden Rechte
                    sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
                }
            } catch (Exception e1) {
                if (debug) {
                    VKCLogManager.errorLog(this, "", e1.getMessage(), e1);
                }

                sendWebDavError(res, WebdavStatus.SC_NOT_FOUND);
            }
        } else {
            //GET bezog sich auf eine Kategorie

            //Redirect durchführen
            try {
                res.sendRedirect(req.getContextPath() +
                    SysLinkForm.getCategorySysLink(catId,
                        SysLinkForm.LOGINTYPE_NORMAL_USER));
            } catch (IOException e) {
            }
        }
    }

    /**
     * doDelete Methode zum löschen einer Datei oder eines Verzeichnisses
     *
     * @param req Anfrage vom Client, die verarbeitet wird
     * @param docId Nummer des Dokuments in der Datenbank
     */
    protected void doDelete_http(HttpServletRequest req, HttpServletResponse res) {
        VKCLogManager.webdavAccessLog(req, METHOD_DELETE + " called on path:" + getPath(req));

        Set docIDs = new HashSet();
        Set catIDs = new HashSet();
        VKCUser user = (VKCUser) req.getSession().getAttribute(WebShare.USER_KEY);

        if (isLocked(req)) {
            try {
                res.sendError(WebdavStatus.SC_LOCKED);
            } catch (IOException e1) {
            }

            return;
        }

        Integer sourceId = retrieveCategoryId(getPath(req));

        Integer catId;

        try {
            if (sourceId == null) {
                //Source ist Dokument
                Integer docId = retrieveDocumentId(getPath(req));

                if (docId == null) {
                    sendWebDavError(res, WebdavStatus.SC_NOT_FOUND, req.getRequestURI());

                    return;
                }

                docIDs.add(docId);

                catId = CategoryUtils.retrieveCategoryIdForDocument(docId);
            } else {
                //Source ist ein Verzeichnis
                catId = sourceId;
                catIDs.add(catId);
            }

            VKCCategory sourceCat = VKCCategorySerializer.retrieveCategory(catId);

            if ((sourceId == null) &&
                    hasPermission(sourceCat.getAcl(), req, 'd')) {
                //Dokument löschen
                WebShare.database.delete(sourceCat.getCategoryId(), catIDs, docIDs, user);
            } else {
                if (hasPermission(sourceCat.getAcl(), req, 'D')) {
                    //Kategorie löschen
                    WebShare.database.delete(sourceCat.getCategoryId(), catIDs, docIDs, user);
                } else {
                    //keine ausreichenden Rechte
                    sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
                }
            }
        } catch (DBException e) {
            VKCLogManager.errorLog(this, "3", "Can't delete!", e);

            sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * doPut Ermöglicht Dateien auf den Server zu laden und in das VKC System
     * einzustellen
     *
     * @param req Anfrage vom Client, die verarbeitet wird
     *
     * @throws DataStoreException
     * @throws DBException
     * @throws IOException
     * @throws ServletException
     */
    protected void doPut_http(HttpServletRequest req, HttpServletResponse res) {
        VKCLogManager.webdavAccessLog(req, METHOD_PUT + " called on path:" + getPath(req));

        VKCDocument document = new VKCDocument();

        if (isLocked(req)) {
            try {
                res.sendError(WebdavStatus.SC_LOCKED);
            } catch (IOException e1) {
            }

            return;
        }

        InputStream in = null;

        try {
            in = req.getInputStream();
        } catch (IOException e) {
            sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);

            return;
        }

        try {
            String requestPath = getPath(req);

            String name = null;

            Integer preDocId = retrieveDocumentId(requestPath);
            VKCDocument preDoc = null;

            if (preDocId != null) {
                //Name von Vorgängerdokument holen
                try {
                    preDoc = VKCDocumentSerializer.retrieveDocument(preDocId);
                    name = preDoc.getName();
                } catch (DBException e1) {
                }
            }

            if (name == null) {
                //FileName holen 
                int lastSlash = requestPath.lastIndexOf("/");
                name = VKCStringUtils.filterUnicodeChars(URLUtil.URLDecode(
                            requestPath.substring(lastSlash + 1), reqEncoding));

                //                if (name.indexOf("(DID ") == 0) {
                //                    name = name.substring(name.indexOf(')') + 2);
                //                }
            }

            VKCDocumentType docType = mimeTypeCatcher(req);
            Integer categoryId = retrieveParentCategoryId(getPath(req));

            if (categoryId == null) {
                sendWebDavError(res, WebdavStatus.SC_NOT_FOUND,
                    req.getRequestURI());

                return;
            }

            VKCAcl parentCatAcl = null;

            try {
                parentCatAcl = VKCAclSerializer.getAclByCategoryId(categoryId);
            } catch (DBException e2) {
                sendWebDavError(res, WebdavStatus.SC_NOT_FOUND,
                    req.getRequestURI());

                return;
            }

            if (hasPermission(parentCatAcl, req, 'w')) {
                Integer userId = ((VKCUser) req.getSession()
                                               .getAttribute(WebShare.USER_KEY)).getUserId();

                //Stream im Archiv speichern
                String archiveId = WebShare.archive.store(in);

                //Eigenschaften des Dokuments setzen
                document.setPublished(new Date(System.currentTimeMillis()));
                document.setPublisherId(userId);

                // obsolete code: jp of 19.1.2005
                //document.setLastAccessed(new Date(System.currentTimeMillis()));
                //document.setLastAccessorId(userId);
                //document.setLastChanged(new Date(System.currentTimeMillis()));
                // set tracking data to null
                document.setLastAccessed(null);
                document.setLastAccessorId(null);
                document.setLastChanged(null);
                document.setLastChangerId(null);

                document.setArchiveId(archiveId);
                document.setName(name);
                document.setDocTypeId(docType.getDocTypeId());
                document.setDocSize(new Long(req.getContentLength()));

                if (preDoc != null) {
                    //neue Version mit alten Metadaten anlegen
                    document.setPreDocId(preDocId);
                    document.setVersionNo(new Integer(preDoc.getVersionNo()
                                                            .intValue() + 1));
                    document.setTitle(preDoc.getTitle());
                    document.setKeywords(preDoc.getKeywords());
                    document.setDescription(preDoc.getDescription());
                    document.setExpires(preDoc.getExpires());

                    try {
                        // Dokument aus dem Suchindex entfernen
                        Search.removeDocument(preDoc.getDocumentId(), true);
                        // Dokument aus der Kategorie entfernen
                        VKCDocumentSerializer.deleteCategoryDocument(preDoc.getDocumentId());
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }

                    //eventuelles Lock
                } else {
                    //neues Dokument anlegen
                    document.setPreDocId(null);

                    int lastDotId = name.lastIndexOf('.');

                    if (lastDotId > 0) {
                        String title = name.substring(0, lastDotId);
                        document.setTitle(title);
                    } else {
                        document.setTitle(name);
                    }
                }

                document.setCategoryId(categoryId);

                try {
                    if (docType.isConvertible()) {
                        VKCDocumentSerializer.saveDocument(document, false, true);

                        ConversionStatus cs = new ConversionStatus();
                        cs.setDocumentId(document.getDocumentId());
                        cs.setSendmail(false);
                        cs.makePersistent();
                    } else {
                        VKCDocumentSerializer.saveDocument(document, true, true);
                    }
                } catch (DBException e3) {
                    if (!e3.getMessage().equals(VKCDatabase.QUOTA_EXCEEDED)) {
                        VKCLogManager.errorLog(this, "2",
                            "Can't save Document!", e3);

                        if (debug) {
                            System.out.println(e3.getMessage());
                        }

                        sendWebDavError(res,
                            WebdavStatus.SC_INTERNAL_SERVER_ERROR);

                        return;
                    }

                    if (archiveId != null) {
                        WebShare.archive.delete(archiveId);
                    }

                    sendWebDavError(res, WebdavStatus.SC_INSUFFICIENT_STORAGE);

                    return;
                }

                res.setStatus(WebdavStatus.SC_CREATED);
            } else {
                //keine ausreichenden Rechte
                sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
            }
        } catch (DataStoreException e) {
            VKCLogManager.errorLog(this, "1", "Can't write to Datastore!", e);

            sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * doHead_HTTP Liefert Informationen über die Existenz der angefragte Ressource.
     *
     * @param request Anfrage vom Client, die verarbeitet wird
     * @param response Antwort vom Server, die generiert wird
     *
     * @throws IOException
     * @throws ServletException
     */
    protected void doHead_http(HttpServletRequest req, HttpServletResponse res) {
        VKCLogManager.webdavAccessLog(req, METHOD_HEAD + " called on path:" + getPath(req));

        try {
            Integer resourceId = retrieveCategoryId(getPath(req));

            if (resourceId == null) {
                //Source ist Dokument
                resourceId = retrieveDocumentId(getPath(req));

                if (resourceId == null) {
                    sendWebDavError(res, WebdavStatus.SC_NOT_FOUND);

                    return;
                }

                VKCAcl parentAcl = VKCAclSerializer.getAclByCategoryId(retrieveParentCategoryId(
                            getPath(req)));

                if (!hasPermission(parentAcl, req, 'r')) {
                    sendWebDavError(res, HttpServletResponse.SC_UNAUTHORIZED);
                } else {
                    VKCDocument document = VKCDocumentSerializer.retrieveDocument(resourceId);
                    res.setHeader("Content-Type",
                        document.getDocumentType().getMimeType());
                    res.setHeader("Content-Length",
                        document.getDocSize().toString());

                    if (document.getLastChanged() != null) {
                        res.setHeader("Last-Modified",
                            getHTTPDate(document.getLastChanged()));
                    } else {
                        //"Last-Modified" muss wegen der MSDAIPP.DLL Version 8.103.5219.0 immer gesendet werden!
                        res.setHeader("Last-Modified",
                            getHTTPDate(document.getPublished()));
                    }
                }
            } else {
                VKCAcl categoryAcl = VKCAclSerializer.getAclByCategoryId(resourceId);

                if (!hasPermission(categoryAcl, req, 'r')) {
                    sendWebDavError(res, HttpServletResponse.SC_UNAUTHORIZED);
                }
            }
        } catch (DBException e) {
            VKCLogManager.errorLog(this, "7", "Database Error!", e);

            sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
    * doOptions Legt die opertaiven Methoden, die der Client ausführen darf,
    * auf der angefragten Collection fest
    *
    * @param req Anfrage vom Client, die verarbeitet wird
    * @param resp Antwort vom Server, die generiert wird
    *
    * @throws IOException
    * @throws ServletException
    */
    protected void doOptions(HttpServletRequest req, HttpServletResponse res) {
        VKCLogManager.webdavAccessLog(req, METHOD_OPTIONS + " called on path:" + getPath(req));

        res.addHeader("DAV", "1,2");

        String methodsAllowed = null;

        // Festlegen der zulässigen Methoden
        methodsAllowed = "OPTIONS, GET, HEAD, POST, DELETE, TRACE, " +
            "PROPFIND, PROPATCH, COPY, MOVE, LOCK, UNLOCK, MKCOL, PUT";

        // Einfügen in den Options-Response-Header
        res.addHeader("Allow", methodsAllowed);

        res.addHeader("MS-Author-Via", "DAV");
        res.setContentType("text/xml");
    }

    /**
     * Liefert einen Vector aller Properties zurück die der Client in einem
     * PROPFIND erfragt.
     *
     * @param req Request
     * @return Vector mit allen Properties aus dem Request
     */
    private Vector retrievePropertiesFromPropfindRequest(HttpServletRequest req) {
        if (req.getContentLength() == 0) {
            return null;
        }

        Vector properties = null;
        int type = FIND_ALL_PROP;

        Node propNode = null;

        try {
            DocumentBuilder documentBuilder = getDocumentBuilder();

            Document document = documentBuilder.parse(new InputSource(
                        req.getInputStream()));

            // Get the root element of the document
            Element rootElement = document.getDocumentElement();
            NodeList childList = rootElement.getChildNodes();

            for (int i = 0; i < childList.getLength(); i++) {
                Node currentNode = childList.item(i);

                if (debug) {
                    System.out.println("Node: " + currentNode);
                }

                switch (currentNode.getNodeType()) {
                case Node.TEXT_NODE:
                    break;

                case Node.ELEMENT_NODE:

                    if (currentNode.getNodeName().endsWith("prop")) {
                        type = FIND_BY_PROPERTY;
                        propNode = currentNode;
                    }

                    if (currentNode.getNodeName().endsWith("propname")) {
                        type = FIND_PROPERTY_NAMES;
                    }

                    if (currentNode.getNodeName().endsWith("allprop")) {
                        type = FIND_ALL_PROP;
                    }

                    break;
                }
            }
        } catch (Exception e) {
        }

        if (type == FIND_BY_PROPERTY) {
            properties = new Vector();

            if (propNode != null) {
                NodeList childList = propNode.getChildNodes();

                for (int i = 0; i < childList.getLength(); i++) {
                    Node currentNode = childList.item(i);

                    switch (currentNode.getNodeType()) {
                    case Node.TEXT_NODE:
                        break;

                    case Node.ELEMENT_NODE:

                        String nodeName = currentNode.getNodeName();
                        String propertyName = null;

                        if (nodeName.indexOf(':') != -1) {
                            propertyName = nodeName.substring(nodeName.indexOf(':') +
                                    1);
                        } else {
                            propertyName = nodeName;
                        }

                        // href is a live property which is handled differently
                        properties.addElement(propertyName);

                        break;
                    }
                }
            }
        }

        return properties;
    }

    /**
     * Propfind helper method.
     *
     * @param generatedXML XML response to the Propfind request
     * @param isParent muss <code>true</code> sein, wenn es sich um die aktuelle
     * Kategorie und nicht um Unterkategorien handelt.
     */
    private void parseCategoryProperties(VKCSubCategory category,
        String absoluteUri, XMLWriter generatedXML, Vector propertiesVector,
        boolean isParent) {
        generatedXML.writeElement("a", "response", XMLWriter.OPENING);

        String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " +
                WebdavStatus.getStatusText(WebdavStatus.SC_OK));

        //Kategorienamen bereinigen
        String catName = cleanCatName(category.getName(),
                category.getSubCategoryId());

        // Generating href element
        generatedXML.writeElement("a", "href", XMLWriter.OPENING);

        if (isParent) {
            generatedXML.writeText(rootUrl + rewriteUrl(absoluteUri + "/"));
        } else {
            if (category.getName().indexOf("http:/") < 0) {
                generatedXML.writeText(rootUrl +
                    rewriteUrl(absoluteUri + "/" + catName + "/"));
            } else {
                generatedXML.writeText(rewriteUrl(catName));
            }
        }

        generatedXML.writeElement("a", "href", XMLWriter.CLOSING);

        Vector propertiesNotFound = new Vector();

        // Parse the list of properties
        generatedXML.writeElement("a", "propstat", XMLWriter.OPENING);
        generatedXML.writeElement("a", "prop", XMLWriter.OPENING);

        Enumeration properties = propertiesVector.elements();

        while (properties.hasMoreElements()) {
            String property = (String) properties.nextElement();

            if (property.equals("displayname")) {
                generatedXML.writeElement("a", "displayname", XMLWriter.OPENING);

                if (isParent) {
                    generatedXML.writeData(absoluteUri.substring(absoluteUri.lastIndexOf(
                                '/') + 1));
                } else {
                    generatedXML.writeData(catName);
                }

                generatedXML.writeElement("a", "displayname", XMLWriter.CLOSING);
            } else if (property.equals("creationdate")) {
                Date creationdate = category.getCreated();

                if (creationdate != null) {
                    generatedXML.writeProperty("a", "creationdate",
                        getISOCreationDate(creationdate));
                }
            } else if (property.equals("getlastmodified")) {
                Date lastmodified = category.getLastChanged();

                if (lastmodified != null) {
                    generatedXML.writeProperty("a", "getlastmodified",
                        getHTTPDate(lastmodified));
                }else if(!propertiesVector.contains("creationdate")){
                	 Date creationdate = category.getCreated();

                     if (creationdate != null) {
                         generatedXML.writeProperty("a", "getlastmodified",
                        		 getHTTPDate(creationdate));
                     }
                }
            } else if (property.equals("lastaccessed")) {
                Date lastaccessed = category.getLastAccessed();

                if (lastaccessed != null) {
                    generatedXML.writeProperty("a", "lastaccessed",
                        getDBTIMESTAMPDate(lastaccessed));
                }
            } else if (property.equals("getlastaccessed")) {
                Date lastaccessed = category.getLastAccessed();

                if (lastaccessed != null) {
                    generatedXML.writeProperty("a", "getlastaccessed",
                        getHTTPDate(lastaccessed));
                }
            } else if (property.equals("resourcetype")) {
                generatedXML.writeElement("a", "resourcetype", XMLWriter.OPENING);
                generatedXML.writeElement("a", "collection",
                    XMLWriter.NO_CONTENT);
                generatedXML.writeElement("a", "resourcetype", XMLWriter.CLOSING);
            } else if (property.equals("getcontentlength")) {
                generatedXML.writeProperty("a", "getcontentlength", "0");
            } else if (property.equals("iscollection")) {
                generatedXML.writeProperty("a", "iscollection", "1");
            } else if (property.equals("isstructureddocument")) {
                generatedXML.writeProperty("a", "isstructureddocument", "0");
            } else if (property.equals("isroot")) {
                if (category.getSubCategoryId().intValue() == VKCCategory.ROOT_CATEGORY_ID) {
                    generatedXML.writeProperty("a", "isroot", "1");
                } else {
                    generatedXML.writeProperty("a", "isroot", "0");
                }
            } else if (property.equals("supportedlock")) {
                String supportedLocks = "<lockentry>" +
                    "<lockscope><exclusive/></lockscope>" +
                    "<locktype><write/></locktype>" + "</lockentry>" +
                    "<lockentry>" + "<lockscope><shared/></lockscope>" +
                    "<locktype><write/></locktype>" + "</lockentry>";
                generatedXML.writeElement("a", "supportedlock",
                    XMLWriter.OPENING);
                generatedXML.writeText(supportedLocks);
                generatedXML.writeElement("a", "supportedlock",
                    XMLWriter.CLOSING);
            } else {
                propertiesNotFound.addElement(property);
            }
        }

        generatedXML.writeElement("a", "prop", XMLWriter.CLOSING);
        generatedXML.writeElement("a", "status", XMLWriter.OPENING);
        generatedXML.writeText(status);
        generatedXML.writeElement("a", "status", XMLWriter.CLOSING);
        generatedXML.writeElement("a", "propstat", XMLWriter.CLOSING);

        Enumeration propertiesNotFoundList = propertiesNotFound.elements();

        if (propertiesNotFoundList.hasMoreElements()) {
            status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " +
                    WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));

            generatedXML.writeElement("a", "propstat", XMLWriter.OPENING);
            generatedXML.writeElement("a", "prop", XMLWriter.OPENING);

            while (propertiesNotFoundList.hasMoreElements()) {
                generatedXML.writeElement("a",
                    (String) propertiesNotFoundList.nextElement(),
                    XMLWriter.NO_CONTENT);
            }

            generatedXML.writeElement("a", "prop", XMLWriter.CLOSING);
            generatedXML.writeElement("a", "status", XMLWriter.OPENING);
            generatedXML.writeText(status);
            generatedXML.writeElement("a", "status", XMLWriter.CLOSING);
            generatedXML.writeElement("a", "propstat", XMLWriter.CLOSING);
        }

        generatedXML.writeElement("a", "response", XMLWriter.CLOSING);
    }

    /**
     * Propfind helper method.
     *
     * @param resources Resources object associated with this context
     * @param generatedXML XML response to the Propfind request
     * @param path Path of the current resource
     * @param type Propfind type
     * @param propertiesVector If the propfind type is find properties by
     * name, then this Vector contains those properties
     */
    private void parseDocumentProperties(VKCDocument document,
        String absoluteUri, XMLWriter generatedXML, Vector propertiesVector,
        Hashtable docNames) {
        String docName = cleanDocName(document);

        if (docNames != null) {
            //Prüfen ob docName evtl. mehrfach existiert
            if (((Integer) docNames.get(docName)).intValue() > 1) {
                //ID an Namen anhängen und in renamedDocIds speichern
                docName = "(DID " + document.getDocumentId() + ") " + docName;
            }
        }

        generatedXML.writeElement("a", "response", XMLWriter.OPENING);

        String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " +
                WebdavStatus.getStatusText(WebdavStatus.SC_OK));

        // Generating href element
        generatedXML.writeElement("a", "href", XMLWriter.OPENING);
        generatedXML.writeText(rootUrl +
            rewriteUrl(absoluteUri + "/" + docName));
        generatedXML.writeElement("a", "href", XMLWriter.CLOSING);

        Vector propertiesNotFound = new Vector();

        // Parse the list of properties
        generatedXML.writeElement("a", "propstat", XMLWriter.OPENING);
        generatedXML.writeElement("a", "prop", XMLWriter.OPENING);

        Enumeration properties = propertiesVector.elements();

        while (properties.hasMoreElements()) {
            String property = (String) properties.nextElement();

            if (property.equals("creationdate")) {
                generatedXML.writeProperty("a", "creationdate",
                    getISOCreationDate(document.getPublished()));
            } else if (property.equals("getlastmodified")) {
                Date lastChanged = document.getLastChanged();

                if (lastChanged != null) {
                    generatedXML.writeProperty("a", "getlastmodified",
                        getHTTPDate(lastChanged));
                }else if(!propertiesVector.contains("creationdate") && document.getPublished()!= null){                	  
                	  generatedXML.writeProperty("a", "getlastmodified",
                      getHTTPDate(document.getPublished()));
                }
            } else if (property.equals("getlastaccessed")) {
                if (document.getLastAccessed() != null) {
                    generatedXML.writeProperty("a", "getlastaccessed",
                        getHTTPDate(document.getLastAccessed()));
                } else {
                    propertiesNotFound.addElement(property);
                }
            } else if (property.equals("lastaccessed")) {
                if (document.getLastAccessed() != null) {
                    generatedXML.writeProperty("a", "lastaccessed",
                        getDBTIMESTAMPDate(document.getLastAccessed()));
                } else {
                    propertiesNotFound.addElement(property);
                }
            } else if (property.equals("displayname")) {
                generatedXML.writeElement("a", "displayname", XMLWriter.OPENING);
                generatedXML.writeData(docName);
                generatedXML.writeElement("a", "displayname", XMLWriter.CLOSING);
            } else if (property.equals("getcontentlanguage")) {
                generatedXML.writeProperty("a", "getcontentlanguage",
                    Locale.getDefault().toString());
            } else if (property.equals("getcontentlength")) {
                generatedXML.writeProperty("a", "getcontentlength",
                    (String.valueOf(document.getDocSize())));
            } else if (property.equals("getcontenttype")) {
                String mimeType = document.getDocumentType().getMimeType();

                if (mimeType == null) {
                    mimeType = "";
                }

                generatedXML.writeProperty("a", "getcontenttype", mimeType);
            } else if (property.equals("getetag")) {
                generatedXML.writeProperty("a", "getetag", "");
            } else if (property.equals("resourcetype")) {
                generatedXML.writeElement("a", "resourcetype",
                    XMLWriter.NO_CONTENT);
            } else if (property.equals("iscollection")) {
                generatedXML.writeProperty("a", "iscollection", "0");
            } else if (property.equals("supportedlock")) {
                String supportedLocks = "<a:lockentry>" +
                    "<a:lockscope><a:exclusive/></a:lockscope>" +
                    "<a:locktype><a:write/></a:locktype></a:lockentry>" +
                    "<a:lockentry><a:lockscope><a:shared/></a:lockscope>" +
                    "<a:locktype><a:write/></a:locktype></a:lockentry>";
                generatedXML.writeElement("a", "supportedlock",
                    XMLWriter.OPENING);
                generatedXML.writeText(supportedLocks);
                generatedXML.writeElement("a", "supportedlock",
                    XMLWriter.CLOSING);
            } else {
                propertiesNotFound.addElement(property);
            }
        }

        generatedXML.writeElement("a", "prop", XMLWriter.CLOSING);
        generatedXML.writeElement("a", "status", XMLWriter.OPENING);
        generatedXML.writeText(status);
        generatedXML.writeElement("a", "status", XMLWriter.CLOSING);
        generatedXML.writeElement("a", "propstat", XMLWriter.CLOSING);

        Enumeration propertiesNotFoundList = propertiesNotFound.elements();

        if (propertiesNotFoundList.hasMoreElements()) {
            status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " +
                    WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));

            generatedXML.writeElement("a", "propstat", XMLWriter.OPENING);
            generatedXML.writeElement("a", "prop", XMLWriter.OPENING);

            while (propertiesNotFoundList.hasMoreElements()) {
                generatedXML.writeElement("a",
                    (String) propertiesNotFoundList.nextElement(),
                    XMLWriter.NO_CONTENT);
            }

            generatedXML.writeElement("a", "prop", XMLWriter.CLOSING);
            generatedXML.writeElement("a", "status", XMLWriter.OPENING);
            generatedXML.writeText(status);
            generatedXML.writeElement("a", "status", XMLWriter.CLOSING);
            generatedXML.writeElement("a", "propstat", XMLWriter.CLOSING);
        }

        generatedXML.writeElement("a", "response", XMLWriter.CLOSING);
    }

    /**
     * doPropfind Erzeugt eine XML Datei, die an den Explorer weitergeschickt
     * wird. Dadurch wird die Darstellung von Dateien und Verzeichnisse
     * ermöglicht. Meta-Informationen über die Verzeichnisse und Dateien
     * liefert die Methode catView().
     *
     * @param req Anfrage vom Client, die verarbeitet wird
     * @param res Antwort vom Server, die generiert wird
     * @param subCatVectors Daten aller Unterverzeichnisse im aktuellen
     *        Verzeichnis
     * @param subDocVectors Daten aller Dateien im aktuellen Verzeichnis
     *
     * @throws IOException
     */
    protected void doPropfind(HttpServletRequest req, HttpServletResponse res)
        throws IOException {
        VKCLogManager.webdavAccessLog(req, METHOD_PROPFIND + " called on path:" + getPath(req));

        int depth = INFINITY;

        String depthStr = req.getHeader("Depth");

        //Prüfung auf depth (Kapitel 8.1.2) 
        if (depthStr == null) {
            depth = INFINITY;
        } else {
            if (depthStr.equals("0")) {
                depth = 0;
            } else if (depthStr.equals("1")) {
                depth = 1;
            } else if (depthStr.equals("infinity")) {
                depth = INFINITY;
            }
        }

        //Properties aus der Anfrage holen
        Vector properties = null;

        properties = retrievePropertiesFromPropfindRequest(req);

        //Abfrage ob es sich bei dem Request um eine Eigenschaften-Abfrage eienr Datei handelt.
        //Fragt die Datenbank ab, ob das Dokument existiert.
        Integer sourceId = retrieveCategoryId(getPath(req));

        boolean isCategory = false;

        if (sourceId != null) {
            isCategory = true;
        }

        String absoluteUri = req.getServletPath() + getPath(req);

        //evtl. abschließenden Slash entfernen
        if (absoluteUri.charAt(absoluteUri.length() - 1) == '/') {
            absoluteUri = absoluteUri.substring(0, absoluteUri.length() - 1);
        }

        res.setStatus(WebdavStatus.SC_MULTI_STATUS);
        res.setContentType("text/xml; charset=\"utf-8\"");

        //res.setContentType("text/xml");
        //Erzeugen der XML-Datei
        XMLWriter generatedXML = new XMLWriter(res.getWriter(), debug);
        generatedXML.writeXMLHeader();

        generatedXML.writeElement("a",
            "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);

        if (isCategory) {
            VKCCategory currentCategory = null;

            try {
                currentCategory = VKCCategorySerializer.retrieveCategory(sourceId);
            } catch (DBException e) {
                sendWebDavError(res, WebdavStatus.SC_NOT_FOUND);
                VKCLogManager.errorLog(this, "8",
                    "Cant find category: " + sourceId + "!", e);

                return;
            }

            if (hasPermission(currentCategory.getAcl(), req, 'r')) {
                List subCats = currentCategory.getAccessibleSubCategories((VKCUser) req.getSession()
                                                                                       .getAttribute(WebShare.USER_KEY));

                //Teil der XML-Datei zur Verzeichnisdarstellung
                Vector catProperties;

                if (properties != null) {
                    catProperties = properties;
                } else {
                    catProperties = knownCatProperties;
                }

                //aktuelles Verzeichnis listen
                VKCSubCategory currentSubCat = new VKCSubCategory();
                currentSubCat.setName(currentCategory.getName());
                currentSubCat.setSubCategoryId(currentCategory.getCategoryId());
                currentSubCat.setCreated(currentCategory.getCreated());
                currentSubCat.setLastAccessed(currentCategory.getLastAccessed());
                currentSubCat.setLastChanged(currentCategory.getLastChanged());
                parseCategoryProperties(currentSubCat, absoluteUri,
                    generatedXML, catProperties, true);

                if (depth > 0) {
                    String catPath;
                    String parentCatPath = getPath(req);

                    for (int i = 0; i < subCats.size(); i++) {
                        VKCSubCategory actCat = (VKCSubCategory) subCats.get(i);
                        parseCategoryProperties(actCat, absoluteUri,
                            generatedXML, catProperties, false);

                        //Pfad in den ID-Cache eintragen
                        catPath = parentCatPath + "/" + actCat.getName();
                        pathIds.put(catPath, actCat.getSubCategoryId());
                    }

                    // Teil der XML-Datei zur Dateidarstellung
                    Vector docProperties;

                    if (properties != null) {
                        docProperties = properties;
                    } else {
                        docProperties = knownDocProperties;
                    }

                    List subDocs = currentCategory.getDocuments();

                    //Dateinamen aller Dokumente holen, wegen evtl. doppelter Namen
                    Hashtable docNames = new Hashtable(subDocs.size());
                    Integer value;
                    String cleanName;

                    for (int i = 0; i < subDocs.size(); i++) {
                        VKCDocument actDoc = (VKCDocument) subDocs.get(i);

                        //Notizen ignorieren
                        if (actDoc.getDocTypeId().intValue() != NOTE_DOC_TYPE) {
                            cleanName = cleanDocName(actDoc);
                            value = (Integer) docNames.get(cleanName);

                            if (value == null) {
                                //Name tritt zum ersten Mal auf
                                docNames.put(cleanName, new Integer(1));
                            } else {
                                //Name existiert schon
                                value = new Integer(value.intValue() + 1);
                                docNames.put(cleanName, value);
                            }
                        }
                    }

                    for (int i = 0; i < subDocs.size(); i++) {
                        VKCDocument actDoc = (VKCDocument) subDocs.get(i);

                        //Notizen ignorieren
                        if (actDoc.getDocTypeId().intValue() != NOTE_DOC_TYPE) {
                            parseDocumentProperties(actDoc, absoluteUri,
                                generatedXML, docProperties, docNames);
                        }
                    }
                }

                try {
                    updateCategoryAccess(currentCategory, req);
                } catch (DBException e1) {
                    sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);
                    VKCLogManager.errorLog(this, "9",
                        "Can't update counter fpr category: " + sourceId + "!",
                        e1);

                    return;
                }
            } else {
                sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
                VKCLogManager.errorLog(this, "10", "User not authorized!");
            }
        } else {
            //Document-Properties erfragt
            Vector docProperties;

            if (properties != null) {
                docProperties = properties;
            } else {
                docProperties = knownDocProperties;
            }

            try {
                Integer docId = retrieveDocumentId(getPath(req));

                if (docId == null) {
                    sendWebDavError(res, WebdavStatus.SC_NOT_FOUND,
                        req.getRequestURI());

                    return;
                }

                VKCDocument actDoc = VKCDocumentSerializer.retrieveDocument(docId);

                Integer catId = CategoryUtils.retrieveCategoryIdForDocument(actDoc.getDocumentId());
                VKCAcl currentCatAcl = VKCAclSerializer.getAclByCategoryId(catId);

                if (hasPermission(currentCatAcl, req, 'r')) {
                    parseDocumentProperties(actDoc, absoluteUri, generatedXML,
                        docProperties, null);
                } else {
                    sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
                    VKCLogManager.errorLog(this, "11", "User not authorized!");
                }
            } catch (DBException e) {
                sendWebDavError(res, WebdavStatus.SC_NOT_FOUND);
                VKCLogManager.errorLog(this, "12", "Ressource not found!", e);

                return;
            }
        }

        generatedXML.writeElement("a", "multistatus", XMLWriter.CLOSING);
        generatedXML.sendData(res);
    }

    /**
     * Bereinigt einen Dateinamen so, dass er von WebDAV-Clients dargestellt
     * werden kann.
     *
     * @param fileName Dateiname
     * @return bereinigter Dateiname
     */
    public static final String cleanDocName(VKCDocument doc) {
        String fileName = doc.getName();
        Integer id = doc.getDocumentId();
        int docTypeId = doc.getDocTypeId().intValue();
        boolean webCopy = false;

        //WebCopy?
        if (((docTypeId == HTML_DOC_TYPE) || (docTypeId == HTM_DOC_TYPE)) &&
                (new Integer(1).equals(doc.getZipped()))) {
            webCopy = true;
        }

        if (fileName.indexOf("://") >= 0) {
            //bei dem Dateinamen handelt es sich um eine Url
            int lastSlash = fileName.lastIndexOf('/');

            if (lastSlash < (fileName.length() - 1)) {
                //Url ohne Slash am Ende, d.h. Dateiname ist angegeben
                String cleanName = fileName.substring(lastSlash + 1);
                int questionMark = cleanName.indexOf('?');

                if (questionMark >= 0) {
                    //Url enthält noch Parameter die abgeschnitten werden müssen
                    if (webCopy) {
                        return "(DID " + id + ") " +
                        removeSpecialChars(cleanName.substring(0, questionMark)) +
                        ".zip";
                    }

                    return "(DID " + id + ") " +
                    removeSpecialChars(cleanName.substring(0, questionMark));
                }

                if (webCopy) {
                    return "(DID " + id + ") " + removeSpecialChars(cleanName) +
                    ".zip";
                }

                return "(DID " + id + ") " + removeSpecialChars(cleanName);
            }

            //Kein Dateiname angegeben
            if (webCopy) {
                return "(DID " + id + ") VKCWebCopy.zip";
            }

            return "(DID " + id + ") index.html";
        }

        String cleanName = removeSpecialChars(fileName);

        if (cleanName.equals(fileName)) {
            return fileName;
        }

        return "(DID " + id + ") " + cleanName;
    }

    /**
      * Bereinigt einen Kategorienamen so, dass er von WebDAV-Clients dargestellt
      * werden kann.
      *
      * @param catName Kategoriename
      * @return bereinigter Kategoriename
      */
    public static final String cleanCatName(String catName, Integer id) {
        String cleanName = removeSpecialChars(catName);

        if (cleanName.equals(catName)) {
            return catName;
        }

        return "(CID " + id + ") " + cleanName;
    }

    /**
     * Ersetzt Zeichen die im WebDav-Protokoll nicht verwendet werden dürfen durch
     * ein Leerzeichen.
     *
     * @param name
     * @return
     */
    private static String removeSpecialChars(String name) {
        String s = VKCStringUtils.decodeXmlEntities(name);
        char[] result = s.toCharArray();

        for (int i = result.length - 1; i >= 0; i--) {
            switch (result[i]) {
            //            case ';':
            case ':':
            case '*':
            case '?':
            case '<':
            case '>':
            case '|':
            case '\\':
            case '/':
                result[i] = ' ';
            }
        }

        return VKCStringUtils.filterUnicodeChars(new String(result).trim());
    }

    /**
    * doCopy Die Methode kopiert eine Ressource von einem Verzeichnis in ein
    * anderes. Dabei wird eine Kopie der physichen Datei aus dem Datastore
    * und eine Kopie des VKC Dokuments angelegt.
    *
    * @param req Anfrage vom Client, die verarbeitet wird
    *
    * @throws DBException
    * @throws IOException
    * @throws ServletException
    */
    protected void doCopy(HttpServletRequest req, HttpServletResponse res) {
        VKCLogManager.webdavAccessLog(req, METHOD_COPY + " called on path:" + getPath(req));
        
        Set docIDs = new HashSet();
        Set catIDs = new HashSet();
        Integer docId = null;
        VKCUser user = (VKCUser) req.getSession().getAttribute(WebShare.USER_KEY);
        boolean existingResource = false;

        boolean overwrite = true;

        if (req.getHeader("Overwrite").toUpperCase().equals("F")) {
            overwrite = false;
        }

        try {
            String path = getPath(req);
            Integer sourceId = retrieveParentCategoryId(path);
            Integer catId = retrieveCategoryId(path);

            if (catId == null) {
                //Source ist Dokument
                docId = retrieveDocumentId(path);

                if (docId == null) {
                    sendWebDavError(res, WebdavStatus.SC_NOT_FOUND,
                        req.getRequestURI());

                    return;
                }

                docIDs.add(docId);
                catId = CategoryUtils.retrieveCategoryIdForDocument(docId);
            } else {
                //Source ist ein Verzeichnis
                catIDs.add(catId);
            }

            String targetPath = URLUtil.URLDecode(req.getHeader("Destination")
                                                     .substring((rootUrl +
                        req.getServletPath()).length()), reqEncoding);

            //evtl. letzten Slash enfernen
            int pathLength = targetPath.length() - 1;

            if (targetPath.charAt(pathLength) == '/') {
                targetPath = targetPath.substring(0, pathLength);
            }

            Integer targetId = retrieveParentCategoryId(targetPath);

            if (targetId == null) {
                sendWebDavError(res, WebdavStatus.SC_NOT_FOUND, targetPath);

                return;
            }

            //Testen obs das Ziel schon gibt und bei OVERWRITE=F verweigern,
            //ansonsten Ziel vorher löschen.
            Integer targetCatId = retrieveCategoryId(targetPath);
            Integer targetDocId = null;
            HashSet targetCatIDs = null;
            HashSet targetDocIDs = null;

            if (targetCatId == null) {
                targetDocId = retrieveDocumentId(targetPath);

                if (targetDocId != null) {
                    targetCatIDs = new HashSet(1);
                    targetDocIDs = new HashSet(1);
                    targetDocIDs.add(targetDocId);
                }
            } else {
                targetCatIDs = new HashSet(1);
                targetDocIDs = new HashSet(1);
                targetCatIDs.add(targetCatId);
            }

            if ((targetCatId != null) || (targetDocId != null)) {
                if (!overwrite) {
                    sendWebDavError(res, WebdavStatus.SC_PRECONDITION_FAILED,
                        targetPath);

                    return;
                }

                VKCCategory cat;

                if (targetCatId != null) {
                    cat = VKCCategorySerializer.retrieveCategory(targetCatId);
                } else {
                    cat = VKCCategorySerializer.retrieveCategory(targetId);
                }

                if (hasPermission(cat.getAcl(), req, 'D')) {
                    //Target löschen
                    WebShare.database.delete(cat.getCategoryId(), targetCatIDs,
                        targetDocIDs, user);
                    existingResource = true;
                } else {
                    //keine ausreichenden Rechte
                    sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);

                    return;
                }
            }

            VKCCategory sourceCat = VKCCategorySerializer.retrieveCategory(catId);
            VKCCategory targetCat = VKCCategorySerializer.retrieveCategory(targetId);

            if (hasPermission(sourceCat.getAcl(), req, 'r') &&
                    hasPermission(targetCat.getAcl(), req, 'w')) {
                //handelt es sich um ein Dokument?
                if (docId != null) {
                    //Dokument kopieren
                    int lastSlash;

                    //alten Dateinamen berechnen
                    String oldName = getPath(req);
                    lastSlash = oldName.lastIndexOf('/');
                    oldName = oldName.substring(lastSlash + 1);

                    //alten Dateinamen berechnen
                    lastSlash = targetPath.lastIndexOf('/');

                    String newName = targetPath.substring(lastSlash + 1);

                    if (!oldName.equals(newName)) {
                        //Dokument umbenennen, wenn Namen verschieden
                        WebShare.database.copyDocument(targetCat, docId,
                            VKCStringUtils.filterUnicodeChars(newName), user);
                    } else {
                        WebShare.database.copyDocument(targetCat, docId, null,
                            user);
                    }
                } else {
                    //Kategorie kopieren
                    if (targetId.intValue() != sourceId.intValue()) {
                        WebShare.database.copy(targetCat, catIDs, docIDs, user);
                    } else {
                        //Kategorie wird in gleiche Parent-Kategorie kopiert!
                        //D.h. neue Kategorie mit anderem Namen in Parent-Kategorie 
                        //erstellen und Unternkategorien kopieren.
                        //Wird so gemacht, weil sich der Kategoriename nicht 
                        //ändern lässt, wenn man das copy direkt benutzt.
                        //
                        //Kategorienamen holen 
                        int lastSlash = path.lastIndexOf("/");

                        String sourceCatName = path.substring(lastSlash + 1);

                        lastSlash = targetPath.lastIndexOf("/");

                        String targetCatName = targetPath.substring(lastSlash +
                                1);

                        //Kategorie kopieren
                        VKCCategory newCat;

                        if (!sourceCatName.equals(targetCatName)) {
                            //Kategorie umbenennen, wenn Namen verschieden
                            newCat = WebShare.database.copyCategory(targetCat,
                                    catId,
                                    VKCStringUtils.filterUnicodeChars(
                                        targetCatName), false, user);
                        } else {
                            newCat = WebShare.database.copyCategory(targetCat,
                                    catId, null, false, user);
                        }

                        //TODO Muss gemacht werden, weil irgendetwas mit den Unterkategorien
                        //nicht stimmt.
                        newCat = VKCCategorySerializer.retrieveCategory(newCat.getCategoryId());

                        //Unterkategorien kopieren
                        VKCCategory catToCopy = VKCCategorySerializer.retrieveCategory(catId);
                        List subCats = catToCopy.getAccessibleSubCategories(user);

                        if (subCats.size() > 0) {
                            catIDs.clear();
                            docIDs.clear();

                            for (Iterator it = subCats.iterator();
                                    it.hasNext();) {
                                VKCSubCategory subCat = (VKCSubCategory) it.next();
                                catIDs.add(subCat.getSubCategoryId());
                            }

                            WebShare.database.copy(newCat, catIDs, docIDs, user);
                        }
                    }
                }

                //Alles OK, kein Fehler!
                if (existingResource) {
                    sendWebDavError(res, WebdavStatus.SC_NO_CONTENT);
                } else {
                    sendWebDavError(res, WebdavStatus.SC_CREATED);
                }
            } else {
                //keine ausreichenden Rechte
                sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
            }
        } catch (DBException e) {
            if (!e.getMessage().equals(VKCDatabase.QUOTA_EXCEEDED)) {
                VKCLogManager.errorLog(this, "5", "Database Error!", e);

                sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);
            } else {
                sendWebDavError(res, WebdavStatus.SC_INSUFFICIENT_STORAGE);
            }
        }
    }

    private void sendWebDavError(HttpServletResponse res, int error) {
        try {
            res.sendError(error);
        } catch (IOException e) {
        }
    }

    private void sendWebDavError(HttpServletResponse res, int error, String uri) {
        try {
            res.sendError(error, uri);
        } catch (IOException e) {
        }
    }

    /**
     * PROPPATCH Method.
     *
     * @param req Anfrage vom Client, die verarbeitet wird
     * @param resp Antwort vom Server, die generiert wird
     *
     * @throws IOException
     * @throws ServletException
     */
    protected void doProppatch(HttpServletRequest req, HttpServletResponse res) {
        VKCLogManager.webdavAccessLog(req, METHOD_PROPPATCH + " called on path:" + getPath(req));

        if (isLocked(req)) {
            sendWebDavError(res, WebdavStatus.SC_LOCKED);

            return;
        }

        sendWebDavError(res, HttpServletResponse.SC_METHOD_NOT_ALLOWED);
    }

    /**
     * doMkcol Erstellt ein Verzeichnis im Datenbanksystem
     *
     * @param req Anfrage vom Client, die verarbeitet wird
     * @param resp Antwort vom Server, die generiert wird
     */
    protected void doMkcol(HttpServletRequest req, HttpServletResponse res) {
        String requestPath = getPath(req);
        VKCLogManager.webdavAccessLog(req, METHOD_MKCOL + " called on path:" + getPath(req));

        //evtl. abschließenden Slash entfernen
        if (requestPath.charAt(requestPath.length() - 1) == '/') {
            requestPath = requestPath.substring(0, requestPath.length() - 1);
        }

        if (isLocked(req)) {
            try {
                res.sendError(WebdavStatus.SC_LOCKED);
            } catch (IOException e1) {
            }

            return;
        }

        try {
            //Kategoriename holen 
            int lastSlash = requestPath.lastIndexOf("/");
            String folderName = VKCStringUtils.filterUnicodeChars(requestPath.substring(lastSlash +
                        1));

            //            //Falls CID im Verzeichnisnamen, dann dein gleichen Namen ohne CID vorschlagen.
            //            if (folderName.indexOf("(CID ") == 0) {
            //                String path = getFullPath(req);
            //                path = path.substring(0, path.lastIndexOf("/"));
            //                res.setStatus(WebdavStatus.SC_MOVED_PERMANENTLY);
            //                res.setHeader("Location",
            //                    rootUrl +
            //                    rewriteUrl(path + "/" +
            //                        folderName.substring(folderName.indexOf(")") + 2)));
            //
            //                return;
            //            }
            VKCCategory newCategory = new VKCCategory();

            //Gibts fälschlicherweise einen Body?
            if (req.getContentLength() > 0) {
                sendWebDavError(res, WebdavStatus.SC_UNSUPPORTED_MEDIA_TYPE,
                    req.getRequestURI());

                return;
            }

            //Testen obs die Kategorie mit dem Namen schon gibt
            Integer catId = retrieveCategoryId(getPath(req));

            if (catId != null) {
                sendWebDavError(res, WebdavStatus.SC_METHOD_NOT_ALLOWED,
                    req.getRequestURI());

                return;
            }

            //Testen obs in der Vaterkategorie schon ein Dokument mit diesem Namen gibt
            Integer docId = retrieveDocumentId(getPath(req));

            if (docId != null) {
                sendWebDavError(res, WebdavStatus.SC_METHOD_NOT_ALLOWED,
                    req.getRequestURI());

                return;
            }

            Integer parentCatId = retrieveParentCategoryId(getPath(req));

            if (parentCatId == null) {
                sendWebDavError(res, WebdavStatus.SC_CONFLICT,
                    req.getRequestURI());

                return;
            }

            //Schaut nach, in welcher Category er momentan ist.
            VKCCategory parentCategory = VKCCategorySerializer.retrieveCategory(parentCatId);

            if (hasPermission(parentCategory.getAcl(), req, 'C')) {
                VKCAcl acl = parentCategory.getAcl();

                try {
                    PermissionManager.setCategoryOwnerPermissions(new Integer(
                            VKCAclType.USER_TYPE),
                        ServletUtils.getUser(req).getUserId(),
                        ServletUtils.getUser(req).getUserId(), acl);
                } catch (Exception ex) {
                }

                acl.setAclId(null);

                // Setzt die Metadaten der neu erzeugten Collection 
                newCategory.setName(folderName);
                newCategory.setAcl(acl);
                newCategory.setKeywordsByString(null);
                newCategory.setDescription(null);
                newCategory.setParentCategoryId(parentCatId);
                newCategory.setCreated(new Timestamp(System.currentTimeMillis()));
                newCategory.setCreatorId(ServletUtils.getUser(req).getUserId());

                // Fügt die Collection in das Datenbanksystem ein
                VKCCategorySerializer.saveCategory(newCategory);
            } else {
                sendWebDavError(res, HttpServletResponse.SC_UNAUTHORIZED);
            }
        } catch (DBException e) {
            VKCLogManager.errorLog(this, "4", "Problem with database!", e);

            sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * doMove Die Methode benennt Dateien und Verzeichnisse um oder verschiebt
     * Dateien.
     *
     * @param req Anfrage vom Client, die verarbeitet wird
     * @param resp Antwort vom Server, die generiert wird
     */
    protected void doMove(HttpServletRequest req, HttpServletResponse res) {
        VKCLogManager.webdavAccessLog(req, METHOD_MOVE + " called on path:" + getPath(req));

        Set docIDs = new HashSet();
        Set catIDs = new HashSet();
        VKCUser user = (VKCUser) req.getSession().getAttribute(WebShare.USER_KEY);
        boolean existingResource = false;

        if (isLocked(req)) {
            try {
                res.sendError(WebdavStatus.SC_LOCKED);
            } catch (IOException e1) {
            }

            return;
        }

        boolean overwrite = true;

        if (req.getHeader("Overwrite").toUpperCase().equals("F")) {
            overwrite = false;
        }

        String destPath = URLUtil.URLDecode(req.getHeader("Destination")
                                               .substring((rootUrl +
                    req.getServletPath()).length()), reqEncoding);

        Integer destId = retrieveParentCategoryId(destPath);

        if (destId == null) {
            sendWebDavError(res, WebdavStatus.SC_NOT_FOUND,
                req.getHeader("Destination"));

            return;
        }

        //Testen obs das Ziel schon gibt und bei OVERWRITE=F verweigern,
        //ansonsten Ziel vorher löschen.
        Integer targetCatId = retrieveCategoryId(destPath);
        Integer targetDocId = null;
        HashSet targetCatIDs = null;
        HashSet targetDocIDs = null;

        if (targetCatId == null) {
            targetDocId = retrieveDocumentId(destPath);

            if (targetDocId != null) {
                targetCatIDs = new HashSet(1);
                targetDocIDs = new HashSet(1);
                targetDocIDs.add(targetDocId);
            }
        } else {
            targetCatIDs = new HashSet(1);
            targetDocIDs = new HashSet(1);
            targetCatIDs.add(targetCatId);
        }

        if ((targetCatId != null) || (targetDocId != null)) {
            if (!overwrite) {
                sendWebDavError(res, WebdavStatus.SC_PRECONDITION_FAILED,
                    req.getHeader("Destination"));

                return;
            }

            try {
                VKCCategory cat;

                if (targetCatId != null) {
                    cat = VKCCategorySerializer.retrieveCategory(targetCatId);
                } else {
                    cat = VKCCategorySerializer.retrieveCategory(destId);
                }

                if (hasPermission(cat.getAcl(), req, 'D')) {
                    //Target löschen
                    WebShare.database.delete(cat.getCategoryId(), targetCatIDs,
                        targetDocIDs, user);
                    existingResource = true;
                } else {
                    //keine ausreichenden Rechte
                    sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);

                    return;
                }
            } catch (DBException e) {
                VKCLogManager.errorLog(this, "15", "Database Error!", e);

                sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);

                return;
            }
        }

        Integer sourceId = retrieveCategoryId(getPath(req));

        try {
            // Vergleich ob Parent-Verzeichnisse von Request und Destination gleich sind
            if (!(new File(destPath).getParent()
                                        .equals(new File(getPath(req)).getParent()))) {
                //echtes Verschieben
                Integer catId;

                if (sourceId == null) {
                    //Source ist Dokument
                    Integer docId = retrieveDocumentId(getPath(req));

                    if (docId == null) {
                        sendWebDavError(res, WebdavStatus.SC_NOT_FOUND,
                            req.getRequestURI());

                        return;
                    }

                    docIDs.add(docId);
                    catId = CategoryUtils.retrieveCategoryIdForDocument(docId);
                } else {
                    //Source ist ein Verzeichnis
                    catId = retrieveParentCategoryId(getPath(req));
                    catIDs.add(sourceId);
                }

                VKCCategory sourceCat = VKCCategorySerializer.retrieveCategory(catId);
                VKCCategory targetCat = VKCCategorySerializer.retrieveCategory(destId);

                if (debug) {
                    System.out.println("sourceCat: " + sourceCat);
                    System.out.println("targetCat: " + targetCat);
                }

                if ((sourceId == null) &&
                        hasPermission(sourceCat.getAcl(), req, 'd') &&
                        hasPermission(targetCat.getAcl(), req, 'w')) {
                    //Dokument verschieben
                    WebShare.database.move(sourceCat, targetCat, catIDs,
                        docIDs, user);

                    //Alles OK, kein Fehler!
                    if (existingResource) {
                        sendWebDavError(res, WebdavStatus.SC_NO_CONTENT);
                    } else {
                        sendWebDavError(res, WebdavStatus.SC_CREATED);
                    }
                } else {
                    if (hasPermission(sourceCat.getAcl(), req, 'D') &&
                            hasPermission(targetCat.getAcl(), req, 'C')) {
                        //Kategorie verschieben
                        WebShare.database.move(sourceCat, targetCat, catIDs,
                            docIDs, user);

                        //Alles OK, kein Fehler!
                        if (existingResource) {
                            sendWebDavError(res, WebdavStatus.SC_NO_CONTENT);
                        } else {
                            sendWebDavError(res, WebdavStatus.SC_CREATED);
                        }
                    } else {
                        //keine ausreichenden Rechte
                        sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
                    }
                }
            } else {
                //Umbenennen
                //Neuen Dateinamen berechnen
                String dest = URLUtil.URLDecode(req.getHeader("Destination"),
                        reqEncoding);

                if (dest.charAt(dest.length() - 1) == '/') {
                    dest = dest.substring(0, dest.length() - 1);
                }

                String newName = VKCStringUtils.filterUnicodeChars(dest.substring(dest.lastIndexOf(
                                '/') + 1));

                if (newName.indexOf("(DID ") == 0) {
                    sendWebDavError(res, WebdavStatus.SC_FORBIDDEN);
                }

                // Ist die Quelle eine Ressource wird das VKC Dokument mit dem neuen Namen in der DB abgelegt
                if (sourceId == null) {
                    VKCAcl parentCatAcl = VKCAclSerializer.getAclByCategoryId(retrieveParentCategoryId(
                                getPath(req)));

                    if (hasPermission(parentCatAcl, req, 'w')) {
                        Integer docId = retrieveDocumentId(getPath(req));
                        VKCDocument document = VKCDocumentSerializer.retrieveDocument(docId);
                        document.setName(newName);
                        VKCDocumentSerializer.saveDocument(document, true, false);

                        //Alles OK, kein Fehler!
                        if (existingResource) {
                            sendWebDavError(res, WebdavStatus.SC_NO_CONTENT);
                        } else {
                            sendWebDavError(res, WebdavStatus.SC_CREATED);
                        }
                    } else {
                        //keine ausreichenden Rechte
                        sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
                    }
                } else {
                    // Ist die Quelle eine Collection, werden die Metadaten der Collection aus der DB geladen 
                    // Der Name wird neu gestetzt und gespeichert. 
                    VKCCategory category = VKCCategorySerializer.retrieveCategory(sourceId);

                    if (hasPermission(category.getAcl(), req, 'c')) {
                        category.setName(newName);
                        VKCCategorySerializer.saveCategory(category);

                        //Alles OK, kein Fehler!
                        if (existingResource) {
                            sendWebDavError(res, WebdavStatus.SC_NO_CONTENT);
                        } else {
                            sendWebDavError(res, WebdavStatus.SC_CREATED);
                        }
                    } else {
                        //keine ausreichenden Rechte
                        sendWebDavError(res, WebdavStatus.SC_UNAUTHORIZED);
                    }
                }
            }
        } catch (DBException e) {
            VKCLogManager.errorLog(this, "6", "Database Error!", e);

            sendWebDavError(res, WebdavStatus.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
         * Encodes the 128 bit (16 bytes) MD5 into a 32 character String.
         *
         * @param binaryData Array containing the digest
         * @return Encoded MD5, or null if encoding failed
         */
    public String encode(byte[] binaryData) {
        if (binaryData.length != 16) {
            return null;
        }

        char[] buffer = new char[32];

        for (int i = 0; i < 16; i++) {
            int low = binaryData[i] & 0x0f;
            int high = (binaryData[i] & 0xf0) >> 4;
            buffer[i * 2] = hexadecimal[high];
            buffer[(i * 2) + 1] = hexadecimal[low];
        }

        return new String(buffer);
    }

    /**
     * LOCK Method.
     */
    protected void doLock(HttpServletRequest req, HttpServletResponse resp,
        VKCUser user) throws ServletException, IOException {
        VKCLogManager.webdavAccessLog(req, METHOD_LOCK + " called on path:" + getPath(req));

        /*
        if (readOnly) {
            resp.sendError(WebdavStatus.SC_FORBIDDEN);
        
            return;
        }
        */
        if (isLocked(req)) {
            resp.sendError(WebdavStatus.SC_LOCKED);

            return;
        }

        LockInfo lock = new LockInfo();

        // Parsing lock request
        // Parsing depth header
        String depthStr = req.getHeader("Depth");

        if (depthStr == null) {
            lock.depth = INFINITY;
        } else {
            if (depthStr.equals("0")) {
                lock.depth = 0;
            } else {
                lock.depth = INFINITY;
            }
        }

        // Parsing timeout header
        int lockDuration = DEFAULT_TIMEOUT;
        String lockDurationStr = req.getHeader("Timeout");

        if (lockDurationStr == null) {
            lockDuration = DEFAULT_TIMEOUT;
        } else {
            if (lockDurationStr.startsWith("Second-")) {
                lockDuration = (new Integer(lockDurationStr.substring(7))).intValue();
            } else {
                if (lockDurationStr.equalsIgnoreCase("infinity")) {
                    lockDuration = MAX_TIMEOUT;
                } else {
                    try {
                        lockDuration = (new Integer(lockDurationStr)).intValue();
                    } catch (NumberFormatException e) {
                        lockDuration = MAX_TIMEOUT;
                    }
                }
            }

            if (lockDuration == 0) {
                lockDuration = DEFAULT_TIMEOUT;
            }

            if (lockDuration > MAX_TIMEOUT) {
                lockDuration = MAX_TIMEOUT;
            }
        }

        lock.expiresAt = System.currentTimeMillis() + (lockDuration * 1000);

        int lockRequestType = LOCK_CREATION;

        Node lockInfoNode = null;

        DocumentBuilder documentBuilder = getDocumentBuilder();

        try {
            if (req.getContentLength() != 0) {
                Document document = documentBuilder.parse(new InputSource(
                            req.getInputStream()));

                // Get the root element of the document
                Element rootElement = document.getDocumentElement();
                lockInfoNode = rootElement;
            } else {
                lockRequestType = LOCK_REFRESH;
            }
        } catch (Exception e) {
            lockRequestType = LOCK_REFRESH;
        }

        if (lockInfoNode != null) {
            // Reading lock information
            NodeList childList = lockInfoNode.getChildNodes();
            StringWriter strWriter = null;
            DOMWriter domWriter = null;

            Node lockScopeNode = null;
            Node lockTypeNode = null;
            Node lockOwnerNode = null;

            for (int i = 0; i < childList.getLength(); i++) {
                Node currentNode = childList.item(i);

                switch (currentNode.getNodeType()) {
                case Node.TEXT_NODE:
                    break;

                case Node.ELEMENT_NODE:

                    String nodeName = currentNode.getNodeName();

                    if (nodeName.endsWith("lockscope")) {
                        lockScopeNode = currentNode;
                    }

                    if (nodeName.endsWith("locktype")) {
                        lockTypeNode = currentNode;
                    }

                    if (nodeName.endsWith("owner")) {
                        lockOwnerNode = currentNode;
                    }

                    break;
                }
            }

            if (lockScopeNode != null) {
                childList = lockScopeNode.getChildNodes();

                for (int i = 0; i < childList.getLength(); i++) {
                    Node currentNode = childList.item(i);

                    switch (currentNode.getNodeType()) {
                    case Node.TEXT_NODE:
                        break;

                    case Node.ELEMENT_NODE:

                        String tempScope = currentNode.getNodeName();

                        if (tempScope.indexOf(':') != -1) {
                            lock.scope = tempScope.substring(tempScope.indexOf(
                                        ':') + 1);
                        } else {
                            lock.scope = tempScope;
                        }

                        break;
                    }
                }

                if (lock.scope == null) {
                    // Bad request
                    resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                }
            } else {
                // Bad request
                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
            }

            if (lockTypeNode != null) {
                childList = lockTypeNode.getChildNodes();

                for (int i = 0; i < childList.getLength(); i++) {
                    Node currentNode = childList.item(i);

                    switch (currentNode.getNodeType()) {
                    case Node.TEXT_NODE:
                        break;

                    case Node.ELEMENT_NODE:

                        String tempType = currentNode.getNodeName();

                        if (tempType.indexOf(':') != -1) {
                            lock.type = tempType.substring(tempType.indexOf(':') +
                                    1);
                        } else {
                            lock.type = tempType;
                        }

                        break;
                    }
                }

                if (lock.type == null) {
                    // Bad request
                    resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                }
            } else {
                // Bad request
                resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
            }

            if (lockOwnerNode != null) {
                childList = lockOwnerNode.getChildNodes();

                for (int i = 0; i < childList.getLength(); i++) {
                    Node currentNode = childList.item(i);

                    switch (currentNode.getNodeType()) {
                    case Node.TEXT_NODE:
                        lock.owner += currentNode.getNodeValue();

                        break;

                    case Node.ELEMENT_NODE:
                        strWriter = new StringWriter();
                        domWriter = new DOMWriter(strWriter, true);
                        domWriter.print(currentNode);
                        lock.owner += strWriter.toString();

                        break;
                    }
                }

                if (lock.owner == null) {
                    // Bad request
                    resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
                }
            } else {
                lock.owner = new String();
            }
        }

        //Benutzernamen des Clients überschreiben 
        //(Windows-Explorer sendet fälschlicherweise den Windows-Benutzernamen)
        lock.owner = user.getUser();

        String resourceId = null;
        Integer id = retrieveFirstDocumentId(getPath(req));

        if (id != null) {
            resourceId = "D" + id;
        } else {
            id = retrieveCategoryId(getPath(req));
            resourceId = "C" + id;
        }

        lock.resourceId = resourceId;

        //Abfrage ob es sich bei dem Request um eine Eigenschaften-Abfrage eienr Datei handelt.
        //Fragt die Datenbank ab, ob das Dokument existiert.
        Integer sourceId = retrieveCategoryId(getPath(req));

        boolean exists = true;

        if (sourceId == null) {
            sourceId = retrieveDocumentId(getPath(req));

            if (sourceId == null) {
                exists = false;
            }
        }

        Enumeration locksList = null;

        if (lockRequestType == LOCK_CREATION) {
            // Generating lock id
            String lockTokenStr = req.getServletPath() + "-" + lock.type + "-" +
                lock.scope + "-" + req.getUserPrincipal() + "-" + lock.depth +
                "-" + lock.owner + "-" + lock.tokens + "-" + lock.expiresAt +
                "-" + System.currentTimeMillis() + "-catalina";
            String lockToken = encode(md5Helper.digest(lockTokenStr.getBytes()));

            if ((exists) && (lock.depth == INFINITY)) {
                // Locking a collection (and all its member resources)
                // Checking if a child resource of this collection is
                // already locked
                Vector lockPaths = new Vector();
                locksList = collectionLocks.elements();

                while (locksList.hasMoreElements()) {
                    LockInfo currentLock = (LockInfo) locksList.nextElement();

                    if (currentLock.hasExpired()) {
                        resourceLocks.remove(currentLock.resourceId);

                        continue;
                    }

                    if ((currentLock.resourceId.startsWith(lock.resourceId)) &&
                            ((currentLock.isExclusive()) ||
                            (lock.isExclusive()))) {
                        // A child collection of this collection is locked
                        lockPaths.addElement(currentLock.resourceId);
                    }
                }

                locksList = resourceLocks.elements();

                while (locksList.hasMoreElements()) {
                    LockInfo currentLock = (LockInfo) locksList.nextElement();

                    if (currentLock.hasExpired()) {
                        resourceLocks.remove(currentLock.resourceId);

                        continue;
                    }

                    if ((currentLock.resourceId.startsWith(lock.resourceId)) &&
                            ((currentLock.isExclusive()) ||
                            (lock.isExclusive()))) {
                        // A child resource of this collection is locked
                        lockPaths.addElement(currentLock.resourceId);
                    }
                }

                if (!lockPaths.isEmpty()) {
                    // One of the child paths was locked
                    // We generate a multistatus error report
                    Enumeration lockPathsList = lockPaths.elements();

                    resp.setStatus(WebdavStatus.SC_CONFLICT);

                    XMLWriter generatedXML = new XMLWriter();
                    generatedXML.writeXMLHeader();

                    generatedXML.writeElement(null,
                        "multistatus" + generateNamespaceDeclarations(),
                        XMLWriter.OPENING);

                    while (lockPathsList.hasMoreElements()) {
                        generatedXML.writeElement(null, "response",
                            XMLWriter.OPENING);
                        generatedXML.writeElement(null, "href",
                            XMLWriter.OPENING);
                        generatedXML.writeText((String) lockPathsList.nextElement());
                        generatedXML.writeElement(null, "href",
                            XMLWriter.CLOSING);
                        generatedXML.writeElement(null, "status",
                            XMLWriter.OPENING);
                        generatedXML.writeText("HTTP/1.1 " +
                            WebdavStatus.SC_LOCKED + " " +
                            WebdavStatus.getStatusText(WebdavStatus.SC_LOCKED));
                        generatedXML.writeElement(null, "status",
                            XMLWriter.CLOSING);

                        generatedXML.writeElement(null, "response",
                            XMLWriter.CLOSING);
                    }

                    generatedXML.writeElement(null, "multistatus",
                        XMLWriter.CLOSING);

                    Writer writer = resp.getWriter();
                    writer.write(generatedXML.toString());
                    writer.close();

                    return;
                }

                boolean addLock = true;

                // Checking if there is already a shared lock on this path
                locksList = collectionLocks.elements();

                while (locksList.hasMoreElements()) {
                    LockInfo currentLock = (LockInfo) locksList.nextElement();

                    if (currentLock.resourceId.equals(lock.resourceId)) {
                        if (currentLock.isExclusive()) {
                            resp.sendError(WebdavStatus.SC_LOCKED);

                            return;
                        }

                        if (lock.isExclusive()) {
                            resp.sendError(WebdavStatus.SC_LOCKED);

                            return;
                        }

                        currentLock.tokens.addElement(lockToken);
                        lock = currentLock;
                        addLock = false;
                    }
                }

                if (addLock) {
                    lock.tokens.addElement(lockToken);
                    collectionLocks.addElement(lock);
                }
            } else {
                // Locking a single resource
                // Retrieving an already existing lock on that resource
                LockInfo presentLock = (LockInfo) resourceLocks.get(lock.resourceId);

                if (presentLock != null) {
                    if ((presentLock.isExclusive()) || (lock.isExclusive())) {
                        // If either lock is exclusive, the lock can't be
                        // granted
                        resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);

                        return;
                    }

                    presentLock.tokens.addElement(lockToken);
                    lock = presentLock;
                } else {
                    lock.tokens.addElement(lockToken);
                    resourceLocks.put(lock.resourceId, lock);

                    // Checking if a resource exists at this path
                    exists = true;

                    if (!exists) {
                        // "Creating" a lock-null resource
                        int slash = lock.resourceId.lastIndexOf('/');
                        String parentPath = lock.resourceId.substring(0, slash);

                        Vector lockNulls = (Vector) lockNullResources.get(parentPath);

                        if (lockNulls == null) {
                            lockNulls = new Vector();
                            lockNullResources.put(parentPath, lockNulls);
                        }

                        lockNulls.addElement(lock.resourceId);
                    }
                }
            }
        }

        if (lockRequestType == LOCK_REFRESH) {
            String ifHeader = req.getHeader("If");

            if (ifHeader == null) {
                ifHeader = "";
            }

            // Checking resource locks
            LockInfo toRenew = (LockInfo) resourceLocks.get(resourceId);
            Enumeration tokenList = null;

            if (toRenew != null) {
                // At least one of the tokens of the locks must have been given
                tokenList = toRenew.tokens.elements();

                while (tokenList.hasMoreElements()) {
                    String token = (String) tokenList.nextElement();

                    if (ifHeader.indexOf(token) != -1) {
                        toRenew.expiresAt = lock.expiresAt;
                        lock = toRenew;
                    }
                }
            }

            // Checking inheritable collection locks
            Enumeration collectionLocksList = collectionLocks.elements();

            while (collectionLocksList.hasMoreElements()) {
                toRenew = (LockInfo) collectionLocksList.nextElement();

                if (resourceId.equals(toRenew.resourceId)) {
                    tokenList = toRenew.tokens.elements();

                    while (tokenList.hasMoreElements()) {
                        String token = (String) tokenList.nextElement();

                        if (ifHeader.indexOf(token) != -1) {
                            toRenew.expiresAt = lock.expiresAt;
                            lock = toRenew;
                        }
                    }
                }
            }
        }

        // Set the status, then generate the XML response containing
        // the lock information
        XMLWriter generatedXML = new XMLWriter();
        generatedXML.writeXMLHeader();
        generatedXML.writeElement("a",
            "prop" + generateNamespaceDeclarations(), XMLWriter.OPENING);

        generatedXML.writeElement("a", "lockdiscovery", XMLWriter.OPENING);

        String lockToken = lock.toXML(generatedXML, true);
        resp.setHeader("Lock-Token", "<" + lockToken + ">");

        generatedXML.writeElement("a", "lockdiscovery", XMLWriter.CLOSING);

        generatedXML.writeElement("a", "prop", XMLWriter.CLOSING);

        resp.setStatus(WebdavStatus.SC_OK);
        resp.setContentType("text/xml; charset=utf-8");

        Writer writer = resp.getWriter();
        writer.write(generatedXML.toString());
        writer.close();
    }

    /**
     * UNLOCK Method.
     */
    protected void doUnlock(HttpServletRequest req, HttpServletResponse resp)
        throws IOException {
        VKCLogManager.webdavAccessLog(req, METHOD_UNLOCK + " called on path:" + getPath(req));

        /*
        if (readOnly) {
            resp.sendError(WebdavStatus.SC_FORBIDDEN);
        
            return;
        }
        */
        if (isLocked(req)) {
            resp.sendError(WebdavStatus.SC_LOCKED);

            return;
        }

        String resourceId = null;
        Integer id = retrieveFirstDocumentId(getPath(req));

        if (id != null) {
            resourceId = "D" + id;
        } else {
            id = retrieveCategoryId(getPath(req));
            resourceId = "C" + id;
        }

        String lockTokenHeader = req.getHeader("Lock-Token");

        if (lockTokenHeader == null) {
            lockTokenHeader = "";
        }

        // Checking resource locks
        LockInfo lock = (LockInfo) resourceLocks.get(resourceId);
        Enumeration tokenList = null;

        if (lock != null) {
            // At least one of the tokens of the locks must have been given
            tokenList = lock.tokens.elements();

            while (tokenList.hasMoreElements()) {
                String token = (String) tokenList.nextElement();

                if (lockTokenHeader.indexOf(token) != -1) {
                    lock.tokens.removeElement(token);
                }
            }

            if (lock.tokens.isEmpty()) {
                resourceLocks.remove(resourceId);

                // Removing any lock-null resource which would be present
                lockNullResources.remove(resourceId);
            }
        }

        // Checking inheritable collection locks
        Enumeration collectionLocksList = collectionLocks.elements();

        while (collectionLocksList.hasMoreElements()) {
            lock = (LockInfo) collectionLocksList.nextElement();

            if (resourceId.equals(lock.resourceId)) {
                tokenList = lock.tokens.elements();

                while (tokenList.hasMoreElements()) {
                    String token = (String) tokenList.nextElement();

                    if (lockTokenHeader.indexOf(token) != -1) {
                        lock.tokens.removeElement(token);

                        break;
                    }
                }

                if (lock.tokens.isEmpty()) {
                    collectionLocks.removeElement(lock);

                    // Removing any lock-null resource which would be present
                    lockNullResources.remove(resourceId);
                }
            }
        }

        resp.setStatus(WebdavStatus.SC_NO_CONTENT);
    }

    /**
     * URL rewriter.
     *
     * @param path Path which has to be rewiten
     */
    private String rewriteUrl(String path) {
        return URLUtil.URLEncode(path, resEncoding);
    }

    private boolean hasPermission(VKCAcl acl, HttpServletRequest req,
        char permToken) {
        VKCUser user = (VKCUser) req.getSession().getAttribute(WebShare.USER_KEY);

        return PermissionManager.hasPermission(acl, user, permToken);
    }

    /**
     * Get creation date in ISO format.
     */
    private String getISOCreationDate(Date creationDate) {
        StringBuffer creationDateValue = new StringBuffer(creationDateFormat.format(
                    creationDate));

        return creationDateValue.toString();
    }

    /**
     * Get VKC date in HTTP format.
     */
    private String getHTTPDate(Date date) {
        //Zeitzonenversatz holen
        int timeOffset = timeZone.getRawOffset();

        //Evtl. Sommerzeit berücksichtigen
        if (timeZone.inDaylightTime(date)) {
            timeOffset += 3600000;
        }

        Date utcDate = new Date(date.getTime() - timeOffset);
        StringBuffer dateValue = new StringBuffer(lastModifiedDateFormat.format(
                    utcDate));

        return dateValue.toString();
    }

    /**
      * Get VKC date in DBTYPE_DBTIMESTAMP format.
      */
    private String getDBTIMESTAMPDate(Date date) {
        //Zeitzonenversatz holen
        int timeOffset = timeZone.getRawOffset();

        //Evtl. Sommerzeit berücksichtigen
        if (timeZone.inDaylightTime(date)) {
            timeOffset += 3600000;
        }

        Date utcDate = new Date(date.getTime() - timeOffset);
        StringBuffer dateValue = new StringBuffer(lastAccessedDateFormat.format(
                    utcDate));

        return dateValue.toString();
    }

    private String generateNamespaceDeclarations() {
        return " xmlns:a=\"" + DEFAULT_NAMESPACE + "\"";
    }

    /**
     * Check to see if a document is currently write locked.
     *
     * @param documentId ID of the document
     *
     * @return User name if the resource is locked (and no appropriate lock
     *         token has been found for at least one of the non-shared locks
     *         which are present on the resource).
     */
    public static String getLockUserName(Integer documentId) {
        try {
            Integer firstDocId = VKCDocumentSerializer.getFirstVersionDocId(documentId);

            LockInfo lock = getLock("D" + firstDocId, "");

            if (lock != null) {
                return lock.owner;
            }
        } catch (DBException e) {
        }

        return null;
    }

    /**
     * Check to see if a resource is currently write locked.
     *
     * @param resourceId ID of the resource
     * @param ifHeader "If" HTTP header which was included in the request
     *
     * @return boolean true if the resource is locked (and no appropriate lock
     *         token has been found for at least one of the non-shared locks
     *         which are present on the resource).
     */
    private static boolean isLocked(String resourceId, String ifHeader) {
        return ((getLock(resourceId, ifHeader) == null) ? false : true);
    }

    /**
     * Get LockInfo of a resource.
     *
     * @param resourceId ID of the resource
     * @param ifHeader "If" HTTP header which was included in the request
     *
     * @return LockInfo or null if resource is not locked.
     */
    private static LockInfo getLock(String resourceId, String ifHeader) {
        // Checking resource locks
        LockInfo lock = (LockInfo) resourceLocks.get(resourceId);
        Enumeration tokenList = null;

        if ((lock != null) && (lock.hasExpired())) {
            resourceLocks.remove(resourceId);
        } else if (lock != null) {
            // At least one of the tokens of the locks must have been given
            tokenList = lock.tokens.elements();

            boolean tokenMatch = false;

            while (tokenList.hasMoreElements()) {
                String token = (String) tokenList.nextElement();

                if (ifHeader.indexOf(token) != -1) {
                    tokenMatch = true;
                }
            }

            if (!tokenMatch) {
                return lock;
            }
        }

        // Checking inheritable collection locks
        Enumeration collectionLocksList = collectionLocks.elements();

        while (collectionLocksList.hasMoreElements()) {
            lock = (LockInfo) collectionLocksList.nextElement();

            if (lock.hasExpired()) {
                collectionLocks.removeElement(lock);
            } else if (resourceId.startsWith(lock.resourceId)) {
                tokenList = lock.tokens.elements();

                boolean tokenMatch = false;

                while (tokenList.hasMoreElements()) {
                    String token = (String) tokenList.nextElement();

                    if (ifHeader.indexOf(token) != -1) {
                        tokenMatch = true;
                    }
                }

                if (!tokenMatch) {
                    return lock;
                }
            }
        }

        return null;
    }

    /**
     * Return JAXP document builder instance.
     */
    private DocumentBuilder getDocumentBuilder() throws ServletException {
        DocumentBuilder documentBuilder = null;

        try {
            documentBuilder = DocumentBuilderFactory.newInstance()
                                                    .newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new ServletException(
                "[cannot find message associated with key 'webdavservlet.jaxpfailed' due to java.util.MissingResourceException: Can't find resource for bundle java.util.PropertyResourceBundle, key webdavservlet.jaxpfailed]");
        }

        return documentBuilder;
    }

    /**
     * Check to see if a resource is currently write locked. The method will
     * look at the "If" header to make sure the client has give the
     * appropriate lock tokens.
     *
     * @param req Servlet request
     *
     * @return boolean true if the resource is locked (and no appropriate lock
     *         token has been found for at least one of the non-shared locks
     *         which are present on the resource).
     */
    private boolean isLocked(HttpServletRequest req) {
        String resourceId = null;
        Integer id = retrieveFirstDocumentId(getPath(req));

        if (id != null) {
            resourceId = "D" + id;
        } else {
            id = retrieveCategoryId(getPath(req));
            resourceId = "C" + id;
        }

        String ifHeader = req.getHeader("If");

        if (ifHeader == null) {
            ifHeader = "";
        }

        String lockTokenHeader = req.getHeader("Lock-Token");

        if (lockTokenHeader == null) {
            lockTokenHeader = "";
        }

        return isLocked(resourceId, ifHeader + lockTokenHeader);
    }

    /**
     * Extrahiert den Pfad aus dem Request
     *
     * @param req
     */
    private String getPath(HttpServletRequest req) {
        //String path = req.getPathInfo();
        String path;

        try {
            if (uriEncoding == ISO88591) {
                path = URLDecoder.decode(req.getRequestURI().substring(pathStart),
                        reqEncoding);
            } else {
                path = toUTF8(req.getRequestURI().substring(pathStart));
            }
        } catch (UnsupportedEncodingException e) {
            path = "";
        }

        if (path.length() != 0) {
            //            if (iso88591 && !agent11) {
            //                path = toUTF8(path);
            //                if (debug) {
            //                    System.out.println("Konvertierung nach UTF8!");
            //                }
            //            }
            //            if (path == null) {
            //                return "";
            //            }
            //path = VKCCoder.decode(path);

            //evtl. abschließenden Slash entfernen
            int pathLength = path.length() - 1;

            if (path.charAt(pathLength) == '/') {
                path = path.substring(0, pathLength);
            }

            if (debug) {
                System.out.println("getPath: " + path);
            }
        }

        return path;
    }

    //    /**
    //     * Extrahiert den Pfad aus dem Request
    //     *
    //     * @param req
    //     */
    //    private String getFullPath(HttpServletRequest req) {
    //        String path = req.getRequestURI();
    //
    //        if (iso88591 && !agent11) {
    //            path = toUTF8(path);
    //        }
    //
    //        if (path == null) {
    //            return "/";
    //        }
    //
    //        //evtl. abschließenden Slash entfernen
    //        int pathLength = path.length() - 1;
    //
    //        if (path.charAt(pathLength) == '/') {
    //            path = path.substring(0, pathLength);
    //        }
    //
    //        return path;
    //    }

    /**
    * Holds a lock information.
    */
    private class LockInfo {
        // ------------------------------------------------- Instance Variables
        String resourceId = "/";
        String type = "write";
        String scope = "exclusive";
        int depth = 0;
        String owner = "";
        Vector tokens = new Vector();
        long expiresAt = 0;
        Date creationDate = new Date();

        // -------------------------------------------------------- Constructor

        /**
         * Constructor.
         */
        public LockInfo() {
        }

        // ----------------------------------------------------- Public Methods

        /**
         * Get a String representation of this lock token.
         */
        public String toString() {
            String result = "Type:" + type + "\n";
            result += ("Scope:" + scope + "\n");
            result += ("Depth:" + depth + "\n");
            result += ("Owner:" + owner + "\n");

            result += ("Expiration:" + formats[0].format(new Date(expiresAt)) +
            "\n");

            Enumeration tokensList = tokens.elements();

            while (tokensList.hasMoreElements()) {
                result += ("Token:" + tokensList.nextElement() + "\n");
            }

            return result;
        }

        /**
         * Return true if the lock has expired.
         */
        public boolean hasExpired() {
            return (System.currentTimeMillis() > expiresAt);
        }

        /**
         * Return true if the lock is exclusive.
         */
        public boolean isExclusive() {
            return (scope.equals("exclusive"));
        }

        /**
         * Get an XML representation of this lock token. This method will
         * append an XML fragment to the given XML writer.
         */
        public void toXML(XMLWriter generatedXML) {
            toXML(generatedXML, false);
        }

        /**
         * Get an XML representation of this lock token. This method will
         * append an XML fragment to the given XML writer.
         */
        public String toXML(XMLWriter generatedXML, boolean showToken) {
            String lockToken = null;

            generatedXML.writeElement("a", "activelock", XMLWriter.OPENING);

            generatedXML.writeElement("a", "locktype", XMLWriter.OPENING);
            generatedXML.writeElement("a", type, XMLWriter.NO_CONTENT);
            generatedXML.writeElement("a", "locktype", XMLWriter.CLOSING);

            generatedXML.writeElement("a", "lockscope", XMLWriter.OPENING);
            generatedXML.writeElement("a", scope, XMLWriter.NO_CONTENT);
            generatedXML.writeElement("a", "lockscope", XMLWriter.CLOSING);

            generatedXML.writeElement("a", "depth", XMLWriter.OPENING);

            if (depth == INFINITY) {
                generatedXML.writeText("Infinity");
            } else {
                generatedXML.writeText("0");
            }

            generatedXML.writeElement("a", "depth", XMLWriter.CLOSING);

            generatedXML.writeElement("a", "owner", XMLWriter.OPENING);
            generatedXML.writeText(owner);
            generatedXML.writeElement("a", "owner", XMLWriter.CLOSING);

            generatedXML.writeElement("a", "timeout", XMLWriter.OPENING);

            long timeout = (expiresAt - System.currentTimeMillis()) / 1000;
            generatedXML.writeText("Second-" + timeout);
            generatedXML.writeElement("a", "timeout", XMLWriter.CLOSING);

            generatedXML.writeElement("a", "locktoken", XMLWriter.OPENING);

            if (showToken) {
                Enumeration tokensList = tokens.elements();

                while (tokensList.hasMoreElements()) {
                    generatedXML.writeElement("a", "href", XMLWriter.OPENING);
                    lockToken = "opaquelocktoken:" + tokensList.nextElement();
                    generatedXML.writeText(lockToken);
                    generatedXML.writeElement("a", "href", XMLWriter.CLOSING);
                }
            } else {
                generatedXML.writeElement("a", "href", XMLWriter.OPENING);
                generatedXML.writeText("opaquelocktoken:dummytoken");
                generatedXML.writeElement("a", "href", XMLWriter.CLOSING);
            }

            generatedXML.writeElement("a", "locktoken", XMLWriter.CLOSING);

            generatedXML.writeElement("a", "activelock", XMLWriter.CLOSING);

            return lockToken;
        }
    }
}
