A tiny and super fast library that aims to
- map a fixed or variable length text file to bean instances, ready to be chewed by an import procedure (
Unmarshaller
) - and export record beans into a fixed or variable length text file (
Marshaller
).
Almost everybody has written an import procedure of some sort: the customer is always filling your email box with data to import and that he doesn't want to manually type, despite your cool web interface.
Fixed-length are a must for every public institution (at least in Italy): regardless of the age of the destination system, everyone can read a plain text file
JRecordBind aims to leverage the boring parsing task and let the developer focus on real problems: understanding the data and find an easy way to feed the persistence layer.
JRecordBind is (AFAIK) the only tool aimed at fixed-length files that's able to marshall and unmarshall. By the way you may be a producer of fixed length files, not just a consumer.
JRecordBind supports hierarchical fixed length files: records of some type that are "sons" of other record types.
JRecordBind uses XML Schema for the definition file: that could make your learning curve steeper. Which Java?
Since version 2.3.3, JRecordBind supports both Java 1.5 and Java 6. For Maven users, you need to tweak the artifactId. See below.
If you need support, drop an email. If you have found a bug, file it! file it now!
If you are a software developer, yes, you should. At least you should remind the existence of JRecordBind, for the time some customer of yours will ask you to "import this file from our host"
When you need to import a fixed-length file, someone has given you a wide documentation regarding how the file is structured: each field, its length, its value and how to convert it.
JRecordBind needs that specification: it's the starting point. You need to map the documentation into an XSD file. Here's an example:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://schemas.assist-si.it/jrb/simple" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.assist-si.it/jrb/simple" xmlns:jrb="http://jrecordbind.dev.java.net/2/xsd"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:complexType name="SimpleRecord">
<xs:sequence>
<xs:element name="name" type="xs:string" jrb:length="20"/>
<xs:element name="surname" type="xs:string" jrb:length="20"/>
<xs:element name="taxCode" type="xs:string" jrb:length="16"/>
<xs:element name="birthday" type="xs:date" jrb:length="8" jrb:converter="it.assist.jrecordbind.test.SimpleRecordDateConverter"/>
<xs:element name="oneInteger" type="xs:int" jrb:length="2"/>
<xs:element name="oneFloat" type="xs:float" jrb:length="3" jrb:converter="it.assist.jrecordbind.test.SimpleRecordFloatConverter"/>
</xs:sequence>
</xs:complexType>
<xs:element name="main" type="SimpleRecord" jrb:length="100"/>
</xs:schema>
It's a standard XML Schema file (xsd) plus some custom attributes and a mandatory "main" element. The "main" element will be the starting point, the main bean JRecordBean will (un)marshall. The custom attributes are:
ATTRIBUTE | SCOPE | MEANING | MANDATORY |
jrb:length | "main" element | it's the total length of the fixed length file | Yes. Can be omitted to obtain dynamic length files, if the "delimiter" attribute is specified |
jrb:length | single elements | the length of that particular field | Yes. Can be omitted if the file has dynamic length |
jrb:delimiter | "main" element | what delimits each field | No. It becomes mandatory only if you need dynamic length files |
jrb:padder | "main" element | the default padder when not specified | No. JRecordBind will use its default (see the javadoc) |
jrb:padder | single elements | a custom padder for that field | No. JRecordBind will use its default (see the javadoc) |
jrb:lineSeparator | "main" element | what ends each row | No. By default a "new line" char will be used. DOS format files can be achieved with the value " " |
jrb:converter | single elements | how to convert that field to/from a string: there're some defaults | No. JRecordBind will use its default (see the javadoc) |
jrb:row | single elements | if a bean is split into more than one row, from the second row on, you need to specify the current row number (zero based) | No. JRecordBind will default it to 0 (the whole record on one line) |
jrb:subclass | xs:complexType declaration | if you need to extend/override some generated bean, you can make JRecordBind instantiate a particular class instead of the one it generates. The specified class must extend the generated class. See below for an example | No. JRecordBind will default to its generated class |
jrb:setter | xs:choice declaration | if you are using jaxb bindings.xjb with the "globalBindings choiceContentProperty='true'" option active, you need to specify the name of the method jaxb has actually generated | Yes, if you have set choiceContentProperty='true'. No, otherwise |
When you are ready with your definition, generate the beans:
either you use the JAXB2 Maven plugin (for an example configuration, give a look at the test pom.xml)
or an Ant target like the following
<target name="regenerate">
<taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask">
<classpath refid="classpath" />
</taskdef>
<xjc destdir="${src.gen}" binding="bindings.xjb.xml">
<schema dir="${src.test}" includes="*.def.xsd" />
</xjc>
</target>
You are now ready to (un)marshall your fixed length files
Given an fixed-length text file, honoring the definition, for example
WALTER LIPPMANN ABCDEF79D18K999A1889092381197
DAVID JOHNSON ABCDEF79E18S999B1889092381197
you can call the Unmarshaller
this way:
Unmarshaller<SimpleRecord> unmarshaller = new Unmarshaller<SimpleRecord>(new InputStreamReader(
SimpleRecordUnmarshallTest.class.getResourceAsStream("/simple.def.xsd")));
Iterator<SimpleRecord> iter = unmarshaller.unmarshall(new InputStreamReader(
SimpleRecordUnmarshallTest.class.getResourceAsStream("simple_test.txt")));
assertTrue(iter.hasNext());
SimpleRecord record = iter.next();
assertEquals("JOHN ", record.getName());
assertEquals("SMITH ", record.getSurname());
assertEquals("ABCDEF88L99H123B", record.getTaxCode());
The presence of an Iterator assure you a very small memory footprint.
Given a record bean full of data, you write:
//some bean taken somewhere
SimpleRecord record = new SimpleRecord();
record.setName("WALTER");
[...]
//setting up the marshaller
Marshaller<SimpleRecord> marshaller = new Marshaller<SimpleRecord>(new InputStreamReader(
SimpleRecordMarshallTest.class.getResourceAsStream("/simple.def.xsd")));
//setting up the destination Writer
Writer writer = new StringWriter();
//marshalling
marshaller.marshall(record, writer);
System.out.println(writer.toString());
and get the original input back
Hierarchical fixed-length files uses ID fields to differentiate the various records: you'll have something like "Record 000 is the address, record A01 are the vehicles..." and so on.
JRecordBind can easily recognize each record type if you use the xsd "fixed" standard attribute: see this example
I.E. you are telling JRecordBind that the "recordId" field, of type string and 3 chars long, will always have the "A00" fixed value
Since version 2.1, you can omit the jrb:length
attribute while specifying the jrb:delimiter
: this way you can achieve dynamic field length.
Since version 2.2 JRecordBind supports the jrb:subclass
attribute at the xs:complexType
level. By specifying the fully qualified name of a class extending the generated class, JRecordBind will instantiate that class instead of its generated one, allowing you to extend/override the generated class.
Click here for an xsd example and here for a class example.
Since version 2.3, you can specify the jrb:setter
attribute at the xs:choice
level.
JAXB allows you to have only one method in classes defined as "choice" in the xsd, but that's defined outside of the xsd, in a file called bindings.xjb (using the "choiceContentProperty" option). JRecordBind knows nothing about that file, so you need to duplicate that information into the xsd, in a way JRecordBind can understand.
So, for instance, if you have the elements One
and Two
inside an xs:choice
element, by default the generated choice class will have the methods setOne
and setTwo
. Specifying choiceContentProperty=true
in the bindings.xjb, that class will have the method setOneOrTwo
only, screwing up JRecordBind. If you add the attribute jrb:setter='oneOrTwo'
at the xs:choice
level, JRecordBind won't be fooled by the JAXB trick.
When the Unmarshaller
reads from the file, by default it returns the current line.
Since version 2.3.3, if you want to customize this behaviour, you can create a new Unmarshaller
passing your implementation of the LineReader
interface.
Check out this test for an example
Since version 2.3.4, you can customize the line separator using the attribute jrb:lineSeparator
. By default, lines will be separated by a "new line" char (\n). If you want to produce DOS format files, specify the attribute this way
jrb:lineSeparator=" "
When JRecordBind unmarshalls a file, it doesn't know if the spaces it finds in String properties are worth keeping or not, so it keeps them all.
If you are sure these spaces are just a headache and want to get rid of them, you could use the Trimmer
utility object. Trimmer
will look for String fields (adhering to the JavaBeans specification), get the value, trim it and set it back.
Trimmer
is NOT recursive (it doesn't know anything about your object model), so it's up to you to make it work recursively.
Each feature of JRecordBind has at least one xsd file that tests it.
Take a look at the repository
JRecordBind comes precompiled against both Java 1.5 and Java 6. Java 6 is the default.
Downloads are available from the download section
Maven users can add JRecordBind as a dependency. First you need to ensure you have the maven2 java.net repository in place
Then add JRecordBind dependency
Java 6 users will add:
<dependency>
<groupId>it.assist.jrecordbind</groupId>
<artifactId>jrecordbind</artifactId>
<version>2.3.7</version>
</dependency>
Java 1.5 users will add:
<dependency>
<groupId>it.assist.jrecordbind</groupId>
<artifactId>jrecordbind</artifactId>
<version>2.3.7</version>
<classifier>jdk5</classifier>
</dependency>