Transfer object to a servlet

Hello!
I have a vaadin and java related question. I use vaadin to create an application in which I dynamically create pdf documents. I used itext to write a servlet which creates a dynamic pdf. I need to call the servlet from the vaadin application and I need to transfer an object to the servlet.
How can I do this?

At the moment I use


modalWindow.getParent().open(new ExternalResource("../../../pdf/"), "_blank");

to call the servlet but with that method I cannot transfer an object tot the servlet.

What do I have to change?
I hope someone can help me!

Thanks

This is a FAQ, and perhaps deserves a section in the Book.

If you have java code in your servlet that produces a stream, the simplest way to integrate with Vaadin is to take the code out of the servlet and call it directly from Vaadin.

To do that, you need two things.

  1. Take the bytes that you produce and allow Vaadin to read them.
  2. Make Vaadin send them to the user.

In order to do 1) you need to take the code that outputs the PDF and place it in the writePDF routine below. The code in this example takes your output and allows Vaadin to read it, without using any temporary files or writing
everything to memory. Anything that your routine writes on “out” will be readable by Vaadin and will be sendable to the user.


package org.concordiainternational.competition.spreadsheet;

import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

import com.vaadin.terminal.StreamResource;

/**
 * Encapsulate a PDF as a StreamSource so that it can be used as a source of
 * data when the user clicks on a link. This class converts the output stream
 * produced by the writePDF method to an input stream that the Vaadin framework
 * can consume.
 */
@SuppressWarnings("serial")
public class PDFStreamSource implements StreamResource.StreamSource {

	@Override
	public InputStream getStream() {
		try {
			PipedInputStream in = new PipedInputStream();
			final PipedOutputStream out = new PipedOutputStream(in);

			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						writePdf(out);
					} catch (Throwable e) {
						throw new RuntimeException(e);
					}
				}
			}).start();

			return in;
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	protected void writePdf(PipedOutputStream out) {
		// call PDF library to write on "out"
	}

}

Once you have that code, you need to send the result to the user. In my Application code, I have a routine like the following (so “this” refers to the current application):

	public void openPdf(StreamSource streamSource, final String filename) {
        StreamResource streamResource = new StreamResource(streamSource, filename + ".pdf", this); //$NON-NLS-1$
        streamResource.setCacheTime(5000); // no cache (<=0) does not work with IE8
        streamResource.setMIMEType("application/pdf"); //$NON-NLS-1$
        this.getMainWindow().open(streamResource, "_top"); //$NON-NLS-1$
    }

So to put things together, in a ButtonClickListener (or something like that) that would create a PDFStreamSource, and pass it as an argument to openPDF. The PDFStreamSource will buffer your PDF as you generate it, and the openPDF routine will copy it to the user.

Thank you for your help! But however I have some understanding problems.

At the moment I have created as you wrote the PDFStreamSource class. This class has the following content including the pdf creation, I think the class should be right.


@SuppressWarnings("serial")
public class PDFStreamSource implements StreamResource.StreamSource {

	@Override
	public InputStream getStream() {
		try {
			PipedInputStream in = new PipedInputStream();
			final PipedOutputStream out = new PipedOutputStream(in);
			
			new Thread(new Runnable() {
				@Override
			    public void run() {
					try {
						writePdf(out);
					} catch (Throwable e) {
						throw new RuntimeException(e);
					}
				}
			}).start();
			
			return in;
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	protected void writePdf(PipedOutputStream out) throws IOException, DocumentException {
		Document document = new Document();
		
		PdfWriter.getInstance(document,out);
		
		document.open();
		
		// Doc information
		document.addTitle("Benutzervalidierungsdaten");
		document.addSubject("Benutzerdaten");
		document.addKeywords("PDF Daten");
		document.addAuthor("IT-Dienste");
		document.addCreator("Some Company");
		
		// The template to parse
		PdfReader pdfTemplate = new PdfReader("../../../../../../vorlage.pdf");
	        // creation of the pdf document with a template
		PdfStamper stamper = new PdfStamper(pdfTemplate, out);
		stamper.setFormFlattening(true);
		stamper.getAcroFields().setField("name", "Max Mustermann");
		stamper.getAcroFields().setField("username", "u213");
		stamper.getAcroFields().setField("password", "xt516");
		stamper.getAcroFields().setField("email", "max.mustermann@domain.com");
		stamper.getAcroFields().setField("comment", "Benutzerdaten gilten für alle Dienste die die Benutzervalidierung verwenden");
		stamper.close();
		pdfTemplate.close();
	}
}

Until now everything is clear. But then I do not know what I have to do. Your second routine has no association to PDFStreamSource class to me.
I need to transfer an object which cotains all datas which are replacing the placeholder on my pdf template.

Perhaps you can help me in understading the last step. Thank you!

Did not read your code in detail, but looks good (other than the PDFTemplate part, more on this below).

The last part you are missing is as follows

final Button examplePdfButton = new Button("Generate PDF");
final Button.ClickListener exampleClickListener = new Button.ClickListener() {

    @Override
	public void buttonClick(ClickEvent event) {
        final PDFStreamSource streamSource = new PDFStreamSource();
        String now = new SimpleDateFormat("yyyy-MM-dd_HHmmss").format(new Date());
        app.openPdf(streamSource, "example_" + now);
    }
};
examplePdfButton.addListener(exampleClickListener);

Concerning the way you obtain the template, the best practice is to read the template as a resource. I tend to do this using the class loader so that I can run unit tests (the other alternative is to use the web application context). You should be using something like

InputStream templateStream = getClass().getClassLoader().getResourceAsStream("templates/vorlage.pdf");
PdfReader pdfTemplate = new PdfReader(templateStream);

If using maven, you templates would then go in src/main/resources/templates ; if using regular Eclipse, you can either add a templates directory alongside your classes (as a top-level directory), or add an additional directory on your source path (say “resources” and add a templates directory in there). Using any of those techniques, your templates will be copied in the proper /templates location in your war.

Thank you for your help, you are very kind. Especially for the hint for the template. But unfortunately I have one more problem.

I did the following:

1.) I changed the PDFStreamSource little:


public class PDFStreamSource implements StreamResource.StreamSource {
	
	private static Logger log = Logger.getLogger(PDFStreamSource.class);
	private List<ADUser> users;
	
	public PDFStreamSource(List<ADUser> users){
		this.users = users;
	}
	public PDFStreamSource(ADUser user){
		this.users = new ArrayList<ADUser>();
		this.users.add(user);
	}

	@Override
	public InputStream getStream() {
		try {
			log.debug("Called getStream() method");
			PipedInputStream in = new PipedInputStream();
			final PipedOutputStream out = new PipedOutputStream(in);
			
			new Thread(new Runnable() {
				@Override
			    public void run() {
					try {
						writePdf(out);
					} catch (Throwable e) {
						throw new RuntimeException(e);
					}
				}
			}).start();
			
			return in;
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	protected void writePdf(PipedOutputStream out) throws IOException, DocumentException {
		log.debug("Calling writePdf() method...");
		log.debug("Username: "+this.users.get(0).getUsername());
		
		Document document = new Document();
		
		PdfWriter.getInstance(document,out); // Code 2
		// PdfWriter.getInstance(document,response.getOutputStream());
		
		document.open();
		
		// Doc information
		document.addTitle("Benutzervalidierungsdaten");
		document.addSubject("Benutzerdaten");
		document.addKeywords("PDF Daten");
		document.addAuthor("IT-Dienste");
		document.addCreator("Some company");
		
		// The template to parse
		InputStream templateStream = getClass().getClassLoader().getResourceAsStream("ntavorlage.pdf");
		PdfReader pdfTemplate = new PdfReader(templateStream);
		//PdfStamper stamper = new PdfStamper(pdfTemplate, out);
		PdfStamper stamper = new PdfStamper(pdfTemplate, out);
		stamper.setFormFlattening(true);
		stamper.getAcroFields().setField("name", "Max Mustermann");
		stamper.getAcroFields().setField("username", "user001");
		stamper.getAcroFields().setField("password", "passwd");
		stamper.getAcroFields().setField("email", "max.mustermann@domain.at");
		stamper.getAcroFields().setField("comment", "Benutzerdaten gilten für alle Dienste die die Benutzervalidierung verwenden");
		stamper.close();
		pdfTemplate.close();
	}
}

2.) In my main application class I entered the following method:


