From d1d6b068bce44c574323853d041e56269bb16bc4 Mon Sep 17 00:00:00 2001 From: Edge Dalmacio Date: Tue, 24 Jun 2014 17:14:47 +0300 Subject: [PATCH] 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 INTEXT-73 Replaced String[] with List INTEXT-73 fixed test Polishing code format --- .../smpp/core/SmesMessageSpecification.java | 288 ++++++++++++------ .../outbound/SmppOutboundChannelAdapter.java | 30 +- .../smpp/outbound/SmppOutboundGateway.java | 29 +- .../smpp/TestSmppOutboundGateway.java | 47 ++- .../TestSmppOutboundGatewayWithChain.java | 51 ++-- 5 files changed, 304 insertions(+), 141 deletions(-) diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java index f4574c5..40ee5bd 100644 --- a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/core/SmesMessageSpecification.java @@ -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 tiny bit easier. For internal use only. + * Fluent API to help make specifying all these parameters just a tiny 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 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.valueIfHeaderExists(DST_NPI, msg)); - spec.setSourceAddressNumberingPlanIndicator(SmesMessageSpecification.valueIfHeaderExists(SRC_NPI, msg)); + spec.setDestinationAddressNumberingPlanIndicator( + SmesMessageSpecification.valueIfHeaderExists(DST_NPI, msg)); + spec.setSourceAddressNumberingPlanIndicator( + SmesMessageSpecification.valueIfHeaderExists(SRC_NPI, msg)); spec.setDestinationAddressTypeOfNumber(SmesMessageSpecification.valueIfHeaderExists(DST_TON, msg)); spec.setSourceAddressTypeOfNumber(SmesMessageSpecification.valueIfHeaderExists(SRC_TON, msg)); spec.setServiceType(SmesMessageSpecification.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. *

- * 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 and 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 and 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 send() throws Exception { validate(); - final String msgId; + List msgIds = new LinkedList(); 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 - * validity period 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. + * validity period 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. *

- * 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. *

* That information came from the NowSMS website.. - * - * @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. *

* 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 *

* 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(); smppSession = null; messagePayloadParameter = null; return this; @@ -663,6 +774,7 @@ public class SmesMessageSpecification { } return this; } + } /* private static String fromPropertyToHeaderConstant(String n) { diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java index a9fb59e..3fdafd3 100644 --- a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundChannelAdapter.java @@ -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 all the tedium of converting and validating the configuration. *

- * This adapter supports mobile terminated (MT) messaging, where the recipient is a directory phone number. + * This adapter supports mobile terminated (MT) 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 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"; } + } diff --git a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java index 2a22732..5c0caa5 100644 --- a/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java +++ b/spring-integration-smpp/src/main/java/org/springframework/integration/smpp/outbound/SmppOutboundGateway.java @@ -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. *

- * 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. *

- * 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. *

* * @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 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"; } + } diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java index 1c8aebb..e7e941d 100644 --- a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGateway.java @@ -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 smsMsg = MessageBuilder.withPayload(this.smsMessageToSend) + Message 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 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) response.getPayload()).size()); + log.info("received the SMS Message ID: " + response.getPayload()); + } + } diff --git a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGatewayWithChain.java b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGatewayWithChain.java index 3435ae5..1cb17d3 100644 --- a/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGatewayWithChain.java +++ b/spring-integration-smpp/src/test/java/org/springframework/integration/smpp/TestSmppOutboundGatewayWithChain.java @@ -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 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 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); + } }