Monday, May 9, 2011

ISO 8583 with Java and jPOS

I've been busy working on a project that uses ISO 8583. It's a standard for systems that exchange electronic transactions made by cardholders using payment cards. We are using a Java-based library (jPOS) to do take care of un/parsing ISO messages (i.e. requests/responses). As I've worked on some code, it was evident that I could use some helper methods to format commonly used types like date, time, and monetary amounts.

An ISO 8583 message is composed of data elements. Time data elements are formatted as HHmmss (hours-minutes-seconds) strings. Date data elements are formatted as MMdd (month-day) strings. The current code base had a lot of duplicated code that used SimpleDateFormat instances to set the date/time data elements. To remove duplication (and avoid future duplication), we needed a helper method that would format date/time objects correctly and set the desired data element.

We didn't want to extend the ISO 8583 message (ISOMsg) class of jPOS. Instead, we created a helper class that wraps the ISOMsg class, and provides methods that we needed.

Again, we start with a test.

import static org.junit.Assert.*;

import java.util.*;
import org.junit.*;

import org.jpos.iso.ISOException;
import org.jpos.iso.ISOMsg;

public class ISOMsgHelperTest {
private ISOMsg isoMsg = new ISOMsg();
private ISOMsgHelper helper = new ISOMsgHelper(isoMsg);
private Date dateTime =
new GregorianCalendar(2011, Calendar.APRIL, 15, 9, 30, 59).getTime();

@Test
public void setsIsoDateAsMonthDay() throws Exception {
helper.setIsoDate(12, dateTime);
assertEquals("0415", isoMsg.getString(12));
}

@Test
public void setsIsoTimeAsHourMinuteSecond() throws Exception {
helper.setIsoDate(13, dateTime);
assertEquals("093059", isoMsg.getString(13));
}

@Test
public void setsIsoDateTime() throws Exception {
helper.setIsoDate(7, dateTime);
assertEquals("0415093059", isoMsg.getString(7));
}
}


And here's the class that passes the test.


import java.util.Date;
import java.text.*;

import org.jpos.iso.ISOException;
import org.jpos.iso.ISOMsg;

public class ISOMsgHelper {
private ISOMsg isoMsg;
public ISOMsgHelper(ISOMsg isoMsg) {
if (isoMsg == null) {
throw new IllegalArgumentException("isoMsg cannot be null");
}
this.isoMsg = isoMsg;
}
public void setIsoDate(int fldNo, Date date) throws ISOException {
this.isoMsg.set(fldNo, dateFormat.get().format(date));
}
public void setIsoTime(int fldNo, Date time) throws ISOException {
this.isoMsg.set(fldNo, timeFormat.get().format(time));
}
public void setIsoDateTime(int fldNo, Date dateTime) throws ISOException {
this.isoMsg.set(fldNo, dateTimeFormat.get().format(dateTime));
}

public Date getIsoDate(int fldNo) throws ISOException {
if (this.isoMsg.hasField(fldNo)) {
try {
return dateFormat.get().parse(this.isoMsg.getString(fldNo));
} catch (ParseException e) {
throw new ISOException("Error getting date field " + fldNo, e);
}
}
return null;
}
// Other getters were removed to improve clarity

// Need to make SimpleDateFormat objects thread-safe
private static final ThreadLocal<SimpleDateFormat> dateFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("MMdd");
}
};

private static final ThreadLocal<SimpleDateFormat> timeFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("HHmmss");
}
};

private static final ThreadLocal<SimpleDateFormat> dateTimeFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("MMddHHmmss");
}
};

}


Now, we can easily set date/time fields! And the date formatting objects are nicely encapsulated in one place.

ISOMsg isoMsg = ...;
ISOMsgHelper isoMsgHelper = new ISOMsgHelper(isoMsg);
Date now = new Date();
isoMsgHelper.setIsoDate(12, now); // local transaction date
isoMsgHelper.setIsoTime(13, now); // local transaction time
...
isoMsgHelper.setIsoDateTime(7, new Date()); // transmission date/time


So far, it has removed a lot of duplicated code. We are expanding ISOMsgHelper to support other commonly used formats. Hope this helps!
Monday, February 14, 2011

Test-Driven Development of FizzBuzz

It's been a while since I last posted an entry here. So, let me take some time out to go back to basics. I've recently given a short talk about TDD (Test-Driven Development) using JUnit 4 to some young Java developers. I chose the FizzBuzz problem as an example.

Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz.” For numbers which are multiples of both three and five print “FizzBuzz.”


I started with a test. I also decided to have the class return a string (instead of printing it out onto the console).


import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {
private final FizzBuzz fizzbuzz = new FizzBuzz();
}


With this, I had to create the corresponding class.


public class FizzBuzz {
}


