Seam, iText i zapis do pliku
Seam Framework pozwala na generowanie plików PDF przy pomocy widoków JSF oraz biblioteki iText. Zagadnienie to zostało szerzej opisane w dokumentacji.
Jednak wygenerowany tą metodą dokument jest jedynie przesyłany do klienta HTTP. Podczas pracy nad projektem potrzebowaliśmy zachować nowy plik PDF na serwerze.
Po przeszukaniu Internetu, znaleźliśmy rozwiązanie, które jednak (pomijam że nie zadziałało) nie było zbyt eleganckie. Postanowiłem, że zamiast przechwytywać odpowiedź serwera, lepiej będzie zmienić mechanizm generowania dokumentu PDF.
Konfiguracja iText
Przed rozpoczęciem pracy z biblioteką iText, do pliku deployed-jars-war.list należy dodać niezbędne archiwa – itext.jar i itext-rtf.jar.
Aby dokumenty były pobierane z rozszerzeniem .pdf, trzeba do pliku web.xml dodać konfigurację servletu:
<servlet> <servlet-name>Document Store Servlet</servlet-name> <servlet-class>org.jboss.seam.document.DocumentStoreServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Document Store Servlet</servlet-name> <url-pattern>*.pdf</url-pattern> </servlet-mapping>
Dodatkowo, w pliku components.xml powinny się znaleźć następujące zmiany:
<components
<!-- … -->
xmlns:document="http://jboss.com/products/seam/document"
<!-- … -->
xsi:schemaLocation="
<!-- … -->
http://jboss.com/products/seam/document http://jboss.com/products/seam/document-2.1.xsd
<!-- … -->
">
<!-- … -->
<document:document-store use-extensions="true"/>
<!-- … -->
</components>
Dokument PDF
Do testów utworzyłem prosty dokument PDF, uwzględniając odpowiednie kodowanie polskich znaków. Poniżej znajduje się przykładowy wydruk printout.xhtml:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<p:document
xmlns:p="http://jboss.com/products/seam/pdf"
xmlns:s="http://jboss.com/products/seam/taglib"
title="Przykładowy wydruk"
fileName="wydruk.pdf"
xmlns:f="http://java.sun.com/jsf/core">
<p:font size="10" name="times-roman" encoding="Cp1250">
<p:paragraph>
Przykładowy wydruk
</p:paragraph>
</p:font>
</p:document>
Teraz wyświetlenie widoku powoduje wygenerowanie i pobranie pliku o nazwie wydruk.pdf.
Komponent sesyjny
Postanowiłem utworzyć nowy komponent, z poziomu którego będę uruchamiał generowanie dokumentów. Mam zamiar przekazywać nazwę pliku do zapisu na serwerze poprzez kontekst konwersacji:
@Name("documentPrintout")
@Scope(ScopeType.CONVERSATION)
public class DocumentPrintout {
@Out(required=false)
private String fileName;
public String show() {
return "/printout.xhtml";
}
@Begin
public String showAndSave() {
fileName = "/tmp/printout.pdf";
return "/printout.xhtml";
}
}
Pierwsza metoda powinna wywołać jedynie pobranie pliku. Druga powstała w celu zapisania dokumentu PDF na serwerze.
Rozszerzenie magazynu dokumentów
Aby zapisać plik, trzeba pobrać odpowiedź servletu DocumentStoreServlet. Najlepszym sposobem będzie rozszerzenie jego funcjonalności:
public class DocumentStoreServletProxy extends DocumentStoreServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String fileName = request.getParameter("fileName");
final OutputStream output = response.getOutputStream();
final OutputStream file =
fileName == null ? null : new FileOutputStream(fileName);
final ServletOutputStream forkOutputStream =
new ServletOutputStream() {
public void write(int b) throws IOException {
output.write(b);
if (file != null) {
file.write(b);
}
}
};
HttpServletResponse forkResponse =
new HttpServletResponseWrapper(response) {
public ServletOutputStream getOutputStream() throws IOException {
return forkOutputStream;
}
public PrintWriter getWriter() throws IOException {
return new PrintWriter(forkOutputStream);
}
};
super.doGet(request, forkResponse);
if (file != null) {
file.flush();
file.close();
}
output.flush();
output.close();
}
}
Powyższy pośrednik przekazuje odpowiedź servletu do klienta HTTP, ale także do pliku znajdującego się na serwerze.
Ale servlety nie mają dostępu do kontekstu. Dlatego należy dokonać jeszcze niewielkich zmian w magazynie dokumentów, aby przenieść nazwę pliku z kontekstu do adresu URL:
@Name("org.jboss.seam.document.documentStore")
@Scope(ScopeType.CONVERSATION)
@Install(precedence=Install.DEPLOYMENT)
public class ExtendedDocumentStore extends DocumentStore {
@In(required=false)
@Out(required=false)
private String fileName;
@Override
public String preferredUrlForContent(
String baseName, String extension, String contentId) {
String url = super.preferredUrlForContent(
baseName, extension, contentId);
if (fileName != null) {
url = String.format("%s&fileName=%s", url, fileName);
}
fileName = null;
return url;
}
}
Poza przeciążeniem klasy, przesłaniam też zainstalowany komponent org.jboss.seam.document.documentStore (dzięki adnotacji @Install(precedence=Install.DEPLOYMENT)).
Teraz w pliku web.xml mogę zamienić servlet obsługujący dokumenty PDF:
<servlet> <servlet-name>Document Store Servlet</servlet-name> <servlet-class>pl.info.czerwinski.pdf.DocumentStoreServletProxy</servlet-class> </servlet>
Do przetestowania aplikacji używam następującego formularza:
<h:form>
<h:commandButton
value="Pobierz"
action="#{documentPrintout.show}"/>
<h:commandButton
value="Pobierz i zapisz"
action="#{documentPrintout.showAndSave}"/>
</h:form>
Drugi przycisk powinien spowodować zapisanie wygenerowanego dokumentu we wskazanym miejscu na serwerze (w tym wypadku /tmp/printout.pdf).



