/*
 * $Id: DisplayServlet.java,v 1.57 2008/04/17 10:01:01 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: DisplayServlet.java,v $
 * Revision 1.57  2008/04/17 10:01:01  marbobo
 * + Velocity-Templates angepasst
 *
 * Revision 1.56  2007/10/30 13:19:18  marbobo
 * + removed unnecessary statements
 *
 * Revision 1.55  2006/12/04 11:31:40  lawern
 * INFOLOG-Ausgabe bei user abort
 *
 * Revision 1.54  2006/12/04 09:39:44  lawern
 * ClientAbortException abgefangen
 *
 * Revision 1.53  2006/10/09 12:19:27  marbobo
 * + Beim erhöhen der Zugriffsanzahl wird nicht mehr saveDocument aufgerufen
 *
 * Revision 1.52  2006/09/29 21:10:10  marbobo
 * + Sucheoptimiert
 *
 * Revision 1.51  2006/07/24 11:23:26  lawern
 * "charset=UTF-8" für PDF- und PS-HTML-Konvertierungen gesetzt
 *
 * Revision 1.50  2006/04/04 08:01:22  korowa
 * Beim Zugrif auf konvertierten PDF-Stream wurde Anfrage falsch interpretiert.
 * -->Gefixt.
 *
 * Revision 1.49  2006/01/12 14:37:31  korowa
 * Bug bei Linkverarbeitung beseitig
 *
 * Revision 1.48  2005/11/04 08:07:35  lawern
 * Fehler mit IE bei gesetztem Tomcat-Security-Constraint gefixt.
 *
 * Revision 1.47  2005/10/27 10:34:38  korowa
 * bei fehlendem Zugriffsrecht wird einem Benutzer eine Möglichkeiit angeboten
 * sich unter anderem Benutzer mit Zugriffsrechten anzumelden
 *
 * Revision 1.46  2005/09/15 00:48:49  marbobo
 * + kleine Anpassung
 *
 * Revision 1.45  2005/09/14 00:53:11  marbobo
 * - response.setBufferSize() entfernt (hat bei mir immer wieder zur Problemen geführt)
 * + Anzah-Zugriffe wird nun auch für LINK-Typen gezählt
 *
 * Revision 1.44  2005/09/01 08:32:00  korowa
 * Es werden mehrere URL angeboten, welche direkten Zugriff auf Dateien
 *  und automatisches Anmelden als Gast erlauben.
 *
 * Revision 1.43  2005/07/16 23:57:22  marbobo
 * + Editierbare Dateien (lnk) werden nun richtig geöffnet und gespeichert
 *
 * Revision 1.42  2005/03/21 20:24:43  marbobo
 * + parameter "bytes" in Methode accessLog() auf long umgestellt
 *
 * Revision 1.41  2005/03/21 09:17:48  lawern
 * Web-Kopien werden jetzt in .zip umbenannt.
 *
 * Revision 1.40  2005/03/17 16:08:39  lawern
 * text/html als Default-MimeType bei Zip-Dateien.
 *
 * Revision 1.39  2005/02/17 08:29:45  bigmac
 * Content-Disposition Header wird nur noch für ausgewählte Formate gesetzt
 *
 * Revision 1.38  2005/02/11 09:00:28  lawern
 * Änderung der Kodierung im Content-Disposition Feld.
 *
 * Revision 1.37  2005/02/10 15:05:27  lawern
 * URLEncode bei Content-Disposition
 *
 * Revision 1.36  2005/02/10 08:12:45  bigmac
 * Content-Disposition Header wird mit Dateinamen belegt
 *
 * Revision 1.35  2005/01/20 15:25:49  marbobo
 * + Anpassungen an den Methoden getRootURL() und getContextName()
 *
 * Revision 1.34  2005/01/05 15:38:05  marbobo
 * + Bugfix beim Zählen von Zugriffsanzahl für HTML- und HTM-Dokumente
 *
 * Revision 1.33  2004/11/10 15:02:49  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.32  2004/10/31 02:59:26  marbobo
 * + Refactoring
 *
 * Revision 1.31  2004/10/28 11:04:53  koppy
 * mini-bugfix: Ausgabe der zusätzlichen Dokumentenformate bei einem Link
 *
 * Revision 1.30  2004/09/08 14:34:57  jonyp
 * set special context header, if it is an outlook file
 *
 * Revision 1.29  2004/08/27 16:43:03  koppy
 * Bei Dateiendungen wie .php, .asp etc. in gezippten WWW-Kopien
 * wird defaultmäßig text/html gesetzt.
 *
 * Revision 1.28  2004/07/28 23:11:07  marbobo
 * + History nicht updaten falls user = Gast
 *
 * Revision 1.27  2004/06/27 17:28:53  marbobo
 * + Quellcode maintenance
 *
 * Revision 1.26  2004/06/15 15:23:30  jonyp
 * deleted unused import
 *
 * Revision 1.25  2004/06/08 08:23:02  lawern
 * Fehler mit zwei gleichen Dateinamen in einem Verzeichnis behoben.
 *
 * Revision 1.24  2004/06/07 14:17:15  lawern
 * Schickt jetzt Windows2000-kompatible .lnk-Dateien, wenn man an den Dateinamen .lnk anhängt.
 * Allerdings funktioniert das nur, wenn hinter dem Dateinamen kein ";jsessionid..." steht.
 *
 * Revision 1.23  2004/05/28 06:49:50  lawern
 * Mime-Type-Zuordnung überarbeitet.
 *
 * Revision 1.22  2004/05/28 06:29:45  lawern
 * Mime-Type wird jetzt auch für Dateien in Zip-Archiven richtig gesetzt.
 *
 * Revision 1.21  2004/05/05 13:07:31  jonyp
 * + update document history
 *
 * Revision 1.20  2004/03/15 13:40:03  bigmac
 * Anpassungen an PermissionManager und weitere Refactorings
 *
 * Revision 1.19  2004/03/15 10:30:52  bigmac
 * Anpassungen an updateAccessData()
 *
 * Revision 1.18  2004/03/14 19:17:58  bigmac
 * Dateiname wird mit URLDecoder dekodiert
 *
 * Revision 1.17  2004/03/07 16:53:22  bigmac
 * Anpassungen an VKCAclSerializer
 *
 * Revision 1.16  2004/03/07 13:45:49  bigmac
 * Anpassungen an VKCDocumentTypeSerializer, VKCDocumentClassSerializer, VKCDocumentStatusSerializer
 *
 * Revision 1.15  2004/02/26 14:56:03  koppy
 * *** empty log message ***
 *
 * Revision 1.14  2004/02/26 13:59:45  koppy
 * ANzeige von www-Kopien
 *
 * Revision 1.13  2003/12/19 01:04:38  koppy
 * + setzen von content-length hinzugefügt
 *
 * Revision 1.12  2003/12/10 14:42:59  koppy
 * Typ von archiveId geändert (Integer -->String)
 *
 * Revision 1.11  2003/11/30 13:06:21  koppy
 * displayDocument(...) vereinfacht
 *
 * Revision 1.10  2003/11/29 22:22:19  koppy
 * Aufgeräumt
 *
 * Revision 1.9  2003/11/28 17:18:25  koppy
 * Debug ausgabe rausgenommen
 *
 * Revision 1.8  2003/09/01 14:16:11  lawern
 * Behandlung von unbekannten Mime-Typen eingebaut.
 *
 * Revision 1.7  2003/08/02 17:12:16  bigmac
 * *** empty log message ***
 *
 * Revision 1.6  2003/06/22 22:56:03  koppy
 * Weiterleitung bei HTML Verweisen
 *
 * Revision 1.5  2003/06/18 12:37:31  koppy
 * getUnzippedStream(..) nach vkc.util.ZipUtils verschoben
 *
 * Revision 1.4  2003/06/17 16:01:55  koppy
 * bugfix bei mehreren Dokumentversionen
 *
 * Revision 1.3  2003/06/11 11:23:31  lawern
 * Unterverzeichnisse werden jetzt auch gefunden
 *
 * Revision 1.2  2003/05/26 16:14:16  koppy
 * Auslesen von Dokumenten überarbeitet
 *
 * Revision 1.1  2003/05/22 14:41:49  koppy
 * <No Comment Entered>
 *
 */
