Wednesday, March 17, 2010

Editing tables with modal panels with RichFaces and Seam




A typical GUI use case is to be able to edit a table with a modal dialog (one row at a time). This is easily done with RichFaces and Seam.

In the template we define a datatable and the modal dialogues that are displayed when we right-click on the row.

Note that:
Due to a limitation in JSF, UIInputs that do not fail validation are sticky i.e. they retain their values through multiple requests. This behavior makes sense on many occasions as most of the time users will just correct the errors and resubmit the form, but on the odd occasion is not really desirable such as when using a form to display multiple objects selected by Ajax requests like in this case since if we leave some empty values in the form and select another row the new values selected will not be shown in the panel. See http://wiki.apache.org/myfaces/ClearInputComponents

That's why we have a cancel() method that clears submitted values if we close the dialog while we still have validation errors.

Monday, March 15, 2010

I18N with Seam


Define the available locales in faces-config.xml.


        com.sun.facelets.FaceletViewHandler
        
            sv
            sv            
        
    

Then when you need to present for example a date you can just the locale selector like

@In
private LocaleSelector localeSelector;                 

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("d MMMM yyyy", localeSelector.getLocale());

Wednesday, March 10, 2010

Culture and science

Is there a way of understanding why humans continuously and constantly and without exception engage in cultural activity? It seems to be something that we are biologically inclined to do. If we are, then what is the nature of that drive? What is it doing for us? The better off humans are the more time they spend engaged in issues of style: buying art, buying gadgets, clothes, cars, furniture or whatever and making choices between one look of things and another look of things.

The root of this behavior lies in the need to play at being someone else or at inhabiting other worlds. We have a fantastic ability to move from the world in our heads to the "possible" world in our heads and all the other possible worlds that we can imagine.

The most important thing humans can do is to imagine how things could otherwise be and make choices about them. That's the key to our evolutionary success. We only notice how powerful that process is when we meet people who can't do it - severely autistic children for example, who are incapable of switching worlds - who in many senses can appear completely intelligent, but they are completely incapable of seeing that there's any world other than the one they perceive at this moment. This makes them incapable of cooperation and deception since they can neither create common worlds nor create situations in which one could see a different world from the one it really exists.

To a very large degree, cooperation and deception is what distinguishes humans from other animals. The constant engagement in culture enables us to continually rehearse this ability we have - imagining, exploring, extrapolating other worlds.

This is the point at which there is a deep connection between art and science: each is a highly organized form of saying "let's see what would happen if the world was like this".

Monday, March 8, 2010

External configuration with Seam



Sometimes you want to load environment-specific configuration properties from a file located in the file system. With Spring this is very easy as all you need to do is to use a property placeholder

<context:property-placeholder location="file:/opt/etc/myprops.properties"/>


Seam does not have an out-of-the-box component to do that but you can write your own

@Name("environment")
@Scope(ScopeType.APPLICATION)
@AutoCreate
public class Environment {
    private Properties properties;

    @Create
    public void setup() {
        try {
            properties = new Properties();
            properties.load(new FileInputStream("/opt/etc/myprops.properties"));
        } catch (IOException e) {
            throw new RuntimeException("Unable to load environment.properties", e);
        }
    }

    public String getProperty(String name) {
        return properties.getProperty(name);
    }
}

Then you can use this component anywhere in your application. For example:

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:async="http://jboss.com/products/seam/async"
            xmlns:transaction="http://jboss.com/products/seam/transaction"
            xmlns:mail="http://jboss.com/products/seam/mail"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd
                 http://jboss.com/products/seam/async http://jboss.com/products/seam/async-2.2.xsd
                 http://jboss.com/products/seam/transaction http://jboss.com/products/seam/transaction-2.2.xsd
                 http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.2.xsd
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">


    <core:init transaction-management-enabled="false"/>

    <transaction:no-transaction/>

    <mail:mail-session session-jndi-name="mail/pdfGeneratorSession"/>


    <component name="ftpHandler">
        <property name="ftpHost">#{environment.getProperty('ftpHost')}</property>
        <property name="ftpPort">#{environment.getProperty('ftpPort')}</property>
        <property name="ftpUser">#{environment.getProperty('ftpUser')}</property>
        <property name="ftpPassw">#{environment.getProperty('ftpPassw')}</property>
    </component>

