Sunday, March 6, 2011

Struts2, Tiles2, Spring, Hibernate, JQuery plugin - Using JODReports for ODT Templates

Disclaimer:
All the software applications that appear below represent trademarks and they are the property of their respective owners.
Use and implement my notes in this article at your own risk.

Discussion here:
1. General Considerations;
2. Prerequisites;
3. ODT Templates;
4. JODReports Configuration;
5. KeyValuePairBean;
6. Struts 2 Actions;
7. DAOs;
8. Annotated Hibernate entities;

1. General Considerations:
This sample is purposed to show how to export OpenOffice ODT, Microsoft Word DOC, Adobe PDF documents by using an OpenOffice ODT template; We will export 3 fields, all string type: the title of the document, a chosen Zone fron "zone" table from "blog" database and an author. The document type will be chosen by user (ODT, DOC, PDF).

NOTE: if you don't know for example what is "blog" database, or other aspects, then you should read the "Struts2, Tiles2, Spring, Hibernate, JQuery plugin - General Considerations", which is used as a basis for this sample.

2. Prerequisites:
Make sure you have read the "Struts2, Tiles2, Spring, Hibernate, JQuery plugin - General Considerations", and installed and configured all that is written there;

Besides the components above, you should also google for the following Java libraries and put them on the buid path of the app:
- "jodconverter-2.2.2.jar";
- "jodreports-2.4.0.jar";
- "juh-2.3.0.jar";
- "jurt-2.3.0.jar";
- "ridl-2.3.0.jar";
- "unoil-2.3.0.jar";
- "xom-1.2.5.jar".

3. ODT Templates:
ODT Template is actually a synonim for an OpenOffice Writer document - ie. the analogue of the Microsoft Word DOC document;
the term of template is used because the ODT will be used in a given form in which some input fields were mapped; these fields will be completed with the Java values we send from our app.
ODT is used because of its simplicity in use.

4. JODReports Configuration:
JOD is a Java tool that behaves as a reporting tool; it allows the rendering of ODT templates as sources by using OpenOffice components, a kind of mail merge.
JOD is capable to export ODT files by itself, but if we want to convert files in DOC or PDF we will need to call the OpenOffice service whose engine will convert the ODT results to documents as DOC and PDF.
So, you will have to install also OpenOffice 3.x and to create a batch file (*.bat) that will launch the OpenOffice service engine. Your batch file should contain:
<<
"C:\Program Files\OpenOffice.org 3\program\soffice" -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard
>>
In this case, OpenOffice service will rule on the 8100 port.
The OpenOffice service should be launched before your app is started. If you intend to restart the machine for several times, then you should better load this batch file on startup.
Integration of JOD OpenOffice document conversion capabilities with Struts2 is made via Spring services.
You will have to paste these beans definitions in your applicationContext.xml:
<<
    <bean id="openOfficeConnection" class="com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection" destroy-method="disconnect">
        <constructor-arg index="0">
            <value>127.0.0.1</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>8100</value>
        </constructor-arg>
    </bean>

    <bean id="documentFormatRegistry" class="com.artofsolving.jodconverter.DefaultDocumentFormatRegistry"/>

    <bean id="documentConverter" class="com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter">
        <constructor-arg index="0">
            <ref bean="openOfficeConnection"/>
        </constructor-arg>
        <constructor-arg index="1">
            <ref bean="documentFormatRegistry"/>
        </constructor-arg>
    </bean>

>>

5. KeyValuePairBean:
This bean is used as a structure to store key/value pair for zone list elements.

6. Struts 2 Actions:
When using Struts, the results are rendered by using Struts Actions, which represent the level of Controller from MVC architecture.
Our Struts action should return a stream type result, as the result is in fact the exported document.
The Struts Action is denoted "writeODFAction". We will send to it the HTTP parameters "title", "zones", "author" and "documentType" by using this JSP Struts code written in "jod.jsp":
<<
    <s:form action="writeODF" id="writeODF">
    <s:property value="odfTry" />
        <s:textfield id="title" name="title" label="Title of the document" labelposition="left"></s:textfield>
        <s:select id="zones" name="zones" labelposition="left" label="Zone" list="%{zones}" listKey="value" listValue="value" />
        <s:textfield id="author" name="author" label="Author" labelposition="left"></s:textfield>
        <s:select name="documentType" label="Document Type" labelposition="left" list="#{'odt':'odt','doc':'doc','pdf':'pdf'}"></s:select>
        <s:submit cssClass="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only ui-button-text" name="btnSubmitODT" value="create document from template" />
    </s:form>

