org.springframework.integration.smpp.core.SmesMessageSpecification.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.smpp.core.SmesMessageSpecification.java

Source

/*
 * Copyright 2002-2013 the original author or authors.
 *
 * 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.springframework.integration.smpp.core;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jsmpp.bean.*;
import org.jsmpp.session.ClientSession;
import org.jsmpp.session.SMPPSession;
import org.jsmpp.util.AbsoluteTimeFormatter;
import org.jsmpp.util.TimeFormatter;
import org.springframework.integration.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.Date;

import static org.springframework.integration.smpp.core.SmppConstants.*;

/**
 * fluent API to help make specifying all these parameters just a <em>tiny</em> bit easier. For internal use only.
 *
 * @author Josh Long
 * @since 1.0
 */
public class SmesMessageSpecification {

    private static Log log = LogFactory.getLog(SmesMessageSpecification.class);
    private TimeFormatter timeFormatter = new AbsoluteTimeFormatter();

    private int maxLengthSmsMessages = 140;
    private String sourceAddress;
    private String destinationAddress;
    private String serviceType;
    private TypeOfNumber sourceAddressTypeOfNumber;
    private NumberingPlanIndicator sourceAddressNumberingPlanIndicator;
    private TypeOfNumber destinationAddressTypeOfNumber;
    private NumberingPlanIndicator destinationAddressNumberingPlanIndicator;
    private ESMClass esmClass;
    private byte protocolId;
    private byte priorityFlag;
    private String scheduleDeliveryTime = timeFormatter.format(new Date());
    private String validityPeriod;
    private RegisteredDelivery registeredDelivery;
    private byte replaceIfPresentFlag;
    private DataCoding dataCoding;
    private byte smDefaultMsgId;
    private byte[] shortMessage;
    private ClientSession smppSession;
    private OptionalParameter messagePayloadParameter;