package vkc.servlets;

import vkc.converter.DocConverter;

import vkc.db.entities.VKCAdditionalDocumentFormat;
import vkc.db.entities.VKCCategory;
import vkc.db.entities.VKCDocument;
import vkc.db.entities.VKCDocumentType;
import vkc.db.entities.VKCUser;

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

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.FileUtils;
import vkc.util.LnkCreator;
import vkc.util.ServletUtils;
import vkc.util.StringUtils;
import vkc.util.VKCCategoryPathNode;
import vkc.util.VKCInputStream;
import vkc.util.VKCStringUtils;
import vkc.util.ZipUtils;

import vkc.view.WebShare;

import vkc.view.form.SysLinkForm;

import vkc.webdav.URLUtil;
import vkc.webdav.WebDavServlet;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import java.net.URLDecoder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipFile;

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


/**
 * Display für die Wiedergabe eines Dokumentes aus dem Archiv
 *
 * @author koppy
 */
public class DisplayServlet extends HttpServlet {
    /**
     * Method to process a HTTP Request
     *
     * @param request
     * @param response
     *
     * @throws ServletException
     */
    public void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException {
        ZipFile zipFile = null;
        InputStream stream = null;
        response.setBufferSize(8192);

        try {
            DocQuery docQuery = new DocQuery(request.getRequestURI());
            VKCDocument doc = VKCDocumentSerializer.retrieveDocument(docQuery.getDocumentId());

            VKCUser user = ServletUtils.getUser(request);

            if ((user == null) || !hasReadPermission(doc, request)) {
                VKCLogManager.accessLog(request.getRemoteHost(), null,
                    (user == null) ? null : user.getUser(),
                    request.getContextPath() +
                    SysLinkForm.getDocumentSysLink(docQuery.getDocumentId(),
                        SysLinkForm.LOGINTYPE_NORMAL_USER), 403,
                    doc.getDocSize().longValue());
                response.sendRedirect(request.getContextPath() +
                    SysLinkForm.getDocumentSysLink(doc.getDocumentId(),
                        SysLinkForm.LOGINTYPE_NORMAL_USER));

                return;
            }

            if ((doc.getDocTypeId().intValue() == DocConverter.link) &&
                    (docQuery.getDocFormatId() == null)) {
                VKCLogManager.accessLog(request.getRemoteHost(), null,
                    user.getUser(),
                    request.getContextPath() +
                    SysLinkForm.getDocumentSysLink(docQuery.getDocumentId(),
                        SysLinkForm.LOGINTYPE_NORMAL_USER), 301,
                    doc.getDocSize().longValue());
                // HTML-Link
                response.sendRedirect(VKCStringUtils.decodeXmlEntities(
                        doc.getName()));
                updateAccess(doc, request);
                updateHistory(doc, request);

                return;
            }

            String archiveId = doc.getArchiveId();
            VKCDocumentType docType = doc.getDocumentType();
            boolean zipped = new Integer(1).equals(doc.getZipped());
            boolean updateAccess = true;

            if (zipped) {
                updateAccess = doc.getDisplayName()
                                  .equals(docQuery.getFileName());
            } else if ((doc.getDocTypeId().intValue() == DocConverter.htm) ||
                    (doc.getDocTypeId().intValue() == DocConverter.html)) {
                updateAccess = doc.getDisplayName()
                                  .equals(docQuery.getFileName());
            }

            if (docQuery.getDocFormatId() != null) {
                // Ein anderes Format wird angefordert
                VKCAdditionalDocumentFormat docFormat = getDocFormat(doc,
                        docQuery.getDocFormatId());

                if (docFormat == null) {
                    VKCLogManager.accessLog(request.getRemoteHost(), null,
                        user.getUser(),
                        request.getContextPath() +
                        SysLinkForm.getDocumentSysLink(
                            docQuery.getDocumentId(),
                            SysLinkForm.LOGINTYPE_NORMAL_USER), 404,
                        doc.getDocSize().longValue());
                    response.sendError(HttpServletResponse.SC_NOT_FOUND,
                        docQuery.getFileName());

                    return;
                }

                docType = docFormat.getDocType();
                zipped = new Integer(1).equals(docFormat.getZipped());
                archiveId = docFormat.getArchiveId();

                if (zipped) {
                    // Es wird bislang davon ausgegangen, dass nur HTML gezippt vorliegen kann
                    // (als AdditionalDocumentFormat)
                    updateAccess = "index.html".equals(docQuery.getFileName());
                }
            }

            String mimeType;

            if (!docQuery.fileName.endsWith(".lnk")) {
                //Behandlung von normalen Dateien
                if (zipped) {
                    File zipedFile = WebShare.archive.getFile(archiveId);
                    zipFile = new ZipFile(zipedFile);

                    String fileName = docQuery.getFileName();
                    stream = ZipUtils.getUnzippedStream(zipFile, fileName);

                    //Mime-Type für Dateien aus Zip-Archiven anhand deren Dateiendung setzen
                    int dotIndex = fileName.lastIndexOf('.');

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

                        VKCDocumentType dType = VKCDocumentTypeSerializer.getDocumentTypeByExtension(suf);

                        // Falls es sich um WWW-Kopien der Art .asp, .php etc handelt
                        if (dType == null) {
                            mimeType = "text/html";
                        } else {
                            if ((dType.getDocTypeId().intValue() == DocConverter.html) &&
                                    ((doc.getDocTypeId().intValue() == DocConverter.pdf) ||
                                    (doc.getDocTypeId().intValue() == DocConverter.ps))) {
                                //UTF-8 für PDF- und PS-HTML-Konvertierungen setzen
                                mimeType = "text/html; charset=utf-8";
                            } else {
                                mimeType = dType.getMimeType();
                            }
                        }

                        //unbekannte Mime-Typen behandeln
                        if (mimeType.equals("?")) {
                            mimeType = "application/" + suf;
                        }
                    } else {
                        //unbekannte Mime-Typen ohne Dateiendung behandeln
                        mimeType = "text/html";
                    }
                } else {
                    mimeType = docType.getMimeType();

                    //unbekannte Mime-Typen behandeln
                    if (mimeType.equals("?")) {
                        String docName = doc.getName();
                        int dotIndex = docName.lastIndexOf('.');

                        if ((dotIndex >= 0) &&
                                ((dotIndex + 1) < docName.length())) {
                            mimeType = "application/" +
                                docName.substring(dotIndex + 1);
                        } else {
                            mimeType = "application/unknown";
                        }
                    }

                    try {
                        stream = WebShare.archive.getInputStream(archiveId);
                    } catch (DataStoreException ex) {
                        VKCLogManager.errorLog(this, "", ex.getMessage(), ex);
                    }
                }
            } else {
                //Behandlung von .lnk-Dateien
                VKCCategory cat = doc.getCategory();
                ArrayList pathList = new ArrayList();

                for (Iterator iter = cat.getAbsPathDescr().getPathNodes()
                                        .iterator(); iter.hasNext();) {
                    VKCCategoryPathNode node = (VKCCategoryPathNode) iter.next();

                    if (!node.isRootNode()) {
                        String cleanNodeName = WebDavServlet.cleanCatName(node.getPathName(),
                                node.getPathId());
                        pathList.add(cleanNodeName);
                    }
                }

                String[] path = new String[pathList.size()];
                pathList.toArray(path);

                //Dateinamen codieren
                List subDocs = cat.getDocuments();

                //Zählen wie oft der Dateiname in der Kategorie vorkommt
                int counter = 0;
                String cleanName = WebDavServlet.cleanDocName(doc);

                VKCDocument actDoc;

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

                    if (WebDavServlet.cleanDocName(actDoc).equals(cleanName)) {
                        counter++;
                    }
                }

                //Wenn der Name mehrfach vorkommt muss der Name codiert werden
                if (counter > 1) {
                    //ID an Namen anhängen
                    cleanName = "(DID " + doc.getDocumentId() + ") " +
                        cleanName;
                }

                stream = LnkCreator.getStream(WebShare.getRootURL() +
                        WebShare.getContextPath() + "/dav",
                        VKCStringUtils.decodeXmlEntities(path),
                        VKCStringUtils.decodeXmlEntities(cleanName));
                mimeType = "application/x-ms-shortcut";

                //Statistik nicht aktualisieren, da dies später über das WebDavServlet
                //passiert.
                updateAccess = false;
            }

