BaseMessagePreparator.java
package fr.sii.ogham.sms.sender.impl.cloudhopper.preparator;
import static com.cloudhopper.smpp.SmppConstants.ESM_CLASS_UDHI_MASK;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudhopper.smpp.pdu.SubmitSm;
import com.cloudhopper.smpp.type.Address;
import fr.sii.ogham.core.exception.InvalidMessageException;
import fr.sii.ogham.sms.encoder.Encoded;
import fr.sii.ogham.sms.exception.message.PhoneNumberTranslatorException;
import fr.sii.ogham.sms.exception.message.SplitMessageException;
import fr.sii.ogham.sms.message.Contact;
import fr.sii.ogham.sms.message.PhoneNumber;
import fr.sii.ogham.sms.message.Recipient;
import fr.sii.ogham.sms.message.Sms;
import fr.sii.ogham.sms.message.addressing.AddressedPhoneNumber;
import fr.sii.ogham.sms.message.addressing.translator.PhoneNumberTranslator;
import fr.sii.ogham.sms.sender.impl.cloudhopper.exception.DataCodingException;
import fr.sii.ogham.sms.sender.impl.cloudhopper.exception.MessagePreparationException;
import fr.sii.ogham.sms.splitter.EncodedSegment;
import fr.sii.ogham.sms.splitter.MessageSplitter;
import fr.sii.ogham.sms.splitter.Segment;
/**
* Base preparator that creates {@link SubmitSm}s.
*
* <p>
* The message content is not set. It lets implementations implement
* {@code fill(SubmitSm, Segment)} method to set the content.
*
* <p>
* This preparator detects which charset should be used to encode message string
* and splits it if needed. It also converts {@link PhoneNumber} to
* {@link AddressedPhoneNumber}.
*
* @author Aurélien Baudet
*
*/
public abstract class BaseMessagePreparator implements MessagePreparator {
private static final Logger LOG = LoggerFactory.getLogger(BaseMessagePreparator.class);
/**
* Split message if needed into several parts
*/
private final MessageSplitter messageSplitter;
/**
* Determines the data coding to use according to encoding
*/
private final DataCodingProvider dataCodingProvider;
/**
* This phone number translator will handle the fallback addressing policy
* (TON / NPI).
*/
private final PhoneNumberTranslator phoneNumberTranslator;
/**
* The preparator can split messages and provide data coding information.
*
* <p>
* The phone number won't be translated meaning that if phone number is not
* already converted to {@link AddressedPhoneNumber} then an
* {@link IllegalStateException} is thrown.
*
* @param messageSplitter
* Split message in several parts if needed
* @param dataCodingProvider
* Determines the data coding to use according to encoding
*/
public BaseMessagePreparator(MessageSplitter messageSplitter, DataCodingProvider dataCodingProvider) {
this(messageSplitter, dataCodingProvider, null);
}
/**
* Initializes the preparator with message splitter, data coding provider
* and phone number translator.
*
* @param messageSplitter
* Split message in several parts if needed
* @param dataCodingProvider
* Determines the data coding to use according to encoding
* @param phoneNumberTranslator
* Fallback phone translator to handle addressing policy
*/
public BaseMessagePreparator(MessageSplitter messageSplitter, DataCodingProvider dataCodingProvider, PhoneNumberTranslator phoneNumberTranslator) {
super();
this.messageSplitter = messageSplitter;
this.dataCodingProvider = dataCodingProvider;
this.phoneNumberTranslator = phoneNumberTranslator;
}
@Override
public List<SubmitSm> prepareMessages(Sms message) throws MessagePreparationException {
try {
return createMessages(message);
} catch (PhoneNumberTranslatorException | DataCodingException | InvalidMessageException e) {
throw new MessagePreparationException("Failed to prepare messages", message, e);
} catch (SplitMessageException e) {
throw new MessagePreparationException("Failed to split SMPP message before sending it", message, e);
}
}
/**
* Fill the {@link SubmitSm} with the message content.
*
* @param originalMessage
* the SMS that is about to be sent
* @param submit
* the submit to fill
* @param part
* the message content
* @throws MessagePreparationException
* when message couldn't be prepared correctly
*/
protected abstract void fill(Sms originalMessage, SubmitSm submit, Segment part) throws MessagePreparationException;
private List<SubmitSm> createMessages(Sms message) throws PhoneNumberTranslatorException, SplitMessageException, DataCodingException, MessagePreparationException, InvalidMessageException {
List<SubmitSm> messages = new ArrayList<>();
for (Recipient recipient : message.getRecipients()) {
messages.addAll(createMessages(message, recipient));
}
return messages;
}
private List<SubmitSm> createMessages(Sms message, Recipient recipient) throws PhoneNumberTranslatorException, SplitMessageException, DataCodingException, MessagePreparationException, InvalidMessageException {
List<SubmitSm> messages = new ArrayList<>();
List<Segment> parts = messageSplitter.split(message.getContent().toString());
if (parts.size() <= 1) {
addSubmit(message, recipient, messages, parts.get(0));
return messages;
}
LOG.debug("Content split into {} parts", parts.size());
for (Segment part : parts) {
addEsmClassSubmit(message, recipient, messages, part);
}
return messages;
}
private void addEsmClassSubmit(Sms message, Recipient recipient, List<SubmitSm> messages, Segment part) throws PhoneNumberTranslatorException, DataCodingException, MessagePreparationException, InvalidMessageException {
SubmitSm submit = createMessage(message, recipient, part);
submit.setEsmClass(ESM_CLASS_UDHI_MASK);
messages.add(submit);
}
private void addSubmit(Sms message, Recipient recipient, List<SubmitSm> messages, Segment part) throws PhoneNumberTranslatorException, DataCodingException, MessagePreparationException, InvalidMessageException {
SubmitSm submit = createMessage(message, recipient, part);
messages.add(submit);
}
private SubmitSm createMessage(Sms message, Recipient recipient, Segment part) throws PhoneNumberTranslatorException, DataCodingException, MessagePreparationException, InvalidMessageException {
SubmitSm submit = new SubmitSm();
submit.setSourceAddress(toAddress("sender", message, message.getFrom()));
submit.setDestAddress(toAddress("recipient", message, recipient));
if (part instanceof EncodedSegment) {
Encoded encoded = ((EncodedSegment) part).getEncoded();
submit.setDataCoding(dataCodingProvider.provide(encoded).getByteValue());
}
fill(message, submit, part);
return submit;
}
private Address toAddress(String field, Sms msg, Contact contact) throws PhoneNumberTranslatorException, InvalidMessageException {
if (contact == null) {
throw new InvalidMessageException(field+" is not set", msg, "Missing "+field+" phone number");
}
PhoneNumber phoneNumber = contact.getPhoneNumber();
if (phoneNumber == null) {
throw new InvalidMessageException(field+" phone number is not set", msg, "Missing "+field+" phone number");
}
return toAddress(phoneNumber);
}
/**
* Transforms a {@link PhoneNumber} in a {@link Address} type.
*
* @param phoneNumber
* The given phone number
* @return corresponding address with number, TON and NPI
* @throws PhoneNumberTranslatorException
* If an error occurs during fallback phone number translation
*/
private Address toAddress(PhoneNumber phoneNumber) throws PhoneNumberTranslatorException {
AddressedPhoneNumber addressedPhoneNumber;
if (phoneNumber instanceof AddressedPhoneNumber) {
addressedPhoneNumber = (AddressedPhoneNumber) phoneNumber;
} else if (phoneNumberTranslator != null) {
LOG.warn("Fallback addressing policy used for PhoneNumber '{}'. You might decorate your sender with a PhoneNumberTranslatorSender.", phoneNumber);
addressedPhoneNumber = phoneNumberTranslator.translate(phoneNumber);
} else {
throw new IllegalStateException("Must provide addressing policy with the phone number or with a fallback phone number translator.");
}
LOG.debug("Addressing policy applied on {} ", addressedPhoneNumber);
return new Address(addressedPhoneNumber.getTon().value(), addressedPhoneNumber.getNpi().value(), addressedPhoneNumber.getNumber());
}
}