// SMSLib for Java v3
// A Java API library for sending and receiving SMS via a GSM modem
// or other supported gateways.
// Web Site: http://www.smslib.org
//
// Copyright (C) 2002-2012, Thanasis Delenikas, Athens/GREECE.
// SMSLib is distributed under the terms of the Apache License version 2.0
//
// 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.smslib;
import java.util.Date;
import java.util.List;
import org.ajwcc.pduUtils.gsm3040.Pdu;
import org.ajwcc.pduUtils.gsm3040.PduFactory;
import org.ajwcc.pduUtils.gsm3040.PduGenerator;
import org.ajwcc.pduUtils.gsm3040.PduParser;
import org.ajwcc.pduUtils.gsm3040.PduUtils;
import org.ajwcc.pduUtils.gsm3040.SmsSubmitPdu;
import org.ajwcc.pduUtils.gsm3040.ie.InformationElementFactory;
/**
* Class representing an outbound sms message.
*/
public class OutboundMessage extends Message
{
private static final long serialVersionUID = 2L;
/**
* Enumeration representing the failure reasons of a failed outbound
* message.
*/
public enum FailureCauses
{
/**
* No error, everything OK.
*/
NO_ERROR,
/**
* Bad destination number - fatal error.
*/
BAD_NUMBER,
/**
* Bad message format - fatal error.
*/
BAD_FORMAT,
/**
* Generic gateway failure - transient error, retry later.
*/
GATEWAY_FAILURE,
/**
* No credit left - fatal error.
*/
NO_CREDIT,
/**
* Authentication problem (pin, passwords, etc) - fatal error.
*/
GATEWAY_AUTH,
/**
* Unable to route message - transient error.
*/
NO_ROUTE,
/**
* EzTexting specific: Local opt out (the recipient/number is on your opt-out list.)
*/
LOCAL_OPTOUT,
/**
* EzTexting specific: Globally opted out phone number (the phone number has been opted out from all messages sent from our short code)
*/
GLOBAL_OPTOUT,
/**
* Unknown generic problems encountered.
*/
UNKNOWN
}
/**
* Class representing the status of an outbound message.
*/
public enum MessageStatuses
{
/**
* A not-yet-sent outbound message.
*/
UNSENT,
/**
* An already-sent outbound message.
*/
SENT,
/**
* A sent-but-failed outbound message.
*/
FAILED
}
protected String recipient;
private Date dispatchDate;
private int validityPeriod;
private boolean statusReport;
private String from;
private MessageStatuses messageStatus;
private FailureCauses failureCause;
private int retryCount;
private int priority;
private String refNo;
private String errorMessage;
//private long deliveryDelay;
private Date scheduledDeliveryDate;
/**
* Outbound message constructor. This parameterless constructor creates an
* empty outbound message.
*
* @see #OutboundMessage(String, String)
*/
public OutboundMessage()
{
super(MessageTypes.OUTBOUND, null, null);
setRecipient("");
setValidityPeriod(-1);
setStatusReport(false);
setDCSMessageClass(MessageClasses.MSGCLASS_NONE);
setFrom("");
setDispatchDate(null);
setDate(new Date());
setEncoding(MessageEncodings.ENC7BIT);
setMessageStatus(MessageStatuses.UNSENT);
setFailureCause(FailureCauses.NO_ERROR);
setPriority(0);
setRefNo("");
setGatewayId("*");
setRetryCount(0);
}
/**
* Outbound message constructor.
*
* @param myRecipient
* The recipient of the message.
* @param text
* The text of the message.
*/
public OutboundMessage(String myRecipient, String text)
{
super(MessageTypes.OUTBOUND, new Date(), text);
setRecipient(myRecipient);
setValidityPeriod(-1);
setStatusReport(false);
setDCSMessageClass(MessageClasses.MSGCLASS_NONE);
setFrom("");
setDispatchDate(null);
setDate(new Date());
setEncoding(MessageEncodings.ENC7BIT);
setMessageStatus(MessageStatuses.UNSENT);
setFailureCause(FailureCauses.NO_ERROR);
setPriority(0);
setRefNo("");
setGatewayId("*");
setRetryCount(0);
}
/**
* Returns the recipient of this outbound message.
*
* @return The recipient of the message.
* @see #setRecipient(String)
*/
public String getRecipient()
{
return this.recipient;
}
/**
* Set the recipient of the message.
*
* @param myRecipient
* The recipient of the message.
* @see #getRecipient()
*/
public void setRecipient(String myRecipient)
{
this.recipient = myRecipient;
}
/**
* Returns the dispatch date of this message. If the message has not been
* sent yet, the dispatch date is null.
*
* @return The message dispatch date.
*/
public Date getDispatchDate()
{
if (this.dispatchDate != null) return new java.util.Date(this.dispatchDate.getTime());
return null;
}
public void setDispatchDate(Date myDispatchDate)
{
this.dispatchDate = myDispatchDate;
}
/**
* Returns true if this message is to be sent out as a flash SMS. Otherwise,
* it returns false.
*
* @return True for a Flash message.
* @see #setFlashSms(boolean)
*/
public boolean getFlashSms()
{
if (getDCSMessageClass() == MessageClasses.MSGCLASS_FLASH) return true;
return false;
}
/**
* Set the flash message indication. Set this to true for this message to be
* sent as a flash message. Flash messages appear directly on the handset,
* so use this feature with care, because it may be a bit annoying.
* Furthermore, keep in mind that flash messaging is not supported on all
* phones.
* <p>
* The default is non-flash (false).
*
* @param flashSms
* True for a flash sms.
*/
public void setFlashSms(boolean flashSms)
{
if (flashSms) setDCSMessageClass(MessageClasses.MSGCLASS_FLASH);
else setDCSMessageClass(MessageClasses.MSGCLASS_NONE);
}
/**
* Returns true if a status/delivery report will be asked for this message.
*
* @return True if a status report will be generated.
*/
public boolean getStatusReport()
{
return this.statusReport;
}
/**
* Sets the status report request. If you set it to true, a status report
* message will be generated, otherwise no status report message will be
* generated.
* <p>
* The default is (false).
*
* @param myStatusReport
* The status report request status.
*/
public void setStatusReport(boolean myStatusReport)
{
this.statusReport = myStatusReport;
}
/**
* Returns the message validity period (in hours).
*
* @return The message validity period.
* @see #setValidityPeriod(int)
*/
public int getValidityPeriod()
{
return this.validityPeriod;
}
/**
* Sets the message validity period.
*
* @param myValidityPeriod
* The message validity period in hours.
* @see #getValidityPeriod()
*/
public void setValidityPeriod(int myValidityPeriod)
{
this.validityPeriod = myValidityPeriod;
}
/**
* Receives the custom originator string. Set it to empty string to leave
* the default behavior.
*
* @return The custom originator string.
* @see #setFrom(String)
*/
public String getFrom()
{
return this.from;
}
/**
* Sets the custom originator string. Some gateways allow you to define a
* custom string as the originator. When the message arrives at the
* recipient, the latter will not see your number but this string.
* <p>
* Note that this functionality is not supported on GSM modems / phones. It
* is supported on most bulk sms operators.
*
* @param myFrom
* The custom originator string.
* @see #getFrom()
*/
public void setFrom(String myFrom)
{
this.from = myFrom;
}
/**
* Returns the message status.
*
* @return The message status.
* @see MessageStatuses
*/
public MessageStatuses getMessageStatus()
{
return this.messageStatus;
}
public void setMessageStatus(MessageStatuses myMessageStatus)
{
this.messageStatus = myMessageStatus;
}
public FailureCauses getFailureCause()
{
return this.failureCause;
}
/**
* Mark message as failed and set cause of failure.
*
* @param myFailureCause
* Cause of failure
*/
public void setFailureCause(FailureCauses myFailureCause)
{
if (myFailureCause != FailureCauses.NO_ERROR) this.messageStatus = MessageStatuses.FAILED;
this.failureCause = myFailureCause;
}
/**
* Return value of internal sending retry counter.
*
* @return Number of sending message retries
*/
public int getRetryCount()
{
return this.retryCount;
}
public void setRetryCount(int myRetryCount)
{
this.retryCount = myRetryCount;
}
void incrementRetryCount()
{
this.retryCount++;
}
/**
* Returns the priority of the message.
*
* @return The priority of the message.
*/
public int getPriority()
{
return this.priority;
}
/**
* Sets the priority of the message.
*
* @param myPriority
* The new priority.
*/
public void setPriority(int myPriority)
{
this.priority = myPriority;
}
/**
* Returns the message Reference Number. The Reference Number comes into
* existence when the message is sent. Its format depends on the gateway:
* For modems, its a number. For bulk sms operators, this is a hex string.
* If the message has not been sent yet, the Reference number is blank.
*
* @return The message reference number.
*/
public String getRefNo()
{
return this.refNo;
}
public void setRefNo(String myRefNo)
{
this.refNo = myRefNo;
}
/**
* Returns the error message associated with the failure of this
* outbound message to be sent out.
*
* @return The error message.
*/
public String getErrorMessage()
{
return errorMessage;
}
public void setErrorMessage(String errorMessage)
{
this.errorMessage = errorMessage;
}
@Override
public String toString()
{
String str = "\n";
str += "===============================================================================";
str += "\n";
str += "<< " + getClass().getSimpleName() + " >>";
str += "\n";
str += "-------------------------------------------------------------------------------";
str += "\n";
str += " Gateway Id: " + getGatewayId();
str += "\n";
str += " Message Id: " + getMessageId();
str += "\n";
str += " Message UUID: " + getUuid();
str += "\n";
str += " Encoding: " + (getEncoding() == MessageEncodings.ENC7BIT ? "7-bit" : (getEncoding() == MessageEncodings.ENC8BIT ? "8-bit" : "UCS2 (Unicode)"));
str += "\n";
str += " Date: " + getDate();
str += "\n";
str += " SMSC Ref No: " + getRefNo();
str += "\n";
str += " Recipient: " + getRecipient();
str += "\n";
str += " Dispatch Date: " + getDispatchDate();
str += "\n";
str += " Message Status: " + getMessageStatus();
str += "\n";
str += " Failure Cause: " + getFailureCause();
str += "\n";
str += " Validity Period (Hours): " + getValidityPeriod();
str += "\n";
str += " Status Report: " + getStatusReport();
str += "\n";
str += " Source / Destination Ports: " + getSrcPort() + " / " + getDstPort();
str += "\n";
str += " Flash SMS: " + getFlashSms();
str += "\n";
if (this instanceof OutboundBinaryMessage)
{
OutboundBinaryMessage binaryMessage = (OutboundBinaryMessage) this;
if (binaryMessage.getDataBytes() != null)
{
String binaryString = PduUtils.bytesToPdu((binaryMessage).getDataBytes());
str += " Binary: " + binaryString;
str += "\n";
}
else
{
str += " Binary: null";
str += "\n";
}
}
else
{
str += " Text: " + getText();
str += "\n";
try
{
str += " PDU data: " + getPduUserData();
str += "\n";
}
catch (Exception e)
{
str += " PDU data: <cannot extract properly, udh present>";
str += "\n";
}
}
str += " Scheduled Delivery: "+scheduledDeliveryDate;
str += "\n";
str += "===============================================================================";
str += "\n";
return str;
}
public List<String> getPdus(String smscNumber, int mpRefNo)
{
PduGenerator pduGenerator = new PduGenerator();
SmsSubmitPdu pdu = createPduObject();
initPduObject(pdu, smscNumber);
return pduGenerator.generatePduList(pdu, mpRefNo);
}
protected SmsSubmitPdu createPduObject()
{
// if you want to be able to change some other parts of the first octet
// do it here
SmsSubmitPdu pdu;
if (this.statusReport)
{
pdu = PduFactory.newSmsSubmitPdu(PduUtils.TP_SRR_REPORT | PduUtils.TP_VPF_INTEGER);
}
else
{
pdu = PduFactory.newSmsSubmitPdu();
}
return pdu;
}
protected void initPduObject(SmsSubmitPdu pdu, String smscNumber)
{
if ((getDstPort() > -1) && (getSrcPort() > -1))
{
// port info
pdu.addInformationElement(InformationElementFactory.generatePortInfo(getDstPort(), getSrcPort()));
}
// smscInfo
// address type field + #octets for smscNumber
// NOTE: make sure the + is not present when computing the smscInfoLength
String smscNumberForLengthCheck = smscNumber;
if (smscNumber.startsWith("+"))
{
smscNumberForLengthCheck = smscNumber.substring(1);
}
pdu.setSmscInfoLength(1 + (smscNumberForLengthCheck.length() / 2) + ((smscNumberForLengthCheck.length() % 2 == 1) ? 1 : 0));
// set address
pdu.setSmscAddress(smscNumber);
// set address type using the address (+ needs to be passed with it, if present)
pdu.setSmscAddressType(PduUtils.getAddressTypeFor(smscNumber));
// message reference
// just use 0 since this is not tracked by the ModemGateway
pdu.setMessageReference(0);
// destination address info
pdu.setAddress(getRecipient());
pdu.setAddressType(PduUtils.getAddressTypeFor(getRecipient()));
// protocol id
pdu.setProtocolIdentifier(0);
// data coding scheme
if (!pdu.isBinary())
{
int dcs = 0;
if (getEncoding() == MessageEncodings.ENC7BIT)
{
dcs = PduUtils.DCS_ENCODING_7BIT;
}
else if (getEncoding() == MessageEncodings.ENC8BIT)
{
dcs = PduUtils.DCS_ENCODING_8BIT;
}
else if (getEncoding() == MessageEncodings.ENCUCS2)
{
dcs = PduUtils.DCS_ENCODING_UCS2;
}
else if (getEncoding() == MessageEncodings.ENCCUSTOM)
{
// just use this
dcs = PduUtils.DCS_ENCODING_7BIT;
}
if (getDCSMessageClass() == MessageClasses.MSGCLASS_FLASH)
{
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_FLASH;
}
else if (getDCSMessageClass() == MessageClasses.MSGCLASS_ME)
{
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_ME;
}
else if (getDCSMessageClass() == MessageClasses.MSGCLASS_SIM)
{
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_SIM;
}
else if (getDCSMessageClass() == MessageClasses.MSGCLASS_TE)
{
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_TE;
}
pdu.setDataCodingScheme(dcs);
}
// validity period
pdu.setValidityPeriod(this.validityPeriod);
// add payload
setPduPayload(pdu);
}
protected void setPduPayload(SmsSubmitPdu pdu)
{
pdu.setDecodedText(getText());
}
@Override
public String getPduUserData()
{
// generate
PduGenerator pduGenerator = new PduGenerator();
SmsSubmitPdu pdu = createPduObject();
initPduObject(pdu, "");
// NOTE: - the mpRefNo is arbitrarily set to 1
// - this won't matter since we aren't looking at the UDH in this method
// - this method is not allowed for 7-bit messages with UDH
// since it is probable that the returned value will not be
// correct due to the encoding's dependence on the UDH
// - if the user wishes to extract the UD per part, he would need to get all pduStrings
// using getPdus(String smscNumber, int mpRefNo), use a
// PduParser on each pduString in the returned list, then access the UD via the Pdu object
List<String> pdus = pduGenerator.generatePduList(pdu, 1);
// my this point, pdu will be updated with concat info (in udhi), if present
if ((pdu.hasTpUdhi()) && (getEncoding() == MessageEncodings.ENC7BIT)) { throw new RuntimeException("getPduUserData() not supported for 7-bit messages with UDH"); }
// sum up the ud parts
StringBuffer ud = new StringBuffer();
for (String pduString : pdus)
{
Pdu newPdu = new PduParser().parsePdu(pduString);
ud.append(PduUtils.bytesToPdu(newPdu.getUserDataAsBytes()));
}
return ud.toString();
}
@Override
public String getPduUserDataHeader()
{
// generate
PduGenerator pduGenerator = new PduGenerator();
SmsSubmitPdu pdu = createPduObject();
initPduObject(pdu, "");
// NOTE: - the mpRefNo is arbitrarily set to 1
// - if the user wishes to extract the UDH per part, he would need to get all pduStrings
// using getPdus(String smscNumber, int mpRefNo), use a
// PduParser on each pduString in the returned list, then access the UDH via the Pdu object
List<String> pdus = pduGenerator.generatePduList(pdu, 1);
Pdu newPdu = new PduParser().parsePdu(pdus.get(0));
byte[] udh = newPdu.getUDHData();
if (udh != null) return PduUtils.bytesToPdu(udh);
return null;
}
@Override
public void setEncoding(MessageEncodings encoding)
{
if (encoding == MessageEncodings.ENC8BIT)
{
if (this instanceof OutboundBinaryMessage) super.setEncoding(encoding);
else throw new RuntimeException("Cannot use 8-bit encoding with OutgoingMessage, use OutgoingBinaryMessage instead");
}
else
{
// 7-bit / ucs2
super.setEncoding(encoding);
}
}
protected void copyTo(OutboundMessage msg)
{
super.copyTo(msg);
msg.setRecipient(getRecipient());
msg.setDispatchDate(getDispatchDate());
msg.setValidityPeriod(getValidityPeriod());
msg.setStatusReport(getStatusReport());
msg.setFlashSms(getFlashSms());
msg.setFrom(getFrom());
msg.setMessageStatus(getMessageStatus());
msg.setFailureCause(getFailureCause());
msg.retryCount = getRetryCount();
msg.setPriority(getPriority());
msg.setRefNo(getRefNo());
}
public void setScheduledDeliveryDate(Date scheduledDeliveryDate) {
this.scheduledDeliveryDate=scheduledDeliveryDate;
}
public Date getScheduledDeliveryDate() {
return scheduledDeliveryDate;
}
public long getDeliveryDelay() {
return (scheduledDeliveryDate==null)? 0: scheduledDeliveryDate.getTime()-System.currentTimeMillis();
}
public void setDeliveryDelay(long deliveryDelay) {
scheduledDeliveryDate=new Date(System.currentTimeMillis()+deliveryDelay);
}
}