System Message Pump

Written by Kevin Cloutier on Jul 25, 2018 10:32 am

There are many times that you want to get a message from the system. You may also want to communicate between processes. To do either of these we employ the Message Pump.

In this example  we will monitor the messages that are going around the message pump inside the JNIOR.

We first need a class that will define all the message pump types

package com.integpg.messagepumpsample;

/**
 * A class that contains an enumeration of values for each message type.
 */
public class SystemMessageTypes {

    public static final int SM_SHUTDOWN = 0x01;
    public static final int SM_PROBE = 0x02;
    public static final int SM_GCRUN = 0x10;
    public static final int SM_WATCHDOG = 0x11;
    public static final int SM_SYSLOG = 0x12;
    public static final int SM_REGUPDATE = 0x40;
    public static final int SM_WEBSTARTUP = 0x60;
    public static final int SM_WEBSHUTDOWN = 0x61;
    public static final int SM_PROTCMDMSG = 0x70;
    public static final int SM_PROTCMDRESP = 0x71;
    public static final int SM_PIPEOPEN = 0x80;
    public static final int SM_PIPECLOSE = 0x81;
    public static final int SM_USER = 0x400;



    /**
     * @return a String definition based on the type parameter
     */
    public static String getMessageNameByType(int type) {
        switch (type) {
            case SM_SHUTDOWN:
                return "Shutdown";
            case SM_PROBE:
                return "Probe";
            case SM_GCRUN:
                return "GC Run";
            case SM_WATCHDOG:
                return "Watchdog";
            case SM_SYSLOG:
                return "Syslog";
            case SM_REGUPDATE:
                return "Registry Update";
            case SM_WEBSTARTUP:
                return "Web Server Startup";
            case SM_WEBSHUTDOWN:
                return "Web Server Shutdown";
            case SM_PROTCMDMSG:
                return "Protocol Command Message";
            case SM_PROTCMDRESP:
                return "Protocol Command Reesponse";
            case SM_PIPEOPEN:
                return "Pipe Opened";
            case SM_PIPECLOSE:
                return "Pipe Closed";
            default:
                if (type >= SM_USER) return "User Defined Type " + String.valueOf(type);
                else return "Unknown Type " + String.valueOf(type);
        }
    }
}

Not only does the above class define the types but it gives us a nice helper function to get a nice textual description for the message type.

We will create a class that will monitor the message pump. The biggest note to observe is that we MUST forward the message once we receive it so that other processes can see the message. The message pump message delivery is also time sensitive. If we receive the message and perform a lengthy process before re-posting it the we could cause the system to assert as the message pump timing has been violated.

package com.integpg.messagepumpsample;

import com.integpg.system.MessagePump;
import com.integpg.system.SystemMsg;
import java.util.Vector;

public class MessagePumpEngine implements Runnable {

    private static final MessagePumpEngine THIS = new MessagePumpEngine();
    private static final MessagePump MESSAGE_PUMP = new MessagePump();
    private static final Vector LISTENERS = new Vector<>();

    private static Thread _thread;



    /**
     * Singleton constructor
     */
    private MessagePumpEngine() {
    }



    /**
     * adds a listener that will be alerted of all received messages. The listener will be responsible for determining
     * if the message has meaning to them
     */
    public static void addListener(MessagePumpListener listener) {
        synchronized (LISTENERS) {
            LISTENERS.addElement(listener);
        }
    }



    /**
     * starts our message pump engine.
     */
    static void start() {
        if (null == _thread) {
            _thread = new Thread(THIS);
            _thread.setName("message-pump-engine");
            _thread.setDaemon(true);
            _thread.start();
        }
    }



    @Override
    public void run() {
        System.out.println("open the message pump and start monitoring.\r\n");
        MESSAGE_PUMP.open();

        for (;;) {
            // read all messages from the message pump
            SystemMsg systemMsg = MESSAGE_PUMP.getMessage();

            // we must repost as fast as we can
            MESSAGE_PUMP.postMessage(systemMsg);

            // notify all of our listeners
            synchronized (LISTENERS) {
                for (int i = 0; i < LISTENERS.size(); i++) {
                    MessagePumpListener listener = (MessagePumpListener) LISTENERS.elementAt(i);
                    listener.messageReceived(systemMsg);
                }
            }

        }
    }



    /**
     * Exposes the postMesssage method of the System MessagePump
     *
     * @param msg the message the will be sent to the system message pump
     */
    public static void postMessage(SystemMsg msg) {
        MESSAGE_PUMP.postMessage(msg);
    }
}

