JasperReports Tips

JasperReports is a java based open source reporting framework with similar, if not more, functionality to that of Crystal Reports. There is a visual report designer called iReport, but my experience of using it for anything but the simplest report has been a waste. It is useful for dragging and dropping report layouts around, but once you get the hang of it, it’s far easier to do it directly in XML.

Documentation is available, but it’s quite often difficult to get simple answers via a google search – as is possible for most open source software. The current books also lack good examples when they explain some of the more powerful features. Simple things like, I don’t want my report to have nulls printed on it, what are my options are suprisingly awkward to locate! My own, FAQs …

  • Null values in reports, what can I do?

    If you want to replace the null value with a blank entry this is easy to achieve with the isBlankWhenNull attribute of textField

    <textField isBlankWhenNull="true"> ... </textField>

    If you need to replace the null value with something else, you can do this directly in the report as shown below. Using the ternary operator the GROUP_NAME field is checked for a null, if it is null the text None is used instead.

    <textFieldExpression>(($F{GROUP_NAME} != null) ? $F{GROUP_NAME} : "None")</textFieldExpression>
  • How to add a row count?

    If you need a row count and don’t want to add unnecessary fields into the SQL statement you can use a variable. Straight after your field definitions include the following, assuming you have a field named USER_NAME in your fied definitions!

    <variable name="row_count" class="java.lang.Integer" calculation="Count">
        <variableExpression><![CDATA[$F{USER_NAME}]]></variableExpression>
        <initialValueExpression><![CDATA[new java.lang.Integer(0)]]></initialValueExpression>
    </variable>

    This can be used in the report as follow

    <textField>
        <reportElement x="0" y="0" width="30" height="20"/>
        <textFieldExpression class="java.lang.Integer"><![CDATA[$V{row_count}]]></textFieldExpression>
    </textField>
  • Highlight certain rows depending on the value of other data?

    If you need to highlight some text, the trick is to create two versions of the textField that displays it and use the <printWhenExpression> tag to determine which one displays the text. The below example highlight in red if the PASS_DATE field is null.

    <!-- main textField -->
    <textField>
        <reportElement x="466" y="0" width="228" height="20">
            <printWhenExpression>new Boolean($F{PASS_DATE} != null)</printWhenExpression>
        </reportElement>
        <textFieldExpression class="java.lang.String"><![CDATA[$F{USER_NAME}]]></textFieldExpression>
    </textField>
    
    <!-- highlight textField -->
    <textField>
        <reportElement x="466" y="0" width="228" height="20" forecolor="red">
            <printWhenExpression>new Boolean($F{PASS_DATE} == null)</printWhenExpression>
        </reportElement>
        <textFieldExpression class="java.lang.String"><![CDATA[$F{USER_NAME}]]></textFieldExpression>
    </textField>

JSTL Internationalisation

Some notes on how to get JSTL pages ready.

Setup

Inside a JSP page included on every other, for example a header jsp file.

<fmt:setLocale value="${param.locale}" scope="request" />
<fmt:setTimeZone value="${param.timeZone}" scope="request" />
<fmt:setBundle basename="Messages"/>

At your classpath root create a fallback localised file called Messages.properties in which to store key/value pairs. Files for other languages take the form of Messages_en.properties, Messages_en_US.properties, etc.

page.text.title=Page Title
page.text.message=Page Message
page.text.welcomeMessage=Welcome {0}
page.text.returnLink=<a href="{0}" title="Return">Return</a>

Examples

<h1>Page Title</h1>
<h1><fmt:message value="page.text.title"/></h1>

<h1>Welcome <c:out value="${person.name}"/></h1>
<h1&gt<fmt:message value="page.text.welcomeMessage"><fmt:param value="${fn:escapeXml(person.name)"}/></fmt:message></h1>

Java Authentication – LDAP and Active Directory

Been asked to integrate your application’s authentication with an LDAP directory (Active Directory is LDAP v3 compliant)? Me too! There is a fair amount of information about this topic available by searching, but when I was doing this I couldn’t find one place that had everything explained in detail, so I decided to document how I did it.

What follows will explain how to validate a username/password combination against an LDAP compliant directory server using java and the opensource LDAP library called jldap.

First off, go and download the jldap jar file and browse around the code samples as it’s a well documented library. Second, take a look how easy it is to make a connection to an LDAP directory server.

LDAPConnection conn = new LDAPConnection();
conn.connect("localhost", 389);
conn.bind("cn=admin,dc=tilion,dc=org,dc=uk", "password");