    /**
     * this method takes an inbound SMS message and converts it to a Spring Integration message
     *
     * @param dsm            the {@link DeliverSm} from {@link AbstractReceivingMessageListener#onTextMessage(org.jsmpp.bean.DeliverSm, String)}
     * @param txtMessage the String from {@link AbstractReceivingMessageListener#onTextMessage(org.jsmpp.bean.DeliverSm, String)}
     * @return a Spring Integration message
     */
    public static Message<?> toMessageFromSms(DeliverSm dsm, String txtMessage) {

        Assert.isTrue(!dsm.isSmscDeliveryReceipt(), "the message should not be a delivery confirmation receipt!");

        MessageBuilder<String> mb = MessageBuilder.withPayload(txtMessage);
        mb.setHeader(SmppConstants.SMS, dsm);
        mb.setHeader(SmppConstants.REPLACE_IF_PRESENT, dsm.getReplaceIfPresent());
        mb.setHeader(SmppConstants.SHORT_MESSAGE, dsm.getShortMessage());
        mb.setHeader(SmppConstants.OPTIONAL_PARAMETERS, dsm.getOptionalParameters());
        mb.setHeader(SmppConstants.UDHI_AND_REPLY_PATH, dsm.isUdhiAndReplyPath());
        mb.setHeader(SmppConstants.VALIDITY_PERIOD, dsm.getValidityPeriod());
        mb.setHeader(SmppConstants.COMMAND_LENGTH, dsm.getCommandLength());
        mb.setHeader(SmppConstants.COMMAND_ID, dsm.getCommandId());
        mb.setHeader(SmppConstants.SME_ACK_NOT_REQUESTED, dsm.isSmeAckNotRequested());
        mb.setHeader(SmppConstants.DATA_CODING, dsm.getDataCoding());
        mb.setHeader(SmppConstants.REPLY_PATH, dsm.isReplyPath());
        mb.setHeader(SmppConstants.SOURCE_ADDR_TON, dsm.getSourceAddrTon());
        mb.setHeader(SmppConstants.SM_DEFAULT_MSG_ID, dsm.getSmDefaultMsgId());
        mb.setHeader(SmppConstants.UDHI, dsm.isUdhi());
        mb.setHeader(SmppConstants.SME_MANUAL_ACKNOWLEDGMENT, dsm.isSmeManualAcknowledgment());
        mb.setHeader(SmppConstants.CONVERSATION_ABORT, dsm.isConversationAbort());
        mb.setHeader(SmppConstants.DEST_ADDRESS, dsm.getDestAddress());
        mb.setHeader(SmppConstants.ESM_CLASS, dsm.getEsmClass());
        mb.setHeader(SmppConstants.COMMAND_ID_AS_HEX, dsm.getCommandIdAsHex());
        mb.setHeader(SmppConstants.SME_DELIVERY_AND_MANUAL_ACK_REQUESTED, dsm.isSmeDeliveryAndManualAckRequested());
        mb.setHeader(SmppConstants.SMSC_DELIVERY_RECEIPT, dsm.isSmscDeliveryReceipt());
        mb.setHeader(SmppConstants.SME_MANUAL_ACK_REQUESTED, dsm.isSmeManualAckRequested());
        mb.setHeader(SmppConstants.PRIORITY_FLAG, dsm.getPriorityFlag());
        mb.setHeader(SmppConstants.DEST_ADDR_TON, dsm.getDestAddrTon());
        mb.setHeader(SmppConstants.COMMAND_STATUS_AS_HEX, dsm.getCommandStatusAsHex());
        mb.setHeader(SmppConstants.SERVICE_TYPE, dsm.getServiceType());
        mb.setHeader(SmppConstants.INTERMEDIATE_DELIVERY_NOTIFICATION, dsm.isIntermedietDeliveryNotification());
        mb.setHeader(SmppConstants.SOURCE_ADDR_NPI, dsm.getSourceAddrNpi());
        mb.setHeader(SmppConstants.REGISTERED_DELIVERY, dsm.getRegisteredDelivery());
        mb.setHeader(SmppConstants.DEST_ADDR_NPI, dsm.getDestAddrNpi());
        mb.setHeader(SmppConstants.COMMAND_STATUS, dsm.getCommandStatus());
        mb.setHeader(SmppConstants.DEFAULT_MESSAGE_TYPE, dsm.isDefaultMessageType());
        mb.setHeader(SmppConstants.PROTOCOL_ID, dsm.getProtocolId());
        mb.setHeader(SmppConstants.SOURCE_ADDR, dsm.getSourceAddr());
        mb.setHeader(SmppConstants.SEQUENCE_NUMBER, dsm.getSequenceNumber());
        mb.setHeader(SmppConstants.SCHEDULE_DELIVERY_TIME, dsm.getScheduleDeliveryTime());
        mb.setHeader(SmppConstants.SME_DELIVERY_ACK_REQUESTED, dsm.isSmeDeliveryAckRequested());
        return mb.build();
    }

