Saving PDF files to the server file system

Introduction

Suppose we need to save a PDF file to the file system instead of sending it to the user's browser. I will explain later why we would need this.

The Source{{`Writing}}`Transformer could be used for this, but pipeline components having side effects aren't considered to be a good practice. So we describe here a more "modern" and correct solution using the cocoon.processPipelineTo method from flowscript, to save the result of a pipeline to a file.

Process

<map:pipeline internal-only="false">
  <map:match pattern="**/*.do">
    <map:generate src="{1}/{2}.xml"/>
    <map:transform src="convert.xsl" label="content"/>
    <map:serialize type="fo2pdf"/>
  </map:match>
</map:pipeline>
<map:flow language="javascript">
  <map:script src="pdf.flow"/>
</map:flow>

pdf.flow - is our future flowscript file. It's extension can be anything (.js, .fff or .flow). I use ".flow" just to distinguish it from usual JavaScript files with ".js" extension.

<map:match pattern="**/*.pdf">
  <map:call function="makepdf">
    <map:parameter name="folder" value="{realpath:/}"/>
  </map:call>
</map:match>

As you see this match is linked to "*.pdf" extension. This match will call the "makepdf()" function from "pdf.flow" script file (which in its turn will call the .do match).

function makepdf() {
   var xml_file;
   var pdf_file;
   var outstreamPDF;

   // the parameter passed to the script from the pipeline. 
   // This is the real path to the application context
   var folder = Packages.java.lang.String(cocoon.parameters["folder"]);           

   try {      
      // creating links to files
      pdf_file = Packages.java.io.File( folder + "/target_folder/book.pdf");
      xml_file = Packages.java.io.File( folder + "/source_folder/book.xml");

      // creating outputstream to dump the results of conversion to the file
      outstreamPDF = new Packages.java.io.FileOutputStream( pdf_file );

      // calling cocoon pipeline using processPipelineTo() method and dumping the results to the outputstream
      cocoon.processPipelineTo( "blablabla/book.do", null, outstreamPDF );

      // do not forget to close the outputstream
      outstreamPDF.close();

      // since the result is dumped to the filesystem we need to send smth. to the browser
      // to make it happy. So let's send just a usual .txt file with OK message
      cocoon.sendPage("yes.txt", null);

   } catch( ex ) {
      cocoon.log.error(ex);
      // Smth. went wrong. Sending a error.txt file to the browser
      cocoon.sendPage("error.txt", null);
   }      
}

This function is calling the "*.do" match in the pipeline that we created before. Test how it works by typing http://<host>/blablabla/test.pdf in your browser. You should get a OK text message and file should be dumped to the folder specified in parameters.

<map:pipeline internal-only="true">

Points of further improvement

<map:match pattern="justdo.it">
  <map:call function="makepdf">
   <map:parameter name="folder" value="{realpath:/}"/>
  </map:call>
</map:match>            

This match will not take the name of the file to convert and it is not supposed to be called from browser (though you can type http://<host>/blablabla/justdo.it and run the flow script). Instead this match will be called from cron.