If you’re not too sure about LDAP syntax you may like to read the Wiki LDAP entry. In short, LDAP uses a tree structure where each entry has a unique identifier, known as it’s Distinguished Name (DN). In the username above cn=admin is the Relative Distinguished Name (RDN) and dc=tilion,dc=org,dc=uk is the DN of it’s parent entry. Put together these form the DN for a user with privileges to bind to the LDAP directory (DC stands for Domain Component). Entries generally have a CN attribute, known as the Common Name along with a whole load of more familiar named attributes.

If binding to an Active Directory server, the username is more likely to be of the format cn=Administrator,cn=Users,dc=tilion,dc=org,dc=uk. Part of the complexity with LDAP queries is that there is no fixed format for where particular types of entry live from server to server. Most Active Directory servers will be alike, but won’t be the same when compared to a Novell directory or an OpenLDAP server. For the purposes of authentication we need to locate where in the directory the entries that represent a user object live.

  • cn=Users,dc=tilion,dc=org,dc=uk is the default for Active Directory
  • ou=People,dc=tilion,dc=org,dc=uk is the default an OpenLDAP server storing unix users accounts (the one I have anyway!)

Whoever set up the LDAP server should be able to tell you the base DN for your environment. For example, when setting up Active Directory you specify the name (check terminology) in the format machine.domain.ext, which would lead to the base DN of dc=machine,dc=domain,dc=ext.

Here is where we get to the querk. Imagine we need to check a login where the username is darren and the password, well, lets just say it’s the right password for the username. On an OpenLDAP server, all you need to do is try binding to the directory as shown below.

conn.bind("uid=darren,ou=People,dc=tilion,dc=org,dc=uk", "password");

uid is the attribute that holds the actual username value (the CN, or Common Name is often different to the actual username).

Unfortunately, Active Directory is different, in that you can only bind to it using a DN, which references the actual entry via it’s CN. What you have to do is perform a query to check if the username exists, grab it’s CN and then perform a bind using the CN and the given password. So, let’s see this in action …

// assume we have a connection, which is already bound
LDAPSearchResults searchResults = conn.search(
        "cn=Users,dc=tilion,dc=org,dc=uk",
        LDAPConnection.SCOPE_ONE,
        sAMAccountName + "=" + &lt;username&gt;,  // &lt;username&gt; came from the user trying to login
        null,
        false);
LDAPEntry entry = searchResults.next();
if (entry != null) {  // the username is valid, lets pull out the CN from the attributes
    String cnValue = null;
    LDAPAttributeSet attrSet = entry.getAttributeSet();
    Iterator allAttrs = attrSet.iterator();
    while (allAttrs.hasNext()) {
        LDAPAttribute attr = (LDAPAttribute)allAttrs.next();
        String attrName = attr.getName();
        if (attrName.equalsIgnoreCase("cn")) {  // we got the CN
            cnValue = attr.getStringValues().nextElement();
        } else {
            continue;
        }
    }

    if (cnValue == null) {
        // return auth failed, the username doesn't exist
    }

    // attempt a bind with CN and given password
    LDAPConnection tmp = new LDAPConnection();
    tmp.connect(HOST, PORT);
    tmp.bind("cn=" + cnValue + "," + "cn=Users,dc=tilion,dc=org,dc=uk", &lt;password&gt;);  // &lt;password&gt; came from the user trying to login

    // return auth successful, username and password are valid

    // an LDAPException is thrown if the credentials are invalid
}

Concepts covered, you’re probably wondering how are you going to find all those cn,dn,dc,xyz details about your particular LDAP directory? That’s exactly why I created a standalone application to query an LDAP server when I was learning this stuff. You can download the LDAP test application (NOT UPLOADED YET!), which includes the the compiled jar, full source and a maven pom.xml.

The code shown here is for illustration purposes only and should not be used in production without proper error handling additions. It is as concise as possible to illustrate a point.

Useful Attributes

A quick round up of useful attributes in various LDAP compliant servers.

Active Directory

  • sAMAccountName holds the username
  • displayName holds the full name
  • mail holds the email address

OpenLDAP (holding unix user accounts)

  • uid holds the username
  • cn holds the full name
  • mailacceptinggeneralid holds the email address

Reading/writing mixed endian binary files in java

Shameless reproduction of content from http://www.heatonresearch.com/articles/22/page2.html for my own reference.

The BinaryFile class can be seen in BinaryFile.java. To use the BinaryFile class, create a RandomAccessFile class to the file that you would like to work with. This file can be opened for read or write access. Then construct a BinaryFile object, passing in your RandomAccessFile object to the constructor. The following two lines prepare to read/write to a file called “test.dat”.

file=new RandomAccessFile("test.dat","rw");
bin=new BinaryFile(file);

Once this is complete, you can call the various methods provided, to access different data types. The methods to access the various data types are prefixed with either read or write and then the type. For example, the method to read a fixed length string is readFixedLengthString. The complete class is shown in Listing 1.