    /**
     * this method will take an inbound Spring Integration {@link Message} and map it to a {@link SmesMessageSpecification}
     * which we can use to send the SMS message.
     *
     * @param msg a new {@link Message}
     * @param smppSession the SMPPSession
     * @return a {@link SmesMessageSpecification}
     */
    public static SmesMessageSpecification fromMessage(ClientSession smppSession, Message<?> msg) {
        if (log.isDebugEnabled()) {
            log.debug("Message: " + msg);
        }
        String srcAddy = valueIfHeaderExists(SRC_ADDR, msg);
        String dstAddy = valueIfHeaderExists(DST_ADDR, msg);
        String smsTxt = valueIfHeaderExists(SMS_MSG, msg);
        if (!StringUtils.hasText(smsTxt)) {
            Object payload = msg.getPayload();
            if (payload instanceof String) {
                smsTxt = (String) payload;
            }
        }
        final DataCoding dataCodingFromHeader = SmesMessageSpecification.dataCodingFromHeader(msg);
        final SmesMessageSpecification spec = new SmesMessageSpecification().reset().setSmppSession(smppSession)
                .setSourceAddress(srcAddy).setDestinationAddress(dstAddy).setDataCoding(dataCodingFromHeader);
        spec.setMaxLengthSmsMessages(maximumCharactersFromHeader(msg));
        spec.setEsmClass(SmesMessageSpecification.esmClassFromHeader(msg));
        if (msg.getHeaders().containsKey(SmppConstants.USE_MSG_PAYLOAD_PARAM)) {
            spec.setShortMessageUsingPayload(smsTxt);
        } else {
            spec.setShortTextMessage(smsTxt);
        }
        spec.setDestinationAddressNumberingPlanIndicator(
                SmesMessageSpecification.<NumberingPlanIndicator>valueIfHeaderExists(DST_NPI, msg));
        spec.setSourceAddressNumberingPlanIndicator(
                SmesMessageSpecification.<NumberingPlanIndicator>valueIfHeaderExists(SRC_NPI, msg));
        spec.setDestinationAddressTypeOfNumber(
                SmesMessageSpecification.<TypeOfNumber>valueIfHeaderExists(DST_TON, msg));
        spec.setSourceAddressTypeOfNumber(SmesMessageSpecification.<TypeOfNumber>valueIfHeaderExists(SRC_TON, msg));
        spec.setServiceType(SmesMessageSpecification.<String>valueIfHeaderExists(SERVICE_TYPE, msg));
        spec.setScheduleDeliveryTime(
                SmesMessageSpecification.<Date>valueIfHeaderExists(SCHEDULED_DELIVERY_TIME, msg));
        spec.setValidityPeriod(SmesMessageSpecification.<String>valueIfHeaderExists(VALIDITY_PERIOD, msg));

        // byte landmine. autoboxing causes havoc with <em>null</em> bytes.
        Byte priorityFlag1 = SmesMessageSpecification.<Byte>valueIfHeaderExists(PRIORITY_FLAG, msg);
        if (priorityFlag1 != null) {
            spec.setPriorityFlag(priorityFlag1);
        }

        Byte smDefaultMsgId1 = SmesMessageSpecification.<Byte>valueIfHeaderExists(SM_DEFAULT_MSG_ID, msg);
        if (smDefaultMsgId1 != null) {
            spec.setSmDefaultMsgId(smDefaultMsgId1);
        }

        Byte replaceIfPresentFlag1 = SmesMessageSpecification.<Byte>valueIfHeaderExists(REPLACE_IF_PRESENT_FLAG,
                msg);
        if (replaceIfPresentFlag1 != null) {
            spec.setReplaceIfPresentFlag(replaceIfPresentFlag1);
        }

        Byte protocolId1 = SmesMessageSpecification.<Byte>valueIfHeaderExists(PROTOCOL_ID, msg);
        if (null != protocolId1) {
            spec.setProtocolId(protocolId1);
        }

        spec.setRegisteredDelivery(registeredDeliveryFromHeader(msg));

        return spec;
    }

    private static DataCoding dataCodingFromHeader(Message<?> msg) {
        Object dc = msg.getHeaders().get(DATA_CODING);
        if (dc instanceof DataCoding) {
            return (DataCoding) dc;
        }
        if (dc instanceof Byte) {
            return DataCodings.newInstance((Byte) dc);
        }

        return null;
    }

    /**
     * Getting maximum characters from header. This will allow checking maximum character based on
     * {@link SmppConstants#MAXIMUM_CHARACTERS} header or determine the maximum character based on data coding
     * header {@link SmppConstants#DATA_CODING}.
     * <p/>
     * The order of the selection is
     * <ol>
     *     <li>If {@link SmppConstants#MAXIMUM_CHARACTERS} is set, use it</li>
     *     <li>If {@link SmppConstants#DATA_CODING} is set, find maximum character for the data coding</li>
     *     <li>Using default maximum character which is 140</li>
     * </ol>
     * @param msg the Spring Integration message
     * @return maximum character can be sent through the session
     */
    private static int maximumCharactersFromHeader(Message<?> msg) {
        if (msg.getHeaders().containsKey(MAXIMUM_CHARACTERS)) {
            return msg.getHeaders().get(MAXIMUM_CHARACTERS, Integer.class);
        }
        if (msg.getHeaders().containsKey(DATA_CODING)) {
            final Object dc = msg.getHeaders().get(DATA_CODING);
            if (dc instanceof Byte) {
                return DataCodingSpecification.getMaxCharacters((Byte) dc);
            } else {
                return DataCodingSpecification.getMaxCharacters(((DataCoding) dc).toByte());
            }
        }
        return 140;
    }

