fr.sii.ogham.sms.builder.SmsBuilder Maven / Gradle / Ivy
package fr.sii.ogham.sms.builder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.sii.ogham.core.builder.Builder;
import fr.sii.ogham.core.builder.ContentTranslatorBuilder;
import fr.sii.ogham.core.builder.MessageFillerBuilder;
import fr.sii.ogham.core.builder.MessagingSenderBuilder;
import fr.sii.ogham.core.builder.TemplateBuilder;
import fr.sii.ogham.core.condition.AndCondition;
import fr.sii.ogham.core.condition.Condition;
import fr.sii.ogham.core.condition.RequiredClassCondition;
import fr.sii.ogham.core.condition.RequiredPropertyCondition;
import fr.sii.ogham.core.exception.builder.BuildException;
import fr.sii.ogham.core.filler.MessageFiller;
import fr.sii.ogham.core.message.Message;
import fr.sii.ogham.core.sender.ConditionalSender;
import fr.sii.ogham.core.sender.ContentTranslatorSender;
import fr.sii.ogham.core.sender.FillerSender;
import fr.sii.ogham.core.sender.MessageSender;
import fr.sii.ogham.core.sender.MultiImplementationSender;
import fr.sii.ogham.core.translator.content.ContentTranslator;
import fr.sii.ogham.core.util.BuilderUtils;
import fr.sii.ogham.sms.SmsConstants;
import fr.sii.ogham.sms.message.addressing.translator.PhoneNumberTranslator;
import fr.sii.ogham.sms.sender.SmsSender;
import fr.sii.ogham.sms.sender.impl.CloudhopperSMPPSender;
import fr.sii.ogham.sms.sender.impl.OvhSmsSender;
import fr.sii.ogham.sms.sender.impl.PhoneNumberTranslatorSender;
import fr.sii.ogham.template.TemplateConstants;
/**
*
* Specialized builder for SMS sender.
*
* There exists several implementations to send an SMS:
*
* - Using
* cloudhopper (SMPP
* library)
* - Using jsmpp (SMPP library)
* - Using any other library
* - Using HTTP request to drive OVH
* API
* - Using REST, HTTP or SOAP requests to drive
* smsglobal APIs
* - Using any other web service
* - ...
*
*
* This builder provides a {@link MultiImplementationSender}. The aim of the
* {@link MultiImplementationSender} is to choose the best implementation for
* sending the SMS according to the runtime environment (detection of libraries
* in the classpath, availability of a particular property, ...).
*
*
* This builder lets you the possibility to register any new implementation. It
* allows you to enable or not templating support and automatic filling of
* message values (like sender address for example).
*
*
* @author Aurélien Baudet
* @see SmsSender
* @see OvhSmsSender
* @see CloudhopperSMPPSender
* @see TemplateBuilder
* @see ContentTranslatorBuilder
* @see MessageFillerBuilder
*/
public class SmsBuilder implements MessagingSenderBuilder {
private static final Logger LOG = LoggerFactory.getLogger(SmsBuilder.class);
/**
* The sender instance constructed by this builder
*/
private ConditionalSender sender;
/**
* The specialized {@link MultiImplementationSender}. It is useful to
* register new implementations
*/
private SmsSender smsSender;
/**
* The builder for message filler used to add values to the message
*/
private MessageFillerBuilder messageFillerBuilder;
/**
* Map of possible implementations with associated conditions
*/
private final Map, Builder extends MessageSender>> implementations;
/**
* The builder for the translator that will update the content of the
* message
*/
private ContentTranslatorBuilder contentTranslatorBuilder;
/**
* Builder for phone number transformations for receiver
*/
private PhoneNumberTranslatorBuilder recipientNumberTranslatorBuilder;
/**
* Builder for phone number transformations for sender
*/
private PhoneNumberTranslatorBuilder senderNumberTranslatorBuilder;
/**
* Own property key for template resolution parent path
*/
private String templateParentPathKey;
/**
* Own property key for template resolution extension
*/
private String templateExtensionKey;
public SmsBuilder() {
super();
smsSender = new SmsSender();
sender = smsSender;
implementations = new HashMap<>();
}
@Override
public ConditionalSender build() throws BuildException {
for (Entry, Builder extends MessageSender>> impl : implementations.entrySet()) {
MessageSender s = impl.getValue().build();
LOG.debug("Implementation {} registered", s);
smsSender.addImplementation(impl.getKey(), s);
}
if (contentTranslatorBuilder != null) {
if (templateParentPathKey != null) {
LOG.debug("Use custom property key {} for parent path template resolution", templateParentPathKey);
getTemplateBuilder().setParentPathKey(templateParentPathKey);
}
if (templateExtensionKey != null) {
LOG.debug("Use custom property key {} for extension template resolution", templateExtensionKey);
getTemplateBuilder().setExtensionKey(templateExtensionKey);
}
sender = new ContentTranslatorSender(contentTranslatorBuilder.build(), sender);
}
if (senderNumberTranslatorBuilder == null) {
LOG.debug("Using default phone number translation for sender phone number");
senderNumberTranslatorBuilder = new DefaultPhoneNumberTranslatorBuilder();
}
if (recipientNumberTranslatorBuilder == null) {
LOG.debug("Using default phone number translation for recipient phone number");
recipientNumberTranslatorBuilder = new DefaultPhoneNumberTranslatorBuilder();
}
sender = new PhoneNumberTranslatorSender(senderNumberTranslatorBuilder.build(), recipientNumberTranslatorBuilder.build(), sender);
if (messageFillerBuilder != null) {
MessageFiller messageFiller = messageFillerBuilder.build();
LOG.debug("Automatic filling of message enabled {}", messageFiller);
sender = new FillerSender(messageFiller, sender);
}
return sender;
}
/**
* Tells the builder to use all default behaviors and values:
*
* - Registers OVH HTTP API implementation
* - Enables automatic filling of message based on configuration
* properties
* - Enables templating support
*
*
* Configuration values come from system properties.
*
*
* @return this instance for fluent use
*/
public SmsBuilder useDefaults() {
return useDefaults(BuilderUtils.getDefaultProperties());
}
/**
* Tells the builder to use all default behaviors and values:
*
* - Registers OVH HTTP API implementation
* - Enables automatic filling of message based on configuration
* properties
* - Enables templating support
*
*
* Configuration values come from provided properties.
*
*
* @param properties
* the properties to use instead of default ones
* @return this instance for fluent use
*/
public SmsBuilder useDefaults(Properties properties) {
registerDefaultImplementations(properties);
withPhoneNumberTranslation();
withAutoFilling(properties);
withTemplate();
return this;
}
/**
* Register a new implementation for sending SMS. The implementation is
* associated to a condition. If the condition evaluation returns true at
* runtime then it means that the implementation can be used. If several
* implementations are available, only the first implementation is really
* invoked.
*
* @param condition
* the condition that indicates at runtime if the implementation
* can be used or not
* @param implementation
* the implementation to register
* @return this instance for fluent use
*/
public SmsBuilder registerImplementation(Condition condition, MessageSender implementation) {
smsSender.addImplementation(condition, implementation);
return this;
}
/**
* Register a new implementation for sending SMS. The implementation is
* associated to a condition. If the condition evaluation returns true at
* runtime then it means that the implementation can be used. If several
* implementations are available, only the first implementation is really
* invoked.
*
* @param condition
* the condition that indicates at runtime if the implementation
* can be used or not
* @param builder
* the builder for the implementation to register
* @return this instance for fluent use
*/
public SmsBuilder registerImplementation(Condition condition, Builder extends MessageSender> builder) {
implementations.put(condition, builder);
return this;
}
/**
* Register all default implementations:
*
* - OVH HTTP API implementation
*
*
* Configuration values come from system properties.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @return this instance for fluent use
*/
public SmsBuilder registerDefaultImplementations() {
return registerDefaultImplementations(System.getProperties());
}
/**
* Register all default implementations:
*
* - OVH HTTP API implementation
* - smsgloabl REST API implementation
* - Cloudhopper SMPP implementation
*
*
* Configuration values come from provided properties.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @param properties
* the properties to use
* @return this instance for fluent use
*/
public SmsBuilder registerDefaultImplementations(Properties properties) {
withOvhHttpApi(properties);
withSmsglobalRestApi(properties);
withCloudhopper(properties);
return this;
}
/**
* Enable smsglobal REST API implementation. This implementation is used
* only if the associated condition indicates that smsglobal REST API can be
* used. The condition checks if:
*
* - The property
ogham.sms.smsglobal.api.key
is set
*
*
* @param properties
* the properties to use for checking if property exists
* @return this builder instance for fluent use
*/
public SmsBuilder withSmsglobalRestApi(Properties properties) {
// Use smsglobal REST API only if
// SmsConstants.SMSGLOBAL_REST_API_KEY_PROPERTY is set
// registerImplementation(new
// RequiredPropertyCondition(SmsConstants.SmsGlobal.SMSGLOBAL_REST_API_KEY_PROPERTY,
// properties), new SmsglobalRestSender());
return this;
}
/**
* Enable OVH HTTP API implementation. This implementation is used only if
* the associated condition indicates that OVH HTTP API can be used. The
* condition checks if:
*
* - The property
ogham.sms.ovh.account
is set
* - The property
ogham.sms.ovh.login
is set
* - The property
ogham.sms.ovh.password
is set
*
*
* @param properties
* the properties to use for checking if property exists
* @return this builder instance for fluent use
*/
public SmsBuilder withOvhHttpApi(Properties properties) {
try {
// Use OVH implementation only if SmsConstants.ACCOUNT_PROPERTY is
// set
// @formatter:off
registerImplementation(new AndCondition<>(
new RequiredPropertyCondition(SmsConstants.OvhConstants.ACCOUNT_PROPERTY, properties),
new RequiredPropertyCondition(SmsConstants.OvhConstants.LOGIN_PROPERTY, properties),
new RequiredPropertyCondition(SmsConstants.OvhConstants.PASSWORD_PROPERTY, properties)),
new OvhSmsBuilder().useDefaults(properties));
// @formatter:on
} catch (Exception e) {
LOG.debug("Can't register OVH implementation", e);
}
return this;
}
/**
* Enable Cloudhoppder SMPP implementation. This implementation is used only
* if the associated condition indicates that Cloudhopper SMPP can be used.
* The condition checks if:
*
* - The property
ogham.sms.smpp.host
is set
* - The property
ogham.sms.smpp.port
is set
* - The class
com.cloudhopper.smpp.SmppClient
is available
* in the classpath
*
* The registration can silently fail if the ch-smpp jar is not in the
* classpath. In this case, the Cloudhopper implementation is not registered
* at all.
*
* @param properties
* the properties to use for checking if property exists
* @return this builder instance for fluent use
*/
public SmsBuilder withCloudhopper(Properties properties) {
try {
// Use Cloudhopper SMPP implementation only if SmppClient class is
// in the classpath and the SmppConstants.SMPP_HOST_PROPERTY
// property is set
// @formatter:off
registerImplementation(new AndCondition<>(
new RequiredPropertyCondition(SmsConstants.SmppConstants.HOST_PROPERTY, properties),
new RequiredPropertyCondition(SmsConstants.SmppConstants.PORT_PROPERTY, properties),
new RequiredClassCondition("com.cloudhopper.smpp.SmppClient")),
new CloudhopperSMPPBuilder().useDefaults(properties));
// @formatter:on
} catch (Exception e) {
LOG.debug("Can't register Cloudhopper implementation", e);
}
return this;
}
/**
* Enables automatic filling of SMS with values that come from multiple
* sources. It let you use your own builder instead of using default
* behaviors.
*
* @param builder
* the builder for constructing the message filler
* @return this instance for fluent use
*/
public SmsBuilder withAutoFilling(MessageFillerBuilder builder) {
messageFillerBuilder = builder;
return this;
}
/**
* Enables filling of SMS with values that comes from provided configuration
* properties.
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @param props
* the properties that contains the values to set on the SMS
* @param baseKeys
* the prefix(es) for the keys used for filling the message
* @return this instance for fluent use
*/
public SmsBuilder withAutoFilling(Properties props, String... baseKeys) {
withAutoFilling(new MessageFillerBuilder().useDefaults(props, baseKeys));
return this;
}
/**
* Enables filling of SMS with values that comes from provided configuration
* properties. It uses the default prefix for the keys ("sms" and
* "ogham.sms").
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @param props
* the properties that contains the values to set on the SMS
* @return this instance for fluent use
*/
public SmsBuilder withAutoFilling(Properties props) {
return withAutoFilling(props, SmsConstants.FILL_PREFIXES);
}
/**
* Enables filling of SMS with values that comes from system configuration
* properties. It uses the default prefixes for the keys ("sms" and
* "ogham.sms").
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @return this instance for fluent use
*/
public SmsBuilder withAutoFilling() {
withAutoFilling(BuilderUtils.getDefaultProperties());
return this;
}
/**
* Enables templating support using all default behaviors and values. See
* {@link ContentTranslatorBuilder#useDefaults()} for more information.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @return this instance for fluent use
*/
public SmsBuilder withTemplate() {
return withTemplate(new ContentTranslatorBuilder().useDefaults());
}
/**
* Enables templating support using the provided
* {@link ContentTranslatorBuilder}. It decorates the SMS sender with a
* {@link ContentTranslatorSender}.
*
* @param builder
* the builder to use to build the {@link ContentTranslator}
* instead of using the default one
* @return this instance for fluent use
*/
public SmsBuilder withTemplate(ContentTranslatorBuilder builder) {
contentTranslatorBuilder = builder;
return this;
}
/**
* Disable templating support.
*
* @return this instance for fluent use
*/
public SmsBuilder withoutTemplate() {
this.contentTranslatorBuilder = null;
return this;
}
/**
*
* Calling this method will enable different location for SMS templates from
* default one. The location will be specified by different property keys
* for parent path and extension.
*
*
* By default default properties are:
*
* - ogham.template.prefix (see {@link TemplateConstants#PREFIX_PROPERTY})
*
* - ogham.template.suffix (see
* {@link TemplateConstants#SUFFIX_PROPERTY}
*
*
* Calling this method will change the property keys to:
*
* - ogham.sms.template.prefix (see
* {@link fr.sii.ogham.sms.SmsConstants.TemplateConstants#PREFIX_PROPERTY}
* - ogham.sms.template.suffix (see
* {@link fr.sii.ogham.sms.SmsConstants.TemplateConstants#SUFFIX_PROPERTY}
*
*
* @return this instance for fluent use
*/
public SmsBuilder enableSmsTemplateKeys() {
setTemplateParentPathKey(SmsConstants.TemplateConstants.PREFIX_PROPERTY);
setTemplateExtensionKey(SmsConstants.TemplateConstants.SUFFIX_PROPERTY);
return this;
}
/**
*
* Calling this method will enable different location for SMS templates from
* default one. The location will be specified by a different property key
* for parent path.
*
*
*
* By default default property key is ogham.template.prefix (see
* {@link TemplateConstants#PREFIX_PROPERTY})
*
*
*
* Calling this method will change the property key to the provided key.
*
*
* @param parentPathKey
* the new key for the SMS template parent path
* @return this instance for fluent use
*/
public SmsBuilder setTemplateParentPathKey(String parentPathKey) {
this.templateParentPathKey = parentPathKey;
return this;
}
/**
*
* Calling this method will enable different location for SMS templates from
* default one. The location will be specified by a different property key
* for extension.
*
*
*
* By default default property key is ogham.template.suffix (see
* {@link TemplateConstants#SUFFIX_PROPERTY})
*
*
*
* Calling this method will change the property key to the provided key.
*
*
* @param extensionKey
* the new key for the SMS template extension
* @return this instance for fluent use
*/
public SmsBuilder setTemplateExtensionKey(String extensionKey) {
this.templateExtensionKey = extensionKey;
return this;
}
/**
* Enables Addressing strategy using all default behaviors and values. See
* {@link SenderPhoneNumberTranslatorBuilder} and
* {@link RecipientPhoneNumberTranslatorBuilder}.
*
*
* Automatically called by {@link #useDefaults()} and
* {@link #useDefaults(Properties)}
*
*
* @return this instance for fluent use
*/
public SmsBuilder withPhoneNumberTranslation() {
return withPhoneNumberTranslation(new SenderPhoneNumberTranslatorBuilder().useDefaults(), new RecipientPhoneNumberTranslatorBuilder().useDefaults());
}
/**
* Enables Addressing strategy using the provided
* {@link SenderPhoneNumberTranslatorBuilder} and
* {@link RecipientPhoneNumberTranslatorBuilder}. It decorates the SMS
* sender with a {@link PhoneNumberTranslatorSender}
*
* @param senderBuilder
* the builder to use to build the {@link PhoneNumberTranslator}
* for sender instead of using the default one
* @param recipientBuilder
* the builder to use to build the {@link PhoneNumberTranslator}
* for sender instead of using the default one
* @return this instance for fluent use
*/
public SmsBuilder withPhoneNumberTranslation(PhoneNumberTranslatorBuilder senderBuilder, PhoneNumberTranslatorBuilder recipientBuilder) {
withSenderPhoneNumberTranslation(senderBuilder);
withReceiverPhoneNumberTranslation(recipientBuilder);
return this;
}
/**
* Enables Addressing strategy using the provided
* {@link RecipientPhoneNumberTranslatorBuilder}. It decorates the SMS
* sender with a {@link PhoneNumberTranslatorSender}
*
* @param builder
* the builder to use to build the {@link PhoneNumberTranslator}
* instead of using the default one
* @return this instance for fluent use
*/
public SmsBuilder withReceiverPhoneNumberTranslation(PhoneNumberTranslatorBuilder builder) {
recipientNumberTranslatorBuilder = builder;
return this;
}
/**
* Enables Addressing strategy using the provided
* {@link SenderPhoneNumberTranslatorBuilder}. It decorates the SMS sender
* with a {@link PhoneNumberTranslatorSender}
*
* @param builder
* the builder to use to build the {@link PhoneNumberTranslator}
* instead of using the default one
* @return this instance for fluent use
*/
public SmsBuilder withSenderPhoneNumberTranslation(PhoneNumberTranslatorBuilder builder) {
senderNumberTranslatorBuilder = builder;
return this;
}
/**
*
* Get the builder used for filling messages.
*
*
* Access this builder if you want to:
*
* - Enable/disable automatic filling of messages with values provided in
* configuration
* - Enable/disable automatic filling of subject for messages based on
* templates
* - Add your own message filler
*
*
* @return the builder used for filling messages
*/
public MessageFillerBuilder getMessageFillerBuilder() {
return messageFillerBuilder;
}
/**
*
* Get the builder used transform the content of the message. It may be
* useful to fine tune templating mechanism, resource inlining and messages
* with with several contents.
*
*
* Access this builder if you want to:
*
* - Customize templating mechanism (see
* {@link #getTemplateBuilder()})
* - Enable/disable support for messages with multiple contents
* - Enable/disable support for inlining of resources
* - Add your own content translator
*
*
* @return the builder used to transform the content of the message
*/
public ContentTranslatorBuilder getContentTranslatorBuilder() {
return contentTranslatorBuilder;
}
/**
*
* Shortcut to directly access template builder for fine tuning templating
* mechanism.
*
*
* Access this builder if you want to:
*
* - Customize how template resources are resolved
* - Register a custom lookup mapping resolver for template resources
* - Use your own template engine
* - Customize the template engine configuration
* - Set the parent path and extension for template resolution
* - Set the property key for parent path and extension resolution
*
*
* @return the template builder
*/
public TemplateBuilder getTemplateBuilder() {
return contentTranslatorBuilder.getTemplateBuilder();
}
/**
*
* Get the builder for transformation of recipient phone numbers.
*
*
* Access to this builder if you want to:
*
* - Enable/disable international format transformation
*
*
* @return the builder for transformation of recipient phone numbers
*/
public PhoneNumberTranslatorBuilder getRecipientNumberTranslatorBuilder() {
return recipientNumberTranslatorBuilder;
}
/**
*
* Get the builder for transformation of sender phone numbers.
*
*
* Access to this builder if you want to:
*
* - Enable/disable alpha-numeric format transformation
* - Enable/disable short code format transformation
* - Enable/disable international format transformation
*
*
* @return the builder for transformation of sender phone numbers
*/
public PhoneNumberTranslatorBuilder getSenderNumberTranslatorBuilder() {
return senderNumberTranslatorBuilder;
}
}