Pokazywanie postów oznaczonych etykietą iText. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą iText. Pokaż wszystkie posty

niedziela, 20 lutego 2011

Kto nie ma w głowie - ten klepie zbędny kod

Tytuł jak najbardziej adekwatny do tematu dzisiejszego postu. Niestety powyższa (nomen omen) refleksja naszła mnie dopiero pod koniec projektu - sporej wielkości aplikacji do raportowania z bazy, czyli mapowanie ResultSet'ów na kolekcje klas POJO. Wszystko niby fajnie ale wyniki wyświetlane na stronie trzeba było przesłać do Birt Viewer'a, co skutkowało pisaniem dodatkowej klasy handlera dla każdej tabelki – a można było prościej. W poniższym przykładzie wykorzystam własne adnotacje atrybutów POJO do opisania wyglądu raportu PDF (dla uproszczenia użyłem biblioteki iText).

Mając poniższą klasę POJO
package kuba.demo.dao;

public class Employee {

    private String firstName;
    private String lastName;
    private double salary;

    public Employee() {
    }

    public Employee(String firstName, String lastName, double salary) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.salary = salary;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

oraz managed beana
package kuba.demo.view;

import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.event.ActionEvent;
import kuba.demo.dao.Employee;
import kuba.demo.dao.EmployeesDAO;

@ManagedBean(name = "employeeBean")
@RequestScoped
public class EmployeeBean {

    private List emp;
    
    
    @PostConstruct
    private void init(){

        emp = EmployeesDAO.getAll();
    }


    public List getEmp() {
        return emp;
    }

    public void setEmp(List emp) {
        this.emp = emp;
    }
}

wyświetlam na stronie prostą tablekę JSF

Chciałbym ją wyeksportować do pliku PDF co oczywiście nie jest problemem, tyle  że przy kilkuset tabelach dla każdego eksportu musiałbym pisać osobną klasę opisującą formatowanie tej tabeli.
Mój cel, to sprowadzić kod odpowiedzialny za generowanie PDF do takiej postaci
public void downloadPdf(ActionEvent evt){
 
        PdfBuilder builder = new PdfBuilder(emp);
        builder.printToOutputStream("TestWeb");
    }

Tabelki są jedna różne – gdzie więc kod odpowiedzialny za takie informacje jak nagłówek czy szerokość kolumny? Tu z pomocą przychodzą własne adnotacje.
Moja wyglądają następująco
public class Employee {

    @Pdf(label="Imie",width=100)
    private String firstName;

    @Pdf(label="Nazwisko",width=150)
    private String lastName;

    @Pdf(label="Pensja",width=50)
    private double salary;

    // getters setters
}

Drugim krokiem jest utworzenie typu adnotacji
package kuba.demo.pdf;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Pdf {

    String label() default "";
    int    width() default 20;
}

I to w zasadzie wszystko. Ja musiałem utworzyć jeszcze pomocniczą klasę przechowującą na potrzeby generowania PDF informacje o atrybutach tabeli wczytanych z argumentów adnotacji

package kuba.demo.pdf;

public class PdfTableModel {

    private String fieldName;
    private String label;
    private int width;
    
    // getters setters
}

I wreszcie klasa generująca PDF
package kuba.demo.pdf;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

public class PdfBuilder {

    private static final Logger log = Logger.getLogger(PdfBuilder.class.getName());

    private List data;
    private Class listElement;
    private List tableModel;

    private Document document;

    public PdfBuilder(List data) {
        this.data = data;
    }


    private void scanAnnotations(){

        if(data != null && data.size()>0){
            listElement = data.get(0).getClass();
        }

        tableModel = new ArrayList();

        for(Field field : listElement.getDeclaredFields()){

            Pdf pdf = field.getAnnotation(Pdf.class);

            if(pdf != null){

                String fieldName = field.getName();
                String label = pdf.label();
                int width = pdf.width();

                tableModel.add(new PdfTableModel(fieldName,label,width));
            }
        }
    };


