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

org.jsmpp.session.SMPPOutboundSession Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
/*
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package org.jsmpp.session;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.jsmpp.DefaultPDUReader;
import org.jsmpp.DefaultPDUSender;
import org.jsmpp.InvalidCommandLengthException;
import org.jsmpp.InvalidResponseException;
import org.jsmpp.PDUException;
import org.jsmpp.PDUReader;
import org.jsmpp.PDUSender;
import org.jsmpp.PDUStringException;
import org.jsmpp.SMPPConstant;
import org.jsmpp.SynchronizedPDUSender;
import org.jsmpp.bean.Bind;
import org.jsmpp.bean.BindType;
import org.jsmpp.bean.Command;
import org.jsmpp.bean.DataCoding;
import org.jsmpp.bean.DataSm;
import org.jsmpp.bean.ESMClass;
import org.jsmpp.bean.InterfaceVersion;
import org.jsmpp.bean.NumberingPlanIndicator;
import org.jsmpp.bean.OptionalParameter;
import org.jsmpp.bean.RegisteredDelivery;
import org.jsmpp.bean.TypeOfNumber;
import org.jsmpp.extra.NegativeResponseException;
import org.jsmpp.extra.PendingResponse;
import org.jsmpp.extra.ProcessRequestException;
import org.jsmpp.extra.QueueMaxException;
import org.jsmpp.extra.ResponseTimeoutException;
import org.jsmpp.extra.SessionState;
import org.jsmpp.session.connection.Connection;
import org.jsmpp.session.connection.ConnectionFactory;
import org.jsmpp.session.connection.socket.SocketConnectionFactory;
import org.jsmpp.util.DefaultComposer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * This is an object that used to communicate with an ESME. It hide
 * all un-needed SMPP operation that might harm if the user code use it such as:
 *
 * 
    *
  • DELIVER_SM_RESP, should be called only as response to DELIVER_SM
  • *
  • UNBIND_RESP, should be called only as response to UNBIND_RESP
  • *
  • DATA_SM_RESP, should be called only as response to DATA_SM
  • *
  • ENQUIRE_LINK_RESP, should be called only as response to ENQUIRE_LINK
  • *
  • GENERIC_NACK, should be called only as response to GENERIC_NACK
  • *
* * All SMPP operations (request-response) are blocking, for an example: DELIVER_SM * will be blocked until DELIVER_SM_RESP received or timeout. This looks like * synchronous communication, but the {@link SMPPOutboundSession} implementation give * ability to the asynchronous way by executing the DELIVER_SM operation parallel * on a different thread. The very simple implementation by using Thread pool, * {@link ExecutorService} will do. * * @author pmoerenhout */ public class SMPPOutboundSession extends AbstractSession implements OutboundClientSession { private static final Logger logger = LoggerFactory.getLogger(SMPPOutboundSession.class); /* Utility */ private final PDUReader pduReader; /* Connection */ private final ConnectionFactory connFactory; private final OutboundResponseHandler responseHandler = new ResponseHandlerImpl(); private Connection conn; private DataInputStream in; private OutputStream out; private PDUReaderWorker pduReaderWorker; private MessageReceiverListener messageReceiverListener; private BoundSessionStateListener sessionStateListener = new BoundSessionStateListener(); private SMPPOutboundSessionContext sessionContext = new SMPPOutboundSessionContext(this, sessionStateListener); private BindRequestReceiver bindRequestReceiver = new BindRequestReceiver(responseHandler); /** * Default constructor of {@link SMPPOutboundSession}. The next action might be * connect and bind to a destination message center. * * @see #connectAndOutbind(String, int, String, String) */ public SMPPOutboundSession() { this(new SynchronizedPDUSender(new DefaultPDUSender(new DefaultComposer())), new DefaultPDUReader(), SocketConnectionFactory.getInstance()); } public SMPPOutboundSession(PDUSender pduSender, PDUReader pduReader, ConnectionFactory connFactory) { super(pduSender); this.pduReader = pduReader; this.connFactory = connFactory; } public SMPPOutboundSession(String host, int port, OutbindParameter outbindParam, PDUSender pduSender, PDUReader pduReader, ConnectionFactory connFactory) throws IOException { this(pduSender, pduReader, connFactory); connectAndOutbind(host, port, outbindParam); } @Override public BindRequest connectAndOutbind(String host, int port, OutbindParameter outbindParam) throws IOException { return connectAndOutbind(host, port, outbindParam, 60000); } /** * Open connection and outbind immediately. The default timeout is 60 seconds. * * @param host is the ESME host address. * @param port is the ESME listen port. * @param systemId is the system id. * @param password is the password. * @return the received bind request * @throws IOException if there is an IO error found. */ @Override public BindRequest connectAndOutbind(String host, int port, String systemId, String password) throws IOException { return connectAndOutbind(host, port, new OutbindParameter(systemId, password), 60000); } /** * Open connection and outbind immediately with specified timeout. The default timeout is 60 seconds. * * @param host is the ESME host address. * @param port is the ESME listen port. * @param systemId is the system id. * @param password is the password. * @param timeout is the timeout. * @return the received bind request * @throws IOException if there is an IO error found. */ @Override public BindRequest connectAndOutbind(String host, int port, String systemId, String password, long timeout) throws IOException { return connectAndOutbind(host, port, new OutbindParameter(systemId, password), timeout); } /** * Open connection and outbind immediately. * * @param host is the ESME host address. * @param port is the ESME listen port. * @param outbindParameter is the bind parameters. * @param timeout is the timeout. * @return the SMSC system id. * @throws IOException if there is an IO error found. */ public BindRequest connectAndOutbind(String host, int port, OutbindParameter outbindParameter, long timeout) throws IOException { logger.debug("Connect and bind to {} port {}", host, port); if (getSessionState() != SessionState.CLOSED) { throw new IOException("Session state is not closed"); } conn = connFactory.createConnection(host, port); logger.info("Connected to {}", conn.getInetAddress()); conn.setSoTimeout(getEnquireLinkTimer()); sessionContext.open(); try { in = new DataInputStream(conn.getInputStream()); out = conn.getOutputStream(); pduReaderWorker = new PDUReaderWorker(getPduProcessorDegree(), getQueueCapacity()); pduReaderWorker.start(); sendOutbind(outbindParameter.getSystemId(), outbindParameter.getPassword()); } catch (IOException e) { logger.error("IO error occurred", e); close(); throw e; } try { BindRequest bindRequest = waitForBind(timeout); enquireLinkSender = new EnquireLinkSender(); enquireLinkSender.start(); return bindRequest; } catch (IllegalStateException e) { String message = "System error"; logger.error(message, e); close(); throw new IOException(message + ": " + e.getMessage(), e); } catch (TimeoutException e) { String message = "Wait for bind response timed out"; logger.error(message, e); throw new IOException(message + ": " + e.getMessage(), e); } } /** * Wait for bind request. * * @param timeout is the timeout in milliseconds. * @return the {@link BindRequest}. * @throws IllegalStateException if this invocation of this method has been made or invoke when state is not OPEN. * @throws TimeoutException if the timeout has been reach and {@link SMPPServerSession} are no more valid because * the connection will be close automatically. */ private BindRequest waitForBind(long timeout) throws IllegalStateException, TimeoutException { SessionState currentSessionState = getSessionState(); if (currentSessionState.equals(SessionState.OPEN)) { try { return bindRequestReceiver.waitForRequest(timeout); } catch (IllegalStateException e) { throw new IllegalStateException( "Invocation of waitForBind() has been made", e); } catch (TimeoutException e) { close(); throw e; } } else { throw new IllegalStateException( "waitForBind() should be invoked on OPEN state, actual state is " + currentSessionState); } } @Override public void deliverShortMessage(String serviceType, TypeOfNumber sourceAddrTon, NumberingPlanIndicator sourceAddrNpi, String sourceAddr, TypeOfNumber destAddrTon, NumberingPlanIndicator destAddrNpi, String destinationAddr, ESMClass esmClass, byte protocoId, byte priorityFlag, RegisteredDelivery registeredDelivery, DataCoding dataCoding, byte[] shortMessage, OptionalParameter... optionalParameters) throws PDUException, ResponseTimeoutException, InvalidResponseException, NegativeResponseException, IOException { ensureReceivable("deliverShortMessage"); DeliverSmCommandTask task = new DeliverSmCommandTask(pduSender(), serviceType, sourceAddrTon, sourceAddrNpi, sourceAddr, destAddrTon, destAddrNpi, destinationAddr, esmClass, protocoId, protocoId, registeredDelivery, dataCoding, shortMessage, optionalParameters); executeSendCommand(task, getTransactionTimer()); } @Override public MessageReceiverListener getMessageReceiverListener() { return messageReceiverListener; } @Override public void setMessageReceiverListener( MessageReceiverListener messageReceiverListener) { this.messageReceiverListener = messageReceiverListener; } @Override protected Connection connection() { return conn; } @Override protected AbstractSessionContext sessionContext() { return sessionContext; } @Override protected GenericMessageReceiverListener messageReceiverListener() { return messageReceiverListener; } @Override protected void finalize() throws Throwable { close(); super.finalize(); } private class ResponseHandlerImpl implements OutboundResponseHandler { public void sendBindResp(String systemId, InterfaceVersion interfaceVersion, BindType bindType, int sequenceNumber) throws IOException { sessionContext.bound(bindType); try { pduSender().sendBindResp(out, bindType.responseCommandId(), sequenceNumber, systemId, interfaceVersion); } catch (PDUStringException e) { logger.error("Failed sending bind response", e); // TODO uudashr: we have double checking when accept the bind request } } public DataSmResult processDataSm(DataSm dataSm) throws ProcessRequestException { try { return fireAcceptDataSm(dataSm); } catch (ProcessRequestException e) { throw e; } catch (Exception e) { String msg = "Invalid runtime exception thrown when processing data_sm"; logger.error(msg, e); throw new ProcessRequestException(msg, SMPPConstant.STAT_ESME_RX_T_APPN); } } @Override public void sendDataSmResp(DataSmResult dataSmResult, int sequenceNumber) throws IOException { try { pduSender().sendDataSmResp(out, sequenceNumber, dataSmResult.getMessageId(), dataSmResult.getOptionalParameters()); } catch (PDUStringException e) { /* * There should be no PDUStringException thrown since creation * of MessageId should be save. */ logger.error("Failed sending data_sm_resp", e); } } @Override public PendingResponse removeSentItem(int sequenceNumber) { return removePendingResponse(sequenceNumber); } @Override public void notifyUnbonded() { sessionContext.unbound(); } @Override public void sendDeliverSmResp(int commandStatus, int sequenceNumber, String messageId) throws IOException { pduSender().sendDeliverSmResp(out, commandStatus, sequenceNumber, messageId); logger.debug("deliver_sm_resp with sequence_number {} has been sent", sequenceNumber); } @Override public void sendEnquireLinkResp(int sequenceNumber) throws IOException { logger.debug("Sending enquire_link_resp"); pduSender().sendEnquireLinkResp(out, sequenceNumber); } @Override public void sendGenerickNack(int commandStatus, int sequenceNumber) throws IOException { pduSender().sendGenericNack(out, commandStatus, sequenceNumber); } @Override public void sendNegativeResponse(int originalCommandId, int commandStatus, int sequenceNumber) throws IOException { pduSender().sendHeader(out, originalCommandId | SMPPConstant.MASK_CID_RESP, commandStatus, sequenceNumber); } @Override public void sendUnbindResp(int sequenceNumber) throws IOException { pduSender().sendUnbindResp(out, SMPPConstant.STAT_ESME_ROK, sequenceNumber); } @Override public void processBind(Bind bind) { SMPPOutboundSession.this.bindRequestReceiver.notifyAcceptBind(bind); } } /** * Worker to read the PDU. * * @author uudashr */ private class PDUReaderWorker extends Thread { // start with serial execution of pdu processing, when the session is bound the pool will be enlarge up to the PduProcessorDegree private ThreadPoolExecutor pduExecutor = null; private Runnable onIOExceptionTask = new Runnable() { @Override public void run() { close(); } }; private PDUReaderWorker(final int pduProcessorDegree, final int queueCapacity) { super("PDUReaderWorker-" + getSessionId()); pduExecutor = new ThreadPoolExecutor(pduProcessorDegree, pduProcessorDegree, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(queueCapacity), new RejectedExecutionHandler() { @Override public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) { throw new QueueMaxException("Queue capacity " + queueCapacity + " exceeded"); } }); } @Override public void run() { logger.info("Starting PDUReaderWorker"); while (isReadPdu()) { readPDU(); } close(); pduExecutor.shutdown(); try { pduExecutor.awaitTermination(getTransactionTimer(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { logger.warn("Interrupted while waiting for PDU executor pool to finish"); Thread.currentThread().interrupt(); } logger.debug("{} stopped", getName()); } private void readPDU() { Command pduHeader = null; try { pduHeader = pduReader.readPDUHeader(in); byte[] pdu = pduReader.readPDU(in, pduHeader); /* * When the processing PDU is need user interaction via event, * the code on event might take non-short time, so we need to * process it concurrently. */ PDUProcessOutboundTask task = new PDUProcessOutboundTask(pduHeader, pdu, sessionContext, responseHandler, sessionContext, onIOExceptionTask); pduExecutor.execute(task); } catch (QueueMaxException e) { logger.info("Notify other side to throttle: {} ({} threads active)", e.getMessage(), pduExecutor.getActiveCount()); try { responseHandler.sendNegativeResponse(pduHeader.getCommandId(), SMPPConstant.STAT_ESME_RTHROTTLED, pduHeader.getSequenceNumber()); } catch (IOException ioe) { logger.warn("Failed sending negative response: {}", ioe.getMessage()); close(); } } catch (InvalidCommandLengthException e) { logger.warn("Received invalid command length: {}", e.getMessage()); try { pduSender().sendGenericNack(out, SMPPConstant.STAT_ESME_RINVCMDLEN, 0); } catch (IOException ee) { logger.warn("Failed sending generic nack", ee); } unbindAndClose(); } catch (SocketTimeoutException e) { notifyNoActivity(); } catch (IOException e) { logger.info("Reading PDU session {} in state {}: {}", getSessionId(), getSessionState(), e.getMessage()); close(); } catch (RuntimeException e) { logger.warn("Runtime error while reading", e); close(); } } /** * Notify for no activity. */ private void notifyNoActivity() { logger.debug("No activity notified, sending enquireLink"); if (sessionContext().getSessionState().isBound()) { enquireLinkSender.enquireLink(); } } } /** * Session state listener for internal class use. * * @author uudashr */ private class BoundSessionStateListener implements SessionStateListener { @Override public void onStateChange(SessionState newState, SessionState oldState, Session source) { /** * We need to set SO_TIMEOUT to sessionTimer so when timeout occur, * a SocketTimeoutException will be raised. When Exception raised we * can send an enquireLinkCommand. */ if (newState.isBound()) { try { conn.setSoTimeout(getEnquireLinkTimer()); } catch (IOException e) { logger.error("Failed setting so_timeout for session timer", e); } logger.info("Changing processor degree to {}", getPduProcessorDegree()); pduReaderWorker.pduExecutor.setMaximumPoolSize(getPduProcessorDegree()); pduReaderWorker.pduExecutor.setCorePoolSize(getPduProcessorDegree()); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy