Wicket Notes


Including file content in presentation

I'd been looking around for a way to do this and someone responded with some information in stackoverflow.com, but it was still a little over my head unfamiliar as I am with Wicket. So I got some help from a friend on Wicket internals.

This is an excerpt from my Sample.html file that wants to display the contents of script files that make good examples:

    ...
    <h2> Sample scripts </h2>

    <p>
    A picture is worth a thousand words...
    </p>

    <h4> <i>create.cmd</i>: </h4>
    <div style="margin-left: 40px">
        <pre wicket:id="create" />
    </div>

    <h4> <i>short.suite</i>: </h4>
    <div style="margin-left: 40px">
        <pre wicket:id="suite" />
    </div>
    ...

For each Wicket id, I have to create a Label in TestClientSample.java. It's a bit of a pain, but what I get is priceless.

public Sample()
{
    super();

    add( new FileContentLabel( "create", "/samples/create.cmd" ).setEscapeModelStrings( false ) );
    add( new FileContentLabel( "suite",  "/samples/short.suite" ).setEscapeModelStrings( false ) );
}

The magic is extending Wicket's Lable class. See the Java code below that also contains the file I/O.

The one fly in the ointment was that file I/O isn't totally obvious from a servlet, to wit, just where are my files in the first place? So, I googled around to figure out how to do it using ServletContext. That was a little confusing since I hadn't written the servlet myself and didn't know where to go to satisfy it until Thomas reminded me that Wicket has you inherit from its WebApplication. I added the following to my Application class. (Always useful to work alongside a genius!)

public class Application extends WebApplication
{
    private static ServletContext context = null;

    public Application()
    {
        // can't get ServletContext here: don't try it!
    }

    protected void init()
    {
        context = this.getServletContext();
    }

    public static String getRealpath( String path )
    {
        // let Tomcat tell us where this path really is...
        return context.getRealPath( path );
    }

    ...

I spent a little more time doing it this afternoon than I really had, but it will save me countless hours updating my script examples by hand in our portal app. Earlier already, I WAR'd up our sample scripts to go out downloadable from one of our portal pages, so they were already sitting in my deployed application under webapps.

FileContentLabel.java:

If I haven't mucked the line numbering, the various magic of this solution happens below on lines

package com.hp.web.demoapp.extensions;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.apache.log4j.Logger;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.Model;

import com.hp.web.demoapp.page.Application;

/**
 * Include simple file content in an HTML page.
 *
 * Thomas figured out that Wicket's Label needed to be extended to create the capability
 * of including static file content from (text) files, in our case, just the contents of
 * script files for use in our portal examples.
 *
 * After reading the file, you just use Component.setDefaultModel() to work the magic.
 *
 * @author Russell Bateman
 */
public class FileContentLabel extends Label
{
    private static Logger       log = Logger.getLogger( FileContentLabel.class );
    private static final long   serialVersionUID = 1L;
    private static final String newline = "\n";

    /**
     * Constructor that reads the file and shoves it into the Model.
     *
     * @param wicketid of the label.
     * @param filepath to file whose contents are to be included.
     */
    public FileContentLabel( String wicketid, String filepath )
    {
        super( wicketid );

        String contents = null;

        try
        {
            contents = readFileContents( Application.getRealpath( filepath ) );

            if( contents == null || contents.length() < 1 )
                contents = "(missing file)";
        }
        catch( IOException e )
        {
            log.warn( "IOException reading included content " + filepath );
        }

        setDefaultModel( new Model< String > contents ) );
    }

    private static String readFileContents( String filepath ) throws IOException
    {
        File           f = new File( filepath );
        BufferedReader reader;
        String         line;
        StringBuffer   buffer = new StringBuffer();

        if( !f.exists() )
            return null;

        reader = new BufferedReader( new FileReader( filepath ) );

        do
        {
            line = reader.readLine();

            if( line != null )
            {
                buffer.append( line );
                buffer.append( newline );
            }
        }
        while( line != null );

        reader.close();

        return buffer.toString();
    }
}

Debugging Wicket source code

There's a knack to debugging Wicket that comes with experience. Much of what goes awry can be summed up thus:

  1. Something asked for by the HTML mark-up is not supported by anything in Java.
  2. Something supported by Java is done in such a way, via an add() method call, that requires echoing in the HTML mark-up.
  3. Mixed: something used in HTML is misspelled or otherwise assuming a name that wasn't created in Java.

The second two cases are typically more prevalent than the first in a project undertaken by Java developers.

Hint: when tracking down what's missing/misspelled/extra, it's often necessary to walk the Java hierarchy. A page composition extending another class that does composition work will exhibit trouble that won't be diagnosed without crawling up the class hierarchy. (This may be obvious, but...)

Wicket expresses the first problem as:

    No get method defined for class: class <fully-qualified-classname> expression: <missing variable, etc. >

Wicket expresses the second phenomenon as:

    The component(s) below failed to render. A common problem is that you have added a component in code but
       forgot to reference it in the markup (thus the component will never be rendered).

Wicket then goes on sometimes to name the rendering component and id.

Methodology

Other than what was said higher up here, what I can say is that when I see a Wicket crash, the first thing I do is start looking for field elements (ids) from my page, then I look for any classes I wrote and line numbers in those classes. There's a lot of garbage to sort through; look for element ids, like forgotten in the following error:

Last cause: The component [WebMarkupContainer [Component id = dropdownSpan]] was rendered already. You can render it only once during a render phase. \
Class relative path: org.apache.wicket.markup.html.WebMarkupContainer:paymentCreateForm:forgotten:dropdownSpan

(The problem here was that I was trying to use forgotten twice in the HTML.)