When the above class receives a message from the message pump it will want to alert any listeners.  Lets define the listener interface now.

package com.integpg.messagepumpsample;

import com.integpg.system.SystemMsg;

/**
 * An interface that outlines the method or methods that all classes that implement this interface must define.
 */
public interface MessagePumpListener {

    public void messageReceived(SystemMsg systemMsg);
}

Now that we have laid the ground work for the application lets glue the peices together.  Here is our Main class

package com.integpg.messagepumpsample;

/**
 * HexUtils will give us access to the hex dump so that we can show a meaningful representation of the data in each
 * message
 */
import com.integpg.janoslib.utils.HexUtils;
/**
 * System classes pertaining to the message pump itself and the class that will be returned from the getMessage call
 */
import com.integpg.system.SystemMsg;
/**
 * A class that will format a date string quickly
 */
import com.integpg.janoslib.text.QuickDateFormat;

/**
 * A class to demonstrate the Message Pump feature in the JNIOR.
 */
public class MessagePumpSampleMain implements MessagePumpListener {

    /**
     * the QuickDateFormat() class is part of a library of classes and are beyond the scope of this example.
     */
    private final static QuickDateFormat QUICK_DATE_FORMAT = new QuickDateFormat("MM-dd-yy HH:mm:ss.fff");



    public static void main(String[] args) throws InterruptedException {
        // instantiate our main class
        MessagePumpSampleMain msgPumpSample = new MessagePumpSampleMain();
        msgPumpSample.init();

        // the main method has nothing left to do.  let it sleep forever.  Most if not all of our other threads 
        // are daemon threads that would not keep the process from sxiting if this main thread completes.  
        // Sometimes we might do somehting in this thread but for the sake of this example we have nothing to do.
        Thread.sleep(Integer.MAX_VALUE);
    }



    public void init() {
        // the message pump engine is a singleton.  We do not have to instatiate another instance.  We can just 
        // use static methods to interface with it.  
        //
        // first we need to add in any listeners that should be alerted when a new message is received.  It 
        // will be up to those llisteners to determine if it cares about the message.
        MessagePumpEngine.addListener(this);
        MessagePumpEngine.addListener(new RegistryMessageHandler());

        // we are ready.  start listening
        MessagePumpEngine.start();
    }



    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // the QUICK_DATE_FORMAT.format() call is part of a library of classes and are beyond the scope of this 
        // example.
        String dateString = QUICK_DATE_FORMAT.format(System.currentTimeMillis());
        // so we have a message.  what kind is it?
        String messageName = SystemMessageTypes.getMessageNameByType(systemMsg.type);
        // print out what we know about this message
        System.out.println(dateString + " - Process Msg: " + messageName);

        // if there is data in the message then we should dump it so that we can see what it is.
        if (0 < systemMsg.msg.length) {
            // the HexUtils.hexDump() call is part of a library of classes and are beyond the scope of this 
            // example.
            String hexDumpString = HexUtils.hexDump(systemMsg.msg);
            System.out.println(hexDumpString);
        }
    }

}

In the above lines 48 and 49 we add our listener classes.  Line 48 is the this object representing the Main class.  You can see what happens in the messageReceived() method starting at line 58.  The other listener is the RegistryMessageHandler class.  Here is the definition for that class.

package com.integpg.messagepumpsample;

import com.integpg.system.JANOS;
import com.integpg.system.SystemMsg;

public class RegistryMessageHandler implements MessagePumpListener {

    @Override
    public void messageReceived(SystemMsg systemMsg) {
        // we only care about registry key updates
        if (SystemMessageTypes.SM_REGUPDATE == systemMsg.type) {
            // get the key name from the message data
            String keyName = new String(systemMsg.msg);
            // now get the new value
            String keyValue = JANOS.getRegistryString(keyName, "");
            System.out.println("Registry Update: " + keyName + " = " + keyValue);
        }
    }

}

Each time a message is received from the message pump each of our listeners is notified.  Each listener can check the message type to see if it cares about the message.  A sample output from the application shows three interesting events.  First, I opened the web page for the unit.  The web server is NOT always running unless the web server port is accessed.  Once the web server port was hit it caused the web server to spin up and send the Web Server Startup message.  The next two events were registry updates.  The Main class listener shows us that there was a Registry Update along with the HEX dump of the message payload.  Then the RegistryMessageHandler listener used the registry key that was updated to get the new value.  You can download the sample application JAR file below.

  msgpump-monitor.jar [ Jul 25 2018, 23.38 KB, MD5: 00eb2d3b97bbc67c5d1d13a9901e9fbe ]