    /**
     * need to be a little flexibile about what we take in as {@link SmppConstants#REGISTERED_DELIVERY_MODE}. The value can
     * be a String or a member of the {@link SMSCDeliveryReceipt} enum.
     *
     * @param msg the Spring Integration message
     * @return a value for {@link RegisteredDelivery} or null, which is good because it'll simply let the existing default work
     */
    private static RegisteredDelivery registeredDeliveryFromHeader(Message<?> msg) {
        Object rd = valueIfHeaderExists(REGISTERED_DELIVERY_MODE, msg);

        if (rd instanceof String) {
            String rdString = (String) rd;
            SMSCDeliveryReceipt smscDeliveryReceipt = SMSCDeliveryReceipt.valueOf(rdString);
            Assert.notNull(smscDeliveryReceipt, "the registeredDelivery can't be null");
            return new RegisteredDelivery(smscDeliveryReceipt);
        }

        if (rd instanceof SMSCDeliveryReceipt) {
            SMSCDeliveryReceipt smscDeliveryReceipt = (SMSCDeliveryReceipt) rd;
            return new RegisteredDelivery(smscDeliveryReceipt);
        }

        if (rd instanceof RegisteredDelivery) {
            return (RegisteredDelivery) rd;
        }
        return null;
    }

    /**
     * you need to use the builder API
     *
     * @param smppSession the SMPPSession instance against which we should work.
     * @see SmesMessageSpecification#SmesMessageSpecification()
     */
    SmesMessageSpecification(SMPPSession smppSession) {
        this.smppSession = smppSession;
    }

    /**
     * tries to safely extract the ESMClass
     * @param im message
     * @return esm class
     */
    static private ESMClass esmClassFromHeader(Message<?> im) {
        String h = ESM_CLASS;
        Object o = valueIfHeaderExists(h, im);
        ESMClass response = null;
        if (o instanceof Byte) {
            response = new ESMClass((Byte) o);

        } else if (o instanceof ESMClass) {
            response = (ESMClass) o;
        }
        return response;
    }

    @SuppressWarnings("unchecked")
    static private <T> T valueIfHeaderExists(String h, Message<?> msg) {
        if (msg != null && msg.getHeaders().containsKey(h)) {
            return (T) msg.getHeaders().get(h);
        }
        return null;
    }

    /**
     * Everybody else has to use the builder API. DO NOT make this private or it will not be proxied and that will make me sad!
     *
     * @param ss the {@link SMPPSession}
     * @return the current spec
     */
    SmesMessageSpecification setSmppSession(ClientSession ss) {
        this.smppSession = ss;
        return this;
    }

    /**
     * use the builder API, but we need this to cleanly proxy
     */
    SmesMessageSpecification() {
        this(null);
    }

    /**
     * Conceptually, you could get away with just specifying these three parameters, though I don't know how likely that is in practice.
     *
     * @param srcAddress   the source address
     * @param destAddress the destination address
     * @param txtMessage   the message to send (must be  no more than 140 characters
     * @param ss               the SMPPSession
     * @return the {@link SmesMessageSpecification}
     */
    public static SmesMessageSpecification newSmesMessageSpecification(ClientSession ss, String srcAddress,
            String destAddress, String txtMessage) {

        SmesMessageSpecification smesMessageSpecification = new SmesMessageSpecification();

        smesMessageSpecification.reset().setSmppSession(ss).setSourceAddress(srcAddress)
                .setDestinationAddress(destAddress).setShortTextMessage(txtMessage);

        return smesMessageSpecification;
    }

    /**
     * Only sets the #sourceAddressTypeOfNumber if the current value is null, otherwise, it leaves it.
     *
     * @param sourceAddressTypeOfNumberIfRequired
     *         the {@link TypeOfNumber}
     * @return this
     */
    public SmesMessageSpecification setSourceAddressTypeOfNumberIfRequired(
            TypeOfNumber sourceAddressTypeOfNumberIfRequired) {
        if (this.sourceAddressTypeOfNumber == null) {
            this.sourceAddressTypeOfNumber = sourceAddressTypeOfNumberIfRequired;
        }
        return this;
    }