I continue with adding tests.


import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {
private final FizzBuzz fizzbuzz = new FizzBuzz();

@Test
public void returnsNumber() throws Exception {
assertEquals("1", fizzbuzz.of(1));
}
}


With this, I had to create the corresponding method and make it pass.


public class FizzBuzz {
public String of(int number) {
return String.valueOf(number);
}
}


I continue to add more tests.


import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {
private final FizzBuzz fizzbuzz = new FizzBuzz();

@Test
public void returnsNumber() throws Exception {
assertEquals("1", fizzbuzz.of(1));
assertEquals("2", fizzbuzz.of(2));
}

@Test
public void returnsFizzWhenNumberIsMultipleOfThree() throws Exception {
assertEquals("Fizz", fizzbuzz.of(3));
}
}


With this, I had to add code and make it pass.


public class FizzBuzz {
public String of(int number) {
if (number % 3 == 0) {
return "Fizz";
}
return String.valueOf(number);
}
}


I continue to add more tests.


import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {
private final FizzBuzz fizzbuzz = new FizzBuzz();

@Test
public void returnsNumber() throws Exception {
assertEquals("1", fizzbuzz.of(1));
assertEquals("2", fizzbuzz.of(2));
}

@Test
public void returnsFizzWhenNumberIsMultipleOfThree() throws Exception {
assertEquals("Fizz", fizzbuzz.of(3));
assertEquals("Fizz", fizzbuzz.of(6));
assertEquals("Fizz", fizzbuzz.of(9));
}

@Test
public void returnsBuzzWhenNumberIsMultipleOfFive() throws Exception {
assertEquals("Buzz", fizzbuzz.of(5));
}
}


With this, I had to add code and make it pass.


public class FizzBuzz {
public String of(int number) {
if (number % 3 == 0) {
return "Fizz";
}
if (number % 5 == 0) {
return "Buzz";
}
return String.valueOf(number);
}
}


I continue to add more tests.


import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {
private final FizzBuzz fizzbuzz = new FizzBuzz();

@Test public void
returnsNumber() throws Exception {
assertEquals("1", fizzbuzz.of(1));
assertEquals("2", fizzbuzz.of(2));
}

@Test public void
returnsFizzWhenNumberIsMultipleOfThree() throws Exception {
assertEquals("Fizz", fizzbuzz.of(3));
assertEquals("Fizz", fizzbuzz.of(6));
assertEquals("Fizz", fizzbuzz.of(9));
}

@Test public void
returnsBuzzWhenNumberIsMultipleOfFive() throws Exception {
assertEquals("Buzz", fizzbuzz.of(5));
assertEquals("Buzz", fizzbuzz.of(10));
}

@Test public void
returnsFizzBuzzWhenNumberIsMultipleOfThreeAndFive() throws Exception {
assertEquals("FizzBuzz", fizzbuzz.of(15));
}
}


With this, I had to add code and make it pass.


public class FizzBuzz {
public String of(int number) {
if (number % 3 == 0) {
return "Fizz";
}
if (number % 5 == 0) {
return "Buzz";
}
if (number % 3 == 0 && number % 5 == 0) {
return "FizzBuzz";
}
return String.valueOf(number);
}
}


At this point, my previous tests fail. So, guided by tests, I went back and fixed things up.


public class FizzBuzz {
public String of(int number) {
if (number % 3 == 0 && number % 5 == 0) {
return "FizzBuzz";
}
if (number % 3 == 0) {
return "Fizz";
}
if (number % 5 == 0) {
return "Buzz";
}
return String.valueOf(number);
}
}


Now that all of the tests are passing, I thought about refactoring (by removing duplication). Obviously, the "Fizz" and "Buzz" strings are repeated. Again, guided by tests, I changed my code to something like:


public class FizzBuzz {
public String of(int number) {
StringBuilder b = new StringBuilder();
if (number % 3 == 0) {
b.append("Fizz");
}
if (number % 5 == 0) {
b.append("Buzz");
}
if (number % 3 != 0 && number % 5 != 0) {
return b.append(String.valueOf(number));
}
return b.toString();
}
}


As I glance over the code, I can see there's still some duplication. The modulus was operating on the number twice: one by three, and another by five. Again, guided by tests, I modified my code to remove duplication. Here's the result.


public class FizzBuzz {
public String of(int number) {
boolean multipleOf3 = (number % 3 == 0);
boolean multipleOf5 = (number % 5 == 0);
if (!multipleOf3 && !multipleOf5) {
return String.valueOf(number);
}
StringBuilder b = new StringBuilder();
if (multipleOf3) {
b.append("Fizz");
}
if (multipleOf5) {
b.append("Buzz");
}
return b.toString();
}
}


I know this is simple. But it's a good coding exercise.