All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ie.omk.smpp.net.SmscLink Maven / Gradle / Ivy

The newest version!
package ie.omk.smpp.net;

import ie.omk.smpp.message.SMPPPacket;
import ie.omk.smpp.util.APIConfig;
import ie.omk.smpp.util.PropertyNotFoundException;
import ie.omk.smpp.util.SMPPIO;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

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

/**
 * Abstract super class of all classes that implement a network link to the
 * SMSC. This class uses buffered input and output internally for reading and
 * writing to whatever input/output streams the concrete implementation provides
 * it. Sending and receiving are guarded against multiple-thread access. That
 * is, if more than one thread attempts to write packets to the link, they will
 * not get "mixed" in the output stream. Likewise on read, only one thread will
 * receive an incoming packet.
 * 
 * @author Oran Kelly
 * @version $Id: SmscLink.java 374 2007-08-27 18:14:30Z orank $
 */
public abstract class SmscLink {
    private static final String TIMEOUT_UNSUPPORTED_ERR = "Timeout not supported";

    private static final String END_OF_STREAM_ERR = "EOS reached. No data available";

    private static final String LINK_NOT_UP_ERR = "Link not established.";

    private static final Log LOGGER = LogFactory.getLog(SmscLink.class);

    /** The buffered input of the link. */
    private BufferedInputStream in;

    /** The buffered output of the link. */
    private BufferedOutputStream out;

    /** Object to use to lock reading. */
    private final Object readLock = new Object();

    /** Object to use to lock writing. */
    private final Object writeLock = new Object();

    /** Incoming bytes snoop stream. */
    private OutputStream snoopIn;

    /** Outgoing bytes snoop stream. */
    private OutputStream snoopOut;

    /**
     * Set to automatically flush the output stream after every packet. Default
     * is true.
     */
    private boolean autoFlush;