    /**
     * send the message on its way.
     * <p/>
     * todo can we do something smart here or through an adapter to handle the situation where we have asked for a message receipt? what about if we're using a message receipt <em>and</eM> we're only a receiver or a sender connection and not a transceiver? We need gateway semantics across two unidirectional SMPPSessions, then
     *
     * @return the messageId (required if you want to then track it or correllate it with message receipt confirmations)
     * @throws Exception the {@link SMPPSession#submitShortMessage(String, org.jsmpp.bean.TypeOfNumber, org.jsmpp.bean.NumberingPlanIndicator, String, org.jsmpp.bean.TypeOfNumber, org.jsmpp.bean.NumberingPlanIndicator, String, org.jsmpp.bean.ESMClass, byte, byte, String, String, org.jsmpp.bean.RegisteredDelivery, byte, org.jsmpp.bean.DataCoding, byte, byte[], org.jsmpp.bean.OptionalParameter...)} method throws lots of Exceptions, including {@link java.io.IOException}
     */
    public String send() throws Exception {
        validate();
        final String msgId;
        if (messagePayloadParameter == null) {
            msgId = this.smppSession.submitShortMessage(this.serviceType, this.sourceAddressTypeOfNumber,
                    this.sourceAddressNumberingPlanIndicator, this.sourceAddress,

                    this.destinationAddressTypeOfNumber, this.destinationAddressNumberingPlanIndicator,
                    this.destinationAddress,

                    this.esmClass, this.protocolId, this.priorityFlag, this.scheduleDeliveryTime,
                    this.validityPeriod, this.registeredDelivery, this.replaceIfPresentFlag, this.dataCoding,
                    this.smDefaultMsgId, this.shortMessage);
        } else {
            // SPEC 3.2.3
            log.debug("Sending message using message_payload");
            msgId = this.smppSession.submitShortMessage(this.serviceType, this.sourceAddressTypeOfNumber,
                    this.sourceAddressNumberingPlanIndicator, this.sourceAddress,

                    this.destinationAddressTypeOfNumber, this.destinationAddressNumberingPlanIndicator,
                    this.destinationAddress,

                    this.esmClass, this.protocolId, this.priorityFlag, this.scheduleDeliveryTime,
                    this.validityPeriod, this.registeredDelivery, this.replaceIfPresentFlag, this.dataCoding,
                    this.smDefaultMsgId, new byte[0], this.messagePayloadParameter);
        }

        return msgId;
    }

    protected void validate() {
        Assert.notNull(this.sourceAddress, "the source address must not be null");
        Assert.notNull(this.destinationAddress, "the destination address must not be null");
        final boolean shortMessageSet = this.shortMessage != null && this.shortMessage.length > 0;
        Assert.isTrue(messagePayloadParameter != null ^ shortMessageSet,
                "message can only be set in payload or short message. cannot be both");
        if (messagePayloadParameter == null) {
            Assert.isTrue(shortMessageSet, "the message must not be null");
        }
    }

    public SmesMessageSpecification setSourceAddress(String sourceAddr) {
        if (!nullHeaderWillOverwriteDefault(sourceAddr)) {
            this.sourceAddress = sourceAddr;
        }
        return this;
    }

    /**
     * the 'to' phone number
     *
     * @param destinationAddr the phone number
     * @return the current spec
     */
    public SmesMessageSpecification setDestinationAddress(String destinationAddr) {
        this.destinationAddress = destinationAddr;
        return this;
    }

    public SmesMessageSpecification setServiceType(String serviceType) {
        if (!nullHeaderWillOverwriteDefault(serviceType)) {
            this.serviceType = serviceType;
        }
        return this;
    }

    public SmesMessageSpecification setSourceAddressTypeOfNumber(TypeOfNumber sourceAddrTon) {
        if (!nullHeaderWillOverwriteDefault(sourceAddrTon)) {
            this.sourceAddressTypeOfNumber = sourceAddrTon;
        }
        return this;
    }

    public SmesMessageSpecification setSourceAddressNumberingPlanIndicator(NumberingPlanIndicator sourceAddrNpi) {
        if (!nullHeaderWillOverwriteDefault(sourceAddrNpi)) {
            this.sourceAddressNumberingPlanIndicator = sourceAddrNpi;
        }
        return this;
    }

    public SmesMessageSpecification setDestinationAddressTypeOfNumber(TypeOfNumber destAddrTon) {
        if (!nullHeaderWillOverwriteDefault(destAddrTon)) {
            this.destinationAddressTypeOfNumber = destAddrTon;
        }
        return this;
    }

    /**
     * guard against overwriting perfectly good defaults with null values.
     *
     * @param v value the value
     * @return can the write proceed unabated?
     */
    private boolean nullHeaderWillOverwriteDefault(Object v) {
        if (v == null) {
            if (log.isDebugEnabled()) {
                log.debug("There is a default in place for this property; don't overwrite it with null");
            }
            return true;
        }
        return false;
    }

