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)