blob: cd6260e053e005034066686d3fa044000f5a56a4 [file] [log] [blame]
[[Bindy-Bindy]]
Bindy
~~~~~
The goal of this component is to allow the parsing/binding of
non-structured data (or to be more precise non-XML data) +
to/from Java Beans that have binding mappings defined with annotations.
Using Bindy, you can bind data from sources such as :
* CSV records,
* Fixed-length records,
* FIX messages,
* or almost any other non-structured data
to one or many Plain Old Java Object (POJO). Bindy converts the data
according to the type of the java property. POJOs can be linked together
with one-to-many relationships available in some cases. Moreover, for
data type like Date, Double, Float, Integer, Short, Long and BigDecimal,
you can provide the pattern to apply during the formatting of the
property.
For the BigDecimal numbers, you can also define the precision and the
decimal or grouping separators.
[width="100%",cols="10%,10%,10%,70%",options="header",]
|=======================================================================
|Type |Format Type |Pattern example |Link
|Date |DateFormat |`dd-MM-yyyy` |http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html[http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html]
|Decimal* |Decimalformat |`##.###.###` |http://java.sun.com/j2se/1.5.0/docs/api/java/text/DecimalFormat.html[http://java.sun.com/j2se/1.5.0/docs/api/java/text/DecimalFormat.html]
|=======================================================================
Decimal* = Double, Integer, Float, Short, Long
*Format supported*
This first release only support comma separated values fields and key
value pair fields (e.g. : FIX messages).
To work with camel-bindy, you must first define your model in a package
(e.g. com.acme.model) and for each model class (e.g. Order, Client,
Instrument, ...) add the required annotations (described hereafter) to
the Class or field.
*Multiple models*
If you use multiple models, each model has to be placed in it's own
package to prevent unpredictable results.
From *Camel 2.16* onwards this is no longer the case, as you can safely
have multiple models in the same package, as you configure bindy using
class names instead of package names now.
[[Bindy-Annotations]]
Annotations
~~~~~~~~~~~
The annotations created allow to map different concept of your model to
the POJO like :
* Type of record (csv, key value pair (e.g. FIX message), fixed length
...),
* Link (to link object in another object),
* DataField and their properties (int, type, ...),
* KeyValuePairField (for key = value format like we have in FIX
financial messages),
* Section (to identify header, body and footer section),
* OneToMany
This section will describe them :
[[Bindy-1.CsvRecord]]
1. CsvRecord
^^^^^^^^^^^^
The CsvRecord annotation is used to identified the root class of the
model. It represents a record = a line of a CSV file and can be linked
to several children model classes.
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Annotation name |Record type |Level
|*CsvRecord* |csv |Class
|=======================================================================
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Parameter name |type |Info
|separator |string |mandatory - can be ',' or ';' or 'anything'. This value is interpreted
as a regular expression. If you want to use a sign which has a special
meaning in regular expressions, e.g. the '|' sign, than you have to mask
it, like '|'
|skipFirstLine |boolean |optional - default value = false - allow to skip the first line of the
CSV file
|crlf |string |optional - possible values = WINDOWS,UNIX,MAC, or custom; default value
= WINDOWS - allow to define the carriage return character to use. If you
specify a value other than the three listed before, the value you enter
(custom) will be used as the CRLF character(s)
|generateHeaderColumns |boolean |optional - default value = false - uses to generate the header columns
of the CSV generates
|autospanLine |boolean |*Camel 2.13/2.12.2:* optional - default value = false - if enabled then
the last column is auto spanned to end of line, for example if its a
comment, etc this allows the line to contain all characters, also the
delimiter char.
|isOrdered |boolean |optional - default value = false - allow to change the order of the
fields when CSV is generated
|quote |String |*Camel 2.8.3/2.9:* option - allow to specify a quote character of the
fields when CSV is generated. This annotation is associated to the root class of the model and must be
declared one time.
|quoting |boolean |*Camel 2.11:*optional - default value = false - Indicate if the values
must be quoted when marshaling when CSV is generated.
|=======================================================================
*case 1 : separator = ','*
The separator used to segregate the fields in the CSV record is ',' :
10, J, Pauline, M, XD12345678, Fortis Dynamic 15/15, 2500,
USD,08-01-2009
[source,java]
-----------------------------
@CsvRecord( separator = "," )
public Class Order {
...
}
-----------------------------
*case 2 : separator = ';'*
Compare to the previous case, the separator here is ';' instead of ',' :
10; J; Pauline; M; XD12345678; Fortis Dynamic 15/15; 2500; USD;
08-01-2009
[source,java]
-----------------------------
@CsvRecord( separator = ";" )
public Class Order {
...
}
-----------------------------
*case 3 : separator = '|'*
Compare to the previous case, the separator here is '|' instead of ';' :
10| J| Pauline| M| XD12345678| Fortis Dynamic 15/15| 2500| USD|
08-01-2009
[source,java]
-------------------------------
@CsvRecord( separator = "\\|" )
public Class Order {
...
}
-------------------------------
*case 4 : separator = '\",\"'* +
*Applies for Camel 2.8.2 or older*
When the field to be parsed of the CSV record contains ',' or ';' which
is also used as separator, we whould find another strategy +
to tell camel bindy how to handle this case. To define the field
containing the data with a comma, you will use simple or double quotes +
as delimiter (e.g : '10', 'Street 10, NY', 'USA' or "10", "Street 10,
NY", "USA"). +
Remark : In this case, the first and last character of the line which
are a simple or double quotes will removed by bindy
"10","J","Pauline"," M","XD12345678","Fortis Dynamic 15,15"
2500","USD","08-01-2009"
[source,java]
---------------------------------
@CsvRecord( separator = "\",\"" )
public Class Order {
...
}
---------------------------------
From *Camel 2.8.3/2.9 or never* bindy will automatic detect if the
record is enclosed with either single or double quotes and automatic
remove those quotes when unmarshalling from CSV to Object. Therefore do
*not* include the quotes in the separator, but simple do as below:
"10","J","Pauline"," M","XD12345678","Fortis Dynamic 15,15"
2500","USD","08-01-2009"
[source,java]
-----------------------------
@CsvRecord( separator = "," )
public Class Order {
...
}
-----------------------------
Notice that if you want to marshal from Object to CSV and use quotes,
then you need to specify which quote character to use, using the `quote`
attribute on the @CsvRecord as shown below:
[source,java]
-------------------------------------------
@CsvRecord( separator = ",", quote = "\"" )
public Class Order {
...
}
-------------------------------------------
*case 5 : separator & skipfirstline*
The feature is interesting when the client wants to have in the first
line of the file, the name of the data fields :
order id, client id, first name, last name, isin code, instrument name,
quantity, currency, date
To inform bindy that this first line must be skipped during the parsing
process, then we use the attribute :
[source,java]
-------------------------------------------------
@CsvRecord(separator = ",", skipFirstLine = true)
public Class Order {
...
}
-------------------------------------------------
*case 6 : generateHeaderColumns*
To add at the first line of the CSV generated, the attribute
generateHeaderColumns must be set to true in the annotation like this :
[source,java]
------------------------------------------
@CsvRecord( generateHeaderColumns = true )
public Class Order {
...
}
------------------------------------------
As a result, Bindy during the unmarshaling process will generate CSV
like this :
order id, client id, first name, last name, isin code, instrument name,
quantity, currency, date +
10, J, Pauline, M, XD12345678, Fortis Dynamic 15/15, 2500,
USD,08-01-2009
*case 7 : carriage return*
If the platform where camel-bindy will run is not Windows but Macintosh
or Unix, than you can change the crlf property like this. Three values
are available : WINDOWS, UNIX or MAC
[source,java]
---------------------------------------
@CsvRecord(separator = ",", crlf="MAC")
public Class Order {
...
}
---------------------------------------
Additionally, if for some reason you need to add a different line ending
character, you can opt to specify it using the crlf parameter. In the
following example, we can end the line with a comma followed by the
newline character:
[source,java]
---------------------------------------
@CsvRecord(separator = ",", crlf=",\n")
public Class Order {
...
}
---------------------------------------
*case 8 : isOrdered*
Sometimes, the order to follow during the creation of the CSV record
from the model is different from the order used during the parsing.
Then, in this case, we can use the attribute isOrdered = true to
indicate this in combination with attribute 'position' of the DataField
annotation.
[source,java]
-------------------------------------
@CsvRecord(isOrdered = true)
public Class Order {
@DataField(pos = 1, position = 11)
private int orderNr;
@DataField(pos = 2, position = 10)
private String clientNr;
...
}
-------------------------------------
Remark : pos is used to parse the file, stream while positions is used
to generate the CSV
[[Bindy-2.Link]]
2. Link
^^^^^^^
The link annotation will allow to link objects together.
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Annotation name |Record type |Level
|*Link* |all |Class & Property
|=======================================================================
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Parameter name |type |Info
|linkType |LinkType |optional - by default the value is LinkType.oneToOne - so you are not
obliged to mention it
|=======================================================================
Only one-to-one relation is allowed.
e.g : If the model Class Client is linked to the Order class, then use
annotation Link in the Order class like this :
*Property Link*
[source,java]
---------------------------
@CsvRecord(separator = ",")
public class Order {
@DataField(pos = 1)
private int orderNr;
@Link
private Client client;
...
---------------------------
AND for the class Client :
*Class Link*
[source,java]
---------------------
@Link
public class Client {
...
}
---------------------
[[Bindy-3.DataField]]
3. DataField
^^^^^^^^^^^^
The DataField annotation defines the property of the field. Each
datafield is identified by its position in the record, a type (string,
int, date, ...) and optionally of a pattern
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Annotation name |Record type |Level
|*DataField* |all |Property
|=======================================================================
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Parameter name |type |Info
|pos |int |mandatory - The *input* position of the field. digit number starting
from 1 to ... - See the position parameter.
|pattern |string |optional - default value = "" - will be used to format Decimal, Date,
...
|length |int |optional - represents the length of the field for fixed length format
|precision |int |optional - represents the precision to be used when the Decimal number
will be formatted/parsed
|pattern |string |optional - default value = "" - is used by the Java formatter
(SimpleDateFormat by example) to format/validate data. If using pattern,
then setting locale on bindy data format is recommended. Either set to a
known locale such as "us" or use "default" to use platform default
locale. Notice that "default" requires Camel 2.14/2.13.3/2.12.5.
|position |int |optional - must be used when the position of the field in the CSV
generated (output message) must be different compare to input position
(pos). See the pos parameter.
|required |boolean |optional - default value = "false"
|trim |boolean |optional - default value = "false"
|defaultValue |string |*Camel 2.10:* optional - default value = "" - defines the field's
default value when the respective CSV field is empty/not available
|impliedDecimalSeparator |boolean |*Camel 2.11:* optional - default value = "false" - Indicates if there is
a decimal point implied at a specified location
|lengthPos |int |*Camel 2.11*: optional - can be used to identify a data field in a
fixed-length record that defines the fixed length for this field
|delimiter |string |*Camel 2.11:* optional - can be used to demarcate the end of a variable-length field within a fixed-length record
|=======================================================================
*case 1 : pos*
This parameter/attribute represents the position of the field in the csv
record
*Position*
[source,java]
----------------------------
@CsvRecord(separator = ",")
public class Order {
@DataField(pos = 1)
private int orderNr;
@DataField(pos = 5)
private String isinCode;
...
}
----------------------------
As you can see in this example the position starts at '1' but continues
at '5' in the class Order. The numbers from '2' to '4' are defined in
the class Client (see here after).
*Position continues in another model class*
[source,java]
-----------------------------
public class Client {
@DataField(pos = 2)
private String clientNr;
@DataField(pos = 3)
private String firstName;
@DataField(pos = 4)
private String lastName;
...
}
-----------------------------
*case 2 : pattern*
The pattern allows to enrich or validates the format of your data
*Pattern*
[source,java]
----------------------------------------------------------------------------------------------------------
@CsvRecord(separator = ",")
public class Order {
@DataField(pos = 1)
private int orderNr;
@DataField(pos = 5)
private String isinCode;
@DataField(name = "Name", pos = 6)
private String instrumentName;
@DataField(pos = 7, precision = 2)
private BigDecimal amount;
@DataField(pos = 8)
private String currency;
@DataField(pos = 9, pattern = "dd-MM-yyyy") -- pattern used during parsing or when the date is created
private Date orderDate;
...
}
----------------------------------------------------------------------------------------------------------
*case 3 : precision*
The precision is helpful when you want to define the decimal part of
your number
*Precision*
[source,java]
---------------------------------------------------
@CsvRecord(separator = ",")
public class Order {
@DataField(pos = 1)
private int orderNr;
@Link
private Client client;
@DataField(pos = 5)
private String isinCode;
@DataField(name = "Name", pos = 6)
private String instrumentName;
@DataField(pos = 7, precision = 2) -- precision
private BigDecimal amount;
@DataField(pos = 8)
private String currency;
@DataField(pos = 9, pattern = "dd-MM-yyyy")
private Date orderDate;
...
}
---------------------------------------------------
*case 4 : Position is different in output*
The position attribute will inform bindy how to place the field in the
CSV record generated. By default, the position used corresponds to the
position defined with the attribute 'pos'. If the position is different
(that means that we have an asymetric processus comparing marshaling
from unmarshaling) than we can use 'position' to indicate this.
Here is an example
*Position is different in output*
[source,java]
----------------------------------------------------------
@CsvRecord(separator = ",")
public class Order {
@CsvRecord(separator = ",", isOrdered = true)
public class Order {
// Positions of the fields start from 1 and not from 0
@DataField(pos = 1, position = 11)
private int orderNr;
@DataField(pos = 2, position = 10)
private String clientNr;
@DataField(pos = 3, position = 9)
private String firstName;
@DataField(pos = 4, position = 8)
private String lastName;
@DataField(pos = 5, position = 7)
private String instrumentCode;
@DataField(pos = 6, position = 6)
private String instrumentNumber;
...
}
----------------------------------------------------------
This attribute of the annotation @DataField must be used in combination
with attribute isOrdered = true of the annotation @CsvRecord
*case 5 : required*
If a field is mandatory, simply use the attribute 'required' setted to
true
*Required*
[source,java]
----------------------------------------
@CsvRecord(separator = ",")
public class Order {
@DataField(pos = 1)
private int orderNr;
@DataField(pos = 2, required = true)
private String clientNr;
@DataField(pos = 3, required = true)
private String firstName;
@DataField(pos = 4, required = true)
private String lastName;
...
}
----------------------------------------
If this field is not present in the record, than an error will be raised
by the parser with the following information :
Some fields are missing (optional or mandatory), line :
*case 6 : trim*
If a field has leading and/or trailing spaces which should be removed
before they are processed, simply use the attribute 'trim' setted to
true
*Trim*
[source,java]
----------------------------------------
@CsvRecord(separator = ",")
public class Order {
@DataField(pos = 1, trim = true)
private int orderNr;
@DataField(pos = 2, trim = true)
private Integer clientNr;
@DataField(pos = 3, required = true)
private String firstName;
@DataField(pos = 4)
private String lastName;
...
}
----------------------------------------
*case 7 : defaultValue*
If a field is not defined then uses the value indicated by the
defaultValue attribute
*Default value*
[source,java]
-----------------------------------------------
@CsvRecord(separator = ",")
public class Order {
@DataField(pos = 1)
private int orderNr;
@DataField(pos = 2)
private Integer clientNr;
@DataField(pos = 3, required = true)
private String firstName;
@DataField(pos = 4, defaultValue = "Barin")
private String lastName;
...
}
-----------------------------------------------
This attribute is only applicable to optional fields.
[[Bindy-4.FixedLengthRecord]]
4. FixedLengthRecord
^^^^^^^^^^^^^^^^^^^^
The FixedLengthRecord annotation is used to identified the root class of
the model. It represents a record = a line of a file/message containing
data fixed length formatted and can be linked to several children model
classes. This format is a bit particular beause data of a field can be
aligned to the right or to the left. +
When the size of the data does not fill completely the length of the
field, we can then add 'padd' characters.
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Annotation name |Record type |Level
|*FixedLengthRecord* |fixed |Class
|=======================================================================
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Parameter name |type |Info
|crlf |string |optional - possible values = WINDOWS,UNIX,MAC, or custom; default value
= WINDOWS - allow to define the carriage return character to use. If you
specify a value other than the three listed before, the value you enter
(custom) will be used as the CRLF character(s)
|paddingChar |char |mandatory - default value = ' '
|length |int |mandatory = size of the fixed length record
|hasHeader |boolean |*Camel 2.11* - optional - Indicates that the record(s) of this type may
be preceded by a single header record at the beginning of the file /
stream
|hasFooter |boolean |*Camel 2.11* - optional - Indicates that the record(s) of this type may
be followed by a single footer record at the end of the file / stream
|skipHeader |boolean |*Camel 2.11* - optional - Configures the data format to skip marshalling
/ unmarshalling of the header record. Configure this parameter on the
primary record (e.g., not the header or footer).
|skipFooter |boolean |*Camel 2.11* - optional - Configures the data format to skip marshalling
/ unmarshalling of the footer record Configure this parameter on the
primary record (e.g., not the header or footer)..
|isHeader |boolean |*Camel 2.11* - optional - Identifies this FixedLengthRecord as a header
record
|isFooter |boolean |*Camel 2.11* - optional - Identifies this FixedLengthRecords as a footer
record
|ignoreTrailingChars |boolean |*Camel 2.11.1* - optional - Indicates that characters beyond the last
mapped filed can be ignored when unmarshalling / parsing. This annotation is associated to the root class of the model and must be
declared one time.
|=======================================================================
The hasHeader/hasFooter parameters are mutually exclusive with
isHeader/isFooter. A record may not be both a header/footer and a
primary fixed-length record.
*case 1 : Simple fixed length record*
This simple example shows how to design the model to parse/format a
fixed message
10A9PaulineMISINXD12345678BUYShare2500.45USD01-08-2009
*Fixed-simple*
[source,java]
---------------------------------------------------------------
@FixedLengthRecord(length=54, paddingChar=' ')
public static class Order {
@DataField(pos = 1, length=2)
private int orderNr;
@DataField(pos = 3, length=2)
private String clientNr;
@DataField(pos = 5, length=7)
private String firstName;
@DataField(pos = 12, length=1, align="L")
private String lastName;
@DataField(pos = 13, length=4)
private String instrumentCode;
@DataField(pos = 17, length=10)
private String instrumentNumber;
@DataField(pos = 27, length=3)
private String orderType;
@DataField(pos = 30, length=5)
private String instrumentType;
@DataField(pos = 35, precision = 2, length=7)
private BigDecimal amount;
@DataField(pos = 42, length=3)
private String currency;
@DataField(pos = 45, length=10, pattern = "dd-MM-yyyy")
private Date orderDate;
...
---------------------------------------------------------------
*case 2 : Fixed length record with alignment and padding*
This more elaborated example show how to define the alignment for a
field and how to assign a padding character which is ' ' here''
10A9 PaulineM ISINXD12345678BUYShare2500.45USD01-08-2009
*Fixed-padding-align*
[source,java]
-----------------------------------------------------------------------------------------------
@FixedLengthRecord(length=60, paddingChar=' ')
public static class Order {
@DataField(pos = 1, length=2)
private int orderNr;
@DataField(pos = 3, length=2)
private String clientNr;
@DataField(pos = 5, length=9)
private String firstName;
@DataField(pos = 14, length=5, align="L") // align text to the LEFT zone of the block
private String lastName;
@DataField(pos = 19, length=4)
private String instrumentCode;
@DataField(pos = 23, length=10)
private String instrumentNumber;
@DataField(pos = 33, length=3)
private String orderType;
@DataField(pos = 36, length=5)
private String instrumentType;
@DataField(pos = 41, precision = 2, length=7)
private BigDecimal amount;
@DataField(pos = 48, length=3)
private String currency;
@DataField(pos = 51, length=10, pattern = "dd-MM-yyyy")
private Date orderDate;
...
-----------------------------------------------------------------------------------------------
*case 3 : Field padding*
Sometimes, the default padding defined for record cannnot be applied to
the field as we have a number format where we would like to padd with
'0' instead of ' '. In this case, you can use in the model the attribute
paddingField to set this value.
10A9 PaulineM ISINXD12345678BUYShare000002500.45USD01-08-2009
*Fixed-padding-field*
[source,java]
---------------------------------------------------------------------------
@FixedLengthRecord(length = 65, paddingChar = ' ')
public static class Order {
@DataField(pos = 1, length = 2)
private int orderNr;
@DataField(pos = 3, length = 2)
private String clientNr;
@DataField(pos = 5, length = 9)
private String firstName;
@DataField(pos = 14, length = 5, align = "L")
private String lastName;
@DataField(pos = 19, length = 4)
private String instrumentCode;
@DataField(pos = 23, length = 10)
private String instrumentNumber;
@DataField(pos = 33, length = 3)
private String orderType;
@DataField(pos = 36, length = 5)
private String instrumentType;
@DataField(pos = 41, precision = 2, length = 12, paddingChar = '0')
private BigDecimal amount;
@DataField(pos = 53, length = 3)
private String currency;
@DataField(pos = 56, length = 10, pattern = "dd-MM-yyyy")
private Date orderDate;
...
---------------------------------------------------------------------------
*case 4: Fixed length record with delimiter*
Fixed-length records sometimes have delimited content within the record.
The firstName and lastName fields are delimited with the '^' character
in the following example:
10A9Pauline^M^ISINXD12345678BUYShare000002500.45USD01-08-2009
*Fixed-delimited*
[source,java]
--------------------------------------------------------------------------
@FixedLengthRecord()
public static class Order {
@DataField(pos = 1, length = 2)
private int orderNr;
@DataField(pos = 2, length = 2)
private String clientNr;
@DataField(pos = 3, delimiter = "^")
private String firstName;
@DataField(pos = 4, delimiter = "^")
private String lastName;
@DataField(pos = 5, length = 4)
private String instrumentCode;
@DataField(pos = 6, length = 10)
private String instrumentNumber;
@DataField(pos = 7, length = 3)
private String orderType;
@DataField(pos = 8, length = 5)
private String instrumentType;
@DataField(pos = 9, precision = 2, length = 12, paddingChar = '0')
private BigDecimal amount;
@DataField(pos = 10, length = 3)
private String currency;
@DataField(pos = 11, length = 10, pattern = "dd-MM-yyyy")
private Date orderDate;
--------------------------------------------------------------------------
As of *Camel 2.11* the 'pos' value(s) in a fixed-length record may
optionally be defined using ordinal, sequential values instead of
precise column numbers.
*case 5 : Fixed length record with record-defined field length*
Occasionally a fixed-length record may contain a field that define the
expected length of another field within the same record. In the
following example the length of the instrumentNumber field value is
defined by the value of instrumentNumberLen field in the record.
10A9Pauline^M^ISIN10XD12345678BUYShare000002500.45USD01-08-2009
*Fixed-delimited*
[source,java]
---------------------------------------------------------------------------
@FixedLengthRecord()
public static class Order {
@DataField(pos = 1, length = 2)
private int orderNr;
@DataField(pos = 2, length = 2)
private String clientNr;
@DataField(pos = 3, delimiter = "^")
private String firstName;
@DataField(pos = 4, delimiter = "^")
private String lastName;
@DataField(pos = 5, length = 4)
private String instrumentCode;
@DataField(pos = 6, length = 2, align = "R", paddingChar = '0')
private int instrumentNumberLen;
@DataField(pos = 7, lengthPos=6)
private String instrumentNumber;
@DataField(pos = 8, length = 3)
private String orderType;
@DataField(pos = 9, length = 5)
private String instrumentType;
@DataField(pos = 10, precision = 2, length = 12, paddingChar = '0')
private BigDecimal amount;
@DataField(pos = 11, length = 3)
private String currency;
@DataField(pos = 12, length = 10, pattern = "dd-MM-yyyy")
private Date orderDate;
---------------------------------------------------------------------------
*case 6 : Fixed length record with header and footer*
Bindy will discover fixed-length header and footer records that are
configured as part of the model – provided that the annotated classes
exist either in the same package as the primary @FixedLengthRecord
class, or within one of the configured scan packages. The following text
illustrates two fixed-length records that are bracketed by a header
record and footer record.
101-08-2009 +
10A9 PaulineM ISINXD12345678BUYShare000002500.45USD01-08-2009 +
10A9 RichN ISINXD12345678BUYShare000002700.45USD01-08-2009 +
9000000002
*Fixed-header-and-footer-main-class*
[source,java]
----------------------------------------------------------------------
@FixedLengthRecord(hasHeader = true, hasFooter = true)
public class Order {
@DataField(pos = 1, length = 2)
private int orderNr;
@DataField(pos = 2, length = 2)
private String clientNr;
@DataField(pos = 3, length = 9)
private String firstName;
@DataField(pos = 4, length = 5, align = "L")
private String lastName;
@DataField(pos = 5, length = 4)
private String instrumentCode;
@DataField(pos = 6, length = 10)
private String instrumentNumber;
@DataField(pos = 7, length = 3)
private String orderType;
@DataField(pos = 8, length = 5)
private String instrumentType;
@DataField(pos = 9, precision = 2, length = 12, paddingChar = '0')
private BigDecimal amount;
@DataField(pos = 10, length = 3)
private String currency;
@DataField(pos = 11, length = 10, pattern = "dd-MM-yyyy")
private Date orderDate;
...
}
@FixedLengthRecord(isHeader = true)
public class OrderHeader {
@DataField(pos = 1, length = 1)
private int recordType = 1;
@DataField(pos = 2, length = 10, pattern = "dd-MM-yyyy")
private Date recordDate;
...
}
@FixedLengthRecord(isFooter = true)
public class OrderFooter {
@DataField(pos = 1, length = 1)
private int recordType = 9;
@DataField(pos = 2, length = 9, align = "R", paddingChar = '0')
private int numberOfRecordsInTheFile;
...
}
----------------------------------------------------------------------
*case 7 : Skipping content when parsing a fixed length record. (Camel
2.11.1)*
It is common to integrate with systems that provide fixed-length records
containing more information than needed for the target use case. It is
useful in this situation to skip the declaration and parsing of those
fields that we do not need. To accomodate this, Bindy will skip forward
to the next mapped field within a record if the 'pos' value of the next
declared field is beyond the cursor position of the last parsed field.
Using absolute 'pos' locations for the fields of interest (instead of
ordinal values) causes Bindy to skip content between two fields.
Similarly, it is possible that none of the content beyond some field is
of interest. In this case, you can tell Bindy to skip parsing of
everything beyond the last mapped field by setting the
*ignoreTrailingChars* property on the @FixedLengthRecord declaration.
[source,java]
-------------------------------------------------------------------------------
@FixedLengthRecord(ignoreTrailingChars = true)
public static class Order {
@DataField(pos = 1, length = 2)
private int orderNr;
@DataField(pos = 3, length = 2)
private String clientNr;
... any characters that appear beyond the last mapped field will be ignored
}
-------------------------------------------------------------------------------
[[Bindy-5.Message]]
5. Message
^^^^^^^^^^
The Message annotation is used to identified the class of your model who
will contain key value pairs fields. This kind of format is used mainly
in Financial Exchange Protocol Messages (FIX). Nevertheless, this
annotation can be used for any other format where data are identified by
keys. The key pair values are separated each other by a separator which
can be a special character like a tab delimitor (unicode representation
: \u0009) or a start of heading (unicode representation : \u0001)
*"FIX information"*
More information about FIX can be found on this web site :
http://www.fixprotocol.org/[http://www.fixprotocol.org/]. To work with
FIX messages, the model must contain a Header and Trailer classes linked
to the root message class which could be a Order class. This is not
mandatory but will be very helpful when you will use camel-bindy in
combination with camel-fix which is a Fix gateway based on quickFix
project http://www.quickfixj.org/[http://www.quickfixj.org/].
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Annotation name |Record type |Level
|*Message* |key value pair |Class
|=======================================================================
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Parameter name |type |Info
|pairSeparator |string |mandatory - can be '=' or ';' or 'anything'
|keyValuePairSeparair |string |mandatory - can be '\u0001', '\u0009', '#' or 'anything'
|crlf |string |optional - possible values = WINDOWS,UNIX,MAC, or custom; default value
= WINDOWS - allow to define the carriage return character to use. If you
specify a value other than the three listed before, the value you enter
(custom) will be used as the CRLF character(s)
|type |string |optional - define the type of message (e.g. FIX, EMX, ...)
|version |string |optional - version of the message (e.g. 4.1)
|isOrdered |boolean |optional - default value = false - allow to change the order of the
fields when FIX message is generated. This annotation is associated to the message class of the model and must
be declared one time.
|=======================================================================
*case 1 : separator = 'u0001'*
The separator used to segregate the key value pair fields in a FIX
message is the ASCII '01' character or in unicode format '\u0001'. This
character must be escaped a second time to avoid a java runtime error.
Here is an example :
8=FIX.4.1 9=20 34=1 35=0 49=INVMGR 56=BRKR 1=BE.CHM.001 11=CHM0001-01
22=4 ...
and how to use the annotation
*FIX - message*
[source,java]
------------------------------------------------------------------------------------------
@Message(keyValuePairSeparator = "=", pairSeparator = "\u0001", type="FIX", version="4.1")
public class Order {
...
}
------------------------------------------------------------------------------------------
*Look at test cases*
The ASCII character like tab, ... cannot be displayed in WIKI page. So,
have a look to the test case of camel-bindy to see exactly how the FIX
message looks like (src\test\data\fix\fix.txt) and the Order, Trailer,
Header classes
(src\test\java\org\apache\camel\dataformat\bindy\model\fix\simple\Order.java)
[[Bindy-6.KeyValuePairField]]
6. KeyValuePairField
^^^^^^^^^^^^^^^^^^^^
The KeyValuePairField annotation defines the property of a key value
pair field. Each KeyValuePairField is identified by a tag (= key) and
its value associated, a type (string, int, date, ...), optionaly a
pattern and if the field is required
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Annotation name |Record type |Level
|*KeyValuePairField* |Key Value Pair - FIX |Property
|=======================================================================
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Parameter name |type |Info
|tag |int |mandatory - digit number identifying the field in the message - must be
unique
|pattern |string |optional - default value = "" - will be used to format Decimal, Date,
...
|precision |int |optional - digit number - represents the precision to be used when the
Decimal number will be formatted/parsed
|position |int |optional - must be used when the position of the key/tag in the FIX
message must be different
|required |boolean |optional - default value = "false"
|impliedDecimalSeparator |boolean |*Camel 2.11:* optional - default value = "false" - Indicates if there is
a decimal point implied at a specified location
|=======================================================================
*case 1 : tag*
This parameter represents the key of the field in the message
*FIX message - Tag*
[source,java]
------------------------------------------------------------------------------------------
@Message(keyValuePairSeparator = "=", pairSeparator = "\u0001", type="FIX", version="4.1")
public class Order {
@Link Header header;
@Link Trailer trailer;
@KeyValuePairField(tag = 1) // Client reference
private String Account;
@KeyValuePairField(tag = 11) // Order reference
private String ClOrdId;
@KeyValuePairField(tag = 22) // Fund ID type (Sedol, ISIN, ...)
private String IDSource;
@KeyValuePairField(tag = 48) // Fund code
private String SecurityId;
@KeyValuePairField(tag = 54) // Movement type ( 1 = Buy, 2 = sell)
private String Side;
@KeyValuePairField(tag = 58) // Free text
private String Text;
...
}
------------------------------------------------------------------------------------------
*case 2 : Different position in output*
If the tags/keys that we will put in the FIX message must be sorted
according to a predefine order, then use the attribute 'position' of the
annotation @KeyValuePairField
*FIX message - Tag - sort*
[source,java]
-----------------------------------------------------------------------------------------------------------------
@Message(keyValuePairSeparator = "=", pairSeparator = "\\u0001", type = "FIX", version = "4.1", isOrdered = true)
public class Order {
@Link Header header;
@Link Trailer trailer;
@KeyValuePairField(tag = 1, position = 1) // Client reference
private String account;
@KeyValuePairField(tag = 11, position = 3) // Order reference
private String clOrdId;
...
}
-----------------------------------------------------------------------------------------------------------------
[[Bindy-7.Section]]
7. Section
^^^^^^^^^^
In FIX message of fixed length records, it is common to have different
sections in the representation of the information : header, body and
section. The purpose of the annotation @Section is to inform bindy about
which class of the model represents the header (= section 1), body (=
section 2) and footer (= section 3)
Only one attribute/parameter exists for this annotation.
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Annotation name |Record type |Level
|*Section* |FIX |Class
|=======================================================================
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Parameter name |type |Info
|number |int |digit number identifying the section position
|=======================================================================
*case 1 : Section*
A. Definition of the header section
*FIX message - Section - Header*
[source,java]
---------------------------------------------------------------
@Section(number = 1)
public class Header {
@KeyValuePairField(tag = 8, position = 1) // Message Header
private String beginString;
@KeyValuePairField(tag = 9, position = 2) // Checksum
private int bodyLength;
...
}
---------------------------------------------------------------
B. Definition of the body section
*FIX message - Section - Body*
[source,java]
-----------------------------------------------------------------------------------------------------------------
@Section(number = 2)
@Message(keyValuePairSeparator = "=", pairSeparator = "\\u0001", type = "FIX", version = "4.1", isOrdered = true)
public class Order {
@Link Header header;
@Link Trailer trailer;
@KeyValuePairField(tag = 1, position = 1) // Client reference
private String account;
@KeyValuePairField(tag = 11, position = 3) // Order reference
private String clOrdId;
-----------------------------------------------------------------------------------------------------------------
C. Definition of the footer section
*FIX message - Section - Footer*
[source,java]
----------------------------------------------
@Section(number = 3)
public class Trailer {
@KeyValuePairField(tag = 10, position = 1)
// CheckSum
private int checkSum;
public int getCheckSum() {
return checkSum;
}
----------------------------------------------
[[Bindy-8.OneToMany]]
8. OneToMany
^^^^^^^^^^^^
The purpose of the annotation @OneToMany is to allow to work with a
List<?> field defined a POJO class or from a record containing
repetitive groups.
*Restrictions OneToMany*
Be careful, the one to many of bindy does not allow to handle
repetitions defined on several levels of the hierarchy
The relation OneToMany ONLY WORKS in the following cases :
* Reading a FIX message containing repetitive groups (= group of
tags/keys)
* Generating a CSV with repetitive data
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Annotation name |Record type |Level
|*OneToMany* |all |property
|=======================================================================
[width="100%",cols="10%,10%,80%",options="header",]
|=======================================================================
|Parameter name |type |Info
|mappedTo |string |optional - string - class name associated to the type of the List<Type
of the Class>
|=======================================================================
*case 1 : Generating CSV with repetitive data*
Here is the CSV output that we want :
Claus,Ibsen,Camel in Action 1,2010,35 +
Claus,Ibsen,Camel in Action 2,2012,35 +
Claus,Ibsen,Camel in Action 3,2013,35 +
Claus,Ibsen,Camel in Action 4,2014,35
Remark : the repetitive data concern the title of the book and its
publication date while first, last name and age are common
and the classes used to modeling this. The Author class contains a List
of Book.
*Generate CSV with repetitive data*
[source,java]
-----------------------------
@CsvRecord(separator=",")
public class Author {
@DataField(pos = 1)
private String firstName;
@DataField(pos = 2)
private String lastName;
@OneToMany
private List<Book> books;
@DataField(pos = 5)
private String Age;
...
public class Book {
@DataField(pos = 3)
private String title;
@DataField(pos = 4)
private String year;
-----------------------------
Very simple isn't it !!!
*case 2 : Reading FIX message containing group of tags/keys*
Here is the message that we would like to process in our model :
"8=FIX 4.19=2034=135=049=INVMGR56=BRKR" +
"1=BE.CHM.00111=CHM0001-0158=this is a camel - bindy test" +
"22=448=BE000124567854=1" +
"22=548=BE000987654354=2" +
"22=648=BE000999999954=3" +
"10=220"
tags 22, 48 and 54 are repeated
and the code
*Reading FIX message containing group of tags/keys*
[source,java]
---------------------------------------------------------------------------------------------------
public class Order {
@Link Header header;
@Link Trailer trailer;
@KeyValuePairField(tag = 1) // Client reference
private String account;
@KeyValuePairField(tag = 11) // Order reference
private String clOrdId;
@KeyValuePairField(tag = 58) // Free text
private String text;
@OneToMany(mappedTo = "org.apache.camel.dataformat.bindy.model.fix.complex.onetomany.Security")
List<Security> securities;
...
public class Security {
@KeyValuePairField(tag = 22) // Fund ID type (Sedol, ISIN, ...)
private String idSource;
@KeyValuePairField(tag = 48) // Fund code
private String securityCode;
@KeyValuePairField(tag = 54) // Movement type ( 1 = Buy, 2 = sell)
private String side;
---------------------------------------------------------------------------------------------------
[[Bindy-UsingtheJavaDSL]]
Using the Java DSL
^^^^^^^^^^^^^^^^^^
The next step consists in instantiating the DataFormat _bindy_ class
associated with this record type and providing Java package name(s) as
parameter.
For example the following uses the class `BindyCsvDataFormat` (who
correspond to the class associated with the CSV record type) which is
configured with "com.acme.model" +
package name to initialize the model objects configured in this
package.
[source,java]
------------------------------------------------------------------------
// Camel 2.15 or older (configure by package name)
DataFormat bindy = new BindyCsvDataFormat("com.acme.model");
 
// Camel 2.16 onwards (configure by class name)
DataFormat bindy = new BindyCsvDataFormat(com.acme.model.MyModel.class);
------------------------------------------------------------------------
[[Bindy-Settinglocale]]
Setting locale
++++++++++++++
Bindy supports configuring the locale on the dataformat, such as 
[source,java]
--------------------------------------------------------------------------------
// Camel 2.15 or older (configure by package name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat("com.acme.model");
// Camel 2.16 onwards (configure by class name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat(com.acme.model.MyModel.class);
bindy.setLocale("us");
--------------------------------------------------------------------------------
Or to use the platform default locale then use "default" as the locale
name. Notice this requires Camel 2.14/2.13.3/2.12.5.
[source,java]
--------------------------------------------------------------------------------
// Camel 2.15 or older (configure by package name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat("com.acme.model");
// Camel 2.16 onwards (configure by class name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat(com.acme.model.MyModel.class);
bindy.setLocale("default");
--------------------------------------------------------------------------------
for older releases you can set it using Java code as shown
[source,java]
--------------------------------------------------------------------------------
// Camel 2.15 or older (configure by package name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat("com.acme.model");
// Camel 2.16 onwards (configure by class name)
BindyCsvDataFormat bindy = new BindyCsvDataFormat(com.acme.model.MyModel.class);
bindy.setLocale(Locale.getDefault().getISO3Country());
--------------------------------------------------------------------------------
[[Bindy-Unmarshaling]]
Unmarshaling
++++++++++++
[source,java]
-----------------------------
from("file://inbox")
.unmarshal(bindy)
.to("direct:handleOrders");
-----------------------------
Alternatively, you can use a named reference to a data format which can
then be defined in your link:registry.html[Registry] e.g. your
link:spring.html[Spring] XML file:
[source,java]
---------------------------------
from("file://inbox")
.unmarshal("myBindyDataFormat")
.to("direct:handleOrders");
---------------------------------
The Camel route will pick-up files in the inbox directory, unmarshall
CSV records into a collection of model objects and send the collection +
to the route referenced by 'handleOrders'.
The collection returned is a *List of Map* objects. Each Map within the
list contains the model objects that were marshalled out of each line of
the CSV. The reason behind this is that _each line can correspond to
more than one object_. This can be confusing when you simply expect one
object to be returned per line.
Each object can be retrieve using its class name.
[source,java]
---------------------------------------------------------------------------------------------------------
List<Map<String, Object>> unmarshaledModels = (List<Map<String, Object>>) exchange.getIn().getBody();
int modelCount = 0;
for (Map<String, Object> model : unmarshaledModels) {
for (String className : model.keySet()) {
Object obj = model.get(className);
LOG.info("Count : " + modelCount + ", " + obj.toString());
}
modelCount++;
}
LOG.info("Total CSV records received by the csv bean : " + modelCount);
---------------------------------------------------------------------------------------------------------
Assuming that you want to extract a single Order object from this map
for processing in a route, you could use a combination of a
link:splitter.html[Splitter] and a link:processor.html[Processor] as per
the following:
[source,java]
----------------------------------------------------------------------------------
from("file://inbox")
.unmarshal(bindy)
.split(body())
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
Map<String, Object> modelMap = (Map<String, Object>) in.getBody();
in.setBody(modelMap.get(Order.class.getCanonicalName()));
}
})
.to("direct:handleSingleOrder")
.end();
----------------------------------------------------------------------------------
[[Bindy-Marshaling]]
Marshaling
++++++++++
To generate CSV records from a collection of model objects, you create
the following route :
[source,java]
---------------------------
from("direct:handleOrders")
.marshal(bindy)
.to("file://outbox")
---------------------------
[[Bindy-UsingSpringXML]]
Using Spring XML
^^^^^^^^^^^^^^^^
This is really easy to use Spring as your favorite DSL language to
declare the routes to be used for camel-bindy. The following example
shows two routes where the first will pick-up records from files,
unmarshal the content and bind it to their model. The result is then
send to a pojo (doing nothing special) and place them into a queue.
The second route will extract the pojos from the queue and marshal the
content to generate a file containing the csv record. The example above
is for using Camel 2.16 onwards.
*spring dsl*
[source,java]
-------------------------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">
<!-- Queuing engine - ActiveMq - work locally in mode virtual memory -->
<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="brokerURL" value="vm://localhost:61616"/>
</bean>
<camelContext xmlns="http://camel.apache.org/schema/spring">
 
<dataFormats>
<bindy id="bindyDataformat" type="Csv" classType="org.apache.camel.bindy.model.Order"/>
</dataFormats>
<route>
<from uri="file://src/data/csv/?noop=true" />
<unmarshal ref="bindyDataformat" />
<to uri="bean:csv" />
<to uri="activemq:queue:in" />
</route>
<route>
<from uri="activemq:queue:in" />
<marshal ref="bindyDataformat" />
<to uri="file://src/data/csv/out/" />
</route>
</camelContext>
</beans>
-------------------------------------------------------------------------------------------------
[Note]
====
*Be careful*
Please verify that your model classes implements serializable otherwise
the queue manager will raise an error
====
[[Bindy-Dependencies]]
Dependencies
^^^^^^^^^^^^
To use Bindy in your camel routes you need to add the a dependency on
*camel-bindy* which implements this data format.
If you use maven you could just add the following to your pom.xml,
substituting the version number for the latest & greatest release (see
link:download.html[the download page for the latest versions]).
[source,java]
--------------------------------------
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-bindy</artifactId>
<version>x.x.x</version>
</dependency>
--------------------------------------