 // A method to open a pdf document
    public void openPdf(StreamSource streamSource, final String filename) {
		StreamResource streamResource = new StreamResource(streamSource, filename + ".pdf", this); //$NON-NLS-1$
		streamResource.setCacheTime(5000); // no cache (<=0) does not work with IE8
		streamResource.setMIMEType("application/pdf"); //$NON-NLS-1$
		this.getMainWindow().open(streamResource, "_blank"); // null pointer exception in this line
	}

3.) I added the follwoing code to the click listener:


confirmButton.setWidth("200px");
		confirmButton.addListener(new ClickListener() {

			private static final long serialVersionUID = 1641713157343451455L;

			@Override
			public void buttonClick(ClickEvent event) {
				
				final PDFStreamSource streamSource = new PDFStreamSource(user);
				String now = new SimpleDateFormat("yyyy-MM-dd_HHmmss").format(new Date());
				AdmanagerApplication app = new AdmanagerApplication();
				app.openPdf(streamSource, "example_" + now);
				
				//modalWindow.getParent().open(new ExternalResource("../../../pdf/"), "_blank");
			}});

Now when I click on the button I get a null pointer exception, the application does not get the stream source. Do I have to call any method from the PDFStreamSource class?

Thanks again for your help!

In your click listener, you cannot just create a new application. The new Application object you created does not have a main window, so you get the null pointer exception. When something like this happens, it is helpful to break down the statement in several lines (Window w = app.getMainWindow(); etc.)

The component in which you create the click listener should know about the application it was created in. Try instead

AdmanagerApplication app = (AdmanagerApplication)this.getApplication();
app.openPdf(streamSource, "example_" + now);

Other ways to solve this involve passing the application around as a parameter when creating the componenents, or using a InheritedThreadLocale variable to store the application.

Thank you so much, now it is working. Probably I will write a tutorial soon with my code for future use dor others. Perhaps you can help me in one last thing, it is not directly vaadin related but it seems that you also know much about itext. I am struggeling in creating a pdf document with more than one page. At the moment I want to create one pdf document with three pages containing the same content three time. I know this doesn´t make sense but in future I want to change the data.

Therefor I created the following code with the help of this tutorial: http://itext.ugent.be/articles/eid-pdf/index.php?page=1#start


protected void writePdf(PipedOutputStream out) {
		try {
			log.debug("Calling writePdf() method...");
			log.debug("Username: "+this.users.get(0).getUsername());
			
			Document document = new Document();
			
			PdfWriter.getInstance(document,out); // Code 2
			// PdfWriter.getInstance(document,response.getOutputStream());
			
			// working!!!!!
			//document.open();
			// Doc information
			//document.addTitle("Benutzervalidierungsdaten");
			//document.addSubject("Benutzerdaten");
			//document.addKeywords("PDF Daten");
			//document.addAuthor("IT-Dienste");
			//document.addCreator("Some company");
			
			// Test
			PdfCopy copy = new PdfCopy(document, out);
			document.open();
			for (int i = 0; i < 3; i++) {
				log.debug("Loop nr: "+i);
				InputStream templateStream = getClass().getClassLoader().getResourceAsStream("ntavorlage.pdf");
				PdfReader pdfTemplate = new PdfReader(templateStream);
				PdfStamper stamper = new PdfStamper(pdfTemplate, out);
				stamper.setFormFlattening(true);
				stamper.getAcroFields().setField("name", this.users.get(0).getName());
				stamper.getAcroFields().setField("username", this.users.get(0).getUsername());
				stamper.getAcroFields().setField("password", this.users.get(0).getPassword());
				stamper.getAcroFields().setField("email", this.users.get(0).getEmail());
				stamper.getAcroFields().setField("comment", "Benutzerdaten gilten für alle Dienste die die Benutzervalidierung verwenden");
				stamper.close();
				pdfTemplate = new PdfReader(out.toString());
				copy.addPage(copy.getImportedPage(pdfTemplate, 1));
				//pdfTemplate.close();
			}

			
			// The template to parse
			/*InputStream templateStream = getClass().getClassLoader().getResourceAsStream("ntavorlage.pdf");
			PdfReader pdfTemplate = new PdfReader(templateStream);
			PdfStamper stamper = new PdfStamper(pdfTemplate, out);
			stamper.setFormFlattening(true);
			stamper.getAcroFields().setField("name", this.users.get(0).getName());
			stamper.getAcroFields().setField("username", this.users.get(0).getUsername());
			stamper.getAcroFields().setField("password", this.users.get(0).getPassword());
			stamper.getAcroFields().setField("email", this.users.get(0).getEmail());
			stamper.getAcroFields().setField("comment", "Benutzerdaten gilten für alle Dienste die die Benutzervalidierung verwenden");
			stamper.close();
			pdfTemplate.close();
			*/
			document.close();
		}
		catch(IOException e){
			log.error(e.getMessage());
		}
		catch(DocumentException e){
			log.error(e.getMessage());
		}
	}

This code produces a java.io.PipedOutputStream@1ab2b55 not found as file or resource Exception.

To you probably knwo what do I have to change?

Sorry, I actually know nothing about iText other than the few lines of JavaDoc I read to figure out your code.

The logic of your code looks somewhat strange. I don’t know anything about iText, but it doesn’t feel right to reuse the stream multiple times the way you do. Normally, a stream is used once, read from start to finish. If you need it again, you need to reopen it.

Guessing from the names what PdfCopy and PdfStamper do, I would be amazed if they are meant to be connected together in that way.

My suggestion is for you to create a separate program that only creates a PDF (just print 1 to 500 one number per line), and write it to a stream (no servlet, no nothing, just a main()). Then add whatever features you want (document properties, fields, stamping, etc.)

Thnak you for everything. You have helped me so much. When I have the complete code I promise to display the whole code in this forum.

Thanks!

After a break and a new try I could reach the complete goal, to create a pdf document with multiple pages in vaadin. As I promised I will give a complete summary of how to do that.

In my szeanrio I need to create a dynamic pdf document which contains one ore more authetification datas for users. That means if there is only one user a pdf document with one page will be created, if there are more users one pdf document with multiple pages will be created. As paramter I give a list which contains all users each user represented by a user object. Now let us start:

