java
Mar 08, 2013
Merging odf files using XDocReport and XPages
Often there is the need to create documents from templates, and the need to fill these templates with data available from other sources: let's take a look at this brief solution
In this scenario, we have a odf template to merge with data.
Working in the Lotus Domino environment, one way to achieve this goal could be a server-side component invoked by an XPagdies, to make the casting process: first, I need something in Java that could do the job for me, and after some search I came across XDocReport.
XDocReport means XML Document reporting
XDocReport provides Java API to merge XML document created with MS Office (docx, pptx) or OpenOffice (odt), LibreOffice (odt) with a Java model to generate report and convert it in another format (PDF, XHTML...).
Basicly, to make the template, main steps are:
- create a template document with MS Word (docx) or OpenOffice (odt, ods)
- compose the body of the document itself with variables to fill in
- use Velocity or Freemarker syntax to set variables to replace.
In this case, I opted to use Velocity inside an ODF file.
Now let's start by explaining how to set up the whole project.
Installing server libraries
External libraries (.jar) that serve the purpose have been installed in the installation directory of the Domino server (jvm/lib/ext path).
These are the files:
commons-collections-3.2.1.jar commons-lang-2.4.jar fr.opensagres.xdocreport.converter-1.0.0.jar fr.opensagres.xdocreport.converter.odt.odfdom-1.0.0.jar fr.opensagres.xdocreport.core-1.0.0.jar fr.opensagres.xdocreport.document-1.0.0.jar fr.opensagres.xdocreport.document.odt-1.0.0.jar fr.opensagres.xdocreport.itext.extension-1.0.0.jar fr.opensagres.xdocreport.template-1.0.0.jar fr.opensagres.xdocreport.template.velocity-1.0.0.jar itext-2.1.7.jar odfdom-java-0.8.7.jar org.odftoolkit.odfdom.converter-0.9.0.jar org.odftoolkit.odfdom.converter.core-1.0.0.jar org.odftoolkit.odfdom.converter.pdf-1.0.0.jar org.odftoolkit.odfdom.converter.xhtml-1.0.0.jar oro-2.0.8.jar velocity-1.7.jar
Dont' forget an important security setting in java.policy file:
Server-side tools
To manage the whole process I used an HTML page with a button that calls (via Ajax) an XPages, that deals with the fusion of the data (HTML string) in the template. At the end, I create the PDF file using iText libraries.
Related to XPages, it only call the servlet in the afterPageLoad event:
com.redturtle.XDocReport.mergeAndPDF( facesContext.getExternalContext().getRequest(), facesContext.getExternalContext().getResponse() );
This is how it looks the package, which includes the servlet:
Finally, this is the servlet code:
package com.redturtle;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.odftoolkit.odfdom.converter.pdf.PdfConverter;
import org.odftoolkit.odfdom.converter.pdf.PdfOptions;
import org.odftoolkit.odfdom.doc.OdfTextDocument;
import fr.opensagres.xdocreport.core.document.SyntaxKind;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
import lotus.domino.NotesException;
import lotus.domino.Session;
public class XDocReport {
private static String VERSION = "XDocReport Version 1.0.0";
static PrintWriter outClass = null;
private static Session session = null;
public static void mergeAndPDF (HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException, InterruptedException, NotesException {
// NO DIIOP
session=DominoAccess.getCurrentSession();
try {
// 1) Load ODT file by filling Velocity template engine
InputStream in = new FileInputStream("/tmp/template.odt");
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(in,TemplateEngineKind.Velocity);
FieldsMetadata metadata = report.createFieldsMetadata();
metadata.addFieldAsTextStyling("comments",SyntaxKind.Html,false);
// 2) Create context Java model
IContext context = report.createContext();
context.put("comments", "<b>hello world</b>");
// 3) Generate report by merging Java model with the ODT
OutputStream out = new FileOutputStream(new File("/tmp/ODTProjectWithVelocity_Out.odt"));
report.process(context, out);
// 1) Load ODT into ODFDOM OdfTextDocument
in= new FileInputStream(new File("/tmp/ODTProjectWithVelocity_Out.odt"));
OdfTextDocument document = OdfTextDocument.loadDocument(in);
// 2) Prepare Pdf options
PdfOptions options = PdfOptions.create();
// 3) Convert OdfTextDocument to PDF via IText
out = new FileOutputStream(new File("/tmp/ODTProjectWithVelocityList_Out.pdf"));
PdfConverter.getInstance().convert(document, out, options);
// Only manage response to Ajax call
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
outClass = new PrintWriter(response.getWriter());
printAgent("OK");
return;
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
protected static void printAgent(String string) {
outClass.println(string);
}
}
Pay attention to these points:
- comments - variable name inside ODF template
- metadata.addFieldAsTextStyling("comments",SyntaxKind.Html,false); - fill variable with something in HTML syntax
- <b>hello world</b> - HTML string to merge
NOTE: DominoAccess and JSFUtil are two very helpful classes written by Karsten Lehmann: see the full code. Thanks to them, DIIOP process for communication is not required.
Pro & Cons.
I think that it's a very powerful approach; the only limitation I've found is the restricted set of styles supported by the merge process (see there for details). Feel free to make some test using this approach, and let me know what you think :-)
Document Actions
Apr 12, 2012
"Error cleaning up agent threads" - a never ending story
A real case that has been solved
There are thousands of posts on Lotus Domino forum about this problem: "Error cleaning up agent threads".
I ran into a similar problem using a Java agent. I state that this agent has been inherited from another developer ;-) so it was not easy for me to initially understand what the problem could be.
In any case, every time the agent ended, I had the above error to the log.
In truth, none of the posts that I've seen has been helpful, until, by inspecting the code carefully and isolating the parts of it, I found the solution.
One line of code was:
Utils u = new Utils (..... some parameter ....);
public class Utils extends AgentBase {
In practice, the class is defined to extend agentBase, so it's like it was an agent itself. The result is that each time it's loaded, a thread is istantiated.
When finished, the agent's thread was terminated, but the child's thread lost the reference to his father, causing the error.
This problem is naturally solved by changing the definition of the class:
public class Utils {
It may seem a trivial issue, but I guarantee that it is really sneaky, especially if you are working on other people's code.
As you can check on support forums, every developer has a different solution for this problem. In the end, my suggestion are:
- Never extend AgentBase in external class
- use recycle() methods to clean up memory object, expecially in loop code
Any other input on this issue is welcome.
Stay tuned!
Document Actions
Nov 04, 2011
Say hi to Android
A brief introduction to Android for developers
In the last months, I have been looking for an opportunity to get into the Android world.
Finally, after a while I got the right one: porting an iPhone app to Android platform.
The application is quite simple because it doesn't involve anything far from a common web application: it just retrieves information about events from a web service.
If you make a list of performed tasks, it would look like this:
- retrieving the xml content
- parsing the xml
- storing a few configuration files
- loading images
- building a couple of data structures
- showing everything using the specific views and widgets
Thanks to the reference documents and materials found in the Internet, I got a demo app smiling on my device in a couple of days (not a proper relase, but it could run smoothly).
If you are familiar with Eclipse, you will appreciate the effort made by Google, in order to provide a confortable developement environment. Let's see why.
From the official site you can download:
- Android SDK (JDK required)
- Android Development Tools (ADT) Plugin
You just need to set your SDK. And that's all! Ready to start.
As you can easily guess, the default API provided by Google is Java-based.
I know that some of you can argue "Java is not the best choice", but it's solid and well known by most programmers. It is also well documented and it comes with tons of libraries.
The Android application layout is done by a bunch of xml files. The ADT provides a nice interface to manage them.
All the Java code needed for your app can be taken from your personal toolset or grabbed from the net.
What I love most, is that you can easily connect your device and directly deploy the application on it. Moreover, for those who make use of remote debugging in Java, Android (DDMS) is faster and simpler then ever: just press the debug button and your code will be ready for debugging from your android device. Nothing new to the world, but as I said easier then I thought.
A small disadvantage if you use the virtual device (AVD), because it is really slow, especially in loading.
Just a small consideration: because the hardware behind a mobile device is usually less powerful then a standard pc, you have to be careful.
In a few days the release of AppEventi for Android will be ready. http://www.ferraraterraeacqua.it/
Here is the iPhone app: http://itunes.apple.com/en/app/ferrara-eventi/id449817238?mt=8
Document Actions
Oct 28, 2011
Using JavaMail with TLS
Snippets about sending mail though Java with TLS support
Here is an example to show you how to use JavaMail API method to send an email, using both TLS and SSL connection.
If you are using TLS, remember to disable this property:
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.starttls.enable","true")
props.put("mail.smtp.host", server);
props.put("mail.smtp.starttls.enable","true");
props.put("mail.smtp.auth", "true");
props.put("mail.debug","false");
props.put("mail.smtp.port",port);
props.put("mail.smtp.ssl.enable","true");
props.put("mail.smtp.socketFactory.port", port);
props.put("mail.smtp.socketFactory.fallback", "false");
Remember that, when you set Authentication to server with PasswordAuthentication class, you could experience this error:
"javax.mail.AuthenticationFailedException: 535-5.7.1 Username and Password not accepted."
Send by server if user or password used for authentication failed. Usually means that server requires full user name (user@server.com) to be used.
Document Actions
Jul 19, 2011
Problems with charset in 8.5.1 environment
Strange behaviour managing XML file in Java agent
In this scenario, i've experienced some problems with UTF-8 charset in strings. In 6.5 environment everithing works fine.
This is my code in a Java agent:
org.w3c.dom.Document docXML = attachSegnatura.parseXML(false); XSLTResultTarget out = new XSLTResultTarget(); out.setFileName(path + "SegnaturaRT.xml"); attachSegnatura.transformXML(xsl, out); DOMParser parser = new DOMParser(); parser.parse(path + "SegnaturaRT.xml"); org.w3c.dom.Document docRT = ((DOMParser)parser).getDocument();
tFactory = TransformerFactory.newInstance(); StreamSource xslSS = new StreamSource(pathFile+"Segnatura.xsl"); StreamSource xslRT= new StreamSource(pathFile + "Segnatura.xml"); FileOutputStream fos = new FileOutputStream(pathFile + "SegnaturaRT.xml"); transformer = tFactory.newTransformer(xslSS); transformer.transform(xslRT, new StreamResult(fos)); transformer.reset(); fos.close(); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); docRT = dBuilder.parse(new File(pathFile + "SegnaturaRT.xml"));
Anybody experienced something like this?