    private void createDocument(Document document){

        scanAnnotations();

        try {

            Object row = Class.forName(listElement.getName()).newInstance();

            int tableSize = tableModel.size();
            float[] columns = new float[tableSize];
            
            int i = 0;
            for(PdfTableModel ptm: tableModel){
                columns[i] = ptm.getWidth();
                i++;
            }

            PdfPTable table = new PdfPTable(columns);

            // Table headers
            for(PdfTableModel ptm : tableModel){

                PdfPCell cell = new PdfPCell(new Phrase(ptm.getLabel()));
                cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
                table.addCell(cell);
            }
            
            // Table rows
            for(Object listRow : data){

                row = listRow;

                for(PdfTableModel ptm : tableModel){

                    Field f = row.getClass().getDeclaredField(ptm.getFieldName());
                    f.setAccessible(true);
                    Object cellValue = f.get(row);
                    PdfPCell cell = new PdfPCell(new Phrase(cellValue.toString()));
                    table.addCell(cell);
                 }
            }

            document.add(table);

        } catch (InstantiationException ex) {
            log.severe(ex.getMessage());
        } catch (ClassNotFoundException ex) {
            log.severe(ex.getMessage());
        } catch (IllegalArgumentException ex) {
            log.severe(ex.getMessage());
        } catch (IllegalAccessException ex) {
            log.severe(ex.getMessage());
        } catch (NoSuchFieldException ex) {
            log.severe(ex.getMessage());
        } catch (SecurityException ex) {
            log.severe(ex.getMessage());
        } catch (DocumentException ex) {
            log.severe(ex.getMessage());
        }

    }

    
    public void printToOutputStream(String fileName){

        ServletOutputStream out = null;

        FacesContext ctxContext = FacesContext.getCurrentInstance();
        ExternalContext ectx = ctxContext.getExternalContext();
        HttpServletResponse res = (HttpServletResponse)ectx.getResponse();
        res.setContentType("application/pdf");
        res.setHeader("Content-Disposition", " inline; filename=\""+fileName+".pdf\"");

        try {

             out = res.getOutputStream();
             document = new Document();
             PdfWriter.getInstance(document, out);
             document.open();
             createDocument(document);
             document.close();
             
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        }catch (DocumentException ex) {
            log.severe(ex.getMessage());
        }
    }


    public void printToFile(String filePath){

        try {
                document = new Document();
                PdfWriter.getInstance(document, new FileOutputStream(filePath));
                document.open();
                createDocument(document);
                document.close();
                
        }catch (FileNotFoundException ex) {
            log.severe(ex.getMessage());
        }catch (DocumentException ex) {
            log.severe(ex.getMessage());
        }
    }
}

scanAnnotations() - odczytuje wartości argumentów adnotacji i zapisuje je do listy obiektów PdfTableModel (nagłówki i szerokości kolumn)
createDocument() - buduje tabelę w dokumencie PDF wykorzystując refleksje do odczytania wartości atrybutów POJO
printToOutputStream() - zapisuje dokument PDF do obiektu ServletOutputStream

Jak widać działa :)
Pobierz projekt (NetBeans 6.9.1)

sobota, 8 stycznia 2011

Cyfrowe podpisywanie plików PDF

Dzisiejszy post trochę odbiega od poprzednich, niemniej jednak uznałem, że temat jest warty uwagi. W tym tygodniu zajmowałem się podpisywaniem plików PDF przy pomocy klucza prywatnego i weryfikacją takiego podpisu w oparciu o klucz publiczny.

Zacznijmy od wygenerowania repozytorium z parą takich kluczy przy pomocy keytool'a

keytool -genkey -alias kuba  -keypass demokeypass  -storepass demostorepass  -keystore demo-keystore.jks -validity 360 -dname "CN=Jakub Pawlowski, OU=blog, O=javaspotlight.blogspot.com, L=Lodz, S=Lodzkie, C=PL"

Następnie należy wyeksportować klucz publiczny
keytool -export -alias kuba  -keypass demokeypass  -storepass demostorepass  -keystore demo-keystore.jks -file demo-cert.cer

Poniższe polecenia umożliwiają weryfikacje zawartości obu plików
keytool -list -alias kuba -storepass demostorepass -v -keystore ~/tmp/demo-keystore.jks

keytool -printcert -v -file ~/tmp/demo-cert.cer

Do podpisania PDF'a użyłem oczywiście biblioteki iText (wymagana jest także biblioteka Bouncy Castle).
Klasa odpowiedzialna za podpisanie PDF'a wygląda następująco
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.logging.Logger;

public class PdfSigner {