  1. We write a class which will create the pdf document in future.

@SuppressWarnings("serial")
public class PDFStreamSource implements StreamResource.StreamSource {
	
	private static Logger log = Logger.getLogger(PDFStreamSource.class);
	private List<ADUser> users;
	
	public PDFStreamSource(List<ADUser> users){
		this.users = users;
	}
	public PDFStreamSource(ADUser user){
		this.users = new ArrayList<ADUser>();
		this.users.add(user);
	}

	@Override
	public InputStream getStream() {
		try {
			log.debug("Called getStream() method");
			PipedInputStream in = new PipedInputStream();
			final PipedOutputStream out = new PipedOutputStream(in);
			
			new Thread(new Runnable() {
				@Override
			    public void run() {
					try {
						writePdf(out);
					} catch (Throwable e) {
						throw new RuntimeException(e);
					}
				}
			}).start();
			
			return in;
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	protected void writePdf(PipedOutputStream out) {
                PdfReader reader;
		ByteArrayOutputStream baos;
		PdfStamper stamper;
		Document document = new Document();
		try {
			PdfCopy copy = new PdfCopy(document, out);
			  document.open();
			  for (int i = 0; i < users.size(); i++) {
				  	InputStream templateStream = getClass().getClassLoader().getResourceAsStream("ntavorlage.pdf");
				    reader = new PdfReader(templateStream);
				    baos = new ByteArrayOutputStream();
				    
				    // set fields
				    stamper = new PdfStamper(reader, baos);
				    stamper.getAcroFields().setField("name", this.users.get(i).getName());
					stamper.getAcroFields().setField("username", this.users.get(i).getUsername());
					stamper.getAcroFields().setField("password", this.users.get(i).getPassword());
					stamper.getAcroFields().setField("email", this.users.get(i).getEmail());
					stamper.getAcroFields().setField("comment", "Benutzerdaten gilten für alle Dienste die die Benutzervalidierung verwenden");
					stamper.close();
				    stamper.setFormFlattening(true);
				    stamper.close();
				    
				    // add page
				    reader = new PdfReader(baos.toByteArray());
				    copy.addPage(copy.getImportedPage(reader, 1));
				    log.debug("Page "+i+" of "+users.size()+" created");
				  }
		}
		catch (DocumentException de) {
			  System.err.println(de.getMessage());
		} 
		catch (IOException ioe) {
			  System.err.println(ioe.getMessage());
		}
		document.close();
	}
}

I this class I use a template for the layout of the pdf document. This layout must be stored in the src folder of the project hierachy. this.users.get(i).getUsername() is one element of the list.

  1. We need a method which will create an object of this class and which we can call if we want to create a document. As far as I understood this method can be everywhere. I inserted the method in the main class of my application.

    // A method to open a pdf document
    public void openPdf(StreamSource streamSource, final String filename) {
		StreamResource streamResource = new StreamResource(streamSource, filename + ".pdf", this); //$NON-NLS-1$
		streamResource.setCacheTime(5000); // no cache (<=0) does not work with IE8
		streamResource.setMIMEType("application/pdf"); //$NON-NLS-1$
		this.getMainWindow().open(streamResource, "_blank"); //$NON-NLS-1$
	}
  1. Last step is to call this method from somewhere in the application, e.g. in a click handler of a button

               Button confirmButton = new Button("Confirm");
                confirmButton.setWidth("200px");
		confirmButton.addListener(new ClickListener() {

			private static final long serialVersionUID = 1641713157343451455L;

			@Override
			public void buttonClick(ClickEvent event) {
				
				//modalWindow.getParent().open(new ExternalResource("../../../pdf/"), "_blank");
				
				PasswordGenerator generator = new PasswordGenerator();
				user.setPassword(generator.returnPassword());
				
				// Creating the pdf servlet
				final PDFStreamSource streamSource = new PDFStreamSource(user);
				String now = new SimpleDateFormat("yyyy-MM-dd_HHmmss").format(new Date());
				AdmanagerApplication app = (AdmanagerApplication)modalWindow.getApplication();;
				app.openPdf(streamSource, "pdf_" + now);
				
				
				ADPasswordReset reset = new ADPasswordReset(user);
				if(reset.resetPassword() == true){
					modalWindow.getParent().removeComponent(modalWindow);
				}
				else {
					error.setValue("Password reset was not successfull");
				}
			}});

You can probably add the last two methods into a seperated class, this would enhance the usability.

Now everything is done and you can try it.

Thanks for share guys! It helped me a lot