>>
The data existent in "<s:select id="zones" name="zones" />" is obtained with the help of the starting action, namely "JodAction", using the code:
<<
            List<Zone> listZones = (List<Zone>)zoneDAO.findAll();
            zones = new ArrayList<KeyValuePairBean>();
            for (Zone zone : listZones) {
                zones.add(new KeyValuePairBean(zone.getCod(), zone.getName()));
            }

>>

When sent the 4 HTTP parameters, getters/setters should be created in "writeODFAction".

The main code for "writeODFAction" is here:
<<
    @Override
    @Action(value="/writeODF", results={
        @Result(name="success", type="stream", params={"contentType", "${contentType}", "inputName", "documentStream", "contentDisposition", "filename=${fileName}.${fileType}", "bufferSize", "1024"}),
        @Result(name="input", location="/jsp/empty.jsp")
    })
    public String execute() {
        try {
           
            DocumentFormat outputFormat = documentFormatRegistry.getFormatByFileExtension(documentType);
            Resource templateDirectory = getTemplateDirectory(odtFileName);
           
            File templateFile = null;           
            if (templateDirectory.exists()) {
                templateFile = templateDirectory.getFile();
            } else {
                templateFile = getTemplateFile(odtFileName).getFile();
                if (!templateFile.exists()) {
                    addActionError(getText("error", new String[] {"Template not found!"}));
                    return ActionSupport.INPUT;
                }
            }

            DocumentTemplateFactory documentTemplateFactory = new DocumentTemplateFactory();
            DocumentTemplate template = documentTemplateFactory.getTemplate(templateFile);
            ByteArrayOutputStream odtOutputStream = new ByteArrayOutputStream();
           
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("title", title);
            data.put("zones", zones);
            data.put("author", author);

            template.createDocument(data, odtOutputStream);
           
            setContentType(outputFormat.getMimeType());
            setFileName(odtFileName);
            setFileType(outputFormat.getFileExtension());

            if ("odt".equals(outputFormat.getFileExtension())) {
                setDocumentStream(new ByteArrayInputStream(odtOutputStream.toByteArray()));
            } else {
                ByteArrayInputStream odtInputStream = new ByteArrayInputStream(odtOutputStream.toByteArray());
                ByteArrayOutputStream docOutputStream = new ByteArrayOutputStream();
                DocumentFormat inputFormat = documentFormatRegistry.getFormatByFileExtension("odt");
                documentConverter.convert(odtInputStream, inputFormat, docOutputStream, outputFormat);
                setDocumentStream(new ByteArrayInputStream(docOutputStream.toByteArray()));
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return ActionSupport.SUCCESS;
    }

>>
Notice the presence of various methods:
"getTemplateDirectory()" - obtains the physical directory path with the help of Spring Resource;
"getTemplateFile" - obtains the physical directory path with the help of Spring Resource;

contentType, fileName, fileType are used to render a "stream" action result, together with the "documentStream".
You may observe here annotations to define the "success" and "input" results (overcomes) of the action "writeODFAction".

NOTE: to see how the Struts 2 Actions are accessed, and how their results are rendered, checkout also the "struts.xml" and "tiles.xml" files.

7. DAOs
DAOs, namely Data Access Objects, are used by Spring to obtain data from database via Hibernate.
DAOS are part of the business layer of a database web app.
A DAO class extends in this case the "HibernateDaoSupport" and contains methods to access the database via annotated Hibernate entities.
Any DAO should be described as a bean in the "applicationContext.xml" file in order to be seen by the sessionFactory; for example, for zones:
<<
    <bean id="ZoneDAO" class="blog.hibernate.dao.ZoneDAO">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>

>>

From Java point of view, a DAO should use a session manager, in this case, "HibernateDaoSupport" which is a Spring class that manages Hibernate sessions over annotated entities.
NOTE: a method that uses Hibernate session to get data from database uses HQL (Hibernate Query Language); to understand the HQL syntax you should google for HQL documentation.

8. Annotated Hibernate entities
An annotated entity is a POJO class that describes a database table by means of annotations.
It represents the Model from the MVC architecture.
For example, for the database table "zone", it was defined the Zone.java entity class; to refer table "zone" from database "blog", annotations are placed at the top of the class definition:
<<
@Entity
@Table(name = "zone", catalog = "blog")

>>
, while for each of table "zone" fields, there are specified on their value getters: primary key, foreign key, column name, field length, aso, if they exist.
Here are some examples of annotations, taken from Zone.java:
<<
    @Id
    @GeneratedValue
    @Column(name = "cod", unique = true, nullable = false, length = 4)

>>
<<
    @Column(name = "name", nullable = false, length = 45)
>>
<<
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "zone")
>>

NOTE: if MyEclipse is used as IDE, you may generate both DAO and entity for each table by using Hibernate Reverse Engineering perspective.

No comments:

Post a Comment