    /**
     * Create a new unconnected SmscLink.
     */
    public SmscLink() {
        try {
            autoFlush = APIConfig.getInstance().getBoolean(
                    APIConfig.LINK_AUTO_FLUSH);
        } catch (PropertyNotFoundException x) {
            autoFlush = true;
        } finally {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("autoFlush set to " + autoFlush);
            }
        }
    }

    /**
     * Open the connection to the SMSC. Calling this method will cause the
     * network link to the SMSC to be established. Once this method returns an
     * application may bind to the SMSC to begin it's SMPP session.
     * 
     * @throws java.io.IOException
     *             If an exception occurs while opening the connection.
     */
    public final void open() throws java.io.IOException {
        implOpen();

        int inSize = -1;
        int outSize = -1;
        APIConfig cfg = APIConfig.getInstance();

        inSize = getBufferSize(cfg, APIConfig.LINK_BUFFERSIZE_IN);
        outSize = getBufferSize(cfg, APIConfig.LINK_BUFFERSIZE_OUT);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("IN buffer size: " + inSize);
            LOGGER.debug("OUT buffer size: " + outSize);
        }

        if (inSize < 1) {
            this.in = new BufferedInputStream(getInputStream());
        } else {
            this.in = new BufferedInputStream(getInputStream(), inSize);
        }

        if (outSize < 1) {
            this.out = new BufferedOutputStream(getOutputStream());
        } else {
            this.out = new BufferedOutputStream(getOutputStream(), outSize);
        }
    }

    private int getBufferSize(APIConfig cfg, String propName) {
        int size = -1;

        try {
            String s = cfg.getProperty(propName);
            if (s.toLowerCase().endsWith("k")) {
                size = Integer.parseInt(s.substring(0, s.length() - 1)) * 1024;
            } else if (s.toLowerCase().endsWith("m")) {
                size = Integer.parseInt(s.substring(0, s.length() - 1)) * 1048576;
            } else {
                size = Integer.parseInt(s, 10);
            }
        } catch (PropertyNotFoundException x) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Buffer size is not set in configuration: " + propName
                        + ", using default.");
            }
        } catch (NumberFormatException x) {
            LOGGER.warn("Bad value for config property " + propName, x);
        }

        return size;
    }

    /**
     * Implementation-specific link open. This method will be called by the
     * {@link #open} method. This method is responsible for establishing the
     * underlying network connection to the remote SMSC system. For example, The
     * TCP/IP implementation would create and connect a new
     * java.io.Socket to the SMSC host.
     * 
     * @throws java.io.IOException
     *             If an exception occurs while opening the connection.
     */
    protected abstract void implOpen() throws java.io.IOException;

    /**
     * Close the connection to the SMSC. Calling this method will close the
     * network link to the remote SMSC system. Applications should be unbound
     * from the SMPP link (using {@link ie.omk.smpp.Connection#unbind}) before
     * closing the underlying network link. The connection may be reestablished
     * using {@link #open}.
     * 
     * @throws java.io.IOException
     *             If an exception occurs while closing the connection.
     */
    public final void close() throws java.io.IOException {
        out = null;
        in = null;

        implClose();

        boolean autoClose = true;
        try {
            autoClose = APIConfig.getInstance().getBoolean(
                    APIConfig.LINK_AUTOCLOSE_SNOOP);
        } catch (PropertyNotFoundException x) {
            LOGGER.debug(APIConfig.LINK_AUTOCLOSE_SNOOP
                    + " property not found. Using the default of " + autoClose);
        }

        if (autoClose) {
            try {
                if (snoopOut != null) {
                    snoopOut.close();
                }
                if (snoopIn != null) {
                    snoopIn.close();
                }
            } catch (IOException x) {
                LOGGER.warn("Exception while closing snoop streams.", x);
            }
        } else {
            try {
                if (snoopOut != null) {
                    snoopOut.flush();
                }
                if (snoopIn != null) {
                    snoopIn.flush();
                }
            } catch (IOException x) {
                LOGGER.warn("Exception while flushing snoop streams.", x);
            }
        }
    }

    /**
     * Implementation-specific link close. This method is called by the
     * {@link #close}method after ensuring no further writes or reads can
     * occur. Note that any threads that are writing, reading or blocked on
     * either the readLock or writeLock at the moment this method is called will
     * still execute. Only further reads or writes will be disallowed. An
     * implementation should completely close the underlying network link to the
     * remote SMSC system but it should not free any resources that will
     * preclude the {@link #open}method from reconnecting.
     * 
     * @throws java.io.IOException
     *             if an exception occurs during close.
     * @see #getInputStream
     * @see #getOutputStream
     * @see #close
     */
    protected abstract void implClose() throws java.io.IOException;

    /**
     * Send a packet to the SMSC.
     * 
     * @param pak
     *            the SMPP packet to send.
     * @param withOptional
     *            true to send the optional parameters over the link too, false
     *            to only send the mandatory parameters.
     * @throws java.io.IOException
     *             if an exception occurs during writing or if the connection is
     *             not open.
     */
    public void write(SMPPPacket pak, boolean withOptional)
            throws java.io.IOException {
        if (out == null) {
            throw new IOException(LINK_NOT_UP_ERR);
        }

        synchronized (writeLock) {
            try {
                if (snoopOut != null) {
                    pak.writeTo(snoopOut, withOptional);
                }
            } catch (IOException x) {
                LOGGER.warn("IOException writing to snoop output stream.", x);
            }

            pak.writeTo(out, withOptional);
            if (autoFlush) {
                out.flush();
            }
        }
    }

    /**
     * Flush the output stream of the SMSC link.
     * 
     * @throws java.io.IOException
     *             If an exception occurs while flushing the output stream.
     */
    public void flush() throws java.io.IOException {
        if (out != null) {
            out.flush();
        }
    }

    /**
     * Get the auto flush behaviour of this link. The default behaviour is
     * defined in the smppapi properties file. If no properties are found at
     * runtime, the default behaviour is set to true.
     * 
     * @see #setAutoFlush
     * @see ie.omk.smpp.util.APIConfig
     */
    public boolean getAutoFlush() {
        return autoFlush;
    }

    /**
     * Set the auto flush behaviour of this link. If set to true, the link will
     * flush the output stream after every packet written. In high-load
     * environments this may be undesirable.
     * 
     * @see #getAutoFlush
     */
    public void setAutoFlush(boolean flush) {
        this.autoFlush = flush;
    }

    /**
     * Read the next SMPP packet from the SMSC. This method will block until a
     * full packet can be read from the SMSC. The caller should pass in a byte
     * array to read the packet into. If the passed in byte array is too small,
     * a new one will be allocated and returned to the caller.
     * 
     * @param array
     *            a byte array buffer to read the packet into.
     * @return the handle to the passed in buffer or the reallocated one.
     * @throws java.io.EOFException
     *             If the end of stream is reached before a full packet can be
     *             read.
     * @throws java.io.IOException
     *             If an exception occurs when reading the packet from the input
     *             stream.
     */
    public byte[] read(final byte[] array) throws IOException {
        if (in == null) {
            throw new IOException(LINK_NOT_UP_ERR);
        }

        byte[] buf = array;
        int count = 0;
        synchronized (readLock) {
            try {
                count = readBytes(buf, 0, 4, 16);
                int cmdLen = SMPPIO.bytesToInt(buf, 0, 4);
                if (cmdLen > buf.length) {
                    byte[] newbuf = new byte[cmdLen];
                    System.arraycopy(buf, 0, newbuf, 0, count);
                    buf = newbuf;
                }
                int remaining = cmdLen - count;
                readBytes(buf, count, remaining, remaining);
            } finally {
                dump(snoopIn, array, 0, count);
            }
        }
        return buf;
    }

    /**
     * Get the number of bytes currently available on the input stream.
     */
    public final int available() {
        try {
            synchronized (readLock) {
                return (in != null) ? in.available() : 0;
            }
        } catch (IOException x) {
            LOGGER.debug("IOException in available", x);
            return 0;
        }
    }

    /**
     * Attempt to read the bytes for an SMPP packet from the inbound stream.
     * @param buf The buffer to read bytes in to.
     * @param offset The offset into buffer to begin writing bytes from.
     * @param maxLen The maximum number of bytes to read in.
     * @param minimum The minimum number of bytes to read before returning. Once
     * this method has read at least this number of bytes, it will return.
     * @return The number of bytes read by this method.
     * @throws IOException
     */
    private int readBytes(byte[] buf, int offset, int minimum, int maxLen) throws IOException {
        assert Thread.holdsLock(readLock);
        int ptr = in.read(buf, offset, maxLen);
        if (ptr < minimum) {
            if (ptr == -1) {
                throw new EOFException(END_OF_STREAM_ERR);
            }
            while (ptr < minimum) {
                int count = in.read(buf, offset + ptr, maxLen - ptr);
                if (count < 0) {
                    throw new EOFException(END_OF_STREAM_ERR);
                }
                ptr += count;
           }
        }
        return ptr;
    }
    
    /**
     * Dump bytes to an output stream.
     * 
     * @param s
     *            the stream to write to (if null, do nothing).
     * @param b
     *            the byte array to dump bytes from.
     * @param offset
     *            the offset in b to begin from.
     * @param len
     *            the number of bytes to dump.
     */
    private void dump(OutputStream s, byte[] b, int offset, int len) {
        try {
            if (s != null) {
                s.write(b, offset, len);
            }
        } catch (IOException x) {
            LOGGER.warn("Couldn't write incoming bytes to input snooper.", x);
        }
    }

    /**
     * Get the output stream of the virtual circuit.
     * 
     * @throws java.io.IOException
     *             If the output stream cannot be retrieved or the connection is
     *             not open.
     */
    protected abstract OutputStream getOutputStream()
            throws java.io.IOException;

    /**
     * Get the input stream of the virtual circuit.
     * 
     * @throws java.io.IOException
     *             If the input stream cannot be retrieved or the connection is
     *             not open.
     */
    protected abstract InputStream getInputStream() throws java.io.IOException;

    /**
     * Check whether or not the connection to the SMSC is open.
     */
    public abstract boolean isConnected();

    /**
     * Set the value for read timeout. A link implementation may support timing
     * out on blocking read operations. This method may be used to set such a
     * timeout. If the implementation does not support timeouts, it must throw
     * an UnsuppertedOperationException.
     * @param timeout the timeout value in milliseconds.
     * @throws UnsupportedOperationException if the implementation does not support
     * timeouts.
     * @deprecated Use setTimeout(int)
     */
    public void setTimeout(long timeout) {
        throw new UnsupportedOperationException(TIMEOUT_UNSUPPORTED_ERR);
    }

    /**
     * Set the value for read timeout. A link implementation may support timing
     * out on blocking read operations. This method may be used to set such a
     * timeout. If the implementation does not support timeouts, it must throw
     * an UnsuppertedOperationException.
     * @param timeout the timeout value in milliseconds.
     * @throws UnsupportedOperationException if the implementation does not support
     * timeouts.
     */
    public void setTimeout(int timeout) {
        throw new UnsupportedOperationException(TIMEOUT_UNSUPPORTED_ERR);
    }

    /**
     * Get the value for read timeout.
     * 
     * @see #setTimeout
     * @return the current value for read timeout.
     * @throws UnsupportedOperationException
     *             if the implementation does not support timeouts.
     */
    public int getTimeout() {
        throw new UnsupportedOperationException(TIMEOUT_UNSUPPORTED_ERR);
    }

    /**
     * Set the snooper streams. The snooper streams will receive every byte that
     * is either received or sent using this class. This functionality is
     * intended as a debugging aid for SMPP developers. It will be up to the
     * application using the API to provide valid output streams for the data to
     * be written to. Either or both of the streams may be set to null, which in
     * effect turns off snooping.
     * 
     * @param snoopIn
     *            stream to receive incoming bytes from the SMSC (may be null).
     * @param snoopOut
     *            stream to receive outgoing bytes to the SMSC (may be null).
     */
    public void setSnoopStreams(OutputStream snoopIn, OutputStream snoopOut) {
        this.snoopIn = snoopIn;
        this.snoopOut = snoopOut;
    }
}