com.moadbus.banking.iso.core.protocol.MessageFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.moadbus.banking.iso.core.protocol.MessageFactory.java

Source

/*
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 com.moadbus.banking.iso.core.protocol;

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.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 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 {

    private Log log = LogFactory.getLog(MessageFactory.class);

    /** This map stores the message template for each message type. */
    private Map<Integer, IsoMessage> typeTemplates = new HashMap<Integer, IsoMessage>();
    /** Stores the information needed to parse messages sorted by type. */
    private Map<Integer, Map<Integer, FieldParseInfo>> parseMap = new HashMap<Integer, Map<Integer, FieldParseInfo>>();
    /** Stores the field numbers to be parsed, in order of appearance. */
    private Map<Integer, List<Integer>> parseOrder = new HashMap<Integer, List<Integer>>();

    private TraceNumberGenerator traceGen;
    /** The ISO header to be included in each message type. */
    private Map<Integer, String> isoHeaders = new HashMap<Integer, String>();
    /** 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) {
        IsoMessage m = new IsoMessage(isoHeaders.get(type));
        m.setType(type);
        m.setEtx(etx);
        m.setBinary(useBinary);

        // Copy the values from the template
        IsoMessage templ = typeTemplates.get(type);
        if (templ != null) {
            for (int i = 2; i < 128; i++) {
                if (templ.hasField(i)) {
                    m.setField(i, templ.getField(i).clone());
                }
            }
        }
        if (traceGen != null) {
            m.setValue(11, 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) {
        IsoMessage resp = new IsoMessage(isoHeaders.get(request.getType() + 16));
        resp.setBinary(request.isBinary());
        resp.setType(request.getType() + 16);
        resp.setEtx(etx);
        // Copy the values from the template
        IsoMessage templ = typeTemplates.get(resp.getType());
        if (templ != null) {
            for (int i = 2; i < 128; i++) {
                if (templ.hasField(i)) {
                    resp.setField(i, templ.getField(i).clone());
                }
            }
        }
        for (int i = 2; i < 128; i++) {
            if (request.hasField(i)) {
                resp.setField(i, 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 {
        log.debug(" Message length =" + buf.length);
        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
        Map<Integer, FieldParseInfo> parseGuide = parseMap.get(type);
        List<Integer> index = parseOrder.get(type);
        log.debug(" Parsing bit ");
        log.debug(" Total index =" + index.size());
        for (Integer i : index) {
            FieldParseInfo fpi = parseGuide.get(i);
            if (i == 124) {
                if (1 == 1)
                    ;
            }
            log.debug((i) + ",");
            if (bs.get(i - 1)) {
                IsoValue val = useBinary ? fpi.parseBinary(buf, pos) : fpi.parse(buf, pos);
                log.debug("bit [" + i + "] len=" + val.getLength() + " val=" + val);
                m.setField(i, 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;
                }
            }
        }
        log.debug("...done");
        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<Integer, String> 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(type);
        } else {
            isoHeaders.put(type, value);
        }
    }

    /** Returns the ISO header used for the specified type. */
    public String getIsoHeader(int type) {
        return isoHeaders.get(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(templ.getType(), templ);
        }
    }

    /** Removes the message template for the specified type. */
    public void removeMessageTemplate(int type) {
        typeTemplates.remove(type);
    }

    /**
     * Sets a message template for a specified message type. When new messages
     * of that type are created, they will have the same values as the template.
     *
     * @param type
     *            The message type.
     * @param templ
     *            The message from which fields should be copied, or NULL to
     *            remove the template for this message type.
     * @deprecated Use addMessageTemplate(IsoMessage) and
     *             removeMessageTemplate(int) instead of this.
     */
    public void setMessageTemplate(int type, IsoMessage templ) {
        if (templ == null) {
            typeTemplates.remove(type);
        } else {
            typeTemplates.put(type, templ);
        }
    }

    /**
     * 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 DataElementParseInfo instances, each of which define
     *            what type and length of field to expect. The keys will be the
     *            field numbers.
     */
    public void setParseMap(int type, Map<Integer, FieldParseInfo> map) {
        parseMap.put(type, map);
        ArrayList<Integer> index = new ArrayList<Integer>();
        index.addAll(map.keySet());
        Collections.sort(index);
        log.debug("Adding parse map for type " + Integer.toHexString(type) + " with fields " + index);
        parseOrder.put(type, index);
    }
}