</components>

Friday, March 5, 2010

Easy PDF generation with Seam

Seam is a fantastic integration framework that can do many things. It is mostly known for its integration with JSF 1.x making it actually usable and adding a lot of features that have now become standard in JSF 2.0 i.e. page actions, factory methods, better navigation, etc. 
But there are many more features worth mentioning: JPA lazy loading, BPM support in web applications, integration with quartz, iText integration and many more.

iText integration
The iText library is a widely used open source Java library for generating PDF documents. However, using its programatic API to construct a PDF document is time consuming (think building a XML document using DOM or construct a UI using Swing). Seam neatly ties iText, JSF, and Facelets together and allows developers to declaratively write PDF pages with dynamic content, just as you would do for JSF web pages. Furthermore, you can now use templates in PDF pages.

The key to do this the special XHTML tag library for PDF elements that transparently calls iText when the page is rendered. Let's see a simple example of how we to use the iText integration to send a number of PDF documents attached in an email.
@Name("pdfSender")
@AutoCreate
public class PdfSender {

    @In
    private Renderer renderer;

    @In
    private PdfAssembler pdfAssembler;

    @In
    private FtpHandler ftpHandler;

    public void sendPdf() throws IOException, ParseException {

        Map<String, String> files = ftpHandler.getData();
        if (files.isEmpty()) {
            throw new IllegalArgumentException("No data found on FTP server");
        }

        List<Pdf> pdfList = processFTPData(files);
        Contexts.getEventContext().set("pdfList", pdfList);
        renderer.render("/pdf/sendPdf.xhtml");  //sends mail
        ftpHandler.deleteFiles();
    }

    private List<Pdf> processFTPData(Map<String, String> files) throws ParseException {

        List<Pdf> pdfList = new ArrayList<Pdf>();
        for (final String location : files.keySet()) {
            final String data = files.get(location);
            if (StringUtils.isEmpty(data)) {
                throw new IllegalArgumentException("Empty data file for: " + location);
            }
            pdfList.add(pdfAssembler.assemblePdf(location, data));
        }

        return pdfList;
    }
}

We just gather a bunch of data files via FTP, process them into a collection of DTOs representing the PDF structure and then add it to Seam's event context.  The DTOs are used to transfer data to the view (xhtml templates). Next we just "render" the mail with the attachments.

<?xml version="1.0" encoding="UTF-8"?>
<m:message xmlns="http://www.w3.org/1999/xhtml"
           xmlns:ui="http://java.sun.com/jsf/facelets"
           xmlns:h="http://java.sun.com/jsf/html"
           xmlns:p="http://jboss.com/products/seam/pdf"
           xmlns:m="http://jboss.com/products/seam/mail">

    <m:header name="X-Sent-From" value="PDFGenerator"/>
    <m:from name="PDF Generator" address="#{mailAddress}"/>
    <m:to name="Manuel Palacio">#{mailAddress}</m:to>
    <m:subject>PDF</m:subject>
    <m:body>
        <ui:repeat value="#{pdfList}" var="pdf">
            <m:attachment fileName="#{pdf.location}.pdf">
                <ui:include src="/pdf/pdf.xhtml"/>
            </m:attachment>
        </ui:repeat>
    </m:body>

</m:message> 
 
Pdf.xhtml generates the sections of the document

<p:document xmlns:ui="http://java.sun.com/jsf/facelets"
            xmlns:f="http://java.sun.com/jsf/core"
            xmlns:c="http://java.sun.com/jsp/jstl/core"
            xmlns:p="http://jboss.com/products/seam/pdf"
            title="Report"
            keywords="Report #{pdf.artNr}"
            subject="Report #{pdf.artNr}"
            author="MP"
            creator="MP">


   
     <ui:repeat value="#{pdf.sections}" var="section" varStatus="status">
        <p:chapter number="#{status.index + 1}">
            <p:title><p:font size="18"><p:paragraph spacingAfter="12"
                                                    alignment="center">#{section.title}</p:paragraph></p:font></p:title>

            <ui:repeat value="#{section.areas}" var="area">
                <p:section>
                    <p:title>
                        <p:font size="8" style="bold"><p:paragraph
                                spacingBefore="10">#{area.name}</p:paragraph></p:font>
                    </p:title>
                    <p:paragraph spacingBefore="1" spacingAfter="0" leading="10" keepTogether="true">
                        <ui:include src="/pdf/displayObject.xhtml"/>
                    </p:paragraph>
                </p:section>
            </ui:repeat>
        </p:chapter>
    </ui:repeat>

