wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Rendering ODF documents in your XPages


In Tim's and my Lotusphere session AD111 we rendered data from an XPage into a ODT document. Once you get the hang of it and it is easy to implement export to text, spreadsheet or presentations. One nice aspect: no server side OpenOffice or Symphony code is needed. Here is how you do it:
  1. In DDE use Window, Show Eclipse Views, Other (Alt+Shift+Q Q) and select "Navigator"
  2. Locate the WebContent/WEB-INF folder and add 2 directories: src and lib
  3. Download the Java toolkit from ODF Toolkit and add odfdom.jar to your WebContent/WEB-INF/lib directory
  4. Right click on your NSF and select Project Properties. Then select Java Build Path. In the source tab add the src folder you created in step 2. In the Libraries tab add the odfdom.jar you copied in step 3
  5. Create the class you want to use for rendering the content (rendering logic goes there). It should have a method that takes an OutputStream as parameter (I called it render)
  6. Optional: define your new class to be a managed bean. Once you do that it can be used as if it is a native global variable. To do this locate the faces-config.xml in the WEB-INF directory and add a bean definition. Could look like this:
    <faces-config>
      <managed-bean>
        <managed-bean-name>stuff</managed-bean-name>
        <managed-bean-class>com.lotusphere2010.ad111.Sample</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
      </managed-bean>
    <faces-config>
    
  7. Create a new XPage, go to the page properties, basics and set rendered=false. You can see in the XSP source
    <xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
  8. Add code (see below) in the beforeRenderResponse event. It is important to use this event, so you can get hand on the OutputStream rather than the writer
  9. The ODFToolkit requires one additional setting for security, so locate [NotesInstallDir]/jvm/lib/security/java.policy file (you need to do that on the server too) and add:
    grant { permission java.lang.RuntimePermission "shutdownHooks"; };
  10. You are all set. Enjoy. As usual YMMV
The SSJS code to use in the beforeRenderResponse event would look like this:
// Get the output stream
var exCon = facesContext.getExternalContext();
var response = exCon.getResponse();
var out = response.getOutputStream();