Listing 1: Reading Java Binary Files

import java.io.*;

/**
 * @author Jeff Heaton(<a href="http://www.jeffheaton.com" title="http://www.jeffheaton.com">http://www.jeffheaton.com</a>)
 * @version 1.0
 */
class BinaryFile
{

  /**
   * Use this constant to specify big-endian integers.
   */
  public static final short BIG_ENDIAN = 1;

  /**
   * Use this constant to specify litte-endian constants.
   */
  public static final short LITTLE_ENDIAN = 2;

  /**
   * The underlying file.
   */
  protected RandomAccessFile _file;

  /**
   * Are we in LITTLE_ENDIAN or BIG_ENDIAN mode.
   */
  protected short _endian;

  /**
   * Are we reading signed or unsigned numbers.
   */
  protected boolean _signed;

  /**
   * The constructor.  Use to specify the underlying file.
   *
   * @param f The file to read/write from/to.
   */
  public BinaryFile(RandomAccessFile f)
  {
    _file = f;
    _endian = LITTLE_ENDIAN;
    _signed = false;
  }

  /**
   * Set the endian mode for reading integers.
   *
   * @param i Specify either LITTLE_ENDIAN or BIG_ENDIAN.
   * @exception java.lang.Exception Will be thrown if this method is
   * not passed either BinaryFile.LITTLE_ENDIAN or BinaryFile.BIG_ENDIAN.
   */
  public void setEndian(short i) throws Exception
  {
    if ((i == BIG_ENDIAN) || (i == LITTLE_ENDIAN))
      _endian = i;
    else
      throw (new Exception(
          "Must be BinaryFile.LITTLE_ENDIAN or BinaryFile.BIG_ENDIAN"));
  }

  /**
   * Returns the endian mode.  Will be either BIG_ENDIAN or LITTLE_ENDIAN.
   *
   * @return BIG_ENDIAN or LITTLE_ENDIAN to specify the current endian mode.
   */
  public int getEndian()
  {
    return _endian;
  }

  /**
   * Sets the signed or unsigned mode for integers.  true for signed, false for unsigned.
   *
   * @param b True if numbers are to be read/written as signed, false if unsigned.
   */
  public void setSigned(boolean b)
  {
    _signed = b;
  }

  /**
   * Returns the signed mode.
   *
   * @return Returns true for signed, false for unsigned.
   */
  public boolean getSigned()
  {
    return _signed;
  }

  /**
   * Reads a fixed length ASCII string.
   *
   * @param length How long of a string to read.
   * @return The number of bytes read.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public String readFixedString(int length) throws java.io.IOException
  {
    String rtn = "";

    for (int i = 0; i &lt; length; i++)
      rtn += (char) _file.readByte();
    return rtn;
  }

  /**
   * Writes a fixed length ASCII string.  Will truncate the string if it does not fit in the specified buffer.
   *
   * @param str The string to be written.
   * @param length The length of the area to write to.  Should be larger than the length of the string being written.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeFixedString(String str, int length)
      throws java.io.IOException
  {
    int i;

    // trim the string back some if needed

    if (str.length() &gt; length)
      str = str.substring(0, length);

    // write the string

    for (i = 0; i &lt; str.length(); i++)
      _file.write(str.charAt(i));

    // buffer extra space if needed

    i = length - str.length();
    while ((i--) &gt; 0)
      _file.write(0);
  }

  /**
   * Reads a string that stores one length byte before the string.
   * This string can be up to 255 characters long.  Pascal stores strings this way.
   *
   * @return The string that was read.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public String readLengthPrefixString() throws java.io.IOException
  {
    short len = readUnsignedByte();
    return readFixedString(len);
  }

  /**
   * Writes a string that is prefixed by a single byte that specifies the length of the string.  This is how Pascal usually stores strings.
   *
   * @param str The string to be written.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeLengthPrefixString(String str) throws java.io.IOException
  {
    writeByte((byte) str.length());
    for (int i = 0; i &lt; str.length(); i++)
      _file.write(str.charAt(i));
  }

  /**
   * Reads a fixed length string that is zero(NULL) terminated.  This is a type of string used by C/C++.  For example char str[80].
   *
   * @param length The length of the string.

   * @return The string that was read.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public String readFixedZeroString(int length) throws java.io.IOException
  {
    String rtn = readFixedString(length);
    int i = rtn.indexOf(0);
    if (i != -1)
      rtn = rtn.substring(0, i);
    return rtn;
  }

  /**
   * Writes a fixed length string that is zero terminated.  This is the format generally used by C/C++ for string storage.
   *
   * @param str The string to be written.
   * @param length The length of the buffer to receive the string.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeFixedZeroString(String str, int length)
      throws java.io.IOException
  {
    writeFixedString(str, length);
  }

  /**
   * Reads an unlimited length zero(null) terminated string.
   *
   * @return The string that was read.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public String readZeroString() throws java.io.IOException
  {
    String rtn = "";
    char ch;

    do
    {
      ch = (char) _file.read();
      if (ch != 0)
        rtn += ch;
    } while (ch != 0);
    return rtn;
  }

  /**
   * Writes an unlimited zero(NULL) terminated string to the file.
   *
   * @param str The string to be written.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeZeroString(String str) throws java.io.IOException
  {
    for (int i = 0; i &lt; str.length(); i++)
      _file.write(str.charAt(i));
    writeByte((byte) 0);
  }

  /**
   * Internal function used to read an unsigned byte.  External classes should use the readByte function.
   *
   * @return The byte, unsigned, as a short.
   * @exception java.io.IOException If an IO exception occurs.
   */
  protected short readUnsignedByte() throws java.io.IOException
  {
    return (short) (_file.readByte() & 0xff);
  }

