Add documentation for the problem with CustomField encoders for mutable value objects, since we can't really do anything about it.

git-svn-id: https://j8583.svn.sourceforge.net/svnroot/j8583/j8583@73 db60a039-f72e-0410-b1d2-baa000846441
This commit is contained in:
chochos
2011-05-20 16:39:31 +00:00
parent a8c2aecd3e
commit 13a04b271a
6 changed files with 158 additions and 7 deletions
@@ -178,6 +178,8 @@ public class MessageFactory {
if (templ != null) {
for (int i = 2; i <= 128; i++) {
if (templ.hasField(i)) {
//We could detect here if there's a custom object with a CustomField,
//but we can't copy the value so there's no point.
m.setField(i, templ.getField(i).clone());
}
}
@@ -64,9 +64,7 @@ public class ConfigParser {
InputStream ins = MessageFactory.class.getClassLoader().getResourceAsStream(path);
MessageFactory mfact = new MessageFactory();
if (ins != null) {
if (log.isDebugEnabled()) {
log.debug("ISO8583 Parsing config from classpath file {}", path);
}
log.debug("ISO8583 Parsing config from classpath file {}", path);
try {
parse(mfact, ins);
} finally {
@@ -207,9 +205,7 @@ public class ConfigParser {
public static void configureFromClasspathConfig(MessageFactory mfact, String path) throws IOException {
InputStream ins = MessageFactory.class.getClassLoader().getResourceAsStream(path);
if (ins != null) {
if (log.isDebugEnabled()) {
log.debug("ISO8583 Parsing config from classpath file {}", path);
}
log.debug("ISO8583 Parsing config from classpath file {}", path);
try {
parse(mfact, ins);
} finally {
+75
View File
@@ -0,0 +1,75 @@
-----
Custom Field Encoders
-----
Using Custom Field Encoders
Sometimes there are fields that contain several sub-fields or separate pieces of data. j8583 will only parse
the field for you, but you still have to parse those pieces of data from the field when you parse a message,
and/or encode several pieces of data into a field when creating a message.
j8583 can help with this process, by means of the custom field encoders. To use this feature, first you need
to implement the <<<CustomField>>> interface. Let's say for example that you want to handle a list of strings
inside a LLLVAR field. So you need to implement an encoder that will transform a list into a string and decode
a List from a string:
+-----
public class ListEncoder implements CustomField<List<String>> {
//Transform a List of Strings into a String
public String encodeField(List<String> value) {
StringBuilder sb = new StringBuilder();
for (String s: value) {
sb.append(s).append(',');
}
sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
//Transform a String into a List of Strings
public List<String> decodeField(String value) {
return Arrays.asList(value.split(","));
}
}
+-----
Now that you have your field encoder, you need to pass it to your MessageFactory, indicating the field in which
you want to use it, for example field 62. After this, when your message factory parses a message, the resulting
IsoMessage will contain a list of strings in field 62, and you can pass new messages directly a list into that
field, and it will be encoded into a String when writing the message to a stream or creating the byte array with
the ISO8583 data.
+-----
messageFactory.setCustomField(62, new ListEncoder());
IsoMessage parsed = messageFactory.parse(someBytes, 0);
assert parsed.getObjectValue(62) instanceof List
IsoMessage nm = messageFactory.newMessage(0x200);
nm.setValue(62, Arrays.asList("a", "b", "c", "d", "e"), IsoType.LLLVAR, 0);
//when nm gets written, field 62 will be "009a,b,c,d,e"
+-----
If you setup CustomField encoders in a MessageFactory and then you configure it using the XML configuration file,
the values for fields that have CustomField encoders defined will be decoded with them. In this example, if you
use configure the MessageFactory after setting up the ListEncoder for field 62, then a message template containing
a value for field 62 will have the field decoded as a List of Strings.
<<IMPORTANT:>> If you use a CustomField to handle a custom value object which you have in a message template,
you should make your custom object <<<Cloneable>>>, and then manually replace the value in your message obtained from
the factory with a new copy. Otherwise, all the messages that the factory creates from that template will have
a references to <the same value object>, and that means trouble if the object's values are modified (basically
you have several IsoMessages sharing state in that field). So you should do something like this:
+-----
IsoMessage myIso = messageFactory.newMessage(0x200);
//Let's suppose we have some custom object with its CustomField encoder for field 126
//Our class will have a copy() method or something similar
MyCustomClass customField = ((MyCustomClass)myIso.getObjectValue(126)).copy();
//Now you can modify this instance
customField.setSomeValue("blabla");
myIso.setValue(126, customField, IsoType.LLLVAR, 0);
+-----
Currently j8583 cannot perform this copy operation for you; even if the MessageFactory can detect if your custom
class implements Cloneable, it can't invoke the <<<clone()>>> method because it's protected and it's not guaranteed
that your implementation will only have made it public.
+1
View File
@@ -76,6 +76,7 @@ Usage Guide
CustomField for every field where you want to store custom values, so that parsed messages will return the
objects decoded by the CustomFields instead of just strings; and when you set a value in an IsoMessage, you
can specify the CustomField to be used to encode the value as a String.
{{{encoders.html}Learn more about custom field encoders}}.
* XML configuration
+77
View File
@@ -0,0 +1,77 @@
-----
Codificadores de campos personalizados
-----
Uso de los codificadores de campos personalizados
En ocasiones hay campos que contienen varios sub-campos, o al menos varios datos distintos codificados en uno solo.
j8583 solamente maneja el campo completo, pero todavía falta separar esos datos al leer un mensaje, y/o codificarlos
al generar mensajes nuevos.
j8583 puede ayudar con este proceso, por medio de los codificadores de campos personalizados. Para usar esta
característica, lo primero que hay que hacer es implementar la interfaz <<<CustomField>>>. Digamos por ejemplo
que se tiene que manejar una lista de cadenas dentro de un campo LLLVAR; entonces hay que implementar un
codificador que haga la transformación de lista a cadena y también la inversa, de cadena a lista:
+-----
public class ListEncoder implements CustomField<List<String>> {
//Transforma una lista de caadenas en una sola cadena separada por comas
public String encodeField(List<String> value) {
StringBuilder sb = new StringBuilder();
for (String s: value) {
sb.append(s).append(',');
}
sb.deleteCharAt(sb.length()-1);
return sb.toString();
}
//Separa la cadena por las comas y devuelve una lista
public List<String> decodeField(String value) {
return Arrays.asList(value.split(","));
}
}
+-----
Una vez que se tiene el codificador, hay que pasárselo a la fábrica de mensajes, indicando el campo en donde se
tiene que usar. Pongámoslo para este ejemplo en el campo 62; después de esto, cuando la fábrica de mensajes
genere mensajes a partir de arreglos de bytes, el IsoMessage resultante tendrá una lista de cadenas en el campo
62, y al crear nuevos mensajes se pueden pasar listas de cadenas directamente al campo 62 y serán codificadas
como una cadena al generarse la trama ISO8583.
+-----
messageFactory.setCustomField(62, new ListEncoder());
IsoMessage parsed = messageFactory.parse(unosBytes, 0);
assert parsed.getObjectValue(62) instanceof List
IsoMessage nm = messageFactory.newMessage(0x200);
nm.setValue(62, Arrays.asList("a", "b", "c", "d", "e"), IsoType.LLLVAR, 0);
//cuando se genere la trama de nm, el campo 62 será "009a,b,c,d,e"
+-----
Si se configuran objetos CustomField en una fábrica de mensajes y después se configura con un archivo XML,
los CustomField se usarán para decodificar los campos de las plantillas de mensaje que contengan valores en esas
posiciones. Siguiendo el ejemplo, si se configura esta fábrica de mensajes después de haberle pasado el ListEncoder,
entonces las plantillas de mensaje que tengan valor para el campo 62 contendrán una lista de cadenas en vez
de una simple cadena.
<<IMPORTANTE:>> Si se usa un CustomField para manejar objetos de valor propios, en plantillas de mensajes,
es necesario que dichos objetos implementen <<<Cloneable>>> y tengan un método para poder obtener una copia;
esto para reemplazar manualmente los valores en los mensajes nuevos, con copias frescas que se puedan modificar
con valores distintos en cada mensaje. De otra forma, todos los mensajes creados por la fábrica tendrán referencia
al mismo objeto en ese campo, lo cual causará problemas cuando se modifiquen los valores del objeto, ya que
básicamente se trata de varios IsoMessages compartiendo estado en ese campo. Por lo tanto hay que hacer algo así:
+-----
IsoMessage myIso = messageFactory.newMessage(0x200);
//Supongamos que tenemos un objeto con su CustomField para el campo 126
//La clase que se usa ahí debe tener un método copy() o algo similar
MiClase customField = ((MiClase)myIso.getObjectValue(126)).copy();
//Ahora se puede modificar esta instancia sin afectar otros mensajes
customField.setSomeValue("blabla");
myIso.setValue(126, customField, IsoType.LLLVAR, 0);
+-----
Por el momento, j8583 no puede realizar esta operación internamente; aunque la MessageFactory pueda detectar
que la clase contenida en el campo implementa la interfaz <<<Cloneable>>>, no puede invocar el método <<<clone()>>>
porque es protegido, y no hay garantía de que la clase lo haya reimplementado como público.
+1 -1
View File
@@ -85,7 +85,7 @@ Guía del usuario
CustomField a la fábrica de mensajes por cada campo en el cual se necesitan manejar valores especiales, para
que los mensajes leídos de tramas ya contengan el valor convertido en el campo. Y cuando se fija un valor
en un IsoMessage, se le puede pasar el CustomField para que sepa codificar dicho valor al momento de generar
su trama.
su trama. {{{encoders.html}Más información acerca de los codificadores de campos}}.
* Configuración por XML