The two files below need to be added to the project with the example above for it to run properly.

HexUtils.java

/**
 * A Utility class for dealing with Hex
 */
public class HexUtils {

    public static final char[] HexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    public static final char[] LowerHexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };



    public static String bytesToHex(byte[] bytes) {
        return bytesToHex(bytes, 0, bytes.length);
    }



    public static String bytesToHex(byte[] bytes, int offset, int len) {
        if (null == bytes) return "null";

        char[] hexChars = new char[len * 2];
        int v, i = 0;
        for (int j = offset; j < offset + len; j++) {
            v = bytes[j] & 0xFF;
            hexChars[i++] = HexArray[(v >> 4 & 0x0F)];
            hexChars[i++] = HexArray[v & 0x0F];
        }
        return new String(hexChars);
    }



    public static byte[] hexToBytes(String hexString, int offset, int len) {
        if (null == hexString) return new byte[0];

        byte[] bytes = new byte[len/2];
        int i = 0;
        for (int j = offset; j < len; j += 2) {
            char c = hexString.charAt(j);
            int n = 0;
            if ('0' <= c && '9' >= c) n = c - '0';
            else if ('A' <= c && 'F' >= c) n = c - 55;
            else if ('a' <= c && 'f' >= c) n = c - 87;
            else throw new RuntimeException("error parsing hex");
//            System.out.println(String.format("%s, %d", String.valueOf(c), n));

            int val = n << 4;

            c = hexString.charAt(j + 1);
            n = 0;
            if ('0' <= c && '9' >= c) n = c - '0';
            else if ('A' <= c && 'F' >= c) n = c - 55;
            else if ('a' <= c && 'f' >= c) n = c - 87;
            else throw new RuntimeException("error parsing hex");

            val += n;
//            System.out.println(String.format("%s, %d, %d", String.valueOf(c), n, val));

            bytes[i++] = (byte) val;
        }
        return bytes;
    }



    public static String hexDump(byte[] bytes) {
        return hexDump(bytes, 0, bytes.length);
    }



    public static String hexDump(byte[] bytes, int offset, int length) {
        StringBuilder sb = new StringBuilder((length / 16 + 1) * 83);

        char[] line = new char[81];
        line[0] = '0';
        line[1] = 'x';
        line[line.length - 2] = '\r';
        line[line.length - 1] = '\n';

        int endAddress = offset + length;
        for (int lineAddress = offset - (offset % 16); lineAddress < endAddress; lineAddress += 16) {
            int charPos = 2;
            line[charPos++] = LowerHexArray[(lineAddress >> 12 & 0x0F)];
            line[charPos++] = LowerHexArray[(lineAddress >> 8 & 0x0F)];
            line[charPos++] = LowerHexArray[(lineAddress >> 4 & 0x0F)];
            line[charPos++] = LowerHexArray[lineAddress & 0x0F];
            line[charPos++] = ' ';
            line[charPos++] = ' ';
            line[charPos++] = ' ';

            for (int linePos = 0; linePos < 16; linePos++) {
                int address = lineAddress + linePos;
                if (address < offset) {
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                } else if (address < endAddress) {
                    int c = bytes[address];
                    line[charPos++] = LowerHexArray[(c >> 4 & 0x0F)];
                    line[charPos++] = LowerHexArray[c & 0x0F];
                    line[charPos++] = ' ';
                } else {
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                }

                if (7 == linePos) {
                    // add 2 spaces between each group of 8 bytes
                    line[charPos++] = ' ';
                    line[charPos++] = ' ';
                }
            }
            // add 3 spaces between the second group of 8 bytes and the ASCII output
            line[charPos++] = ' ';
            line[charPos++] = ' ';
            line[charPos++] = ' ';

            for (int linePos = 0; linePos < 16; linePos++) {
                int address = lineAddress + linePos;

                if (address < offset) line[charPos++] = ' ';
                else if (address < endAddress) {
                    char c = (char) bytes[address];
                    if (0x20 > c || 0x80 <= c) line[charPos++] = '.';
                    else line[charPos++] = c;
                } else line[charPos++] = ' ';
                if (7 == linePos) line[charPos++] = ' ';
            }

            sb.append(line, 0, line.length);
        }

        return sb.toString();
    }



    public static long parseHexAsLong(String s) {
        if (s == null) return 0;
        int len = s.length();
        // get the address in two stages because some addresses can be too great for the parseLong method.
        long l = Long.parseLong(s.substring(0, len - 8), 16);
        l <<= 32;
        l += Long.parseLong(s.substring(len - 8), 16);
        return l;
    }
}