  /**
   * Reads an 8-bit byte.  Can be signed or unsigned depending on the signed property.
   *
   * @return A byte stored in a short.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public short readByte() throws java.io.IOException
  {
    if (_signed)
      return (short) _file.readByte();
    else
      return (short) _file.readUnsignedByte();
  }

  /**
   * Writes a single byte to the file.
   *
   * @param b The byte to be written.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeByte(short b) throws java.io.IOException
  {
    _file.write(b & 0xff);
  }

  /**
   * Reads a 16-bit word.  Can be signed or unsigned depending on the signed property.
   * Can be little or big endian depending on the endian property.
   *
   * @return A word stored in an int.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public int readWord() throws java.io.IOException
  {
    short a, b;
    int result;

    a = readUnsignedByte();
    b = readUnsignedByte();

    if (_endian == BIG_ENDIAN)
      result = ((a &lt;&lt; 8) | b);
    else
      result = (a | (b &lt;&lt; 8));

    if (_signed)
      if ((result & 0x8000) == 0x8000)
        result = -(0x10000 - result);

    return result;
  }

  /**
   * Write a word to the file.
   *
   * @param w The word to be written to the file.
   * @exception java.io.IOException If an IO exception occurs.
   */

  public void writeWord(int w) throws java.io.IOException
  {
    if (_endian == BIG_ENDIAN)
    {
      _file.write((w & 0xff00) &gt;&gt; 8);
      _file.write(w & 0xff);
    } else
    {
      _file.write(w & 0xff);
      _file.write((w & 0xff00) &gt;&gt; 8);
    }
  }

  /**
   * Reads a 32-bit double word.  Can be signed or unsigned
   * depending on the signed property.  Can be little or big endian depending on the endian property.
   *
   * @return A double world stored in a long.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public long readDWord() throws java.io.IOException
  {
    short a, b, c, d;
    long result;

    a = readUnsignedByte();
    b = readUnsignedByte();
    c = readUnsignedByte();
    d = readUnsignedByte();

    if (_endian == BIG_ENDIAN)
      result = ((a &lt;&lt; 24) | (b &lt;&lt; 16) | (c &lt;&lt; 8) | d);
    else
      result = (a | (b &lt;&lt; 8) | (c &lt;&lt; 16) | (d &lt;&lt; 24));

    if (_signed)
      if ((result & 0x80000000L) == 0x80000000L)
        result = -(0x100000000L - result);

    return result;
  }

  /**
   * Writes a double word to the file.
   *
   * @param d The double word to be written to the file.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeDWord(long d) throws java.io.IOException
  {
    if (_endian == BIG_ENDIAN)
    {
      _file.write((int) (d & 0xff000000) &gt;&gt; 24);
      _file.write((int) (d & 0xff0000) &gt;&gt; 16);
      _file.write((int) (d & 0xff00) &gt;&gt; 8);
      _file.write((int) (d & 0xff));
    } else
    {
      _file.write((int) (d & 0xff));
      _file.write((int) (d & 0xff00) &gt;&gt; 8);
      _file.write((int) (d & 0xff0000) &gt;&gt; 16);
      _file.write((int) (d & 0xff000000) &gt;&gt; 24);
    }
  }

  /**
   * Allows the file to be aligned to a specified byte boundary.
   * For example, if a 4(double word) is specified, the file pointer will be
   * moved to the next double word boundary.
   *
   * @param a The byte-boundary to align to.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void align(int a) throws java.io.IOException
  {
    if ((_file.getFilePointer() % a) &gt; 0)
    {
      long pos = _file.getFilePointer() / a;
      _file.seek((pos + 1) * a);
    }
  }
}