    public SmesMessageSpecification setDestinationAddressNumberingPlanIndicator(
            NumberingPlanIndicator destAddrNpi) {
        if (!nullHeaderWillOverwriteDefault(destAddrNpi)) {
            this.destinationAddressNumberingPlanIndicator = destAddrNpi;
        }
        return this;
    }

    public SmesMessageSpecification setEsmClass(ESMClass esmClass) {
        if (!nullHeaderWillOverwriteDefault(esmClass)) {
            this.esmClass = esmClass;
        }
        return this;
    }

    public SmesMessageSpecification setProtocolId(byte protocolId) {
        if (!nullHeaderWillOverwriteDefault(protocolId)) {
            this.protocolId = protocolId;
        }
        return this;
    }

    public SmesMessageSpecification setPriorityFlag(byte pf) {
        if (!nullHeaderWillOverwriteDefault(pf)) {
            this.priorityFlag = pf;
        }
        return this;
    }

    /**
     * When you submit a message to an SMSC, it is possible to sometimes specify a
     * <em>validity period</em> for the message. This setting is an instruction to the SMSC that stipulates that
     * if the message cannot be delivered to the recipient within the next N minutes or hours or days,
     * the SMSC should discard the message. This would mean that if the recipient'running mobile phone is
     * turned off, or outSession of coverage for x minutes/hours/days after the message is submitted, the SMSC
     * should not perform further delivery retry and should discard the message.
     * <p/>
     * Of course, there is no guarantee that the operator SMSC will respect this setting, so it needs
     * to be tested with a particular operator first to determine if it can be used reliably.
     * <p/>
     * That information came from <a href="http://www.nowsms.com/smpp-information">the NowSMS website.</a>.
     *
     * @param v the period of validity. There are specific formats for this, however this method provides no validation.
     *          <p/>
     *          todo provide format validation if possible
     * @return the current SmesMessageSpecification
     */
    public SmesMessageSpecification setValidityPeriod(String v) {
        if (!nullHeaderWillOverwriteDefault(v)) {
            this.validityPeriod = v;
        }
        return this;
    }

    public SmesMessageSpecification setScheduleDeliveryTime(Date d) {
        if (!nullHeaderWillOverwriteDefault(d)) {
            this.scheduleDeliveryTime = timeFormatter.format(d);
        }
        return this;
    }

    public SmesMessageSpecification setRegisteredDelivery(RegisteredDelivery rd) {
        if (!nullHeaderWillOverwriteDefault(rd)) {
            this.registeredDelivery = rd;
        }
        return this;
    }

    public SmesMessageSpecification setReplaceIfPresentFlag(byte replaceIfPresentFlag) {
        if (!nullHeaderWillOverwriteDefault(replaceIfPresentFlag)) {
            this.replaceIfPresentFlag = replaceIfPresentFlag;
        }
        return this;
    }

    public SmesMessageSpecification setDataCoding(DataCoding dataCoding) {
        if (!nullHeaderWillOverwriteDefault(dataCoding)) {
            this.dataCoding = dataCoding;
        }
        return this;
    }

    public SmesMessageSpecification setSmDefaultMsgId(byte smDefaultMsgId) {
        this.smDefaultMsgId = smDefaultMsgId;
        return this;
    }

    public SmesMessageSpecification setTimeFormatter(TimeFormatter timeFormatter) {
        if (!nullHeaderWillOverwriteDefault(timeFormatter)) {
            this.timeFormatter = timeFormatter;
        }
        return this;
    }

    /**
     * Setting short message. This will take into account if {@link #dataCoding} or if {@link #maxLengthSmsMessages}
     * is set through header to validate the maximum characters can be set.
     *
     * @param s the text message body
     * @return the SmesMessageSpecification
     */
    public SmesMessageSpecification setShortTextMessage(String s) {
        Assert.notNull(s, "the SMS message payload must not be null");
        if (esmClass != null && GSMSpecificFeature.UDHI.containedIn(esmClass)) {
            log.debug("Setting short message with UDH");
            this.shortMessage = UdhUtil.getMessageWithUdhInBytes(s, dataCoding.toByte());
        } else {
            Assert.isTrue(s.length() <= this.maxLengthSmsMessages,
                    "the SMS message payload must be " + maxLengthSmsMessages + " characters or less.");
            this.shortMessage = DataCodingSpecification.getMessageInBytes(s, dataCoding.toByte());
        }
        return this;
    }

