com.cloudhopper.smpp.util.DeliveryReceipt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ch-smpp Show documentation
Show all versions of ch-smpp Show documentation
Efficient, scalable, and flexible Java implementation of the Short Messaging Peer to Peer Protocol (SMPP)
package com.cloudhopper.smpp.util;
/*
* #%L
* ch-smpp
* %%
* Copyright (C) 2009 - 2012 Cloudhopper by Twitter
* %%
* 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.
* #L%
*/
import com.cloudhopper.commons.util.StringUtil;
import com.cloudhopper.smpp.SmppConstants;
import java.util.Map;
import java.util.TreeMap;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class to represent a Delivery Receipt that may be contained within
* a DataSm or DeliverSm PDU. A delivery receipt has a specific message text
* and a few specific optional parameters.
*
* @author joelauer (twitter: @jjlauer or http://twitter.com/jjlauer)
*/
public class DeliveryReceipt {
private static final Logger logger = LoggerFactory.getLogger(DeliveryReceipt.class);
// template format of the dates included with delivery receipts
private static final DateTimeFormatter dateFormatTemplate = DateTimeFormat.forPattern("yyMMddHHmm");
private static final DateTimeFormatter dateFormatTemplateWithSeconds = DateTimeFormat.forPattern("yyMMddHHmmss");
// an example of a 3rd format 20110303100008 (yyyyMMddHHmmss)
private static final DateTimeFormatter dateFormatTemplateWithFullYearAndSeconds = DateTimeFormat.forPattern("yyyyMMddHHmmss");
public static final String FIELD_ID = "id:";
public static final String FIELD_SUB = "sub:";
public static final String FIELD_DLVRD = "dlvrd:";
public static final String FIELD_SUBMIT_DATE = "submit date:";
public static final String FIELD_DONE_DATE = "done date:";
public static final String FIELD_STAT = "stat:";
public static final String FIELD_ERR = "err:";
public static final String FIELD_TEXT = "text:";
// field "id": id of message originally submitted
private String messageId;
// field "sub": number of messages originally submitted
private int submitCount;
// field "dlvrd": number of messages delivered
private int deliveredCount;
// field "submit date": date message was originally submitted at
private DateTime submitDate;
// field "done date": date message reached a final "done" state
private DateTime doneDate;
// field "stat": final state of message
private byte state;
// field "err": network/smsc specific error code
private int errorCode;
// field "text": first 20 characters of original message
private String text;
public DeliveryReceipt() {
// do nothing
}
public DeliveryReceipt(String messageId, int submitCount, int deliveredCount, DateTime submitDate, DateTime doneDate, byte state, int errorCode, String text) {
this.messageId = messageId;
this.submitCount = submitCount;
this.deliveredCount = deliveredCount;
this.submitDate = submitDate;
this.doneDate = doneDate;
this.state = state;
this.errorCode = errorCode;
this.text = text;
}
public int getDeliveredCount() {
return deliveredCount;
}
public void setDeliveredCount(int deliveredCount) {
this.deliveredCount = deliveredCount;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public DateTime getDoneDate() {
return doneDate;
}
public void setDoneDate(DateTime finalDate) {
this.doneDate = finalDate;
}
public String getMessageId() {
return messageId;
}
public long getMessageIdAsLong() throws NumberFormatException {
return Long.parseLong(this.messageId);
}
/**
* Sets the "id" field to the exact String we'll use. Utility method provided
* for setting this value as a long.
* @param messageId
*/
public void setMessageId(String messageId) {
this.messageId = messageId;
}
/**
* Sets the "id" field parameter as a long value that is zero padded to
* 10 digits.
* @param messageId
*/
public void setMessageId(long messageId) {
this.messageId = String.format("%010d", messageId);
}
public byte getState() {
return state;
}
public void setState(byte state) {
this.state = state;
}
public int getSubmitCount() {
return submitCount;
}
public void setSubmitCount(int submitCount) {
this.submitCount = submitCount;
}
public DateTime getSubmitDate() {
return submitDate;
}
public void setSubmitDate(DateTime submitDate) {
this.submitDate = submitDate;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String toShortMessage() {
StringBuilder buf = new StringBuilder(200);
buf.append(FIELD_ID);
buf.append(this.messageId);
buf.append(" ");
buf.append(FIELD_SUB);
buf.append(String.format("%03d", this.submitCount));
buf.append(" ");
buf.append(FIELD_DLVRD);
buf.append(String.format("%03d", this.deliveredCount));
buf.append(" ");
buf.append(FIELD_SUBMIT_DATE);
if (this.submitDate == null) {
buf.append("0000000000");
} else {
buf.append(dateFormatTemplate.print(this.submitDate));
}
buf.append(" ");
buf.append(FIELD_DONE_DATE);
if (this.submitDate == null) {
buf.append("0000000000");
} else {
buf.append(dateFormatTemplate.print(this.doneDate));
}
buf.append(" ");
buf.append(FIELD_STAT);
buf.append(toStateText(this.state));
buf.append(" ");
buf.append(FIELD_ERR);
buf.append(String.format("%03d", this.errorCode));
buf.append(" ");
buf.append(FIELD_TEXT);
if (this.text != null) {
if (this.text.length() > 20) {
buf.append(this.text.substring(0, 20));
} else {
buf.append(this.text);
}
}
return buf.toString();
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(160);
buf.append("(id=");
buf.append(this.messageId);
buf.append(" sub=");
buf.append(this.submitCount);
buf.append(" dlvrd=");
buf.append(this.deliveredCount);
buf.append(" submitDate=");
buf.append(this.submitDate);
buf.append(" doneDate=");
buf.append(this.doneDate);
buf.append(" state=");
buf.append(toStateText(this.state));
buf.append("[");
buf.append(this.state);
buf.append("] err=");
buf.append(this.errorCode);
buf.append(" text=[");
buf.append(this.text);
buf.append("])");
return buf.toString();
}
static private DateTime parseDateTimeHelper(String value, DateTimeZone zone) {
if (value == null) {
return null;
}
// pick the correct template based on length
if (value.length() == 14) {
return dateFormatTemplateWithFullYearAndSeconds.withZone(zone).parseDateTime(value);
} else if(value.length() == 12) {
return dateFormatTemplateWithSeconds.withZone(zone).parseDateTime(value);
} else {
return dateFormatTemplate.withZone(zone).parseDateTime(value);
}
}
static public void findFieldAndAddToTreeMap(String normalizedText, String field, TreeMap fieldsByStartPos) {
int startPos = normalizedText.indexOf(field);
//logger.debug("Found field " + field + " at startPos " + startPos);
if (startPos >= 0) {
fieldsByStartPos.put(startPos, field);
}
}
static public DeliveryReceipt parseShortMessage(String shortMessage, DateTimeZone zone) throws DeliveryReceiptException {
return parseShortMessage(shortMessage, zone, true);
}
/**
* Parses the text of the short message and creates a DeliveryReceipt from
* the fields. This method is lenient as possible. The order of the fields
* does not matter, as well as permitting some fields to be optional.
* @param shortMessage
* @param zone
* @return
* @throws DeliveryReceiptException
*/
static public DeliveryReceipt parseShortMessage(String shortMessage, DateTimeZone zone, boolean checkMissingFields) throws DeliveryReceiptException {
// for case insensitivity, convert to lowercase (normalized text)
String normalizedText = shortMessage.toLowerCase();
// create a new DLR with fields set to "uninitialized" values
DeliveryReceipt dlr = new DeliveryReceipt(null, -1, -1, null, null, (byte)-1, -1, null);
TreeMap fieldsByStartPos = new TreeMap();
// find location of all possible fields in text of message and add to
// treemap by their startPos so that we'll end up with an ordered list of their occurrence
// a field "value" only technically ends wit the start of the next field "label"
// since a field value could technically contain ":" or spaces
// SMPP really has HORRIBLE specs for delivery receipts...
findFieldAndAddToTreeMap(normalizedText, FIELD_ID, fieldsByStartPos);
findFieldAndAddToTreeMap(normalizedText, FIELD_SUB, fieldsByStartPos);
findFieldAndAddToTreeMap(normalizedText, FIELD_DLVRD, fieldsByStartPos);
findFieldAndAddToTreeMap(normalizedText, FIELD_SUBMIT_DATE, fieldsByStartPos);
findFieldAndAddToTreeMap(normalizedText, FIELD_DONE_DATE, fieldsByStartPos);
findFieldAndAddToTreeMap(normalizedText, FIELD_STAT, fieldsByStartPos);
findFieldAndAddToTreeMap(normalizedText, FIELD_ERR, fieldsByStartPos);
findFieldAndAddToTreeMap(normalizedText, FIELD_TEXT, fieldsByStartPos);
// process all fields in the order they appear
Map.Entry curFieldEntry = fieldsByStartPos.firstEntry();
while (curFieldEntry != null) {
Map.Entry nextFieldEntry = fieldsByStartPos.higherEntry(curFieldEntry.getKey());
// calculate the positions for the substring to extract the field value
int fieldLabelStartPos = curFieldEntry.getKey().intValue();
int startPos = fieldLabelStartPos + curFieldEntry.getValue().length();
int endPos = (nextFieldEntry != null ? nextFieldEntry.getKey().intValue() : normalizedText.length());
String fieldLabel = curFieldEntry.getValue();
String fieldValue = shortMessage.substring(startPos, endPos).trim();
//logger.debug("startPos [" + curFieldEntry.getKey() + "] label [" + curFieldEntry.getValue() + "] value [" + fieldValue + "]");
if (!StringUtil.isEmpty(fieldValue)) {
if (fieldLabel.equalsIgnoreCase(FIELD_ID)) {
dlr.messageId = fieldValue;
} else if (fieldLabel.equalsIgnoreCase(FIELD_SUB)) {
try {
dlr.submitCount = Integer.parseInt(fieldValue);
} catch (NumberFormatException e) {
throw new DeliveryReceiptException("Unable to convert [sub] field with value [" + fieldValue + "] into an integer");
}
} else if (fieldLabel.equalsIgnoreCase(FIELD_DLVRD)) {
try {
dlr.deliveredCount = Integer.parseInt(fieldValue);
} catch (NumberFormatException e) {
throw new DeliveryReceiptException("Unable to convert [dlvrd] field with value [" + fieldValue + "] into an integer");
}
} else if (fieldLabel.equalsIgnoreCase(FIELD_SUBMIT_DATE)) {
try {
dlr.submitDate = parseDateTimeHelper(fieldValue, zone);
} catch (IllegalArgumentException e) {
throw new DeliveryReceiptException("Unable to convert [submit date] field with value [" + fieldValue + "] into a datetime object");
}
} else if (fieldLabel.equalsIgnoreCase(FIELD_DONE_DATE)) {
try {
dlr.doneDate = parseDateTimeHelper(fieldValue, zone);
} catch (IllegalArgumentException e) {
throw new DeliveryReceiptException("Unable to convert [done date] field with value [" + fieldValue + "] into a datetime object");
}
} else if (fieldLabel.equalsIgnoreCase(FIELD_STAT)) {
dlr.state = DeliveryReceipt.toState(fieldValue);
if (dlr.state < 0) {
throw new DeliveryReceiptException("Unable to convert [stat] field with value [" + fieldValue + "] into a valid state");
}
} else if (fieldLabel.equalsIgnoreCase(FIELD_ERR)) {
try {
dlr.errorCode = Integer.parseInt(fieldValue);
} catch (NumberFormatException e) {
throw new DeliveryReceiptException("Unable to convert [err] field with value [" + fieldValue + "] into an integer");
}
} else if (fieldLabel.equalsIgnoreCase(FIELD_TEXT)) {
dlr.text = fieldValue;
} else {
throw new DeliveryReceiptException("Unsupported field [" + fieldValue + "] found");
}
}
curFieldEntry = nextFieldEntry;
}
if (checkMissingFields) {
if (StringUtil.isEmpty(dlr.messageId)) {
throw new DeliveryReceiptException("Unable to find [id] field or empty value in delivery receipt message");
}
if (dlr.submitCount < 0) {
throw new DeliveryReceiptException("Unable to find [sub] field or empty value in delivery receipt message");
}
if (dlr.deliveredCount < 0) {
throw new DeliveryReceiptException("Unable to find [dlvrd] field or empty value in delivery receipt message");
}
if (dlr.submitDate == null) {
throw new DeliveryReceiptException("Unable to find [submit date] field or empty value in delivery receipt message");
}
if (dlr.doneDate == null) {
throw new DeliveryReceiptException("Unable to find [done date] field or empty value in delivery receipt message");
}
if (dlr.state < 0) {
throw new DeliveryReceiptException("Unable to find [stat] field or empty value in delivery receipt message");
}
if (dlr.errorCode < 0) {
throw new DeliveryReceiptException("Unable to find [err] field or empty value in delivery receipt message");
}
}
return dlr;
}
static public byte toState(String stateText) {
if (stateText == null) {
return -1;
}
if (stateText.equalsIgnoreCase("DELIVRD")) {
return SmppConstants.STATE_DELIVERED;
} else if (stateText.equalsIgnoreCase("EXPIRED")) {
return SmppConstants.STATE_EXPIRED;
} else if (stateText.equalsIgnoreCase("DELETED")) {
return SmppConstants.STATE_DELETED;
} else if (stateText.equalsIgnoreCase("UNDELIV")) {
return SmppConstants.STATE_UNDELIVERABLE;
} else if (stateText.equalsIgnoreCase("ACCEPTD")) {
return SmppConstants.STATE_ACCEPTED;
} else if (stateText.equalsIgnoreCase("UNKNOWN")) {
return SmppConstants.STATE_UNKNOWN;
} else if (stateText.equalsIgnoreCase("REJECTD")) {
return SmppConstants.STATE_REJECTED;
} else if (stateText.equalsIgnoreCase("ENROUTE")) {
return SmppConstants.STATE_ENROUTE;
} else {
return -1;
}
}
static public String toStateText(byte state) {
switch (state) {
case SmppConstants.STATE_DELIVERED:
return "DELIVRD";
case SmppConstants.STATE_EXPIRED:
return "EXPIRED";
case SmppConstants.STATE_DELETED:
return "DELETED";
case SmppConstants.STATE_UNDELIVERABLE:
return "UNDELIV";
case SmppConstants.STATE_ACCEPTED:
return "ACCEPTD";
case SmppConstants.STATE_UNKNOWN:
return "UNKNOWN";
case SmppConstants.STATE_REJECTED:
return "REJECTD";
case SmppConstants.STATE_ENROUTE:
return "ENROUTE";
default:
return "BADSTAT";
}
}
/**
* Converts a long value to a hex string. E.g. 98765432101L to "16fee0e525"
* @param value
* @return
*/
static public String toMessageIdAsHexString(long value) {
return String.format("%x", value);
}
/**
* Converts a hex string to a long value. E.g. "16fee0e525" to 98765432101L
* @param value
* @return
*/
static public long toMessageIdAsLong(String value) throws NumberFormatException {
return Long.parseLong(value, 16);
}
}