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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Date;

import org.apache.lucene.document.DateField;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.pdfbox.exceptions.CryptographyException;
import org.pdfbox.exceptions.InvalidPasswordException;
import org.pdfbox.pdmodel.PDDocument;
import org.pdfbox.pdmodel.PDDocumentInformation;
import org.pdfbox.util.PDFTextStripper;

/**
 * Hilfsklasse zum Erzuegen eines Lucene-Dokumentes aus einer PDF-Datei. Der Code basiert auf
 * dem Lucene-Beispiel aus dem PDFBox-Paket von Ben Litchfield und wurde für diesen Artikel an
 * einigen Stellen überarbeitet und angepasst.
 *  
 * @author Bernhard Messer
 * @version $rcs = ' $Id: PDFDocument.java,v 1.6 2005/03/12 23:38:04 dnaber Exp $ ' ;
 */
public class PDFDocument {

  /**
   * Statische Methode zum Erzeugen eines Lucene Dokumentes aus
   * einer PDF-Datei.
   * 
   * @param file die PDF Datei, die verarbeitet werden soll.
   * @return org.apache.lucene.document.Document das Lucene Dokument mit Inhalt
   * und einigen Metadaten aus dem PDF.
   *  
   * @throws java.io.IOException  Im Falle eines Parser-Fehlers
   */
  public static Document getDocument(File file) throws IOException {
    
    // Leeres Lucene-Dokument anlegen:
    Document document = new Document();

    // Der Dateiname wird im Feld "url" gespeichert. Das Feld wird nicht indexiert,
    // aber im Index gespeichert. Es ist also nicht durchsuchbar, kann aber später
    // zur Anzeige aus dem Index geholt werden.
    document.add(new Field("url",
                           file.getAbsolutePath().replace(File.separatorChar, '/'),
                           true, false, false));

    // Das Dateidatum wird im Feld "modified" abgelegt. Das Feld wird indexiert und
    // gespeichert und ist später durchsuchbar. Der eingetragene Feldinhalt wird jedoch
    // nicht "tokenized" und in seine Bestandteile zerlegt.
    document.add(new Field("modified",
                           DateField.timeToString(file.lastModified()),
                           true, true, false));

    // Erstellen einer eindeutigen Dokumenten-ID anhand des Dateinamens
    // und des Datums der letzten Änderung.
    String uid = file.getPath().replace(File.separatorChar, '\u0000')
        + "\u0000" + DateField.timeToString(file.lastModified());

    // Die uid wird als eigenes Feld eingefügt um den Index später inkrementell
    // aktualisieren zu können (nicht Teil dieser Demo, siehe offizielle Lucene-Demo).
    // Das Feld wird nicht im Index gespeichert, es wird indexiert, aber vor der
    // Indexierung nicht zerlegt (tokenized)
    document.add(new Field("uid", uid, false, true, false));

    FileInputStream input = null;
    try {
      input = new FileInputStream(file);
      // Inhalt des PDF-Dokumentes in das Lucene-Dokument einhängen
      addContent(document, input, file.getPath());
    }
    finally {
      if (input != null) {
        input.close();
      }
    }

    // Document zurückgeben
    return document;
  }

  /**
   * Methode zum Erzeugen eines Lucene-Dokumentes mit den Inhalt und einigen Metadaten
   * die aus der original PDF-Datei extrahiert werden.
   * 
   * @param document das Lucene-Dokument, dem der Inhalt und die Meta-Daten hinzugefügt 
   *  werden soll.
   * @param is der InputStream, aus dem der Inhalt ausgelesen wird.
   * @param documentLocation der Pfad des Original-Dokumentes. Wird nur für eine bessere
   *  Ausgabe bei eventuell auftretenden Fehlern benötigt.
   * 
   * @throws IOException Im Falle eines Parser-Fehlers.
   */
  private static void addContent(Document document, InputStream is,
      String documentLocation) throws IOException {
    PDDocument pdfDocument = null;
    try {
      pdfDocument = PDDocument.load(is);

      if (pdfDocument.isEncrypted()) {
        // Falls das Dokument geschützt ist.
        // Ein Versuch es mit einem leeren Passwort zu entschlüsseln.
        pdfDocument.decrypt("");
      }

      // Erzeugen eines temporären Output-Streams mit der Größe des Inhalts
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      OutputStreamWriter writer = new OutputStreamWriter(out);
      PDFTextStripper stripper = new PDFTextStripper();
      stripper.writeText(pdfDocument, writer);
      writer.close();

      byte[] contents = out.toByteArray();
      InputStreamReader input = new InputStreamReader(new ByteArrayInputStream(contents));
      // Der Inhalt wird als Reader-Feld mit Namen "contents" angelegt, d.h.
      // Der Inhalt wird zerlegt und indexiert, aber nicht gespeichert.
      document.add(Field.Text("contents", input));

      // Die Zusammenfassung auf max. 500 Zeichen beschränken
      int summarySize = Math.min(contents.length, 500);
      // Feld "summary" als nicht-indexiertes Feld anhängen. Die Feldwerte werden
      // jedoch im Index gespeichert und können mit den Trefferdokumenten angezeigt werden.
      document.add(new Field("summary", new String(contents, 0, summarySize),
          true, false, false));
      
      // Auslesen der PDF-Metainformationen
      PDDocumentInformation info = pdfDocument.getDocumentInformation();
      if (info.getAuthor() != null) {
        // Hinzfügen des Autor-Feldes
        document.add(new Field("Author", info.getAuthor(), true, true, true));
      }
      
      if (info.getCreationDate() != null) {
        Date date = info.getCreationDate().getTime();
        // Das Erzeugungsdatum nur einfügen wenn es nach dem 1.1.1970 liegt.
        // Ansonsten gibt es eine RuntimeException in Lucene.
        if (date.getTime() >= 0) {
          document.add(new Field("CreationDate", DateField.dateToString(date),
              true, true, true));
        }
      }
      
      if (info.getCreator() != null) {
        document.add(new Field("Creator", info.getCreator(), true, true, true));
      }
      
      if (info.getKeywords() != null) {
        document.add(new Field("Keywords", info.getKeywords(), true, true, true));
      }
      
      if (info.getModificationDate() != null) {
        Date date = info.getModificationDate().getTime();
        // Auch hier das Feld nur einhängen wenn es nach dem 1.1.1970 ist
        if (date.getTime() >= 0) {
          document.add(new Field("ModificationDate", DateField.dateToString(date), true, true, true));
        }
      }
      if (info.getProducer() != null) {
        document.add(new Field("Producer", info.getProducer(), true, true, true));
      }
      if (info.getSubject() != null) {
        document.add(new Field("Subject", info.getSubject(), true, true, true));
      }
      if (info.getTitle() != null) {
        document.add(new Field("Title", info.getTitle(), true, true, true));
      }
      
      if (info.getTrapped() != null) {
        document.add(new Field("Trapped", info.getTrapped(), true, true, true));
      }
    }
    catch (CryptographyException e) {
      throw new IOException("Error decrypting document(" + documentLocation + "): " + e);
    }
    catch (InvalidPasswordException e) {
      // Das Dokument ist passwortgeschützt und der Versuch mit einem leeren Passwort
      // hat nicht funktioniert.
      throw new IOException("Error: The document(" + documentLocation + ") is encrypted and will not be indexed.");
    }
    finally {
      if (pdfDocument != null) {
        pdfDocument.close();
      }
    }
  }
}