QuickDateFormat.java

import com.integpg.system.ArrayUtils;
import com.integpg.system.Timebase;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * This class constructs a date string with a user defined format. it caches date and time locations to help construct
 * the string faster.
 */
public class QuickDateFormat {

    private static final SimpleDateFormat TIMEZON_DATE_FORMAT = new SimpleDateFormat("z");
    private static final long MILLIS_IN_HOUR = 3600000;
    private static final String[] MONTH_NAME_STRINGS = new String[]{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    private static final byte[][] AM_PM_STRING_BYTES = new byte[][]{ "am".getBytes(), "pm".getBytes() };

    private final Calendar _calendar = Calendar.getInstance();

    private final byte[] _dateStampBytes;
    public final int DATE_STAMP_LENGTH;
    public final int DATE_STAMP_LENGTH_WITH_COMMA;
    private final String _pattern;

    int _weekOfYearPos = -1;
    int _monthPos = -1;
    int _monthNamePos = -1;
    int _dayPos = -1;
    int _2yearPos = -1;
    int _4yearPos = -1;
    int _hourPos = -1;
    int _hourOfDayPos = -1;
    int _minutePos = -1;
    int _secondPos = -1;
    int _millisPos = -1;
    int _timezonePos = -1;
    int _ampmPos = -1;

    private final byte[] _buf = new byte[8];
    private long _lastTimeRef = 0;

    private long _nextHour = System.currentTimeMillis() - System.currentTimeMillis() % MILLIS_IN_HOUR;
    private long _nextMinute = 0;



    public QuickDateFormat() {
        this("MM-dd-yyyy HH:mm:ss.fff zzz");
    }



    public QuickDateFormat(String pattern) {
        _pattern = pattern;

        _dateStampBytes = new byte[pattern.length() + 2];
        ArrayUtils.arraycopy(pattern.getBytes(), 0, _dateStampBytes, 0, pattern.length());
        _dateStampBytes[_dateStampBytes.length - 2] = ',';
        _dateStampBytes[_dateStampBytes.length - 1] = ' ';

        DATE_STAMP_LENGTH = _dateStampBytes.length - 2;
        DATE_STAMP_LENGTH_WITH_COMMA = _dateStampBytes.length;

        _weekOfYearPos = pattern.indexOf("WY");
        _monthNamePos = pattern.indexOf("MMM");
        if (-1 == _monthNamePos) {
            _monthPos = pattern.indexOf("MM");
        }
        _dayPos = pattern.indexOf("dd");
        if (-1 == _dayPos) _dayPos = pattern.indexOf("DD");
        _2yearPos = pattern.indexOf("yy");
        if (-1 == _2yearPos) _2yearPos = pattern.indexOf("YY");
        _4yearPos = pattern.indexOf("yyyy");
        if (-1 == _4yearPos) _4yearPos = pattern.indexOf("YYYY");
        _hourPos = pattern.indexOf("hh");
        _hourOfDayPos = pattern.indexOf("HH");
        _minutePos = pattern.indexOf("mm");
        _secondPos = pattern.indexOf("ss");
        _millisPos = pattern.indexOf("fff");
        if (-1 == _millisPos) _millisPos = pattern.indexOf("SSS");
        _timezonePos = pattern.indexOf("zzz");
        _ampmPos = pattern.indexOf("aa");

        updateTimezone();
    }



    public String getDateFormatString() {
        return _pattern;
    }



    private void updateTimezone() {
        if (-1 != _timezonePos) {
            String timezoneString = Timebase.getTimeZone(); //TIMEZON_DATE_FORMAT.format(new Date());
//            System.out.println("timezoneString: " + timezoneString);
//            System.out.println("_dateStampBytes.legnth: " + _dateStampBytes.length);
//            System.out.println("_timezonePos: " + _timezonePos);
            ArrayUtils.arraycopy(timezoneString.getBytes(), 0, _dateStampBytes, _timezonePos, timezoneString.length());
        }
    }



    // 2.0
    public String format(Date date) {
        return format(date.getTime());
    }



    public String format() {
        return format(System.currentTimeMillis());
    }



    public String format(long time) {
        synchronized (_dateStampBytes) {
            if (time <= 0) time = System.currentTimeMillis();

            if (_nextHour <= time) {
                _nextHour += MILLIS_IN_HOUR;
                updateTimezone();
            }

            if (_lastTimeRef > time || time >= _nextMinute) {
                initRefBytes(time);
            }

            int delta = (int) (time - _lastTimeRef);
            int rSecond = delta / 1000;

            if (-1 != _secondPos) {
                stuffInt(_buf, rSecond + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _secondPos, 2);
            }

            if (-1 != _millisPos) {
                int rMillis = delta - 1000 * rSecond;
                stuffInt(_buf, rMillis + 1000, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _millisPos, 3);
            }

            return new String(_dateStampBytes, 0, DATE_STAMP_LENGTH);
        }
    }



    // 2.0
    public byte[] toByteArray() {
        return _dateStampBytes;
    }



    private void initRefBytes(long time) {
        _calendar.setTimeInMillis(time);

        int second = _calendar.get(Calendar.SECOND);
        int millis = _calendar.get(Calendar.MILLISECOND);

        _lastTimeRef = time - (1000 * second + millis);
        _nextMinute = time + 60000 - (time % 60000);

        synchronized (_dateStampBytes) {
            if (-1 != _weekOfYearPos) {
                int weekOfYear = _calendar.get(Calendar.WEEK_OF_YEAR);
//        System.out.println("woy: " + weekOfYear);
                stuffInt(_buf, weekOfYear + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _weekOfYearPos, 2);
            }

            if (-1 != _monthPos) {
                int month = _calendar.get(Calendar.MONTH);
                stuffInt(_buf, month + 101, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _monthPos, 2);
            }

            if (-1 != _monthNamePos) {
                int month = _calendar.get(Calendar.MONTH);
                String monthName = MONTH_NAME_STRINGS[month];
                ArrayUtils.arraycopy(monthName.getBytes(), 0, _dateStampBytes, _monthNamePos, 3);
            }

            if (-1 != _dayPos) {
                int day = _calendar.get(Calendar.DAY_OF_MONTH);
                stuffInt(_buf, day + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _dayPos, 2);
            }

            if (-1 != _2yearPos || -1 != _4yearPos) {
                int year = _calendar.get(Calendar.YEAR);
                stuffInt(_buf, year, 0, false);
                if (-1 != _2yearPos) ArrayUtils.arraycopy(_buf, 2, _dateStampBytes, _2yearPos, 2);
                if (-1 != _4yearPos) ArrayUtils.arraycopy(_buf, 0, _dateStampBytes, _4yearPos, 4);
            }

            if (-1 != _hourPos) {
                int rHour = _calendar.get(Calendar.HOUR);
                stuffInt(_buf, rHour + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _hourPos, 2);
            }

            if (-1 != _hourOfDayPos) {
                int rHour = _calendar.get(Calendar.HOUR_OF_DAY);
                stuffInt(_buf, rHour + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _hourOfDayPos, 2);
            }

            if (-1 != _minutePos) {
                int minute = _calendar.get(Calendar.MINUTE);
                stuffInt(_buf, minute + 100, 0, false);
                ArrayUtils.arraycopy(_buf, 1, _dateStampBytes, _minutePos, 2);
            }

            if (-1 != _ampmPos) {
                byte[] amPmBytes = AM_PM_STRING_BYTES[_calendar.get(Calendar.AM_PM)];
                ArrayUtils.arraycopy(amPmBytes, 0, _dateStampBytes, _ampmPos, 2);
            }
        }
    }



    private void stuffInt(byte[] array, int value, int dec, boolean rjustified) {
        ArrayUtils.arrayFill(array, 0, array.length, (byte) ' ');

        if (value == 0) {
            array[0] = (byte) '0';
            return;
        }

        int dig;
        int offset = 0;
        boolean leading = true;

        if (value < 0) {
            array[offset++] = (byte) '-';
            value = -value;
            rjustified = false;
        }

        for (dig = 1000000; dig >= 1; dig /= 10) {
            if (leading && value < dig) {
                if (rjustified) {
                    offset++;
                }
                continue;
            }

            array[offset] = (byte) '0';
            while (value >= dig) {
                array[offset]++;
                value -= dig;
            }
            offset++;
            leading = false;
        }

        for (dig = dec; dig > 0; dig--) {
            array[offset] = array[offset - 1];
            if (array[offset] == ' ') {
                array[offset] = '0';
            }
            offset--;

            if (dig == 1) {
                array[offset] = (byte) '.';
                if (array[offset - 1] == ' ') {
                    array[offset - 1] = '0';
                }
            }
        }
    }
}
On this page