    private static final Logger log = Logger.getLogger(PdfSigner.class.getName());
    private static final String PDF_PATH = "/home/kuba/tmp/demo-doc.pdf";
    private static final String PDF_PATH_SIGNED = "/home/kuba/tmp/demo-doc-signed.pdf";
    private static final String KEYSTORE_PATH = "/home/kuba/tmp/demo-keystore.jks";
    private static final String KEYSTORE_PASSWORD = "demostorepass";
    private static final String KEY_ALIAS = "kuba";
    private static final String KEY_PASSWORD = "demokeypass";

    private Certificate[] certificates;
    PrivateKey privateKey;

    public PdfSigner() {

        try {
             KeyStore ks = KeyStore.getInstance("JKS");
             FileInputStream fis = new FileInputStream(KEYSTORE_PATH);
             ks.load(fis, KEYSTORE_PASSWORD.toCharArray());
             certificates = ks.getCertificateChain(KEY_ALIAS);
             privateKey = (PrivateKey)ks.getKey(KEY_ALIAS, KEY_PASSWORD.toCharArray());

        } catch (FileNotFoundException ex) {
            log.severe(ex.getMessage());
        } catch (KeyStoreException ex) {
            log.severe(ex.getMessage());
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        } catch (NoSuchAlgorithmException ex) {
            log.severe(ex.getMessage());
        } catch (CertificateException ex) {
           log.severe(ex.getMessage());
        } catch (UnrecoverableEntryException ex) {
          log.severe(ex.getMessage());
        }

    }


    public void signPdf(){

        try {

             PdfReader pdfReader = new PdfReader(PDF_PATH);
             FileOutputStream fos = new FileOutputStream(PDF_PATH_SIGNED);
             PdfStamper stamper = PdfStamper.createSignature(pdfReader, fos, '\0');
             PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
             appearance.setReason("Demo Blog");
             appearance.setCrypto(privateKey, certificates, null, PdfSignatureAppearance.WINCER_SIGNED);
             appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
             stamper.close();
             log.info("Document signed");

        } catch (IOException ex) {
           log.severe(ex.getMessage());
        } catch (DocumentException ex) {
           log.severe(ex.getMessage());
        }

    }

}

Po zaimportowaniu klucza publicznego do Adobe Reader'a i otwarciu podpisanego dokumentu, powinny pokazać się następujące informacje o certyfikacie


Poprawność podpisu można także sprawdzić programowo
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfPKCS7;
import com.itextpdf.text.pdf.PdfReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.logging.Logger;

public class PdfValidator {

    private static final Logger log = Logger.getLogger(PdfValidator.class.getName());
    private static final String PDF_PATH_SIGNED = "/home/kuba/tmp/demo-doc-signed.pdf";
    private static final String CERTIFICATE_PATH = "/home/kuba/tmp/demo-cert.cer";

    private KeyStore keyStore;

    public PdfValidator() {

        try {
             keyStore = KeyStore.getInstance("JKS");
             keyStore.load(null, null);
             FileInputStream fis = new FileInputStream(CERTIFICATE_PATH);
             CertificateFactory cf = CertificateFactory.getInstance("X509");
             X509Certificate cert = (X509Certificate) cf.generateCertificate(fis);
             keyStore.setCertificateEntry("cacert", cert);

        } catch (FileNotFoundException ex) {
            log.severe(ex.getMessage());
        } catch (KeyStoreException ex) {
            log.severe(ex.getMessage());
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        } catch (NoSuchAlgorithmException ex) {
            log.severe(ex.getMessage());
        } catch (CertificateException ex) {
           log.severe(ex.getMessage());
        }
    }
    

    public void validatePdf(){

        try {
            
            PdfReader reader = new PdfReader(PDF_PATH_SIGNED);
            AcroFields af = reader.getAcroFields();
            ArrayList names = af.getSignatureNames();
            
            for (String name : names) {
                
                PdfPKCS7 pk = af.verifySignature(name);
                Calendar calendar = pk.getSignDate();
                Certificate[] certificates = pk.getCertificates();
                log.info("Revision modified: " + !pk.verify());
                Object[] fails = PdfPKCS7.verifyCertificates(certificates, keyStore, null, calendar);
                if (fails == null) {
                    log.info("Certificates verified against the KeyStore");
                } else {
                    log.info("Certificate failed: " + fails[1]);
                }
            }

        } catch (SignatureException ex) {
            log.severe(ex.getMessage());
        } catch (IOException ex) {
            log.severe(ex.getMessage());
        }
    }
}