    /**
     * Setting short message using message_payload ({@link org.jsmpp.bean.OptionalParameter.Tag#MESSAGE_PAYLOAD})
     * optional parameter
     * @param s the text messages body
     * @return the SmesMessageSpecification
     */
    public SmesMessageSpecification setShortMessageUsingPayload(String s) {
        final byte[] content = DataCodingSpecification.getMessageInBytes(s, dataCoding.toByte());
        this.messagePayloadParameter = new OptionalParameter.OctetString(
                OptionalParameter.Tag.MESSAGE_PAYLOAD.code(), content);
        return this;
    }

    /**
     * this is a good value, but not strictly speaking universal. This is intended only for exceptional configuration cases
     * <p/>
     * See: http://www.nowsms.com/long-sms-text-messages-and-the-160-character-limit
     *
     * @param maxLengthSmsMessages the length of sms messages
     * @see #setShortTextMessage(String)
     */
    public void setMaxLengthSmsMessages(int maxLengthSmsMessages) {
        this.maxLengthSmsMessages = maxLengthSmsMessages;
    }

    /**
     * Resets the thread local, pooled objects to a known state before reuse.
     * <p/>
     * Resetting the variables is trivially cheap compared to proxying a new one each time.
     *
     * @return the cleaned up {@link SmesMessageSpecification}
     */
    protected SmesMessageSpecification reset() {

        // configuration params - should they be reset?
        maxLengthSmsMessages = 140;
        timeFormatter = new AbsoluteTimeFormatter();

        sourceAddress = null;
        destinationAddress = null;
        serviceType = "CMT";
        sourceAddressTypeOfNumber = TypeOfNumber.UNKNOWN;
        sourceAddressNumberingPlanIndicator = NumberingPlanIndicator.UNKNOWN;
        destinationAddressTypeOfNumber = TypeOfNumber.UNKNOWN;
        destinationAddressNumberingPlanIndicator = NumberingPlanIndicator.UNKNOWN;
        esmClass = new ESMClass();
        protocolId = 0;
        priorityFlag = 1;
        scheduleDeliveryTime = null;
        validityPeriod = null;
        registeredDelivery = new RegisteredDelivery(SMSCDeliveryReceipt.DEFAULT);
        replaceIfPresentFlag = 0;
        dataCoding = new GeneralDataCoding(Alphabet.ALPHA_DEFAULT, MessageClass.CLASS1, false);
        smDefaultMsgId = 0;
        shortMessage = null; // the bytes to the 140 character text message
        smppSession = null;
        messagePayloadParameter = null;
        return this;
    }

    public SmesMessageSpecification setSourceAddressIfRequired(String defaultSourceAddress) {
        if (!StringUtils.hasText(this.sourceAddress)) {
            this.sourceAddress = defaultSourceAddress;
        }
        return this;
    }
}

/*   private static String fromPropertyToHeaderConstant(String n) {
    
  StringBuffer stringBuffer = new StringBuffer();
    
  for (char c : n.toCharArray()) {
     if (Character.isUpperCase(c)) {
        stringBuffer.append("_");
     }
     stringBuffer.append(c);
  }
    
  String nn = stringBuffer.toString().toUpperCase();
    
  String is = "IS_",
        get = "GET_";
  if (nn.startsWith(is)) nn = nn.substring(is.length());
  if (nn.startsWith(get)) nn = nn.substring(get.length());
    
  return nn;
   }
    
   static public void main(String[] args) throws Throwable {
  String m = "mb.setHeader( SmppConstants.%s,  dsm.%s() );";
  String h = "public static final String %s = \"%s\";";
  Set<String> marshalling = new HashSet<String>();
  Set<String> headers = new HashSet<String>();
  PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(DeliverSm.class);
  for (PropertyDescriptor propertyDescriptor : pds) {
     Method reader = propertyDescriptor.getReadMethod();
     String readerName = reader.getName();
    
     String header = fromPropertyToHeaderConstant(readerName);
     headers.add(header);
     marshalling.add(readerName + ":" + header);
  }
    
  for (String s : headers) System.outSession.println(String.format(h, s, s));
    
  for (String s : marshalling) {
    
     String[] tuple = s.split(":");
    
     System.outSession.println(String.format(m, tuple[1], tuple[0]));
  }
   }
*/