            if (stream == null) {
                VKCLogManager.accessLog(request.getRemoteHost(), null,
                    user.getUser(),
                    request.getContextPath() +
                    SysLinkForm.getDocumentSysLink(docQuery.getDocumentId(),
                        SysLinkForm.LOGINTYPE_NORMAL_USER), 404,
                    doc.getDocSize().longValue());
                // Dokument nicht gefunden
                response.sendError(HttpServletResponse.SC_NOT_FOUND,
                    docQuery.getFileName());

                return;
            }

            displayDocument(stream, mimeType, response, doc, user);

            if (updateAccess) {
                updateAccess(doc, request);
                updateHistory(doc, request);
            }

            VKCLogManager.accessLog(request.getRemoteHost(), null,
                user.getUser(),
                request.getContextPath() +
                SysLinkForm.getDocumentSysLink(docQuery.getDocumentId(),
                    SysLinkForm.LOGINTYPE_NORMAL_USER), 200,
                doc.getDocSize().longValue());
        } catch (Exception ex) {
            VKCLogManager.errorLog(this, "", ex.getMessage(), ex);
        } finally {
            FileUtils.closeInputStream(stream);

            if (zipFile != null) {
                try {
                    zipFile.close();
                } catch (IOException e) {
                }
            }
        }
    }

    /**
     * @param doc
     * @param request
     *
     * @throws IOException
     */
    protected boolean hasReadPermission(VKCDocument doc,
        HttpServletRequest request) throws DBException {
        VKCUser user = ServletUtils.getUser(request);

        if (user == null) {
            return false;
        }

        Integer catId = doc.getCategoryId();

        if (catId == null) {
            Collection docVersions = (Collection) request.getSession()
                                                         .getAttribute("collection.documents");

            if (docVersions == null) {
                // Dokument gehört keiner Kategorie an
                return true;
            }

            catId = ((VKCDocument) docVersions.iterator().next()).getCategoryId();
        }

        VKCAcl acl = VKCAclSerializer.getAclByCategoryId(catId);

        if (!PermissionManager.hasPermission(acl, user, 'r')) {
            return false;
        }

        return true;
    }

    /**
     * Gibt entsprechend der Dokumentenformat ID die Referenz auf das Objekt
     * von VKCAdditionalDocumentFormat zurück
     *
     * @param document Formate dieses Dokumentes
     * @param addDocFormatId die Id des zusätzlichen Dokumentformates
     *
     * @return Referenz auf das Objekt von VKCAdditionalDocumentFormat
     */
    protected VKCAdditionalDocumentFormat getDocFormat(VKCDocument document,
        Integer addDocFormatId) {
        Collection formats = document.getAdditionalDocumentFormats();

        for (Iterator i = formats.iterator(); i.hasNext();) {
            VKCAdditionalDocumentFormat f = (VKCAdditionalDocumentFormat) i.next();

            if (f.getAddDocFormatId().equals(addDocFormatId)) {
                return f;
            }
        }

        return null;
    }

    /**
     * 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 
     */
    protected void updateAccess(VKCDocument doc, HttpServletRequest request)
        throws DBException, IOException {
        doc.setAccessCount(new Integer(doc.getAccessCount().intValue() + 1));
        doc.setLastAccessed(new Date(System.currentTimeMillis()));
        doc.setLastAccessorId(ServletUtils.getUser(request).getUserId());

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

    /**
     * Aktualisiert die Historie des Dokumentenzugriffs
     *
     * @param doc für dieses Dokument
     * @param request mit diesem Request
     *
     * @throws DBException falls beim DB update etwas schief läuft
     */
    protected void updateHistory(VKCDocument doc, HttpServletRequest request)
        throws DBException {
        VKCUser user = ServletUtils.getUser(request);

        if ((user != null) && !user.isGuestUser()) {
            VKCUserDocHistorySerializer.saveDocumentHistory(user.getUserId(),
                doc.getDocumentId());
        }
    }

    /**
     * Liest ein Dokument aus dem Archiv oder lädt es aus dem Cache
     *
     * @param doc das VKCDocument
     * @param zipped hadelt es sich um ein ZIP, oder nicht?
     * @param request
     * @param response
     *
     * @throws IOException
     * @throws DataStoreException
     * @throws DBException
     */
    protected void displayDocument(InputStream stream, String mimeType,
        HttpServletResponse response, VKCDocument doc, VKCUser user)
        throws IOException {
        if (stream instanceof VKCInputStream) {
            response.setContentLength((int) ((VKCInputStream) stream).getLength());
        }

        response.setContentType(mimeType);

        if (stream == null) { //Falls nicht nach einem konvertiertem Stream gefragt wurde
                              // special case for msg-files

            if (mimeType.equals("application/vnd.ms-outlook")) {
                response.setHeader("Content-Disposition", "inline;");
            } else if (!StringUtils.isEmpty(doc.getName())) {
                if (mimeType.equals("application/pdf") ||
                        mimeType.equals("application/msword") ||
                        mimeType.equals("application/vnd.ms-powerpoint") ||
                        mimeType.equals("application/vnd.ms-excel") ||
                        mimeType.equals("application/rtf")) {
                    String filename = VKCStringUtils.decodeXmlEntities(doc.getName());
                    filename = URLUtil.contentDispositionEncode(filename);

                    response.setHeader("Content-Disposition",
                        "attachment; filename=\"" + filename + "\"");
                } else if (mimeType.equals("application/x-ms-shortcut")) {
                    String filename = VKCStringUtils.decodeXmlEntities(doc.getName()) +
                        ".lnk";
                    filename = URLUtil.contentDispositionEncode(filename);

                    response.setHeader("Content-Disposition",
                        "shortcut; filename=\"" + filename + "\"");
                }

                response.setHeader("Charset", "UTF-8");
            }
        }

        //Muss rein, sonst sagt der IE, wenn in der web.xml das security-constraint aktiviert ist: 
        //"Internet Explorer cannot download <filename> from <our website>
        //Internet Explorer was not able to open this Internet Site. The requested site is either unavailable or cannot be found. Please try again later"
        //
        //Quelle: http://forum.java.sun.com/thread.jspa?forumID=45&threadID=233446
        response.setHeader("Pragma", "public");
        response.setHeader("Cache-Control", "max-age=0");

        try {
            OutputStream out = new BufferedOutputStream(response.getOutputStream());
            FileUtils.write(stream, out);
            out.close();
        } catch (java.io.IOException ioe) {
            //Client hat Verbindung beendet 
            VKCLogManager.infoLog(this, "Client abort of user: "+user.getUser());
        }

        if (stream != null) {
            stream.close();
        }
    }

    /**
     * Simple Wrapper class for extracting the
     * needed document information from the request URI
     *
     * @created 29.11.2003
     * @author Andreas Koop
     */
    protected class DocQuery {
        private Integer documentId = null;
        private Integer docFormatId = null;
        private String fileName = null;

        private DocQuery() {
        }

        public DocQuery(String url) throws UnsupportedEncodingException {
            this();
            url = URLDecoder.decode(url, "ISO-8859-1");
            url = url.substring(url.indexOf(getServletName() + "/") +
                    getServletName().length() + 1);

            int slashIndex = url.indexOf("/");
            String docParams = url.substring(0, slashIndex);
            int underscoreIndex = docParams.indexOf("_");

            if (underscoreIndex > -1) {
                documentId = new Integer(url.substring(0, underscoreIndex));
                docFormatId = new Integer(url.substring(underscoreIndex + 1,
                            slashIndex));
            } else {
                documentId = new Integer(docParams);
            }

            fileName = url.substring(slashIndex + 1);
        }

        public Integer getDocumentId() {
            return documentId;
        }

        public Integer getDocFormatId() {
            return docFormatId;
        }

        public String getFileName() {
            return fileName;
        }
    }
}