if (out==null) {
  print("The freakn' stream isn't there");
else {
  print("All good with the stream");
}

// Set the MIME Type to ODF = application/x-vnd.oasis.opendocument.text
response.setContentType("application/x-vnd.oasis.opendocument.text");
response.setHeader("Content-disposition","attachment; filename=sample.odt");
response.setHeader("Cache-Control""no-cache");

// Here would be the business logic adding to stuff

//Get the document out to the screen
stuff.render(out);

// Stop the page from further processing;
facesContext.responseComplete();
out.close();


A sample class writing out a text document would look like this:
package com.lotusphere2010.ad111;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.odftoolkit.odfdom.doc.OdfTextDocument;

public class Sample {

  /**
   * Creates the unavoidable HelloWorld application
   * data
   
   @param args
   @throws IOException 
   */
  public static void main(String[] argsthrows IOException {
    File resultFile = new File("result.odt");
    if (resultFile.exists()) {
      resultFile.delete();
    }
    FileOutputStream out = new FileOutputStream(resultFile);
    Sample sample = new Sample();
    
    sample.render(out);
    out.close();
    
    System.out.println("Done!");

  }

  
  /**
   * Write the completed doc out into the output stream
   
   @param out
   *            the stream to write to
   */
  public void render(OutputStream out) {

    // We create the document into the stream
    OdfTextDocument odfDoc;
    try {
      odfDoc = OdfTextDocument.newTextDocument();
      odfDoc.addText("Hello World");
      
      // Now we render it out
      odfDoc.save(out);
      
    catch (Exception e) {
      // Don't do that in production quality code!
      e.printStackTrace();
    }

  }

  
}

Update: Other posts on rendering your own output in XPages:

Posted by on 27 January 2010 | Comments (2) | categories: XPages

Comments

  1. posted by Karsten Lehmann on Wednesday 27 January 2010 AD:
    Changing the java.policy on the user's workstation is *BAD*. Emoticon smile.gif
    Will this work in 8.5.2 without changing this file? I heard they were working on the security mapping between Java and the Notes Client's ECL system.
  2. posted by Raphael Costa on Tuesday 22 June 2010 AD:
    Great article!
    But, I couldn't put this to work.
    I follow every step, but end up with a message:
    HTTP JVM: All good with the stream
    21/06/2010 20:20:55 HTTP JVM: SEVERE: Could not find resource: /OdfTextDocument.odt
    21/06/2010 20:20:55 HTTP JVM: java.lang.NullPointerException
    21/06/2010 20:20:55 HTTP JVM: at org.odftoolkit.odfdom.doc.OdfDocument.loadTemplate(OdfDocument.java:287)
    21/06/2010 20:20:55 HTTP JVM: at org.odftoolkit.odfdom.doc.OdfTextDocument.newTextDocument(OdfTextDocument.java:99)
    21/06/2010 20:20:55 HTTP JVM: at com.petrobras.odf.TesteODF.render(TesteODF.java:45)
    21/06/2010 20:20:55 HTTP JVM: at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    21/06/2010 20:20:55 HTTP JVM: at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    21/06/2010 20:20:55 HTTP JVM: at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
    21/06/2010 20:20:55 HTTP JVM: at java.lang.reflect.Method.invoke(Method.java:599)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.types.JavaAccessObject.call(JavaAccessObject.java:309)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.types.FBSObject.call(FBSObject.java:153)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.ASTTree.ASTCall.interpret(ASTCall.java:171)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.ASTTree.ASTProgram.interpretEx(ASTProgram.java:102)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.JSExpression._interpretExpression(JSExpression.java:438)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.JSExpression.access$1(JSExpression.java:427)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.JSExpression$2.run(JSExpression.java:417)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.JSExpression$2.run(JSExpression.java)
    21/06/2010 20:20:55 HTTP JVM: at java.security.AccessController.doPrivileged(AccessController.java:284)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.JSExpression.interpretExpression(JSExpression.java:415)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.JSExpression.evaluateValue(JSExpression.java:250)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.jscript.JSExpression.evaluateValue(JSExpression.java:233)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.javascript.JavaScriptInterpreter.interpret(JavaScriptInterpreter.java:193)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.binding.javascript.JavaScriptMethodBinding.invoke(JavaScriptMethodBinding.java:111)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.component.UIViewRootEx.invokePhaseMethodBinding(UIViewRootEx.java:1419)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.controller.FacesControllerImpl.invokePhaseMethodBinding(FacesControllerImpl.java:440)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.controller.FacesControllerImpl.access$0(FacesControllerImpl.java:431)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.controller.FacesControllerImpl$ViewPhaseListener.beforePhase(FacesControllerImpl.java:492)
    21/06/2010 20:20:55 HTTP JVM: at com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:172)
    21/06/2010 20:20:55 HTTP JVM: at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:120)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.controller.FacesControllerImpl.render(FacesControllerImpl.java:257)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.webapp.FacesServlet.serviceView(FacesServlet.java:198)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.webapp.FacesServletEx.serviceView(FacesServletEx.java:187)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.webapp.FacesServlet.service(FacesServlet.java:138)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.webapp.FacesServletEx.service(FacesServletEx.java:131)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.xsp.webapp.DesignerFacesServlet.service(DesignerFacesServlet.java:85)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.designer.runtime.domino.adapter.ComponentModule.invokeServlet(ComponentModule.java:464)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.domino.xsp.module.nsf.NSFComponentModule.invokeServlet(NSFComponentModule.java:673)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.designer.runtime.domino.adapter.ComponentModule$AdapterInvoker.invokeServlet(ComponentModule.java:730)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.designer.runtime.domino.adapter.ComponentModule$ServletInvoker.doService(ComponentModule.java:684)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.designer.runtime.domino.adapter.ComponentModule.doService(ComponentModule.java:453)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(NSFComponentModule.java:657)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.domino.xsp.module.nsf.NSFService.doService(NSFService.java:254)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:259)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:216)
    21/06/2010 20:20:55 HTTP JVM: at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:279)

    Seems like my code cant reach the resources inside the .jar
    Do you have any idea to solve this?