Java tutorial
/* j8583 A Java implementation of the ISO8583 protocol Copyright (C) 2007 Enrique Zamudio Lopez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package flink.iso8583; import java.text.ParseException; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import flink.iso8583.parse.FieldParseInfo; /** This class is used to create messages, either from scratch or from an existing String or byte * buffer. It can be configured to put default values on newly created messages, and also to know * what to expect when reading messages from an InputStream. * <P> * The factory can be configured to know what values to set for newly created messages, both from * a template (useful for fields that must be set with the same value for EVERY message created) * and individually (for trace [field 11] and message date [field 7]). * <P> * It can also be configured to know what fields to expect in incoming messages (all possible values * must be stated, indicating the date type for each). This way the messages can be parsed from * a byte buffer. * * @author Enrique Zamudio */ public class MessageFactory { protected static final Log log = LogFactory.getLog(MessageFactory.class); /** This map stores the message template for each message type. Keys are integers, values are IsoMessages. */ private Map typeTemplates = new HashMap(); /** Stores the information needed to parse messages sorted by type. * Keys are Integers, values are Maps of Integers and FieldParseInfos. */ private Map parseMap = new HashMap(); /** Stores the field numbers to be parsed, in order of appearance. * Keys are Integers, values are Lists of Integers. */ private Map parseOrder = new HashMap(); private TraceNumberGenerator traceGen; /** The ISO header to be included in each message type. * Keys are Integers, values are Strings. */ private Map isoHeaders = new HashMap(); /** Indicates if the current date should be set on new messages (field 7). */ private boolean setDate; /** Indicates if the factory should create binary messages and also parse binary messages. */ private boolean useBinary; private int etx = -1; /** Tells the receiver to create and parse binary messages if the flag is true. * Default is false, that is, create and parse ASCII messages. */ public void setUseBinaryMessages(boolean flag) { useBinary = flag; } /** Returns true is the factory is set to create and parse binary messages, * false if it uses ASCII messages. Default is false. */ public boolean getUseBinaryMessages() { return useBinary; } /** Sets the ETX character to be sent at the end of the message. This is optional and the * default is -1, which means nothing should be sent as terminator. * @param value The ASCII value of the ETX character or -1 to indicate no terminator should be used. */ public void setEtx(int value) { etx = value; } /** Creates a new message of the specified type, with optional trace and date values as well * as any other values specified in a message template. If the factory is set to use binary * messages, then the returned message will be written using binary coding. * @param type The message type, for example 0x200, 0x400, etc. */ public IsoMessage newMessage(int type) { Integer itype = new Integer(type); IsoMessage m = new IsoMessage((String) isoHeaders.get(itype)); m.setType(type); m.setEtx(etx); m.setBinary(useBinary); //Copy the values from the template IsoMessage templ = (IsoMessage) typeTemplates.get(itype); if (templ != null) { for (int i = 2; i < 128; i++) { if (templ.hasField(i)) { m.setField(i, (IsoValue) templ.getField(i).clone()); } } } if (traceGen != null) { m.setValue(11, new Integer(traceGen.nextTrace()), IsoType.NUMERIC, 6); } if (setDate) { m.setValue(7, new Date(), IsoType.DATE10, 10); } return m; } /** Creates a message to respond to a request. Increments the message type by 16, * sets all fields from the template if there is one, and copies all values from the request, * overwriting fields from the template if they overlap. * @param request An ISO8583 message with a request type (ending in 00). */ public IsoMessage createResponse(IsoMessage request) { Integer rtype = new Integer(request.getType() + 16); IsoMessage resp = new IsoMessage((String) isoHeaders.get(rtype)); resp.setBinary(request.isBinary()); resp.setType(request.getType() + 16); resp.setEtx(etx); //Copy the values from the template IsoMessage templ = (IsoMessage) typeTemplates.get(rtype); if (templ != null) { for (int i = 2; i < 128; i++) { if (templ.hasField(i)) { resp.setField(i, (IsoValue) templ.getField(i).clone()); } } } for (int i = 2; i < 128; i++) { if (request.hasField(i)) { resp.setField(i, (IsoValue) request.getField(i).clone()); } } return resp; } /** Creates a new message instance from the buffer, which must contain a valid ISO8583 * message. If the factory is set to use binary messages then it will try to parse * a binary message. * @param buf The byte buffer containing the message. Must not include the length header. * @param isoHeaderLength The expected length of the ISO header, after which the message type * and the rest of the message must come. */ public IsoMessage parseMessage(byte[] buf, int isoHeaderLength) throws ParseException { IsoMessage m = new IsoMessage(isoHeaderLength > 0 ? new String(buf, 0, isoHeaderLength) : null); //TODO it only parses ASCII messages for now int type = 0; if (useBinary) { type = ((buf[isoHeaderLength] & 0xff) << 8) | (buf[isoHeaderLength + 1] & 0xff); } else { type = ((buf[isoHeaderLength] - 48) << 12) | ((buf[isoHeaderLength + 1] - 48) << 8) | ((buf[isoHeaderLength + 2] - 48) << 4) | (buf[isoHeaderLength + 3] - 48); } m.setType(type); //Parse the bitmap (primary first) BitSet bs = new BitSet(64); int pos = 0; if (useBinary) { for (int i = isoHeaderLength + 2; i < isoHeaderLength + 10; i++) { int bit = 128; for (int b = 0; b < 8; b++) { bs.set(pos++, (buf[i] & bit) != 0); bit >>= 1; } } //Check for secondary bitmap and parse if necessary if (bs.get(0)) { for (int i = isoHeaderLength + 10; i < isoHeaderLength + 18; i++) { int bit = 128; for (int b = 0; b < 8; b++) { bs.set(pos++, (buf[i] & bit) != 0); bit >>= 1; } } pos = 18 + isoHeaderLength; } else { pos = 10 + isoHeaderLength; } } else { for (int i = isoHeaderLength + 4; i < isoHeaderLength + 20; i++) { int hex = Integer.parseInt(new String(buf, i, 1), 16); bs.set(pos++, (hex & 8) > 0); bs.set(pos++, (hex & 4) > 0); bs.set(pos++, (hex & 2) > 0); bs.set(pos++, (hex & 1) > 0); } //Check for secondary bitmap and parse it if necessary if (bs.get(0)) { for (int i = isoHeaderLength + 20; i < isoHeaderLength + 36; i++) { int hex = Integer.parseInt(new String(buf, i, 1), 16); bs.set(pos++, (hex & 8) > 0); bs.set(pos++, (hex & 4) > 0); bs.set(pos++, (hex & 2) > 0); bs.set(pos++, (hex & 1) > 0); } pos = 36 + isoHeaderLength; } else { pos = 20 + isoHeaderLength; } } //Parse each field Integer itype = new Integer(type); Map parseGuide = (Map) parseMap.get(itype); List index = (List) parseOrder.get(itype); for (Iterator iter = index.iterator(); iter.hasNext();) { Integer i = (Integer) iter.next(); FieldParseInfo fpi = (FieldParseInfo) parseGuide.get(i); if (bs.get(i.intValue() - 1)) { IsoValue val = useBinary ? fpi.parseBinary(buf, pos) : fpi.parse(buf, pos); m.setField(i.intValue(), val); if (useBinary && !(val.getType() == IsoType.ALPHA || val.getType() == IsoType.LLVAR || val.getType() == IsoType.LLLVAR)) { pos += (val.getLength() / 2) + (val.getLength() % 2); } else { pos += val.getLength(); } if (val.getType() == IsoType.LLVAR) { pos += useBinary ? 1 : 2; } else if (val.getType() == IsoType.LLLVAR) { pos += useBinary ? 2 : 3; } } } return m; } /** Sets whether the factory should set the current date on newly created messages, * in field 7. Default is false. */ public void setAssignDate(boolean flag) { setDate = true; } /** Returns true if the factory is assigning the current date to newly created messages * (field 7). Default is false. */ public boolean getAssignDate() { return setDate; } /** Sets the generator that this factory will get new trace numbers from. There is no * default generator. */ public void setTraceNumberGenerator(TraceNumberGenerator value) { traceGen = value; } /** Returns the generator used to assign trace numbers to new messages. */ public TraceNumberGenerator getTraceNumberGenerator() { return traceGen; } /** Sets the ISO header to be used in each message type. * @param value A map where the keys are the message types and the values are the ISO headers. */ public void setIsoHeaders(Map value) { isoHeaders.clear(); isoHeaders.putAll(value); } /** Sets the ISO header for a specific message type. * @param type The message type, for example 0x200. * @param value The ISO header, or NULL to remove any headers for this message type. */ public void setIsoHeader(int type, String value) { if (value == null) { isoHeaders.remove(new Integer(type)); } else { isoHeaders.put(new Integer(type), value); } } /** Returns the ISO header used for the specified type. */ public String getIsoHeader(int type) { return (String) isoHeaders.get(new Integer(type)); } /** Adds a message template to the factory. If there was a template for the same * message type as the new one, it is overwritten. */ public void addMessageTemplate(IsoMessage templ) { if (templ != null) { typeTemplates.put(new Integer(templ.getType()), templ); } } /** Removes the message template for the specified type. */ public void removeMessageTemplate(int type) { typeTemplates.remove(new Integer(type)); } /** Sets a map with the fields that are to be expected when parsing a certain type of * message. * @param type The message type. * @param map A map of FieldParseInfo instances, each of which define what type and length * of field to expect. The keys will be the field numbers. */ public void setParseMap(Integer type, Map map) { parseMap.put(type, map); ArrayList index = new ArrayList(); index.addAll(map.keySet()); Collections.sort(index); log.trace("Adding parse map for type " + Integer.toHexString(type.intValue()) + " with fields " + index); parseOrder.put(type, index); } }