</p:document>


DisplayObject.xhtml generates the data in each section

<ui:repeat value="#{area.displayObjects}" var="displayObject" xmlns:ui="http://java.sun.com/jsf/facelets"
           xmlns:p="http://jboss.com/products/seam/pdf">
    <p:paragraph>
        <p:font size="8" style="bold">#{displayObject.name}</p:font>
        <p:font size="8">&#160;(#{displayObject.formattedId})</p:font>
        <p:font size="8" rendered="#{not empty displayObject.address}">&#160;#{displayObject.address}</p:font>
    </p:paragraph>
    <ui:repeat value="#{displayObject.events}" var="event">
        <p:paragraph>         
            <p:font size="7">#{event.text}&#160;#{event.date}</p:font>
         </p:paragraph>
    </ui:repeat>
    <p:paragraph spacingAfter="5"/>
</ui:repeat>

It takes time to learn how to tweak all the parameters to make the PDF look the way you want but it's definitely much easier and intuitive than using the iText API directly. For very advances formatting you may want to look at jasperreports.

Now we could define a cron job that runs the sendPdf task at defined intervals.

@Name("schedulerController")
public class SchedulerController {

    @In
    ScheduleProcessor processor;
    
    /**
    Method called when framework is initialized
    */ 
    public void startScheduler(String cronExpression) throws IOException, ParseException, ExecutionException, InterruptedException {
        processor.createQuartzTimer(new Date(), cronExpression);
    }
}

/**
 * Timer class. The methods annotated as asynchronous will be called by Quartz or other implementations
 * as specified by @IntervalCron
 */
@Name("processor")
@AutoCreate
@Scope(ScopeType.APPLICATION)
public class ScheduleProcessor {


    @Logger
    private Log log;

    @In
    private PdfSender pdfSender;

    @Asynchronous
    public QuartzTriggerHandle createQuartzTimer(@Expiration Date when, @IntervalCron String interval) throws IOException, ParseException, ExecutionException, InterruptedException {
        
        log.info("[#0] Processing pdf document with interval #1", when, interval);
        pdfSender.sendPdf();

        return null;
    }
}

In components.xml we make sure the cron job is started when Seam is initialized

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:async="http://jboss.com/products/seam/async"
            xmlns:transaction="http://jboss.com/products/seam/transaction"
            xmlns:mail="http://jboss.com/products/seam/mail"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd
                 http://jboss.com/products/seam/async http://jboss.com/products/seam/async-2.2.xsd
                 http://jboss.com/products/seam/transaction http://jboss.com/products/seam/transaction-2.2.xsd
                 http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.2.xsd
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd">


    <core:init transaction-management-enabled="false"/>

    <transaction:no-transaction/>

    <mail:mail-session session-jndi-name="mail/pdfGeneratorSession"/>


    <component name="ftpHandler">
        <property name="ftpHost">localhost</property>
        <property name="ftpPort">2121</property>
        <property name="ftpUser">admin</property>
        <property name="ftpPassw">admin</property>
    </component>

    <async:quartz-dispatcher/>

    <factory name="mailAddress" scope="STATELESS" value="xxx@xxx.xx" auto-create="true"/>

    <event type="org.jboss.seam.postInitialization">
        <action execute="#{schedulerController.startScheduler('0 0/1 * * * ?')}"/>
    </event>


</components>

Seam has definitely made simple tasks like sending mail and generating PDFs even simpler and more elegant.

Wednesday, March 3, 2010

Interface naming

Some not so good examples of interface naming are:

interface PersonDS{}

PersonDSEJB implements PersonDS {}

class User implements IUser {}

The first example shows how ugly and meaningless it can be when you are forced to implement an interface by the framework.

In the second example the prefix hurts readability. On top of that, when changing from an abstract class to an interface, a coding convention with prefix implies renaming all the occurrences of the class.

There is also another convention, used by many open source projects including Spring

interface User {}

class DefaultUser implements User {}

class AnotherClassOfUser implements User {}

Much better :)