org.smpp.Session Maven / Gradle / Ivy
/*
* Copyright (c) 1996-2001
* Logica Mobile Networks Limited
* All rights reserved.
*
* This software is distributed under Logica Open Source License Version 1.0
* ("Licence Agreement"). You shall use it and distribute only in accordance
* with the terms of the License Agreement.
*
*/
package org.smpp;
import java.io.IOException;
import java.util.Hashtable;
import org.smpp.pdu.*;
import org.smpp.util.*;
/**
* Class Session
provides all methods necessary for communication
* with SMSC using SMPP protocol, i.e. methods for sending PDUs as defined
* in SMPP specification as well as receiving responses for sent PDUs
* and waiting for PDUs whose sending was initiated by SMSC.
* Instance of Session
represents one connection of ESME
* to a SMSC. Multiple connections can be established using
* multiple Sessions
.
*
* Session
uses Connection
object which is
* instantiated outside of the Session
. This way is
* the Session
made independent on the communication protocol
* and Session
's code isn't populated by protocol dependent
* initialisation.
*
* Code example of binding, sending one message and unbinding:
*
* Connection conn = new TCPIPConnection("123.123.123.123", 6543);
* Session session = new Session(conn);
* BindRequest breq = new BindTransmitter();
* breq.setSystemId("MYNAME");
* breq.setPassword("my_pswdx");
* Response resp = session.bind(breq);
* if (resp.getCommandStatus() == Data.ESME_ROK) {
* SubmitSM msg = new SubmitSM();
* msg.setSourceAddr("3538998765432");
* msg.setDestAddr("3538619283746");
* msg.setShortMessage("Hello, world!");
* resp = session.submit(msg);
* if (resp.getCommandStatus() == Data.ESME_ROK) {
* System.out.println("Message submitted. Status=" + resp.getCommandStatus());
* } else {
* System.out.println("Message submission failed. Status=" + resp.getCommandStatus());
* }
* session.unbind();
* } else {
* System.out.println("Couldn't bind. Status=" + resp.getCommandStatus());
* }
*
* Note that the cycle bind - send PDU's - unbind can be called
* several times for once created session.
*
* Particular methods for sending PDUs to SMSC return responses to the sent
* PDUs. They return null if no response is received in time specified
* by the receive timeout in receiver. This means that the methods wait
* for response corresponding to the request.
* The corresponding response is recognized using sequence number of the sent
* PDU and a corresponding response command id.
* The session can work in assynchronous manner, i.e. it doesn't wait
* for response for the sent request, instead all responses are handled
* by instance of callback class ServerPDUEventListener
* whenever they are received.
* The Session
class checks if operations invoked are valid in
* the current state of the session. If not, then such operation throws
* WrongSessionStateException
expcetion. For example it's incorrect
* to try to submit a message if the session is bound as receiver. The checking
* if the operation is valid in the current state of session is turned off
* by default.
*
* @author Logica Mobile Networks SMPP Open Source Team
* @version $Revision: 1.4 $
* @see Connection
* @see Transmitter
* @see Receiver
* @see ServerPDUEventListener
*/
public class Session extends SmppObject {
/**
* Status of the connection. It's set by open
method,
* which is called from bind
method.
* @see #open()
* @see #bind(BindRequest)
*/
private boolean opened = false;
/**
* Status of bounding. It's set by bind
methdod if
* the binding is successfull.
* @see #bind(BindRequest)
*/
private boolean bound = false;
/** Special state for actions which are never allowed for the session. */
public static final int STATE_NOT_ALLOWED = 0x00; // 00000b
/** The connection is closed (or not opened yet) and session isn't bound. */
public static final int STATE_CLOSED = 0x01; // 00001b
/** The connection is opened, but the session isn't bound. */
public static final int STATE_OPENED = 0x02; // 00010b
/** The session is bound as transmitter. */
public static final int STATE_TRANSMITTER = 0x04; // 00100b
/** The session is bound as receiver. */
public static final int STATE_RECEIVER = 0x08; // 01000b
/** The session is bound as transceiver. */
public static final int STATE_TRANSCEIVER = 0x10; // 10000b
/** Special state for actions which are always allowed (e.g. generic_nack.) */
public static final int STATE_ALWAYS = // 11111b
STATE_OPENED | STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER;
/**
* Table with command id's and session states in which they can be used.
* For session on ESME side.
* @see #initialiseStateMatrix()
*/
private static Hashtable esmeStateMatrix;
/**
* Table with command id's and session states in which they can be used.
* For session on MC side. Note that even if this is from the MC side
* the bind states are still entered from the EMSE point of veiw.
* E.g. for deliver_sm this table contains states receiver and transceiver,
* which means that MC can send PDU with deliver_sm command id only in case
* the ESME is bound as receiver or transceiver.
* Note: The MC state matrix is not completly supported by
* the current implementation of session, so it's currently altered
* to reflect the impplementation.
* @see #initialiseStateMatrix()
*/
private static Hashtable mcStateMatrix;
/**
* If PDU with unknown command id is probed this says if this PDU is
* allowed or not. Generaly it's not good idea to allow unknown PDUs, but
* for backward compatiblity we leave it set to false.
*/
private boolean disallowUnknownPDU = false;
/**
* The current state of this Session.
* @see #esmeStateMatrix
* @see #mcStateMatrix
* @see #setState(int)
* @see #getState()
*/
private int state = STATE_CLOSED;
/**
* If the checking of states is active. It's generaly good idea to leave it
* set to true, it'll save you communication bandwidth (MC should reject your
* PDU if you are in wrong state) and it'll discover bad logic in your application.
* For backward compatibility we set that the state is NOT checked.
* @see #enableStateChecking()
* @see #disableStateChecking()
*/
private boolean stateChecking = false;
/**
* Indicates that the session is session for ESME 'point of view.'
* @see #type
*/
public static final int TYPE_ESME = 1;
/**
* Indicates that the session is session for MC 'point of view.'
* MC is Message Center and it is a term for generic Message Centre such as SMSC.
* @see #type
*/
public static final int TYPE_MC = 2;
/**
* If this session is ESME session or if it is used in MC (or simulator
* or routing entity, simply the other side of normal ESME).
* Normally this would be equal to TYPE_ESME as mostly this library will be used
* for developing ESME applications and not MCs. If you don't understand
* what is this variable for, just leave it as it is.
* @see #setType(int)
* @see #getType()
*/
private int type = TYPE_ESME;
/**
* The connection object. It's created outside of the Session
* class and passed as a parameter during the Session
* creation. It's then passed to Transmitter
and
* Receiver
.
* @see Connection
* @see TCPIPConnection
* @see Transmitter
* @see Receiver
*/
private Connection connection;
/**
* Object used for transmitting of PDUs over connection.
* @see Transmitter
*/
private Transmitter transmitter;
/**
* Object used for receiving of PDU from connection.
* @see Receiver
*/
private Receiver receiver;
/**
* If the receiving is asynchronous, pduListener
must
* contain the callback object used for processing of PDUs received
* from the SMSC. Receiver
after receiving a PDU passes
* the received PDU to apropriate member function of the processor.
* @see #asynchronous
* @see #setServerPDUEventListener(ServerPDUEventListener)
* @see #bind(BindRequest)
* @see Receiver
*/
private ServerPDUEventListener pduListener = null;
/**
* Indicates that the sending of PDUs to the SMSC is asynchronous, i.e.
* the session doesn't wait for a response to the sent request as well as
* the receive
functions will return null as all received
* PDUs are passed to the pduListener
object in
* the receiver
.
* @see #pduListener
* @see #setServerPDUEventListener(ServerPDUEventListener)
* @see #bind(BindRequest)
* @see Receiver
*/
private boolean asynchronous = false;
/**
* Default constructor made protected as it's not desirable to
* allow creation of Session
without providing
* Connection
.
*/
protected Session() {
}
/**
* Creates Session
which uses provided Connection
.
* In most cases the connection
parameter will be an instance
* of TCPIPConnection
class.
*
* @param connection connection used for transmitting and receiving
* the data
*/
public Session(Connection connection) {
this.connection = connection;
}
/**
* Opens the connection for communication.
* Sets indication that the connection is opened.
*
* @exception IOException exception during communication
*/
public void open() throws IOException, WrongSessionStateException {
checkState(STATE_CLOSED);
if (!opened) {
connection.open();
opened = true;
setState(STATE_OPENED);
}
}
/**
* Closes the connection for communication.
* Sets indication that the connection is not opened.
*
* @exception IOException exception during communication
*/
public void close() throws IOException, WrongSessionStateException {
checkState(STATE_OPENED);
if (connection.isOpened()) {
connection.close();
opened = false;
setState(STATE_CLOSED);
}
}
/**
* Returns of the connection is opened.
* @return current status of connection
*/
public boolean isOpened() {
return opened && connection.isOpened();
}
/**
* Returns if the session is bound to SMSC.
* @return current status of bound
*/
public boolean isBound() {
return bound;
}
/**
* Sets the listener which is passed all PDUs received by the
* Receiver
from the SMSC.
* Note that this method implicitly sets asynchronous type of
* receiving.
* @param pduListener the listener which processes the received PDUs
*/
private void setServerPDUEventListener(ServerPDUEventListener pduListener) {
this.pduListener = pduListener;
receiver.setServerPDUEventListener(pduListener);
asynchronous = pduListener != null;
}
/**
* Returns the current ServerPDUEventListener
set for
* this session.
*/
private ServerPDUEventListener getServerPDUEventListener() {
return pduListener;
}
/**
* Sets the type of the session. The type can be either ESME or MC viewed
* from the side where the instance of the Session
is used.
* Set the type of the session before opening the session.
* @param type the new type of the session
* @see #getType()
* @see #TYPE_ESME
* @see #TYPE_MC
*/
public void setType(int type) {
this.type = type;
}
/**
* Returns the type of the session.
* @return the current type of the session.
* @see #setType(int)
* @see #TYPE_ESME
* @see #TYPE_MC
*/
public int getType() {
return type;
}
/**
* Opens connection and binds to SMSC using the bind method provided
* as bindReq parameter. Binds synchronously as no server pdu listener
* is provided. On details about bind see
* bind(BindRequest,ServerPDUEventListener).
* @see #bind(BindRequest,ServerPDUEventListener)
*/
final public BindResponse bind(BindRequest bindReq)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(bindReq);
return bind(bindReq, null);
}
/**
* Opens connection and binds to SMSC using the bind method provided
* as bindReq
parameter. For transmittong PDUs creates instance
* of Transmitter
class, for receiving PDUs from SMSC
* creates instance of Receiver
class.
* Receiver starts an extra thread
* which receives PDUs from connection and puts them into a queue
* (if synchronous) or processes them using pduListener
.
* If the bind to SMSC isn't successfull, the thread is stopped and
* the connection is closed.
* If the variable pduListener
is not null
,
* the session is asynchronous, otherwise the session is synchronous.
* Note that the bind
method is always synchronous,
* regardless of the value of the pduListener
variable.
*
* See "SMPP Protocol Specification 3.4, 4.1 BIND Operation."
*
* @param bindReq the bind request
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see BindRequest
* @see BindResponse
* @see BindTransmitter
* @see BindReceiver
* @see BindTransciever
*/
final public BindResponse bind(BindRequest bindReq, ServerPDUEventListener pduListener)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(bindReq);
if (bound) {
// exception? yes, checked above
return null;
} else {
open();
transmitter = new Transmitter(connection);
receiver = new Receiver(transmitter, connection);
BindResponse bindResp = (BindResponse) send(bindReq, false);
bound = ((bindResp != null) && (bindResp.getCommandStatus() == Data.ESME_ROK));
if (!bound) {
//receiver.stop();
close();
} else {
receiver.start();
if (bindReq.isTransmitter()) {
if (bindReq.isReceiver()) {
setState(STATE_TRANSCEIVER);
} else {
setState(STATE_TRANSMITTER);
}
} else {
setState(STATE_RECEIVER);
}
setServerPDUEventListener(pduListener);
}
return bindResp;
}
}
/**
* Sends an outbind PDU over the connection. For sending outbind
* PDU the session must not be bound to smsc (!), this is the
* logic of outbind operation. Don't mess it up with receiving
* outbind PDUs from SMSC! Also this operation is only allowed when your
* session acts as SMSC (MC) session. ESMEs can't send outbinds.
*
* See SMPP Protocol Specification 3.4, 4.1.7 OUTBIND Operation.
*
* @param request The outbind PDU.
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* (this exception doesn't apply for outbind)
* @see Outbind
* @see OutbindReceiver
*/
final public void outbind(Outbind request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
send(request);
}
/**
* Stops receiver
(if applicable), unbinds from SMSC
* by sending Unbind
PDU, waits for UnbindResp
* and then closes the connection.
* Note that the unbind
method is always synchronous
* or asynchronous depending on the value of the asynchronous
* variable.
*
* See "SMPP Protocol Specification 3.4, 4.2 UNBIND Operation."
*
* @return response PDU if successfully unbound, i.e. if still not bound
* after call to the method; null if unbind was not successful, i.e.
* no response has been received for unbind PDU
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* (this exception doesn't apply for unbind)
* @see Unbind
* @see UnbindResp
*/
final public UnbindResp unbind()
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
UnbindResp unbindResp = null;
if (bound) {
Unbind unbindReq = new Unbind();
checkState(unbindReq);
ServerPDUEventListener origListener = null;
if (asynchronous) {
// we must assign the number now as we'll start waiting for
// the response before we actually send the request
unbindReq.assignSequenceNumber();
origListener = getServerPDUEventListener();
UnbindServerPDUEventListener unbindListener =
new UnbindServerPDUEventListener(this, origListener, unbindReq);
setServerPDUEventListener(unbindListener);
synchronized (unbindListener) {
send(unbindReq);
try {
unbindListener.wait(receiver.getReceiveTimeout());
unbindResp = unbindListener.getUnbindResp();
} catch (InterruptedException e) {
// unbind reponse wasn't received in time
}
}
} else {
debug.write(DSESS, "going to unbound sync session");
unbindResp = (UnbindResp) send(unbindReq);
}
bound = (unbindResp == null);
if (!bound) {
setState(STATE_OPENED);
receiver.stop();
receiver = null;
transmitter = null;
close();
} else {
// restore the listener - unbind unsuccessfull
debug.write("Unbind unsuccessfull, restoring listener");
setServerPDUEventListener(origListener);
}
}
return unbindResp;
}
/**
* Sends GenericNack PDU provided as parameter.
*
* See "SMPP Protocol Specification 3.4, 4.3 GENERIC_NACK Operation."
*
* @param response The generic nack PDU.
*
* @exception IOException exception during communication
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* (this exception doesn't apply for genericNack)
* @see GenericNack
*/
final public void genericNack(GenericNack response)
throws ValueNotSetException, TimeoutException, IOException, WrongSessionStateException {
checkState(response);
try {
respond(response);
} catch (WrongSessionStateException e) {
debug.write("strange, generic nack thrown " + e);
debug.write("this shouldn't happend");
event.write(e, "Unexpected exeption caught");
}
}
/**
* Creates and sends GenericNack
PDU with command status
* and sequence number provided as parameters.
*
* See "SMPP Protocol Specification 3.4, 4.3 GENERIC_NACK Operation."
*
* @param commandStatus the code of error to be reported
* @param sequenceNumber the sequence number of the PDU the GenericNack
* is related to
* @exception IOException exception during communication
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* (this exception doesn't apply for genericNack)
* @see GenericNack
*/
final public void genericNack(int commandStatus, int sequenceNumber)
throws ValueNotSetException, TimeoutException, IOException, WrongSessionStateException {
GenericNack gnack = new GenericNack(commandStatus, sequenceNumber);
checkState(gnack);
genericNack(gnack);
}
/**
* Submits provided SubmitSM
PDU to SMSC and returns response
* to the submission.
*
* See "SMPP Protocol Specification 3.4, 4.4 SUBMIT_SM Operation."
*
* @param request the pdu to be submitted
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see SubmitSM
* @see SubmitSMResp
*/
final public SubmitSMResp submit(SubmitSM request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
return (SubmitSMResp) send(request);
}
/**
* Submits provided SubmitMultiSM
PDU to SMSC and returns
* response to the submission.
*
* See "SMPP Protocol Specification 3.4, 4.5 SUBMIT_MULTI Operation."
*
* @param request the submit multi pdu to be submitted
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see SubmitMultiSM
* @see SubmitMultiSMResp
*/
final public SubmitMultiSMResp submitMulti(SubmitMultiSM request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
return (SubmitMultiSMResp) send(request);
}
/**
* Submits provided DeliverSM
PDU to SMSC and returns
* response to the submission.
*
* See "SMPP Protocol Specification 3.4, 4.6 DELIVER_SM Operation."
*
* @param request the deliver pdu to be submitted
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see DeliverSM
* @see DeliverSMResp
*/
final public DeliverSMResp deliver(DeliverSM request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
return (DeliverSMResp) send(request);
}
/**
* Submits provided DataSM
PDU to SMSC and returns
* response to the submission.
*
* See "SMPP Protocol Specification 3.4, 4.7 DATA_SM Operation."
*
* @param request the data pdu to be submitted
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see DataSM
* @see DataSMResp
*/
final public DataSMResp data(DataSM request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
return (DataSMResp) send(request);
}
/**
* Queries status of previous submission by sending the
* QuerySM
PDU to SMSC; returns the query response.
*
* See "SMPP Protocol Specification 3.4, 4.8 QUERY_SM Operation."
*
* @param request the status query pdu to be sent
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see QuerySM
* @see QuerySMResp
*/
final public QuerySMResp query(QuerySM request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
return (QuerySMResp) send(request);
}
/**
* Cancels previously submitted message by sending CancelSM
PDU
* to SMSC; returns response to the cancel PDU.
*
* See "SMPP Protocol Specification 3.4, 4.9 CANCEL_SM Operation."
*
* @param request the cancel pdu to be sent
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see CancelSM
* @see CancelSMResp
*/
final public CancelSMResp cancel(CancelSM request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
return (CancelSMResp) send(request);
}
/**
* Replaces previously submitted message by sending ReplaceSM
* PDU to SMSC and returns response to the replace.
*
* See "SMPP Protocol Specification 3.4, 4.10 REPLACE_SM Operation."
*
* @param request the replace pdu to be sent
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see ReplaceSM
* @see ReplaceSMResp
*/
final public ReplaceSMResp replace(ReplaceSM request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
return (ReplaceSMResp) send(request);
}
/**
* Checks the status of connection between ESME and SMSC by sending
* EnquireLink
PDU to SMSC; returns response
* to the enquiry.
*
* See "SMPP Protocol Specification 3.4, 4.11 ENQUIRE_LINK Operation."
*
* @param request the enquiry pdu to be submitted
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see EnquireLink
* @see EnquireLinkResp
*/
final public EnquireLinkResp enquireLink(EnquireLink request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
return (EnquireLinkResp) send(request);
}
/**
* Simplified veriosn of enquireLink.
* As the EnquireLink
PDU doesn't contain any parameters
* axcept of header, there is might be no need to provide the PDU as
* a parameter to the enquireLink
method.
* This method creates new EnquireLink
object
* and sends it to SMSC; returns response to the enquiry.
*
* See "SMPP Protocol Specification 3.4, 4.11 ENQUIRE_LINK Operation."
*
* @return correspondent response pdu
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see EnquireLink
* @see EnquireLinkResp
*/
final public EnquireLinkResp enquireLink()
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
EnquireLink request = new EnquireLink();
checkState(request);
return enquireLink(request);
}
/**
* Submits provided SubmitSM
PDU to SMSC and returns response to the submission.
*
* See "SMPP Protocol Specification 3.4, 4.12 ALERT_NOTIFICATION Operation."
*
* @param request the pdu to be submitted
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see AlertNotification
*/
final public void alertNotification(AlertNotification request)
throws ValueNotSetException, TimeoutException, PDUException, IOException, WrongSessionStateException {
checkState(request);
send(request);
}
/**
* Returns a PDU received from SMSC. This is blocking receive, caller
* will wait until a PDU will be received.
* Note that this method can be called only when bound as receiver
* or transciever.
*
* @return received pdu
*
* @exception IOException exception during communication
* @exception NotSynchronousException receive called in asynchronous mode
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception UnknownCommandIdException PDU with unknown id was received
* @see Receiver
* @see ReceiverBase
*/
final public PDU receive()
throws UnknownCommandIdException, TimeoutException, NotSynchronousException, PDUException, IOException {
if (!asynchronous) {
return receive(Data.RECEIVE_BLOCKING);
} else {
throw new NotSynchronousException(this);
}
}
/**
* Returns a PDU received from SMSC. This receive will wait for
* maximum timeout
time for a PDU; if there is
* no PDU received in the specified time, the function returns null.
* Note that this method can be called only when bound as receiver
* or transciever.
*
* @return received pdu or null if none received
*
* @exception IOException exception during communication
* @exception NotSynchronousException receive called in asynchronous mode
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception UnknownCommandIdException PDU with unknown id was received
* @see Receiver
* @see ReceiverBase
*/
final public PDU receive(long timeout)
throws UnknownCommandIdException, TimeoutException, NotSynchronousException, PDUException, IOException {
PDU pdu = null;
if (receiver.isReceiver()) {
if (!asynchronous) {
pdu = receiver.receive(timeout);
} else {
throw new NotSynchronousException(this);
}
} else {
// throw?
}
return pdu;
}
/**
* Sends a response PDU. Use for sending responses for PDUs send
* from SMSC, e.g. DELIVERY_SM etc.
*
* @param response the response to be sent
*
* @exception IOException exception during communication
* @exception ValueNotSetException optional param not set but requested
* @see Transmitter
*/
final public void respond(Response response) throws ValueNotSetException, IOException, WrongSessionStateException {
checkState(response);
debug.enter(DSESS, this, "respond(Response)");
debug.write(DSESS, "Sending response " + response.debugString());
try {
transmitter.send(response);
} catch (ValueNotSetException e) {
event.write(e, "Sending a response.");
debug.exit(DSESS, this);
throw e;
}
debug.exit(DSESS, this);
}
/**
* Returns Transmitter
object created for transmitting
* PDUs to SMSC.
*
* @return the Transmitter
object
* @see Transmitter
*/
public Transmitter getTransmitter() {
return transmitter;
}
/**
* Returns Receiver
object created for receiving
* PDUs from SMSC.
*
* @return the Receiver
object
* @see Receiver
*/
public Receiver getReceiver() {
return receiver;
}
/**
* Returns Connection
object provided for
* communication with SMSC.
*
* @return the Connection
object
* @see Connection
*/
public Connection getConnection() {
return connection;
}
/**
* The basic method for sending a Request
PDU to SMSC
* and receiving correspondent Response
, if applicable, and
* returns the received PDU.
* In case there is a PDU received with unknown command id or with invalid
* length, GenericNack
PDU is sent back to SMSC automatically
* to report the error and null is returned.
* If the timeout for receiving PDU set in the Receiver
* object expires, null is returned.
*
* @param request the request PDU (not xxx_RESP) to be sent
* @return the corresponding response or null in case of problems
*
* @exception IOException exception during communication
* @exception PDUException incorrect format of PDU
* @exception TimeoutException rest of data not received for too long time
* @exception ValueNotSetException optional param not set but requested
* @see Request
* @see Response
* @see GenericNack
* @see Transmitter
* @see Receiver
*/
final private Response send(Request request, boolean asynchronous)
throws ValueNotSetException, TimeoutException, PDUException, IOException {
debug.enter(DSESS, this, "send(Request)");
Response response = null;
debug.write(DSESS, "Sending request " + request.debugString());
try {
transmitter.send(request);
} catch (ValueNotSetException e) {
event.write(e, "Sending the request.");
debug.exit(DSESS, this);
throw e;
}
if ((!asynchronous) && (request.canResponse())) {
PDU pdu = null;
Response expResponse = null;
expResponse = request.getResponse();
try {
debug.write(DSESS, "Going to receive response. Expecting " + expResponse.debugString());
try {
pdu = receiver.receive(expResponse);
} catch (NotSynchronousException e) {
debug.write("Unexpected NotSynchronousException caught, ignoring :-)");
}
} catch (UnknownCommandIdException e) {
safeGenericNack(Data.ESME_RINVCMDID, e.getSequenceNumber());
} catch (InvalidPDUException e) {
if ((e.getException() instanceof NotEnoughDataInByteBufferException)
|| (e.getException() instanceof TerminatingZeroNotFoundException)) {
debug.write(DSESS, "wrong length " + e);
debug.write(DSESS, " => sending gnack.");
safeGenericNack(Data.ESME_RINVMSGLEN, e.getPDU().getSequenceNumber());
} else {
debug.write(DSESS, "InvalidPDUException - rethrowing " + e);
debug.exit(DSESS, this);
throw e;
}
} catch (TimeoutException e) {
debug.write(DSESS, "TimeoutException - rethrowing " + e);
debug.exit(DSESS, this);
throw e;
}
if (pdu != null) {
debug.write(DSESS, "Got response(?) pdu " + pdu.debugString());
response = checkResponse(pdu, expResponse);
} else {
debug.write(DSESS, "No response received.");
}
}
debug.exit(DSESS, this);
return response;
}
/**
* Calls send(Request,boolean)
with the current value of
* asynchronous
flag.
* @see #send(Request,boolean)
*/
final private Response send(Request request)
throws ValueNotSetException, TimeoutException, PDUException, IOException {
return send(request, asynchronous);
}
/**
* Checks if the pdu
received is
* matching the expResponse
and returns corrected response.
* If the command id's don't match then if the pdu
's
* command id is generic_nack, then the expected response is set
* the command id of generic_nack and command id, command status
* and sequence number of the received pdu
and this
* way transformed response is returned: the class mathces, but
* the command id says, that the PDU is generic nack.
* If the command id is not generic nack, then we received
* incorrect pdu as the sequence numbers match, so we send generic nack
* back to the smsc.
* if the command id's match, then the pdu
is returned.
* @param pdu the received PDU which should be a response
* @param expResponse the response we expected from smsc
* @return either the received PDU or expected response transformed to
* generic nack.
* @see #send(Request,boolean)
*/
private Response checkResponse(PDU pdu, Response expResponse)
throws ValueNotSetException, TimeoutException, IOException {
Response response = null;
debug.write(DSESS, "checking response if it's what we expected.");
if (pdu.getCommandId() != expResponse.getCommandId()) {
debug.write(DSESS, "Got different response than expected " + expResponse.debugString());
if (pdu.getCommandId() == Data.GENERIC_NACK) {
// it's brutal, but it's necessary
// we transform the response object to carry generic nack
debug.write(DSESS, "Got generic nack. What could we do wrong?");
expResponse.setCommandId(Data.GENERIC_NACK);
expResponse.setCommandLength(pdu.getCommandLength());
expResponse.setCommandStatus(pdu.getCommandStatus());
expResponse.setSequenceNumber(pdu.getSequenceNumber());
response = expResponse;
} else {
debug.write(DSESS, "invalid command id - sending gnack");
safeGenericNack(Data.ESME_RINVCMDID, pdu.getSequenceNumber());
response = null;
}
} else {
// if the commandId is same as of the expected response's command id,
// then the pdu must be Response as well
response = (Response) pdu;
}
return response;
}
/**
* Sends a generic acknowledge with given comand status and sequence number
* and catches andy SmppException
.
* @param commandStatus command status to report
* @param sequenceNumber to allow the other party to match the generic ack with
* their sent request
* @exception IOException if there is a comms error
*/
private void safeGenericNack(int commandStatus, int sequenceNumber) throws IOException {
try {
genericNack(commandStatus, sequenceNumber);
} catch (SmppException e) {
debug.write("Ignoring unexpected SmppException caught sending generic nack.");
event.write(e, "Ignoring unexpected exception caught sending generic nack.");
}
}
/**
* Sets the state of the session.
* It's private as it's only for use by the methods of the session.
* @param state the new state of the session
* @see #checkState(int)
* @see #checkState(PDU)
* @see WrongSessionStateException
*/
private void setState(int state) {
this.state = state;
}
/**
* Returns the current state of the session.
* It's public to allow the user to check the state of the session if necessary.
* @return the current state of the session
* @see #checkState(int)
* @see #checkState(PDU)
* @see WrongSessionStateException
*/
public int getState() {
return state;
}
/**
* Enables checking if the session allows certain operation in the current state.
*/
public void enableStateChecking() {
this.stateChecking = true;
}
/**
* Disables checking if the session allows certain operation in the current state.
*/
public void disableStateChecking() {
this.stateChecking = false;
}
/**
* Checks if the session is in the state which is required by the parameter.
* If not, then exception WrongSessionStateException
is thrown.
* Note that the states are bit values in integer, so there can be a "set"
* of states required, the session must be in one of the states required.
* The checking can be turned off, see
* disableStateChecking.
* @param requestedState the state(s) in which the session is expected to be; if it
* is not, then exception is thrown
* @throws WrongSessionStateException if the session is not in the state
* required
* @see WrongSessionStateException
*/
public void checkState(int requestedState) throws WrongSessionStateException {
if (stateChecking) {
debug.write(
DSESS,
"checking state current=0x"
+ Integer.toHexString(state)
+ " requested esme=0x"
+ Integer.toHexString(requestedState));
if ((state & requestedState) == 0) {
throw new WrongSessionStateException(type, requestedState, state);
}
}
}
/**
* Checks if the session's state allows sending the PDU provided.
* If not, then exception WrongSessionStateException
is thrown.
* For each state there is only a subset of PDUs which can be sent over the
* session. For example, if the session is bound as a receiver, it cannot
* submit messages, but it can send enquire link, generic nack etc.
* This method checks the PDU type (command id) aganst matrix of allowed states.
* The checking can be turned off, see
* disableStateChecking.
* @param pdu the pdu which has to be checked if it's allowed in the current
* state
* @throws WrongSessionStateException if the session is not in the state
* required
* @see #checkState(int)
* @see WrongSessionStateException
*/
public void checkState(PDU pdu) throws WrongSessionStateException {
if (stateChecking) {
Hashtable pduMatrix = getStateMatrix(type);
Integer commandIdInteger = new Integer(pdu.getCommandId());
Integer requestedStateInteger = pduMatrix == null ? null : pduMatrix.get(commandIdInteger);
if (requestedStateInteger != null) {
checkState(requestedStateInteger.intValue());
} else {
if (disallowUnknownPDU) {
throw new WrongSessionStateException();
}
}
}
}
/**
* Checks if the session is in the state which is required by the parameter.
* Note that this method doesn't throw an exception rather it returns false
* if the session is not in one of the provided states.
* @param requestedState the state(s) which have to be checked
* @return if the session is in on of the provided states
* @see #checkState(int)
*/
public boolean isStateAllowed(int requestedState) {
boolean stateAllowed = true;
try {
checkState(requestedState);
} catch (WrongSessionStateException e) {
stateAllowed = false;
}
return stateAllowed;
}
/**
* Checks if the pdu provided is allowed in the current session state.
* Note that this method doesn't throw an exception rather it returns false
* if the pdu is not alloewd in the current session state.
* @param pdu the pdu which has to be checked if it's allowed in the current
* state
* @return if the pdu is allowed to be sent in the current session state
* @see #checkState(PDU)
*/
public boolean isPDUAllowed(PDU pdu) {
boolean pduAllowed = true;
try {
checkState(pdu);
} catch (WrongSessionStateException e) {
pduAllowed = false;
}
return pduAllowed;
}
/**
* Initialises state matrices for checking if PDU is allowed in a certain session
* state.
*/
static {
initialiseStateMatrix();
}
/**
* Initialise list containing which operations (PDUs) valid in which state.
* @see #checkState(PDU)
* @see #type
*/
private static void initialiseStateMatrix() {
esmeStateMatrix = new Hashtable();
addValidState(esmeStateMatrix, Data.BIND_TRANSMITTER, STATE_CLOSED);
addValidState(esmeStateMatrix, Data.BIND_TRANSMITTER_RESP, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.BIND_RECEIVER, STATE_CLOSED);
addValidState(esmeStateMatrix, Data.BIND_RECEIVER_RESP, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.BIND_TRANSCEIVER, STATE_CLOSED);
addValidState(esmeStateMatrix, Data.BIND_TRANSCEIVER_RESP, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.OUTBIND, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.UNBIND, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.UNBIND_RESP, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.SUBMIT_SM, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.SUBMIT_SM_RESP, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.SUBMIT_MULTI, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.SUBMIT_MULTI_RESP, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.DATA_SM, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.DATA_SM_RESP, STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.DELIVER_SM, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.DELIVER_SM_RESP, STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.QUERY_SM, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.QUERY_SM_RESP, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.CANCEL_SM, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.CANCEL_SM_RESP, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.REPLACE_SM, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(esmeStateMatrix, Data.REPLACE_SM_RESP, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.ENQUIRE_LINK, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_ALWAYS);
addValidState(esmeStateMatrix, Data.ENQUIRE_LINK_RESP, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_ALWAYS);
addValidState(esmeStateMatrix, Data.ALERT_NOTIFICATION, STATE_NOT_ALLOWED);
addValidState(esmeStateMatrix, Data.GENERIC_NACK, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_ALWAYS);
mcStateMatrix = new Hashtable();
addValidState(mcStateMatrix, Data.BIND_TRANSMITTER, STATE_NOT_ALLOWED);
addValidState(
mcStateMatrix,
Data.BIND_TRANSMITTER_RESP,
STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_OPENED);
addValidState(mcStateMatrix, Data.BIND_RECEIVER, STATE_NOT_ALLOWED);
addValidState(mcStateMatrix, Data.BIND_RECEIVER_RESP, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_OPENED);
addValidState(mcStateMatrix, Data.BIND_TRANSCEIVER, STATE_NOT_ALLOWED);
addValidState(
mcStateMatrix,
Data.BIND_TRANSCEIVER_RESP,
STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_OPENED);
addValidState(mcStateMatrix, Data.OUTBIND, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_OPENED);
addValidState(mcStateMatrix, Data.UNBIND, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.UNBIND_RESP, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.SUBMIT_SM, STATE_NOT_ALLOWED);
addValidState(mcStateMatrix, Data.SUBMIT_SM_RESP, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.SUBMIT_MULTI, STATE_NOT_ALLOWED);
addValidState(mcStateMatrix, Data.SUBMIT_MULTI_RESP, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.DATA_SM, STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.DATA_SM_RESP, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.DELIVER_SM, STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.DELIVER_SM_RESP, STATE_NOT_ALLOWED);
addValidState(mcStateMatrix, Data.QUERY_SM, STATE_NOT_ALLOWED);
addValidState(mcStateMatrix, Data.QUERY_SM_RESP, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.CANCEL_SM, STATE_NOT_ALLOWED);
addValidState(mcStateMatrix, Data.CANCEL_SM_RESP, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.REPLACE_SM, STATE_NOT_ALLOWED);
addValidState(mcStateMatrix, Data.REPLACE_SM_RESP, STATE_TRANSMITTER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.ENQUIRE_LINK, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_ALWAYS);
addValidState(mcStateMatrix, Data.ENQUIRE_LINK_RESP, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_ALWAYS);
addValidState(mcStateMatrix, Data.ALERT_NOTIFICATION, STATE_RECEIVER | STATE_TRANSCEIVER);
addValidState(mcStateMatrix, Data.GENERIC_NACK, STATE_TRANSMITTER | STATE_RECEIVER | STATE_TRANSCEIVER);
// STATE_ALWAYS);
}
/**
* Adds to the matrix a set of states in which can be sent a PDU with the
* provided command id.
* @param matrix the matrix to add the mapping to
* @param commandId the commandId of the PDU the mapping is created for
* @param state the state(s) in which is the PDU valid
* @see #checkState(PDU)
* @see #isStateAllowed(int)
*/
private static void addValidState(Hashtable matrix, int commandId, int state) {
matrix.put(new Integer(commandId), new Integer(state));
}
/**
* Returns the state matrix for the requested session type.
* @see #type
* @see #TYPE_ESME
* @see #TYPE_MC
* @see #initialiseStateMatrix()
*/
private static Hashtable getStateMatrix(int type) {
switch (type) {
case TYPE_ESME :
return esmeStateMatrix;
case TYPE_MC :
return mcStateMatrix;
default :
return null;
}
}
/**
* God, I would never think that to keep unbind synchronous in
* an asynchronous enviroment would be so funny. Here is the replacement
* listener which encapsulates the original listener and hunts for the
* unbind pdu. Good luck, you source code reader!
* The problem is that we want to return a response from session's unbind()
* even if the session is asynchronous, i.e. all pdus from the smsc
* are passed to an implementation of ServerPDUEventListener
* event responses. We can't simply stop the asynchronicity
* as there can still be some responses expected, so we need a bridge
* which allows us to wait for the unbind response and still serve
* the other pdus from the smsc in asynchronous manner. Thus this
* encapsulating listener, which exactly does the thing.
*/
private class UnbindServerPDUEventListener extends SmppObject implements ServerPDUEventListener {
Session session;
ServerPDUEventListener origListener;
Unbind unbindReq;
UnbindResp expectedResp;
UnbindResp unbindResp = null;
public UnbindServerPDUEventListener(Session session, ServerPDUEventListener origListener, Unbind unbindReq) {
this.session = session;
this.origListener = origListener;
this.unbindReq = unbindReq;
expectedResp = (UnbindResp) unbindReq.getResponse();
}
public void handleEvent(ServerPDUEvent event) {
PDU pdu = event.getPDU();
if (pdu.getSequenceNumber() == unbindReq.getSequenceNumber()) {
synchronized (this) {
try {
unbindResp = (UnbindResp) (session.checkResponse(pdu, expectedResp));
} catch (Exception e) {
debug.write(DSESS, "exception handling unbind " + e);
SmppObject.event.write(e, "exception handling unbind");
}
// notify as session waits for the notification
this.notify();
}
} else {
// all other pdus are processed by the original handler,
// if any
if (origListener != null) {
origListener.handleEvent(event);
}
}
}
public UnbindResp getUnbindResp() {
return unbindResp;
}
}
}
/*
* $Log: not supported by cvs2svn $
* Revision 1.3 2006/02/22 16:45:01 paoloc
* UnbindServerPDUEventListener: if (pdu.equals(unbindReq)): as reported by users, this is incompatible with the new improved PDU.equals(); fixed.
*
* Revision 1.2 2004/09/10 23:03:44 sverkera
* Added isOpened method
*
* Revision 1.1 2003/07/23 00:28:39 sverkera
* Imported
*
*
* Old changelog:
* 08-08-01 [email protected] added asynchronous processing capability
* 02-10-01 [email protected] tracing now belongs to DSESS group
* 20-11-01 [email protected] the receiver thread is now started even for
* the transmitter session - before transmitter
* sessions couldn't receive anything
* 20-11-01 [email protected] receiver thread is started after the bind request
* is sent to the MC, before it was started before the bind
* req was sent to MC, therefore the response could be processed
* by the listener
* 22-11-01 [email protected] implemented session states with checking if certain
* operation is allowed in the current session state
*/