org.jpos.q2.iso.OneShotChannelAdaptorMK2.java Source code

Java tutorial

Introduction

Here is the source code for org.jpos.q2.iso.OneShotChannelAdaptorMK2.java

Source

/*
 * jPOS Project [http://jpos.org]
 * Copyright (C) 2000-2016 Alejandro P. Revilla
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.jpos.q2.iso;

import org.jdom2.Element;
import org.jpos.core.ConfigurationException;
import org.jpos.iso.BaseChannel;
import org.jpos.iso.Channel;
import org.jpos.iso.FactoryChannel;
import org.jpos.iso.FilteredChannel;
import org.jpos.iso.ISOChannel;
import org.jpos.iso.ISOClientSocketFactory;
import org.jpos.iso.ISOFilter;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.ISOPackager;
import org.jpos.q2.QBeanSupport;
import org.jpos.q2.QFactory;
import org.jpos.space.Space;
import org.jpos.space.SpaceFactory;
import org.jpos.space.SpaceUtil;
import org.jpos.util.LogEvent;
import org.jpos.util.LogSource;
import org.jpos.util.Logger;
import org.jpos.util.NameRegistrar;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * OneShotChannelAdaptorMK2 connects and disconnects a channel for every message
 * exchange. It is similar to OneShotChannelAdaptor but uses a thread pool instead
 * of opening threads statically and supports mux pooling by exposing channel readiness.
 *
 * @author Alejandro Revilla
 * @author Thomas L. Kjeldsen
 * @author Victor Salaman
 */
@SuppressWarnings({ "UnusedDeclaration", "StatementWithEmptyBody" })
public class OneShotChannelAdaptorMK2 extends QBeanSupport
        implements OneShotChannelAdaptorMK2MBean, Channel, Runnable {
    Space<String, Object> sp;
    String in, out, ready;
    long delay;
    long checkInterval;
    int maxConnections;
    int[] handbackFields;
    ThreadPoolExecutor threadPool = null;
    AtomicInteger cnt;
    Element channelElement;

    ScheduledExecutorService checkTimer;

    public OneShotChannelAdaptorMK2() {
        super();
    }

    @SuppressWarnings("unchecked")
    private Space<String, Object> grabSpace(Element e) {
        return (Space<String, Object>) SpaceFactory.getSpace(e != null ? e.getText() : "");
    }

    @Override
    protected void initService() throws Exception {
        Element persist = getPersist();
        channelElement = persist.getChild("channel");
        if (channelElement == null) {
            throw new ConfigurationException("channel element missing");
        }
        sp = grabSpace(persist.getChild("space"));
        in = persist.getChildTextTrim("in");
        out = persist.getChildTextTrim("out");
        ready = getName() + ".ready";

        String s = persist.getChildTextTrim("max-connections");
        maxConnections = s != null ? Integer.parseInt(s) : 1;
        handbackFields = cfg.getInts("handback-field");

        s = persist.getChildTextTrim("delay");
        delay = s != null ? Integer.valueOf(s) : 2500;

        s = persist.getChildTextTrim("check-interval");
        checkInterval = s != null ? Integer.valueOf(s) : 60000;

        NameRegistrar.register(getName(), this);
    }

    public void startService() {
        setRealm(getName());
        cnt = new AtomicInteger(0);
        threadPool = new ThreadPoolExecutor(1, maxConnections, 10, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
        new Thread(this).start();

        checkTimer = Executors.newScheduledThreadPool(1);
        checkTimer.scheduleAtFixedRate(new CheckChannelTask(), 0L, checkInterval, TimeUnit.MILLISECONDS);
    }

    public void stopService() {
        if (checkTimer != null) {
            checkTimer.shutdown();
            checkTimer = null;
        }

        takeOffline();
        sp.out(in, new Object());
        threadPool.shutdown();
        while (!threadPool.isTerminated()) {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
            }
        }

        int c = 0;
        while (running()) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
            c++;
            if (c > 10)
                break;
        }
    }

    public void destroyService() {
        NameRegistrar.unregister(getName());
    }

    public boolean isConnected() {
        return sp != null && sp.rdp(ready) != null;
    }

    @Override
    @SuppressWarnings({ "StatementWithEmptyBody", "ConstantConditions" })
    public void run() {
        while (running()) {
            try {
                Object o = sp.in(in, delay);
                if (o instanceof ISOMsg) {
                    if (!isConnected()) {
                        continue;
                    }
                    ISOMsg m = (ISOMsg) o;
                    int i = cnt.incrementAndGet();
                    if (i > 9999) {
                        cnt.set(0);
                        i = cnt.incrementAndGet();
                    }
                    threadPool.execute(new Worker(m, i));
                }
            } catch (Exception e) {
                getLog().warn(getName(), e.getMessage());
            }
        }
    }

    private class CheckChannelTask implements Runnable {
        @Override
        public void run() {
            try {
                Date lastOnline = (Date) sp.rdp(ready);
                final LogEvent ev = getLog().createLogEvent("status");
                if (isChannelConnectable(true)) {
                    if (lastOnline == null) {
                        ev.addMessage("Channel is now online");
                        Logger.log(ev);
                        flushInput();
                    }
                    takeOnline();
                } else {
                    takeOffline();
                    if (lastOnline != null) {
                        ev.addMessage("Channel is now offline");
                        Logger.log(ev);
                    }
                }
            } catch (Throwable e) {
                getLog().warn(getName(), e.getMessage());
            }
        }

        private boolean isChannelConnectable(boolean showExceptions) {
            boolean res = false;

            ISOChannel channel = null;
            try {
                channel = newChannel(channelElement, getFactory());
                if (channel instanceof BaseChannel) {
                    BaseChannel bc = (BaseChannel) channel;
                    bc.setLogger(null, null);
                }
                channel.connect();
                res = true;
            } catch (Exception e) {
                if (showExceptions) {
                    getLog().error(e.getMessage());
                }
            } finally {
                if (channel != null && channel.isConnected()) {
                    try {
                        channel.disconnect();
                    } catch (IOException e) {
                        getLog().error(e);
                    }
                    NameRegistrar.unregister("channel." + channel.getName());
                }
            }

            return res;
        }
    }

    private void flushInput() {
        SpaceUtil.wipe(sp, in);
    }

    private void takeOffline() {
        SpaceUtil.wipe(sp, ready);
    }

    private void takeOnline() {
        sp.put(ready, new Date());
    }

    public void send(ISOMsg m) {
        sp.out(in, m);
    }

    public void send(ISOMsg m, long timeout) {
        sp.out(in, m, timeout);
    }

    public ISOMsg receive() {
        return (ISOMsg) sp.in(out);
    }

    public ISOMsg receive(long timeout) {
        return (ISOMsg) sp.in(out, timeout);
    }

    private ISOChannel newChannel(Element e, QFactory f) throws ConfigurationException {
        String channelName = e.getAttributeValue("class");
        if (channelName == null) {
            throw new ConfigurationException("class attribute missing from channel element.");
        }

        String packagerName = e.getAttributeValue("packager");

        ISOChannel channel = (ISOChannel) f.newInstance(channelName);
        ISOPackager packager;
        if (packagerName != null) {
            packager = (ISOPackager) f.newInstance(packagerName);
            channel.setPackager(packager);
            f.setConfiguration(packager, e);
        }
        QFactory.invoke(channel, "setHeader", e.getAttributeValue("header"));
        f.setLogger(channel, e);
        f.setConfiguration(channel, e);

        if (channel instanceof FilteredChannel) {
            addFilters((FilteredChannel) channel, e, f);
        }

        String socketFactoryString = getSocketFactory();
        if (socketFactoryString != null && channel instanceof FactoryChannel) {
            ISOClientSocketFactory sFac = (ISOClientSocketFactory) getFactory().newInstance(socketFactoryString);
            if (sFac != null && sFac instanceof LogSource) {
                ((LogSource) sFac).setLogger(log.getLogger(), getName() + ".socket-factory");
            }
            getFactory().setConfiguration(sFac, e);
            ((FactoryChannel) channel).setSocketFactory(sFac);
        }

        return channel;
    }

    private void addFilters(FilteredChannel channel, Element e, QFactory fact) throws ConfigurationException {
        for (Object o : e.getChildren("filter")) {
            Element f = (Element) o;
            String clazz = f.getAttributeValue("class");
            ISOFilter filter = (ISOFilter) fact.newInstance(clazz);
            fact.setLogger(filter, f);
            fact.setConfiguration(filter, f);
            String direction = f.getAttributeValue("direction");
            if (direction == null) {
                channel.addFilter(filter);
            } else if ("incoming".equalsIgnoreCase(direction)) {
                channel.addIncomingFilter(filter);
            } else if ("outgoing".equalsIgnoreCase(direction)) {
                channel.addOutgoingFilter(filter);
            } else if ("both".equalsIgnoreCase(direction)) {
                channel.addIncomingFilter(filter);
                channel.addOutgoingFilter(filter);
            }
        }
    }

    public String getInQueue() {
        return in;
    }

    public synchronized void setInQueue(String in) {
        String old = this.in;
        this.in = in;
        if (old != null) {
            sp.out(old, new Object());
        }

        getPersist().getChild("in").setText(in);
        setModified(true);
    }

    public String getOutQueue() {
        return out;
    }

    public synchronized void setOutQueue(String out) {
        this.out = out;
        getPersist().getChild("out").setText(out);
        setModified(true);
    }

    public String getHost() {
        return getProperty(getProperties("channel"), "host");
    }

    public synchronized void setHost(String host) {
        setProperty(getProperties("channel"), "host", host);
        setModified(true);
    }

    public int getPort() {
        int port = 0;
        try {
            port = Integer.parseInt(getProperty(getProperties("channel"), "port"));
        } catch (NumberFormatException e) {
            getLog().error(e);
        }
        return port;
    }

    public synchronized void setPort(int port) {
        setProperty(getProperties("channel"), "port", Integer.toString(port));
        setModified(true);
    }

    public String getSocketFactory() {
        return getProperty(getProperties("channel"), "socketFactory");
    }

    public synchronized void setSocketFactory(String sFac) {
        setProperty(getProperties("channel"), "socketFactory", sFac);
        setModified(true);
    }

    public class Worker implements Runnable {
        ISOMsg req;
        int id;

        public Worker(ISOMsg req, int id) {
            this.req = req;
            this.id = id;
        }

        public void run() {
            Thread.currentThread().setName("channel-worker-" + id);
            ISOChannel channel = null;

            try {
                channel = newChannel(channelElement, getFactory());
                if (getName() != null) {
                    channel.setName(getName() + id);
                }

                ISOMsg handBack = null;
                if (handbackFields.length > 0) {
                    handBack = (ISOMsg) req.clone(handbackFields);
                }
                try {
                    channel.connect();
                } catch (Throwable e) {
                    takeOffline();
                }
                if (channel.isConnected()) {
                    takeOnline();
                    channel.send(req);
                    ISOMsg rsp = channel.receive();
                    channel.disconnect();
                    if (handBack != null) {
                        rsp.merge(handBack);
                    }
                    sp.out(out, rsp);
                }
            } catch (Exception e) {
                getLog().warn("channel-worker-" + id, e.getMessage());
            } finally {
                try {
                    if (channel != null) {
                        channel.disconnect();
                    }
                } catch (Exception e) {
                    getLog().warn("channel-worker-" + id, e.getMessage());
                } finally {
                    NameRegistrar.unregister("channel." + getName() + id);
                }
            }
        }
    }
}