INTEXT-73: Sending multipart SMS messages through

JIRA: https://jira.spring.io/browse/INTEXT-73

minor optimization

added test for multipart messages

Signed-off-by: Edge Dalmacio <edgar.dalmacio@gmail.com>

INTEXT-73 Replaced String[] with List

INTEXT-73 fixed test

Polishing code format
This commit is contained in:
Edge Dalmacio
2014-06-24 17:14:47 +03:00
committed by Artem Bilan
parent 7590253871
commit d1d6b068bc
5 changed files with 304 additions and 141 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -15,59 +15,103 @@
*/
package org.springframework.integration.smpp.core;
import static org.springframework.integration.smpp.core.SmppConstants.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jsmpp.bean.*;
import org.jsmpp.bean.Alphabet;
import org.jsmpp.bean.DataCoding;
import org.jsmpp.bean.DataCodings;
import org.jsmpp.bean.DeliverSm;
import org.jsmpp.bean.ESMClass;
import org.jsmpp.bean.GSMSpecificFeature;
import org.jsmpp.bean.GeneralDataCoding;
import org.jsmpp.bean.MessageClass;
import org.jsmpp.bean.NumberingPlanIndicator;
import org.jsmpp.bean.OptionalParameter;
import org.jsmpp.bean.OptionalParameters;
import org.jsmpp.bean.RegisteredDelivery;
import org.jsmpp.bean.SMSCDeliveryReceipt;
import org.jsmpp.bean.TypeOfNumber;
import org.jsmpp.session.ClientSession;
import org.jsmpp.session.SMPPSession;
import org.jsmpp.util.AbsoluteTimeFormatter;
import org.jsmpp.util.TimeFormatter;
import org.springframework.messaging.Message;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
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.
* Fluent API to help make specifying all these parameters just a <em>tiny</em> bit easier.
* For internal use only.
*
* @author Josh Long
* @author Edge Dalmacio
* @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 List<byte[]> shortMessageParts;
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)}
* 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) {
@@ -116,16 +160,16 @@ public class SmesMessageSpecification {
}
/**
* this method will take an inbound Spring Integration {@link Message} and map it to a {@link SmesMessageSpecification}
* 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);
log.debug("Message: " + msg);
}
String srcAddy = valueIfHeaderExists(SRC_ADDR, msg);
String dstAddy = valueIfHeaderExists(DST_ADDR, msg);
@@ -151,8 +195,10 @@ public class SmesMessageSpecification {
else {
spec.setShortTextMessage(smsTxt);
}
spec.setDestinationAddressNumberingPlanIndicator(SmesMessageSpecification.<NumberingPlanIndicator>valueIfHeaderExists(DST_NPI, msg));
spec.setSourceAddressNumberingPlanIndicator(SmesMessageSpecification.<NumberingPlanIndicator>valueIfHeaderExists(SRC_NPI, msg));
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));
@@ -185,16 +231,16 @@ public class SmesMessageSpecification {
return spec;
}
private static DataCoding dataCodingFromHeader( Message<?> msg) {
private static DataCoding dataCodingFromHeader(Message<?> msg) {
Object dc = msg.getHeaders().get(DATA_CODING);
if(dc instanceof DataCoding){
return (DataCoding)dc ;
if (dc instanceof DataCoding) {
return (DataCoding) dc;
}
if( dc instanceof Byte){
return DataCodings.newInstance((Byte)dc);
if (dc instanceof Byte) {
return DataCodings.newInstance((Byte) dc);
}
return null ;
return null;
}
/**
@@ -218,21 +264,22 @@ public class SmesMessageSpecification {
if (msg.getHeaders().containsKey(DATA_CODING)) {
final Object dc = msg.getHeaders().get(DATA_CODING);
if (dc instanceof Byte) {
return DataCodingSpecification.getMaxCharacters((Byte)dc);
return DataCodingSpecification.getMaxCharacters((Byte) dc);
}
else {
return DataCodingSpecification.getMaxCharacters(((DataCoding)dc).toByte());
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
* 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
* @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);
@@ -270,16 +317,16 @@ public class SmesMessageSpecification {
* @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);
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;
else if (o instanceof ESMClass) {
response = (ESMClass) o;
}
return response;
}
@@ -293,8 +340,8 @@ public class SmesMessageSpecification {
}
/**
* 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!
*
* 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
*/
@@ -311,15 +358,16 @@ public class SmesMessageSpecification {
}
/**
* 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
* 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
* @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) {
public static SmesMessageSpecification newSmesMessageSpecification(ClientSession ss, String srcAddress,
String destAddress, String txtMessage) {
SmesMessageSpecification smesMessageSpecification = new SmesMessageSpecification();
@@ -335,12 +383,12 @@ public class 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) {
public
SmesMessageSpecification setSourceAddressTypeOfNumberIfRequired(TypeOfNumber sourceAddressTypeOfNumberIfRequired) {
if (this.sourceAddressTypeOfNumber == null) {
this.sourceAddressTypeOfNumber = sourceAddressTypeOfNumberIfRequired;
}
@@ -350,40 +398,91 @@ public class SmesMessageSpecification {
/**
* 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}
* 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(s) (required if you want to then track it
* or correlate 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 {
public List<String> send() throws Exception {
validate();
final String msgId;
List<String> msgIds = new LinkedList<String>();
if (messagePayloadParameter == null) {
msgId = this.smppSession.submitShortMessage(
this.serviceType,
this.sourceAddressTypeOfNumber,
this.sourceAddressNumberingPlanIndicator,
this.sourceAddress,
if (this.shortMessageParts.isEmpty()) {
String msgId = this.smppSession.submitShortMessage(
this.serviceType,
this.sourceAddressTypeOfNumber,
this.sourceAddressNumberingPlanIndicator,
this.sourceAddress,
this.destinationAddressTypeOfNumber,
this.destinationAddressNumberingPlanIndicator,
this.destinationAddress,
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);
this.esmClass,
this.protocolId,
this.priorityFlag,
this.scheduleDeliveryTime,
this.validityPeriod,
this.registeredDelivery,
this.replaceIfPresentFlag,
this.dataCoding,
this.smDefaultMsgId,
this.shortMessage);
msgIds.add(msgId);
}
else {
if (log.isDebugEnabled()) {
log.debug("Sending message using sar_msg_ref_num, sar_segment_seqnum and sar_total_segments");
}
OptionalParameter sarMsgRefNum = OptionalParameters.newSarMsgRefNum(RandomUtils.nextInt(0x10000));
OptionalParameter sarTotalSegments = OptionalParameters.newSarTotalSegments(shortMessageParts.size());
String charsetName = DataCodingSpecification.getCharsetName(dataCoding.toByte());
for (int i = 0; i < shortMessageParts.size(); i++) {
byte[] shortMessagePart = shortMessageParts.get(i);
String 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,
shortMessagePart,
sarMsgRefNum,
OptionalParameters.newSarSegmentSeqnum(i + 1),
sarTotalSegments);
if (log.isDebugEnabled()) {
log.debug("sent message : " + new String(shortMessagePart, charsetName));
log.debug("message ID for the sent message is: " + msgId);
}
msgIds.add(msgId);
}
}
}
else {
// SPEC 3.2.3
log.debug("Sending message using message_payload");
msgId = this.smppSession.submitShortMessage(
String msgId = this.smppSession.submitShortMessage(
this.serviceType,
this.sourceAddressTypeOfNumber,
this.sourceAddressNumberingPlanIndicator,
@@ -405,15 +504,17 @@ public class SmesMessageSpecification {
new byte[0],
this.messagePayloadParameter
);
msgIds.add(msgId);
}
return msgId;
return Collections.unmodifiableList(msgIds);
}
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;
final boolean shortMessageSet = this.shortMessage != null && this.shortMessage.length > 0
|| !shortMessageParts.isEmpty();
Assert.isTrue(messagePayloadParameter != null ^ shortMessageSet,
"message can only be set in payload or short message. cannot be both");
if (messagePayloadParameter == null) {
@@ -513,18 +614,21 @@ public class SmesMessageSpecification {
/**
* 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.
* <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.
* 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.
* @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
@@ -577,9 +681,9 @@ public class SmesMessageSpecification {
}
/**
* Setting short message. This will take into account if {@link #dataCoding} or if {@link #maxLengthSmsMessages}
* 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
*/
@@ -590,9 +694,14 @@ public class SmesMessageSpecification {
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());
if (s.length() > this.maxLengthSmsMessages) {
for (String split : s.split("(?<=\\G.{" + String.valueOf(this.maxLengthSmsMessages - 5) + "})")) {
this.shortMessageParts.add(DataCodingSpecification.getMessageInBytes(split, dataCoding.toByte()));
}
}
else {
this.shortMessage = DataCodingSpecification.getMessageInBytes(s, dataCoding.toByte());
}
}
return this;
}
@@ -611,7 +720,8 @@ public class SmesMessageSpecification {
}
/**
* this is a good value, but not strictly speaking universal. This is intended only for exceptional configuration cases
* 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
*
@@ -652,6 +762,7 @@ public class SmesMessageSpecification {
dataCoding = new GeneralDataCoding(Alphabet.ALPHA_DEFAULT, MessageClass.CLASS1, false);
smDefaultMsgId = 0;
shortMessage = null; // the bytes to the 140 character text message
shortMessageParts = new ArrayList<byte[]>();
smppSession = null;
messagePayloadParameter = null;
return this;
@@ -663,6 +774,7 @@ public class SmesMessageSpecification {
}
return this;
}
}
/* private static String fromPropertyToHeaderConstant(String n) {

View File

@@ -1,4 +1,4 @@
/* Copyright 2002-2013 the original author or authors.
/* Copyright 2002-2014 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.
@@ -14,27 +14,34 @@
*/
package org.springframework.integration.smpp.outbound;
import java.util.List;
import org.jsmpp.bean.BindType;
import org.jsmpp.bean.TypeOfNumber;
import org.jsmpp.util.AbsoluteTimeFormatter;
import org.jsmpp.util.TimeFormatter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.messaging.MessageHandler;
import org.springframework.integration.smpp.core.SmesMessageSpecification;
import org.springframework.integration.smpp.session.ExtendedSmppSession;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Sends messages to an SMS gateway using SMPP. Most of the work in terms of converting inbound message headers
* (whose keys, by the way, live in {@link org.springframework.integration.smpp.core.SmppConstants}) is done by {@link org.springframework.integration.smpp.core.SmesMessageSpecification}, which
* Sends messages to an SMS gateway using SMPP. Most of the work in terms
* of converting inbound message headers (whose keys, by the way, live in
* {@link org.springframework.integration.smpp.core.SmppConstants}) is done by
* {@link org.springframework.integration.smpp.core.SmesMessageSpecification}, which
* handles <em>all</em> the tedium of converting and validating the configuration.
* <p/>
* This adapter supports <em>mobile terminated (MT)</em> messaging, where the recipient is a directory phone number.
* This adapter supports <em>mobile terminated (MT)</em> messaging, where the recipient
* is a directory phone number.
*
* @author Josh Long
* @author Edge Dalmacio
* @since 1.0
*/
public class SmppOutboundChannelAdapter extends IntegrationObjectSupport implements MessageHandler {
@@ -94,8 +101,8 @@ public class SmppOutboundChannelAdapter extends IntegrationObjectSupport impleme
try {
// todo support a gateway and have that gateway also handle message delivery receipt notifications
// that will correlate this smsMessageId with the ID that comes back asynchronously from the SMSC indicating that
// the message has been delivered.
// that will correlate this smsMessageId with the ID that comes back asynchronously
// from the SMSC indicating that the message has been delivered.
// this could require that we keep a correlation map since its possible upstream SMSC
// unused return value -- see gateway
@@ -103,9 +110,9 @@ public class SmppOutboundChannelAdapter extends IntegrationObjectSupport impleme
SmesMessageSpecification.fromMessage(this.smppSession, message)
.setTimeFormatter(this.timeFormatter));
String smsMessageId = specification.send();
List<String> smsMessageId = specification.send();
logger.debug( "sent message : "+message.getPayload());
logger.debug("message ID for the sent message is: " + smsMessageId);
logger.debug("message ID(s) for the sent message: " + smsMessageId);
} catch (Exception e) {
throw new RuntimeException("Exception in trying to process the inbound SMPP message", e);
}
@@ -115,4 +122,5 @@ public class SmppOutboundChannelAdapter extends IntegrationObjectSupport impleme
public String getComponentType() {
return "smpp:outbound-channel-adapter";
}
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2002-2013 the original author or authors.
/* Copyright 2002-2014 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.
@@ -14,32 +14,40 @@
*/
package org.springframework.integration.smpp.outbound;
import java.util.List;
import org.jsmpp.bean.BindType;
import org.jsmpp.bean.TypeOfNumber;
import org.jsmpp.util.AbsoluteTimeFormatter;
import org.jsmpp.util.TimeFormatter;
import org.springframework.messaging.Message;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.smpp.core.SmesMessageSpecification;
import org.springframework.integration.smpp.session.ExtendedSmppSession;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Support for request/reply exchanges over SMPP to a SMSC.
* <p/>
* The request is an outbound SMS message, as in the {@link org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter},
* and the reply can either be the messageId of the outbound message which can ultimately be used to track the confirmation,
* or the confirmation of the receipt of the outbound message itself. In the latter case, this class simply does the work
* The request is an outbound SMS message, as in the
* {@link org.springframework.integration.smpp.outbound.SmppOutboundChannelAdapter},
* and the reply can either be the messageId of the outbound message which can ultimately
* be used to track the confirmation, or the confirmation of the receipt of the outbound
* message itself. In the latter case, this class simply does the work
* of waiting for the reply and correlating it to the outbound request.
* <p/>
* By default this component assumes one {@link org.jsmpp.session.SMPPSession} in "transceiver" mode - it can both request and reply.
* Conceptually it should be possible to support two {@link org.jsmpp.session.SMPPSession}running, one in "sender" mode, and another in
* "receiver" mode and handle the duplexing manually. The correlation logic is the same, in any event.
* By default this component assumes one {@link org.jsmpp.session.SMPPSession} in
* "transceiver" mode - it can both request and reply. Conceptually it should be possible
* to support two {@link org.jsmpp.session.SMPPSession}running, one in "sender" mode,
* and another in "receiver" mode and handle the duplexing manually.
* The correlation logic is the same, in any event.
* <p/>
*
* @author Josh Long
* @author Edge Dalmacio
* @since 1.0
*/
public class SmppOutboundGateway extends AbstractReplyProducingMessageHandler {
@@ -62,9 +70,9 @@ public class SmppOutboundGateway extends AbstractReplyProducingMessageHandler {
SmesMessageSpecification.fromMessage(this.smppSession, requestMessage)
.setTimeFormatter(this.timeFormatter));
String smsMessageId = specification.send();
List<String> smsMessageId = specification.send();
logger.debug("message ID for the sent message is: " + smsMessageId);
logger.debug("message ID(s) for the sent message: " + smsMessageId);
return MessageBuilder.withPayload(smsMessageId).build();
} catch (Exception e) {
@@ -112,4 +120,5 @@ public class SmppOutboundGateway extends AbstractReplyProducingMessageHandler {
public String getComponentType() {
return "smpp:outbound-gateway";
}
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2002-2013 the original author or authors.
/* Copyright 2002-2014 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.
@@ -12,30 +12,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.smpp;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jsmpp.bean.SMSCDeliveryReceipt;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.smpp.core.SmppConstants;
import org.springframework.integration.smpp.session.ExtendedSmppSession;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Simple tests for the gateway which differs from the outbound adapter only in that it supports
* sending the message ID back
* Simple tests for the gateway which differs from the outbound adapter only in that it
* supports sending the message ID back
*
*
* @author Josh Long
* @author Edge Dalmacio
* @since 1.0
*/
@ContextConfiguration("classpath:TestSmppOutboundGateway-context.xml")
@@ -44,6 +49,7 @@ public class TestSmppOutboundGateway {
private MessagingTemplate messagingTemplate = new MessagingTemplate();
private Log log = LogFactory.getLog(getClass());
@Value("#{session}")
@@ -52,22 +58,45 @@ public class TestSmppOutboundGateway {
@Value("#{outboundSms}")
private MessageChannel messageChannel;
private String smsMessageToSend = "jSMPP is truly a convenient, and powerful API for SMPP " +
private final static String smsMessageToSend = "jSMPP is truly a convenient, and powerful API for SMPP " +
"on the Java and Spring Integration platforms (sent " + System.currentTimeMillis() + ")";
private final static String longSmsMessageToSend = "SMS messages can have no more than 160 characters. " +
"On some wireless carriers, due to a programming convention called percent-encoding, " +
"certain symbols require more than one character when used.";
@Test
public void testSendingAndReceivingASmppMessageUsingRawApi() throws Throwable {
Message<String> smsMsg = MessageBuilder.withPayload(this.smsMessageToSend)
Message<String> smsMsg = MessageBuilder.withPayload(smsMessageToSend)
.setHeader(SmppConstants.SRC_ADDR, "1616")
.setHeader(SmppConstants.DST_ADDR, "628176504657")
.setHeader(SmppConstants.REGISTERED_DELIVERY_MODE, SMSCDeliveryReceipt.SUCCESS)
.build();
Message<?> response = this.messagingTemplate.sendAndReceive(this.messageChannel,smsMsg);
Message<?> response = this.messagingTemplate.sendAndReceive(this.messageChannel, smsMsg);
Assert.assertNotNull(response);
Assert.assertTrue(response.getPayload() instanceof String);
Assert.assertTrue(response.getPayload() instanceof List);
log.info("received the SMS Message ID: " + response.getPayload());
}
@Test
@SuppressWarnings("unchecked")
public void testSendingAndReceivingAMultipartSmppMessageUsingRawApi() throws Throwable {
Message<String> smsMsg = MessageBuilder.withPayload(longSmsMessageToSend)
.setHeader(SmppConstants.SRC_ADDR, "1616")
.setHeader(SmppConstants.DST_ADDR, "628176504657")
.setHeader(SmppConstants.REGISTERED_DELIVERY_MODE, SMSCDeliveryReceipt.SUCCESS)
.build();
Message<?> response = this.messagingTemplate.sendAndReceive(this.messageChannel, smsMsg);
Assert.assertNotNull(response);
Assert.assertTrue(response.getPayload() instanceof List);
Assert.assertEquals(2, ((List<String>) response.getPayload()).size());
log.info("received the SMS Message ID: " + response.getPayload());
}
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2002-2013 the original author or authors.
/* Copyright 2002-2014 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.
@@ -12,11 +12,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.smpp;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -27,24 +29,27 @@ import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* Simple tests to make sure that gateway will perform retry and route MessagingException to exception channel when
* advice chain is defined with a retry advice and error callback.
* Simple tests to make sure that gateway will perform retry and route MessagingException
* to exception channel when advice chain is defined with a retry advice and error callback.
*
* @author Johanes Soetanto
* @author Edge Dalmacio
* @since 1.0
*/
@ContextConfiguration("classpath:TestSmppOutboundGatewayWithChain-context.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestSmppOutboundGatewayWithChain {
@Autowired
private MessagingTemplate messagingTemplate;
@Autowired
private MessagingTemplate messagingTemplate;
@Autowired
private MessageChannel outChannel;
private String smsMessageToSend = "jSMPP is truly a convenient, and powerful API for SMPP " +
private String smsMessageToSend = "jSMPP is truly a convenient, and powerful API for SMPP " +
"on the Java and Spring Integration platforms (sent " + System.currentTimeMillis() + ")";
@Test
@@ -53,25 +58,25 @@ public class TestSmppOutboundGatewayWithChain {
.setHeader(SmppConstants.SRC_ADDR, "1616")
.setHeader(SmppConstants.DST_ADDR, "NoRouteDestination")
.build();
outChannel.send(smsMsg);
outChannel.send(smsMsg);
Thread.sleep(500);
Message<?> exception = messagingTemplate.receive("exceptionChannel");
Assert.assertNotNull(exception);
Assert.assertTrue(exception.getPayload() instanceof MessagingException);
Thread.sleep(500);
Message<?> exception = messagingTemplate.receive("exceptionChannel");
Assert.assertNotNull(exception);
Assert.assertTrue(exception.getPayload() instanceof MessagingException);
}
@Test
public void testSendingGoesToReplyChannel() throws Throwable {
Message<String> smsMsg = MessageBuilder.withPayload(this.smsMessageToSend)
.setHeader(SmppConstants.SRC_ADDR, "1616")
.setHeader(SmppConstants.DST_ADDR, "1616")
.build();
outChannel.send(smsMsg);
@Test
public void testSendingGoesToReplyChannel() throws Throwable {
Message<String> smsMsg = MessageBuilder.withPayload(this.smsMessageToSend)
.setHeader(SmppConstants.SRC_ADDR, "1616")
.setHeader(SmppConstants.DST_ADDR, "1616")
.build();
outChannel.send(smsMsg);
Thread.sleep(500);
Message<?> exception = messagingTemplate.receive("replyChannel");
Assert.assertNotNull(exception);
Assert.assertTrue(exception.getPayload() instanceof String);
}
Thread.sleep(500);
Message<?> exception = messagingTemplate.receive("replyChannel");
Assert.assertNotNull(exception);
Assert.assertTrue(exception.getPayload() instanceof List);
}
}