CloudhopperBuilder.java

  1. package fr.sii.ogham.sms.builder.cloudhopper;


  2. import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_BIND_TYPE;
  3. import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_CHARSET;
  4. import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_INTERFACE_VERSION;
  5. import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_RESPONSE_TIMEOUT;
  6. import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_UNBIND_TIMEOUT;

  7. import java.util.function.Consumer;
  8. import java.util.function.Supplier;

  9. import org.slf4j.Logger;
  10. import org.slf4j.LoggerFactory;
  11. import org.slf4j.event.Level;

  12. import com.cloudhopper.commons.charset.Charset;
  13. import com.cloudhopper.smpp.SmppBindType;
  14. import com.cloudhopper.smpp.SmppClient;
  15. import com.cloudhopper.smpp.SmppConstants;
  16. import com.cloudhopper.smpp.SmppSessionConfiguration;
  17. import com.cloudhopper.smpp.SmppSessionHandler;
  18. import com.cloudhopper.smpp.impl.DefaultSmppClient;
  19. import com.cloudhopper.smpp.impl.DefaultSmppSessionHandler;
  20. import com.cloudhopper.smpp.pdu.Pdu;
  21. import com.cloudhopper.smpp.pdu.SubmitSm;
  22. import com.cloudhopper.smpp.ssl.SslConfiguration;
  23. import com.cloudhopper.smpp.type.Address;
  24. import com.cloudhopper.smpp.type.LoggingOptions;

  25. import fr.sii.ogham.core.async.ThreadSleepAwaiter;
  26. import fr.sii.ogham.core.builder.Builder;
  27. import fr.sii.ogham.core.builder.MessagingBuilder;
  28. import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilder;
  29. import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilderHelper;
  30. import fr.sii.ogham.core.builder.configurer.Configurer;
  31. import fr.sii.ogham.core.builder.context.BuildContext;
  32. import fr.sii.ogham.core.builder.context.DefaultBuildContext;
  33. import fr.sii.ogham.core.fluent.AbstractParent;
  34. import fr.sii.ogham.core.retry.RetryExecutor;
  35. import fr.sii.ogham.core.retry.SimpleRetryExecutor;
  36. import fr.sii.ogham.sms.builder.SmsBuilder;
  37. import fr.sii.ogham.sms.builder.cloudhopper.UserDataBuilder.UserDataPropValues;
  38. import fr.sii.ogham.sms.encoder.Encoder;
  39. import fr.sii.ogham.sms.message.Sms;
  40. import fr.sii.ogham.sms.message.addressing.translator.CompositePhoneNumberTranslator;
  41. import fr.sii.ogham.sms.message.addressing.translator.DefaultHandler;
  42. import fr.sii.ogham.sms.message.addressing.translator.PhoneNumberTranslator;
  43. import fr.sii.ogham.sms.sender.impl.CloudhopperSMPPSender;
  44. import fr.sii.ogham.sms.sender.impl.cloudhopper.ExtendedSmppSessionConfiguration;
  45. import fr.sii.ogham.sms.sender.impl.cloudhopper.KeepAliveOptions;
  46. import fr.sii.ogham.sms.sender.impl.cloudhopper.encoder.CloudhopperCharsetSupportingEncoder;
  47. import fr.sii.ogham.sms.sender.impl.cloudhopper.encoder.NamedCharset;
  48. import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.CharsetMapToCharacterEncodingGroupDataCodingProvider;
  49. import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.DataCodingProvider;
  50. import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.MessagePreparator;
  51. import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.ShortMessagePreparator;
  52. import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.TlvMessagePayloadMessagePreparator;
  53. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.AlwaysNewSessionStrategy;
  54. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.DefaultErrorAnalyzer;
  55. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.ErrorAnalyzer;
  56. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.ErrorHandler;
  57. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.KeepSessionAliveStrategy;
  58. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.LogErrorHandler;
  59. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.MayReuseSessionStrategy;
  60. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.RespondToDeliveryReceiptHandler;
  61. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.RespondToEnquireLinkRequestHandler;
  62. import fr.sii.ogham.sms.sender.impl.cloudhopper.session.SessionHandlingStrategy;
  63. import fr.sii.ogham.sms.splitter.GsmMessageSplitter;
  64. import fr.sii.ogham.sms.splitter.MessageSplitter;
  65. import fr.sii.ogham.sms.splitter.NoSplitMessageSplitter;
  66. import fr.sii.ogham.sms.splitter.ReferenceNumberGenerator;

  67. /**
  68.  * Configures Cloudhopper:
  69.  *
  70.  * <ul>
  71.  * <li>SMPP protocol parameters (host, port, systemId, password,
  72.  * version...)</li>
  73.  * <li>Session management (name, bind, timeouts, retry...)</li>
  74.  * <li>SSL configuration</li>
  75.  * <li>Logging options</li>
  76.  * </ul>
  77.  *
  78.  * <p>
  79.  * To send {@link Sms} using Cloudhopper, you need to register this builder into
  80.  * a {@link MessagingBuilder} like this:
  81.  *
  82.  * <pre>
  83.  * <code>
  84.  * MessagingBuilder msgBuilder = ...
  85.  * msgBuilder.sms()
  86.  *    .sender(CloudhopperBuilder.class)    // registers the builder and accesses to that builder for configuring it
  87.  * </code>
  88.  * </pre>
  89.  *
  90.  * Once the builder is registered, sending sms through Cloudhopper requires at
  91.  * least host of the SMPP server. You can define it using:
  92.  *
  93.  * <pre>
  94.  * <code>
  95.  * msgBuilder.sms()
  96.  *    .sender(CloudhopperBuilder.class)    // registers the builder and accesses to that builder for configuring it
  97.  *       .host("localhost")
  98.  * </code>
  99.  * </pre>
  100.  *
  101.  * Or you can also use property keys (using interpolation):
  102.  *
  103.  * <pre>
  104.  * <code>
  105.  * msgBuilder
  106.  * .environment()
  107.  *    .properties()
  108.  *       .set("custom.property.for.host", "localhost")
  109.  *       .and()
  110.  *    .and()
  111.  * .sms()
  112.  *    .sender(CloudhopperBuilder.class)    // registers the builder and accesses to that builder for configuring it
  113.  *       .host()
  114.  *         .properties("${custom.property.for.host}")
  115.  * </code>
  116.  * </pre>
  117.  *
  118.  * You can do the same with port of the SMPP server.
  119.  *
  120.  *
  121.  * <p>
  122.  * SMPP server may require authentication. In most cases, authentication is done
  123.  * using system_id/password. You can use this builder to quickly provide your
  124.  * system_id and password:
  125.  *
  126.  * <pre>
  127.  * <code>
  128.  * .sender(CloudhopperBuilder.class)
  129.  *        .systemId("foo")
  130.  *        .password("bar")
  131.  * </code>
  132.  * </pre>
  133.  *
  134.  *
  135.  * @author Aurélien Baudet
  136.  */
  137. public class CloudhopperBuilder extends AbstractParent<SmsBuilder> implements Builder<CloudhopperSMPPSender> {
  138.     private static final Logger LOG = LoggerFactory.getLogger(CloudhopperBuilder.class);

  139.     private final ReadableEncoderBuilder sharedEncoderBuilder;
  140.     private BuildContext buildContext;
  141.     private final ConfigurationValueBuilderHelper<CloudhopperBuilder, String> systemIdValueBuilder;
  142.     private final ConfigurationValueBuilderHelper<CloudhopperBuilder, String> passwordValueBuilder;
  143.     private final ConfigurationValueBuilderHelper<CloudhopperBuilder, String> hostValueBuilder;
  144.     private final ConfigurationValueBuilderHelper<CloudhopperBuilder, Integer> portValueBuilder;
  145.     private final ConfigurationValueBuilderHelper<CloudhopperBuilder, String> systemTypeValueBuilder;
  146.     private final ConfigurationValueBuilderHelper<CloudhopperBuilder, InterfaceVersion> interfaceVersionValueBuilder;
  147.     private final ConfigurationValueBuilderHelper<CloudhopperBuilder, SmppBindType> bindTypeValueBuilder;
  148.     private SessionBuilder sessionBuilder;
  149.     private ExtendedSmppSessionConfiguration sessionConfiguration;
  150.     private Address addressRange;
  151.     private SslBuilder sslBuilder;
  152.     private LoggingBuilder loggingBuilder;
  153.     private SmppClientSupplier clientSupplier;
  154.     private SmppSessionHandlerSupplier smppSessionHandler;
  155.     private MessageSplitterBuilder messageSplitterBuilder;
  156.     private EncoderBuilder encoderBuilder;
  157.     private UserDataBuilder userDataBuilder;
  158.     private DataCodingSchemeBuilder dataCodingBuilder;
  159.     private MessagePreparator preparator;

  160.     /**
  161.      * Default constructor when using without all Ogham work.
  162.      *
  163.      * <strong>WARNING: use is only if you know what you are doing !</strong>
  164.      */
  165.     public CloudhopperBuilder() {
  166.         this(null, new DefaultBuildContext());
  167.     }

  168.     /**
  169.      * Constructor that is called when using Ogham builder:
  170.      *
  171.      * <pre>
  172.      * MessagingBuilder msgBuilder = ...
  173.      * msgBuilder
  174.      * .sms()
  175.      *    .sender(CloudhopperBuilder.class)
  176.      * </pre>
  177.      *
  178.      * @param parent
  179.      *            the parent builder instance for fluent chaining
  180.      * @param buildContext
  181.      *            for registering instances and property evaluation
  182.      */
  183.     public CloudhopperBuilder(SmsBuilder parent, BuildContext buildContext) {
  184.         super(parent);
  185.         this.buildContext = buildContext;
  186.         sharedEncoderBuilder = new ReadableEncoderBuilder(buildContext);
  187.         systemIdValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
  188.         passwordValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
  189.         hostValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
  190.         portValueBuilder = buildContext.newConfigurationValueBuilder(this, Integer.class);
  191.         interfaceVersionValueBuilder = buildContext.newConfigurationValueBuilder(this, InterfaceVersion.class);
  192.         systemTypeValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
  193.         bindTypeValueBuilder = buildContext.newConfigurationValueBuilder(this, SmppBindType.class);
  194.     }

  195.     /**
  196.      * The system_id parameter is used to identify an ESME ( External Short
  197.      * Message Entity) or an SMSC (Short Message Service Centre) at bind time.
  198.      * An ESME system_id identifies the ESME or ESME agent to the SMSC. The SMSC
  199.      * system_id provides an identification of the SMSC to the ESME.
  200.      *
  201.      * <p>
  202.      * The value set using this method takes precedence over any property and
  203.      * default value configured using {@link #systemId()}.
  204.      *
  205.      * <pre>
  206.      * .systemId("my-system-id")
  207.      * .systemId()
  208.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  209.      *   .defaultValue("default-system-id")
  210.      * </pre>
  211.      *
  212.      * <pre>
  213.      * .systemId("my-system-id")
  214.      * .systemId()
  215.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  216.      *   .defaultValue("default-system-id")
  217.      * </pre>
  218.      *
  219.      * In both cases, {@code systemId("my-system-id")} is used.
  220.      *
  221.      * <p>
  222.      * If this method is called several times, only the last value is used.
  223.      *
  224.      * <p>
  225.      * If {@code null} value is set, it is like not setting a value at all. The
  226.      * property/default value configuration is applied.
  227.      *
  228.      * @param systemId
  229.      *            the system_id value
  230.      * @return this instance for fluent chaining
  231.      */
  232.     public CloudhopperBuilder systemId(String systemId) {
  233.         systemIdValueBuilder.setValue(systemId);
  234.         return this;
  235.     }

  236.     /**
  237.      * The system_id parameter is used to identify an ESME ( External Short
  238.      * Message Entity) or an SMSC (Short Message Service Centre) at bind time.
  239.      * An ESME system_id identifies the ESME or ESME agent to the SMSC. The SMSC
  240.      * system_id provides an identification of the SMSC to the ESME.
  241.      *
  242.      * <p>
  243.      * This method is mainly used by {@link Configurer}s to register some
  244.      * property keys and/or a default value. The aim is to let developer be able
  245.      * to externalize its configuration (using system properties, configuration
  246.      * file or anything else). If the developer doesn't configure any value for
  247.      * the registered properties, the default value is used (if set).
  248.      *
  249.      * <pre>
  250.      * .systemId()
  251.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  252.      *   .defaultValue("default-system-id")
  253.      * </pre>
  254.      *
  255.      * <p>
  256.      * Non-null value set using {@link #systemId(String)} takes precedence over
  257.      * property values and default value.
  258.      *
  259.      * <pre>
  260.      * .systemId("my-system-id")
  261.      * .systemId()
  262.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  263.      *   .defaultValue("default-system-id")
  264.      * </pre>
  265.      *
  266.      * The value {@code "my-system-id"} is used regardless of the value of the
  267.      * properties and default value.
  268.      *
  269.      * <p>
  270.      * See {@link ConfigurationValueBuilder} for more information.
  271.      *
  272.      *
  273.      * @return the builder to configure property keys/default value
  274.      */
  275.     public ConfigurationValueBuilder<CloudhopperBuilder, String> systemId() {
  276.         return systemIdValueBuilder;
  277.     }

  278.     /**
  279.      * The system_type parameter is used to categorize the type of ESME that is
  280.      * binding to the SMSC. Examples include “VMS” (voice mail system) and “OTA”
  281.      * (over-the-air activation system). Specification of the system_type is
  282.      * optional - some SMSC’s may not require ESME’s to provide this detail. In
  283.      * this case, the ESME can set the system_type to NULL. The system_type
  284.      * (optional) may be used to categorize the system, e.g., “EMAIL”, “WWW”,
  285.      * etc.
  286.      *
  287.      * <p>
  288.      * The value set using this method takes precedence over any property and
  289.      * default value configured using {@link #systemType()}.
  290.      *
  291.      * <pre>
  292.      * .systemType("my-system-type")
  293.      * .systemType()
  294.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  295.      *   .defaultValue("default-system-type")
  296.      * </pre>
  297.      *
  298.      * <pre>
  299.      * .systemType("my-system-type")
  300.      * .systemType()
  301.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  302.      *   .defaultValue("default-system-type")
  303.      * </pre>
  304.      *
  305.      * In both cases, {@code systemType("my-system-type")} is used.
  306.      *
  307.      * <p>
  308.      * If this method is called several times, only the last value is used.
  309.      *
  310.      * <p>
  311.      * If {@code null} value is set, it is like not setting a value at all. The
  312.      * property/default value configuration is applied.
  313.      *
  314.      * @param systemType
  315.      *            the system type value
  316.      * @return this instance for fluent chaining
  317.      */
  318.     public CloudhopperBuilder systemType(String systemType) {
  319.         systemTypeValueBuilder.setValue(systemType);
  320.         return this;
  321.     }

  322.     /**
  323.      * The system_type parameter is used to categorize the type of ESME that is
  324.      * binding to the SMSC. Examples include “VMS” (voice mail system) and “OTA”
  325.      * (over-the-air activation system). Specification of the system_type is
  326.      * optional - some SMSC’s may not require ESME’s to provide this detail. In
  327.      * this case, the ESME can set the system_type to NULL. The system_type
  328.      * (optional) may be used to categorize the system, e.g., “EMAIL”, “WWW”,
  329.      * etc.
  330.      *
  331.      * <p>
  332.      * This method is mainly used by {@link Configurer}s to register some
  333.      * property keys and/or a default value. The aim is to let developer be able
  334.      * to externalize its configuration (using system properties, configuration
  335.      * file or anything else). If the developer doesn't configure any value for
  336.      * the registered properties, the default value is used (if set).
  337.      *
  338.      * <pre>
  339.      * .systemType()
  340.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  341.      *   .defaultValue("defaut-system-type")
  342.      * </pre>
  343.      *
  344.      * <p>
  345.      * Non-null value set using {@link #systemType(String)} takes precedence
  346.      * over property values and default value.
  347.      *
  348.      * <pre>
  349.      * .systemType("my-system-type")
  350.      * .systemType()
  351.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  352.      *   .defaultValue("defaut-system-type")
  353.      * </pre>
  354.      *
  355.      * The value {@code "my-system-type"} is used regardless of the value of the
  356.      * properties and default value.
  357.      *
  358.      * <p>
  359.      * See {@link ConfigurationValueBuilder} for more information.
  360.      *
  361.      *
  362.      * @return the builder to configure property keys/default value
  363.      */
  364.     public ConfigurationValueBuilder<CloudhopperBuilder, String> systemType() {
  365.         return systemTypeValueBuilder;
  366.     }

  367.     /**
  368.      * The password parameter is used by the SMSC to authenticate the identity
  369.      * of the binding ESME. The Service Provider may require ESME’s to provide a
  370.      * password when binding to the SMSC. This password is normally issued by
  371.      * the SMSC system administrator. The password parameter may also be used by
  372.      * the ESME to authenticate the identity of the binding SMSC (e.g. in the
  373.      * case of the outbind operation).
  374.      *
  375.      * <p>
  376.      * The value set using this method takes precedence over any property and
  377.      * default value configured using {@link #password()}.
  378.      *
  379.      * <pre>
  380.      * .password("my-password")
  381.      * .password()
  382.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  383.      *   .defaultValue("default-password")
  384.      * </pre>
  385.      *
  386.      * <pre>
  387.      * .password("my-password")
  388.      * .password()
  389.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  390.      *   .defaultValue("default-password")
  391.      * </pre>
  392.      *
  393.      * In both cases, {@code password("my-password")} is used.
  394.      *
  395.      * <p>
  396.      * If this method is called several times, only the last value is used.
  397.      *
  398.      * <p>
  399.      * If {@code null} value is set, it is like not setting a value at all. The
  400.      * property/default value configuration is applied.
  401.      *
  402.      * @param password
  403.      *            the password used to authenticate
  404.      * @return this instance for fluent chaining
  405.      */
  406.     public CloudhopperBuilder password(String password) {
  407.         passwordValueBuilder.setValue(password);
  408.         return this;
  409.     }

  410.     /**
  411.      * The password parameter is used by the SMSC to authenticate the identity
  412.      * of the binding ESME. The Service Provider may require ESME’s to provide a
  413.      * password when binding to the SMSC. This password is normally issued by
  414.      * the SMSC system administrator. The password parameter may also be used by
  415.      * the ESME to authenticate the identity of the binding SMSC (e.g. in the
  416.      * case of the outbind operation).
  417.      *
  418.      * <p>
  419.      * This method is mainly used by {@link Configurer}s to register some
  420.      * property keys and/or a default value. The aim is to let developer be able
  421.      * to externalize its configuration (using system properties, configuration
  422.      * file or anything else). If the developer doesn't configure any value for
  423.      * the registered properties, the default value is used (if set).
  424.      *
  425.      * <pre>
  426.      * .password()
  427.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  428.      *   .defaultValue("default-password")
  429.      * </pre>
  430.      *
  431.      * <p>
  432.      * Non-null value set using {@link #password(String)} takes precedence over
  433.      * property values and default value.
  434.      *
  435.      * <pre>
  436.      * .password("my-password")
  437.      * .password()
  438.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  439.      *   .defaultValue("default-password")
  440.      * </pre>
  441.      *
  442.      * The value {@code "my-password"} is used regardless of the value of the
  443.      * properties and default value.
  444.      *
  445.      * <p>
  446.      * See {@link ConfigurationValueBuilder} for more information.
  447.      *
  448.      *
  449.      * @return the builder to configure property keys/default value
  450.      */
  451.     public ConfigurationValueBuilder<CloudhopperBuilder, String> password() {
  452.         return passwordValueBuilder;
  453.     }

  454.     /**
  455.      * The SMPP server host (IP or address).
  456.      *
  457.      * <p>
  458.      * The value set using this method takes precedence over any property and
  459.      * default value configured using {@link #host()}.
  460.      *
  461.      * <pre>
  462.      * .host("localhost")
  463.      * .host()
  464.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  465.      *   .defaultValue("default-host")
  466.      * </pre>
  467.      *
  468.      * <pre>
  469.      * .host("localhost")
  470.      * .host()
  471.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  472.      *   .defaultValue("default-host")
  473.      * </pre>
  474.      *
  475.      * In both cases, {@code host("localhost")} is used.
  476.      *
  477.      * <p>
  478.      * If this method is called several times, only the last value is used.
  479.      *
  480.      * <p>
  481.      * If {@code null} value is set, it is like not setting a value at all. The
  482.      * property/default value configuration is applied.
  483.      *
  484.      * @param host
  485.      *            the host address
  486.      * @return this instance for fluent chaining
  487.      */
  488.     public CloudhopperBuilder host(String host) {
  489.         hostValueBuilder.setValue(host);
  490.         return this;
  491.     }

  492.     /**
  493.      * The SMPP server host (IP or address).
  494.      *
  495.      * <p>
  496.      * This method is mainly used by {@link Configurer}s to register some
  497.      * property keys and/or a default value. The aim is to let developer be able
  498.      * to externalize its configuration (using system properties, configuration
  499.      * file or anything else). If the developer doesn't configure any value for
  500.      * the registered properties, the default value is used (if set).
  501.      *
  502.      * <pre>
  503.      * .host()
  504.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  505.      *   .defaultValue("default-host")
  506.      * </pre>
  507.      *
  508.      * <p>
  509.      * Non-null value set using {@link #host(String)} takes precedence over
  510.      * property values and default value.
  511.      *
  512.      * <pre>
  513.      * .host("localhost")
  514.      * .host()
  515.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  516.      *   .defaultValue("default-host")
  517.      * </pre>
  518.      *
  519.      * The value {@code "localhost"} is used regardless of the value of the
  520.      * properties and default value.
  521.      *
  522.      * <p>
  523.      * See {@link ConfigurationValueBuilder} for more information.
  524.      *
  525.      *
  526.      * @return the builder to configure property keys/default value
  527.      */
  528.     public ConfigurationValueBuilder<CloudhopperBuilder, String> host() {
  529.         return hostValueBuilder;
  530.     }

  531.     /**
  532.      * Set the SMPP server port.
  533.      *
  534.      * <p>
  535.      * The value set using this method takes precedence over any property and
  536.      * default value configured using {@link #port()}.
  537.      *
  538.      * <pre>
  539.      * .port(2775)
  540.      * .port()
  541.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  542.      *   .defaultValue(1775)
  543.      * </pre>
  544.      *
  545.      * <pre>
  546.      * .port(2775)
  547.      * .port()
  548.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  549.      *   .defaultValue(1775)
  550.      * </pre>
  551.      *
  552.      * In both cases, {@code port(2775)} is used.
  553.      *
  554.      * <p>
  555.      * If this method is called several times, only the last value is used.
  556.      *
  557.      * <p>
  558.      * If {@code null} value is set, it is like not setting a value at all. The
  559.      * property/default value configuration is applied.
  560.      *
  561.      * @param port
  562.      *            the SMPP server port
  563.      * @return this instance for fluent chaining
  564.      */
  565.     public CloudhopperBuilder port(Integer port) {
  566.         portValueBuilder.setValue(port);
  567.         return this;
  568.     }

  569.     /**
  570.      * Set the SMPP server port.
  571.      *
  572.      * <p>
  573.      * This method is mainly used by {@link Configurer}s to register some
  574.      * property keys and/or a default value. The aim is to let developer be able
  575.      * to externalize its configuration (using system properties, configuration
  576.      * file or anything else). If the developer doesn't configure any value for
  577.      * the registered properties, the default value is used (if set).
  578.      *
  579.      * <pre>
  580.      * .port()
  581.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  582.      *   .defaultValue(1775)
  583.      * </pre>
  584.      *
  585.      * <p>
  586.      * Non-null value set using {@link #port(Integer)} takes precedence over
  587.      * property values and default value.
  588.      *
  589.      * <pre>
  590.      * .port(2775)
  591.      * .port()
  592.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  593.      *   .defaultValue(1775)
  594.      * </pre>
  595.      *
  596.      * The value {@code 2775} is used regardless of the value of the properties
  597.      * and default value.
  598.      *
  599.      * <p>
  600.      * See {@link ConfigurationValueBuilder} for more information.
  601.      *
  602.      *
  603.      * @return the builder to configure property keys/default value
  604.      */
  605.     public ConfigurationValueBuilder<CloudhopperBuilder, Integer> port() {
  606.         return portValueBuilder;
  607.     }

  608.     /**
  609.      * The SMPP protocol version (one of {@link InterfaceVersion#VERSION_3_3},
  610.      * {@link InterfaceVersion#VERSION_3_4},
  611.      * {@link InterfaceVersion#VERSION_5_0}).
  612.      *
  613.      * <p>
  614.      * The value set using this method takes precedence over any property and
  615.      * default value configured using {@link #interfaceVersion()}.
  616.      *
  617.      * <pre>
  618.      * .interfaceVersion(InterfaceVersion.VERSION_5_0)
  619.      * .interfaceVersion()
  620.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  621.      *   .defaultValue(InterfaceVersion.VERSION_3_4)
  622.      * </pre>
  623.      *
  624.      * <pre>
  625.      * .interfaceVersion(InterfaceVersion.VERSION_5_0)
  626.      * .interfaceVersion()
  627.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  628.      *   .defaultValue(InterfaceVersion.VERSION_3_4)
  629.      * </pre>
  630.      *
  631.      * In both cases, {@code interfaceVersion(InterfaceVersion.VERSION_5_0)} is
  632.      * used.
  633.      *
  634.      * <p>
  635.      * If this method is called several times, only the last value is used.
  636.      *
  637.      * <p>
  638.      * If {@code null} value is set, it is like not setting a value at all. The
  639.      * property/default value configuration is applied.
  640.      *
  641.      * @param version
  642.      *            the version of the SMPP protocol
  643.      * @return this instance for fluent chaining
  644.      */
  645.     public CloudhopperBuilder interfaceVersion(InterfaceVersion version) {
  646.         interfaceVersionValueBuilder.setValue(version);
  647.         return this;
  648.     }

  649.     /**
  650.      * The SMPP protocol version (one of {@link SmppConstants#VERSION_3_3},
  651.      * {@link SmppConstants#VERSION_3_4}, {@link SmppConstants#VERSION_5_0}).
  652.      *
  653.      * <p>
  654.      * The value set using this method takes precedence over any property and
  655.      * default value configured using {@link #interfaceVersion()}.
  656.      *
  657.      * <pre>
  658.      * .interfaceVersion(SmppConstants.VERSION_5_0)
  659.      * .interfaceVersion()
  660.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  661.      *   .defaultValue(SmppConstants.VERSION_3_4)
  662.      * </pre>
  663.      *
  664.      * <pre>
  665.      * .interfaceVersion(SmppConstants.VERSION_5_0)
  666.      * .interfaceVersion()
  667.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  668.      *   .defaultValue(SmppConstants.VERSION_3_4)
  669.      * </pre>
  670.      *
  671.      * In both cases, {@code interfaceVersion(SmppConstants.VERSION_5_0)} is
  672.      * used.
  673.      *
  674.      * <p>
  675.      * If this method is called several times, only the last value is used.
  676.      *
  677.      * <p>
  678.      * If {@code null} value is set, it is like not setting a value at all. The
  679.      * property/default value configuration is applied.
  680.      *
  681.      * @param version
  682.      *            the version of the SMPP protocol
  683.      * @return this instance for fluent chaining
  684.      */
  685.     public CloudhopperBuilder interfaceVersion(Byte version) {
  686.         interfaceVersionValueBuilder.setValue(InterfaceVersion.fromValue(version));
  687.         return this;
  688.     }

  689.     /**
  690.      * The SMPP protocol version (one of {@link InterfaceVersion#VERSION_3_3},
  691.      * {@link InterfaceVersion#VERSION_3_4},
  692.      * {@link InterfaceVersion#VERSION_5_0}).
  693.      *
  694.      *
  695.      * <p>
  696.      * This method is mainly used by {@link Configurer}s to register some
  697.      * property keys and/or a default value. The aim is to let developer be able
  698.      * to externalize its configuration (using system properties, configuration
  699.      * file or anything else). If the developer doesn't configure any value for
  700.      * the registered properties, the default value is used (if set).
  701.      *
  702.      * <pre>
  703.      * .interfaceVersion()
  704.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  705.      *   .defaultValue(InterfaceVersion.VERSION_3_4)
  706.      * </pre>
  707.      *
  708.      * <p>
  709.      * Non-null value set using {@link #interfaceVersion(InterfaceVersion)}
  710.      * takes precedence over property values and default value.
  711.      *
  712.      * <pre>
  713.      * .interfaceVersion(InterfaceVersion.VERSION_5_0)
  714.      * .interfaceVersion()
  715.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  716.      *   .defaultValue(InterfaceVersion.VERSION_3_4)
  717.      * </pre>
  718.      *
  719.      * The value {@code InterfaceVersion.VERSION_5_0} is used regardless of the
  720.      * value of the properties and default value.
  721.      *
  722.      * <p>
  723.      * See {@link ConfigurationValueBuilder} for more information.
  724.      *
  725.      *
  726.      * @return the builder to configure property keys/default value
  727.      */
  728.     public ConfigurationValueBuilder<CloudhopperBuilder, InterfaceVersion> interfaceVersion() {
  729.         return interfaceVersionValueBuilder;
  730.     }

  731.     /**
  732.      * The bind command type (see {@link SmppBindType}).
  733.      *
  734.      * <p>
  735.      * The value set using this method takes precedence over any property and
  736.      * default value configured using {@link #bindType()}.
  737.      *
  738.      * <pre>
  739.      * .bindType(SmppBindType.TRANSCEIVER)
  740.      * .bindType()
  741.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  742.      *   .defaultValue(SmppBindType.RECEIVER)
  743.      * </pre>
  744.      *
  745.      * <pre>
  746.      * .bindType(SmppBindType.TRANSCEIVER)
  747.      * .bindType()
  748.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  749.      *   .defaultValue(SmppBindType.RECEIVER)
  750.      * </pre>
  751.      *
  752.      * In both cases, {@code bindType(SmppBindType.TRANSCEIVER)} is used.
  753.      *
  754.      * <p>
  755.      * If this method is called several times, only the last value is used.
  756.      *
  757.      * <p>
  758.      * If {@code null} value is set, it is like not setting a value at all. The
  759.      * property/default value configuration is applied.
  760.      *
  761.      * @param bindType
  762.      *            the bind type
  763.      * @return this instance for fluent chaining
  764.      */
  765.     public CloudhopperBuilder bindType(SmppBindType bindType) {
  766.         bindTypeValueBuilder.setValue(bindType);
  767.         return this;
  768.     }

  769.     /**
  770.      * The bind command type (see {@link SmppBindType}).
  771.      *
  772.      * <p>
  773.      * This method is mainly used by {@link Configurer}s to register some
  774.      * property keys and/or a default value. The aim is to let developer be able
  775.      * to externalize its configuration (using system properties, configuration
  776.      * file or anything else). If the developer doesn't configure any value for
  777.      * the registered properties, the default value is used (if set).
  778.      *
  779.      * <pre>
  780.      * .bindType()
  781.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  782.      *   .defaultValue(SmppBindType.RECEIVER)
  783.      * </pre>
  784.      *
  785.      * <p>
  786.      * Non-null value set using {@link #bindType(SmppBindType)} takes precedence
  787.      * over property values and default value.
  788.      *
  789.      * <pre>
  790.      * .bindType(SmppBindType.TRANSCEIVER)
  791.      * .bindType()
  792.      *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
  793.      *   .defaultValue(SmppBindType.RECEIVER)
  794.      * </pre>
  795.      *
  796.      * The value {@code SmppBindType.TRANSCEIVER} is used regardless of the
  797.      * value of the properties and default value.
  798.      *
  799.      * <p>
  800.      * See {@link ConfigurationValueBuilder} for more information.
  801.      *
  802.      *
  803.      * @return the builder to configure property keys/default value
  804.      */
  805.     public ConfigurationValueBuilder<CloudhopperBuilder, SmppBindType> bindType() {
  806.         return bindTypeValueBuilder;
  807.     }

  808.     /**
  809.      * Configures how Cloudhopper will encode SMS messages. Charsets defined by
  810.      * the SMPP protocol may be different from NIO charsets.
  811.      *
  812.      * <p>
  813.      * The encoder will be used to transform Java {@link String} into a byte
  814.      * array that is understandable by SMPP servers.
  815.      *
  816.      * <p>
  817.      * This builder configures encoders for both messages that are split and
  818.      * message that are not split.
  819.      *
  820.      * <p>
  821.      * This builder allows to configure:
  822.      * <ul>
  823.      * <li>Enable/disable the standard GSM encoders (GSM 7-bit, GSM 8-bit and
  824.      * UCS-2) as defined in
  825.      * <a href="https://en.wikipedia.org/wiki/GSM_03.38">GSM 03.38
  826.      * specification</a>. It also allows to define different priority order</li>
  827.      * <li>Enable/disable automatic guessing of encoding (based on previously
  828.      * registered priorities).</li>
  829.      * <li>Define a fallback encoder based on {@link Charset}</li>
  830.      * <li>Provide custom {@link Encoder}s</li>
  831.      * </ul>
  832.      *
  833.      * <pre>
  834.      * {@code
  835.      * .encoder()
  836.      *    .gsm7()
  837.      *      .properties("${ogham.sms.cloudhopper.encoder.gsm7bit-packed.priority}", "${ogham.sms.smpp.encoder.gsm7bit-packed.priority}")
  838.      *      .defaultValue(100000)
  839.      *      .and()
  840.      *    .gsm8()
  841.      *      .properties("${ogham.sms.cloudhopper.encoder.gsm8bit.priority}", "${ogham.sms.smpp.encoder.gsm8bit.priority}")
  842.      *      .defaultValue(99000)
  843.      *      .and()
  844.      *    .ucs2()
  845.      *      .properties("${ogham.sms.cloudhopper.encoder.ucs2.priority}", "${ogham.sms.smpp.encoder.ucs2.priority}")
  846.      *      .defaultValue(98000)
  847.      *      .and()
  848.      *    .autoGuess()
  849.      *      .properties("${ogham.sms.cloudhopper.encoder.auto-guess.enable}", "${ogham.sms.smpp.encoder.auto-guess.enable}")
  850.      *      .defaultValue(true)
  851.      *      .and()
  852.      *    .fallback()
  853.      *      .properties("${ogham.sms.cloudhopper.encoder.default-charset}", "${ogham.sms.smpp.encoder.default-charset}")
  854.      *      .defaultValue(CharsetUtil.NAME_GSM)
  855.      *      .and()
  856.      *    .customEncoder(new MyCustomEncoder(), 50000)
  857.      * }
  858.      * </pre>
  859.      *
  860.      * @return the builder to configure the encoder
  861.      */
  862.     public EncoderBuilder encoder() {
  863.         if (encoderBuilder == null) {
  864.             encoderBuilder = new EncoderBuilder(this, buildContext);
  865.             sharedEncoderBuilder.update(encoderBuilder);
  866.         }
  867.         return encoderBuilder;
  868.     }

  869.     /**
  870.      * Configures how Cloudhopper will split messages.
  871.      *
  872.      * <p>
  873.      * The splitter will check if the whole message can fit in a single segment.
  874.      * If not the splitter will split the whole message in several segments with
  875.      * a header to indicate splitting information such as number of segments,
  876.      * reference number and current segment number.
  877.      *
  878.      * <p>
  879.      * {@link Encoder} configured using {@link #encoder()} is used to encode
  880.      * each segment.
  881.      *
  882.      * <p>
  883.      * If automatic guessing of best standard encoder is enabled for
  884.      * {@link Encoder} (using {@code encoder().autoGuess(true)}), and message
  885.      * splitting is enabled, then standard message splitting is configured such
  886.      * as:
  887.      * <ul>
  888.      * <li>If GSM 7-bit encoder is enabled, {@link GsmMessageSplitter} is used
  889.      * to split messages that support this encoding. If whole message can fit in
  890.      * a single segment of 160 characters. Longer message is split into segments
  891.      * of either 153 characters or 152 characters (depending on reference number
  892.      * generation, see {@link ReferenceNumberGenerator})</li>
  893.      * <li>If GSM 8-bit encoder is enabled, {@link GsmMessageSplitter} is used
  894.      * to split messages that support this encoding. If whole message can fit in
  895.      * a single segment of 140 characters. Longer message is split into segments
  896.      * of either 134 characters or 133 characters (depending on reference number
  897.      * generation, see {@link ReferenceNumberGenerator})</li>
  898.      * <li>If UCS-2 encoder is enabled, {@link GsmMessageSplitter} is used to
  899.      * split messages that support this encoding. If whole message can fit in a
  900.      * single segment of 70 characters. Longer message is split into segments of
  901.      * either 67 characters or 66 characters (depending on reference number
  902.      * generation, see {@link ReferenceNumberGenerator})</li>
  903.      * </ul>
  904.      *
  905.      * Each registered splitter uses the same priority as associated
  906.      * {@link Encoder}.
  907.      *
  908.      * If you don't want standard message splitting based on supported
  909.      * {@link Encoder}s, you can either disable message splitting or provide a
  910.      * custom splitter with higher priority.
  911.      *
  912.      * <p>
  913.      * This builder allows to configure:
  914.      * <ul>
  915.      * <li>Enable/disable message splitting</li>
  916.      * <li>Provide a custom split strategy</li>
  917.      * <li>Choose strategy for reference number generation</li>
  918.      * </ul>
  919.      *
  920.      * <p>
  921.      * Examples of usage:
  922.      *
  923.      * <pre>
  924.      * {@code
  925.      * .splitter()
  926.      *   .enable()
  927.      *     .properties("${ogham.sms.cloudhopper.split.enable}", "${ogham.sms.smpp.split.enable}", "${ogham.sms.split.enable}")
  928.      *     .defaultValue(true)
  929.      *     .and()
  930.      *   .customSplitter(new MyCustomSplitter(), 100000)
  931.      *   .referenceNumber()
  932.      *     .random()
  933.      *     .random(new Random())
  934.      *     .generator(new MyCustomReferenceNumberGenerator())
  935.      * }
  936.      * </pre>
  937.      *
  938.      * @return the builder to configure message splitting
  939.      */
  940.     public MessageSplitterBuilder splitter() {
  941.         if (messageSplitterBuilder == null) {
  942.             messageSplitterBuilder = new MessageSplitterBuilder(this, buildContext, sharedEncoderBuilder);
  943.         }
  944.         return messageSplitterBuilder;
  945.     }

  946.     /**
  947.      * Configures Cloudhopper session management (timeouts, retry, session
  948.      * name...).
  949.      *
  950.      * @return the builder to configure the session management
  951.      */
  952.     public SessionBuilder session() {
  953.         if (sessionBuilder == null) {
  954.             sessionBuilder = new SessionBuilder(this, buildContext);
  955.         }
  956.         return sessionBuilder;
  957.     }

  958.     /**
  959.      * Overrides any previously defined Cloudhopper parameters to use the
  960.      * provided session.
  961.      *
  962.      * <p>
  963.      * If this method is called several times, only the last session is used.
  964.      *
  965.      * @param session
  966.      *            the Cloudhopper session to use
  967.      * @return this instance for fluent chaining
  968.      */
  969.     public CloudhopperBuilder session(ExtendedSmppSessionConfiguration session) {
  970.         this.sessionConfiguration = session;
  971.         return this;
  972.     }

  973.     /**
  974.      * The address_range parameter is used in the bind_receiver and
  975.      * bind_transceiver command to specify a set of SME addresses serviced by
  976.      * the ESME client. A single SME address may also be specified in the
  977.      * address_range parameter. UNIX Regular Expression notation should be used
  978.      * to specify a range of addresses. Messages addressed to any destination in
  979.      * this range shall be routed to the ESME.
  980.      *
  981.      * Default to {@code null}.
  982.      *
  983.      * Note: For IP addresses, it is only possible to specify a single IP
  984.      * address. A range of IP addresses are not allowed. IP version 6.0 is not
  985.      * currently supported in this version of the protocol.
  986.      *
  987.      * Note: It is likely that the addr_range field is not supported or
  988.      * deliberately ignored on most Message Centres. The reason for this is that
  989.      * most carriers will not allow an ESME control the message routing as this
  990.      * can carry the risk of mis-routing messages. In such circumstances, the
  991.      * ESME will be requested to set the field to NULL.
  992.      *
  993.      * @param range
  994.      *            the address range
  995.      * @return this instance for fluent chaining
  996.      */
  997.     public CloudhopperBuilder addressRange(Address range) {
  998.         this.addressRange = range;
  999.         return this;
  1000.     }

  1001.     /**
  1002.      * Enable or disable SSL configuration and configure how SSL is handled.
  1003.      *
  1004.      * See <a href=
  1005.      * "https://github.com/fizzed/cloudhopper-smpp/blob/master/SSL.md">How to
  1006.      * use SSL with cloudhopper-smpp</a>
  1007.      *
  1008.      * @return the builder to configure SSL
  1009.      */
  1010.     public SslBuilder ssl() {
  1011.         if (sslBuilder == null) {
  1012.             sslBuilder = new SslBuilder(this, buildContext);
  1013.         }
  1014.         return sslBuilder;
  1015.     }

  1016.     /**
  1017.      * Configure logs:
  1018.      * <ul>
  1019.      * <li>Enable/disable log of {@link Pdu}s</li>
  1020.      * <li>Enable/disable log of bytes</li>
  1021.      * </ul>
  1022.      *
  1023.      * @return the builder to enable/disable some logs
  1024.      */
  1025.     public LoggingBuilder logging() {
  1026.         if (loggingBuilder == null) {
  1027.             loggingBuilder = new LoggingBuilder(this);
  1028.         }
  1029.         return loggingBuilder;
  1030.     }

  1031.     /**
  1032.      * By default, {@link CloudhopperSMPPSender} uses {@link DefaultSmppClient}
  1033.      * client. This option provides a way to use another {@link SmppClient}.
  1034.      *
  1035.      * @param supplier
  1036.      *            an implementation that provides an instance of a
  1037.      *            {@link SmppClient}
  1038.      * @return this instance for fluent chaining
  1039.      */
  1040.     public CloudhopperBuilder clientSupplier(SmppClientSupplier supplier) {
  1041.         this.clientSupplier = supplier;
  1042.         return this;
  1043.     }

  1044.     /**
  1045.      * By default, {@link CloudhopperSMPPSender} uses
  1046.      * {@link DefaultSmppSessionHandler}. This option provides a way to use
  1047.      * another {@link SmppSessionHandler}.
  1048.      *
  1049.      * @param supplier
  1050.      *            an implementation that provides an instance of a
  1051.      *            {@link SmppSessionHandler}
  1052.      * @return this instance for fluent chaining
  1053.      */
  1054.     public CloudhopperBuilder smppSessionHandlerSupplier(SmppSessionHandlerSupplier supplier) {
  1055.         this.smppSessionHandler = supplier;
  1056.         return this;
  1057.     }

  1058.     /**
  1059.      * {@link Sms} message is converted to {@link SubmitSm}(s) using a
  1060.      * {@link MessagePreparator}.
  1061.      *
  1062.      * <p>
  1063.      * You can provide a custom {@link MessagePreparator} instance if the
  1064.      * default behavior doesn't fit your needs.
  1065.      * </p>
  1066.      *
  1067.      * <p>
  1068.      * If a custom {@link MessagePreparator} is set, any other preparator (using
  1069.      * {@link #userData()}) is not used.
  1070.      * </p>
  1071.      *
  1072.      * <p>
  1073.      * If this method is called several times, only the last value is used.
  1074.      * </p>
  1075.      *
  1076.      * <p>
  1077.      * If {@code null} value is provided, then custom {@link MessagePreparator}
  1078.      * is disabled. Other configured preparators are used (using
  1079.      * {@link #userData()}).
  1080.      * </p>
  1081.      *
  1082.      * @param preparator
  1083.      *            the custom preprator instance
  1084.      * @return this instance for fluent chaining
  1085.      * @see #userData()
  1086.      */
  1087.     public CloudhopperBuilder messagePreparator(MessagePreparator preparator) {
  1088.         this.preparator = preparator;
  1089.         return this;
  1090.     }

  1091.     /**
  1092.      * SMS message (named "User Data" in SMPP specification) can be transmitted
  1093.      * using:
  1094.      * <ul>
  1095.      * <li>Either {@code short_message} field (standard field for "User
  1096.      * Data").</li>
  1097.      * <li>Or {@code message_payload} optional parameter.</li>
  1098.      * </ul>
  1099.      *
  1100.      * <p>
  1101.      * This builder allow to configure which strategy to use for sending
  1102.      * message:
  1103.      * <ul>
  1104.      * <li>Either use {@code short_message} field</li>
  1105.      * <li>Or use {@code message_payload} field</li>
  1106.      * </ul>
  1107.      *
  1108.      * <p>
  1109.      * The result of {@link #userData()} configuration affects the message
  1110.      * preparation strategy.
  1111.      * </p>
  1112.      *
  1113.      * <p>
  1114.      * Examples of usage:
  1115.      *
  1116.      * <pre>
  1117.      * {@code
  1118.      * .userData()
  1119.      *   .useShortMessage()
  1120.      *     .properties("${ogham.sms.cloudhopper.user-data.use-short-message}", "${ogham.sms.smpp.user-data.use-short-message}")
  1121.      *     .defaultValue(true)
  1122.      *     .and()
  1123.      *   .useTlvMessagePayload()
  1124.      *     .properties("${ogham.sms.cloudhopper.user-data.use-tlv-message-payload}", "${ogham.sms.smpp.user-data.use-tlv-message-payload}")
  1125.      *     .defaultValue(false)
  1126.      * }
  1127.      * </pre>
  1128.      *
  1129.      * If any of {@code ogham.sms.cloudhopper.user-data.use-short-message}
  1130.      * property or {@code ogham.sms.user-data.use-short-message} property is set
  1131.      * to true, it uses {@code short_message} field.
  1132.      *
  1133.      * If any of {@code ogham.sms.cloudhopper.user-data.use-tlv-message-payload}
  1134.      * property or {@code ogham.sms.user-data.use-tlv-message-payload} property
  1135.      * is set to true, it uses {@code message_payload} field.
  1136.      *
  1137.      * If none of the above properties is set, it uses {@code short_message}
  1138.      * field is used (last value of {@code shortMessage} is set to
  1139.      * {@code "true"}).
  1140.      *
  1141.      * <p>
  1142.      * If {@link #userData()} is not configured at all, then default behavior is
  1143.      * used ({@code short_message} field is used).
  1144.      * </p>
  1145.      *
  1146.      * @return the builder to configure how the "User Data" is sent
  1147.      */
  1148.     public UserDataBuilder userData() {
  1149.         if (userDataBuilder == null) {
  1150.             userDataBuilder = new UserDataBuilder(this, buildContext);
  1151.         }
  1152.         return userDataBuilder;
  1153.     }

  1154.     /**
  1155.      * Data Coding Scheme is a one-octet field in Short Messages (SM) and Cell
  1156.      * Broadcast Messages (CB) which carries a basic information how the
  1157.      * recipient handset should process the received message. The information
  1158.      * includes:
  1159.      * <ul>
  1160.      * <li>the character set or message coding which determines the encoding of
  1161.      * the message user data</li>
  1162.      * <li>the message class which determines to which component of the Mobile
  1163.      * Station (MS) or User Equipment (UE) should be the message delivered</li>
  1164.      * <li>the request to automatically delete the message after reading</li>
  1165.      * <li>the state of flags indicating presence of unread voicemail, fax,
  1166.      * e-mail or other messages</li>
  1167.      * <li>the indication that the message content is compressed</li>
  1168.      * <li>the language of the cell broadcast message</li>
  1169.      * </ul>
  1170.      * The field is described in 3GPP 23.040 and 3GPP 23.038 under the name
  1171.      * TP-DCS (see <a href=
  1172.      * "https://en.wikipedia.org/wiki/Data_Coding_Scheme#SMS_Data_Coding_Scheme">SMS
  1173.      * Data Coding Scheme</a>).
  1174.      *
  1175.      * SMPP 3.4 introduced a new list of {@code data_coding} values (see
  1176.      * <a href="https://en.wikipedia.org/wiki/Short_Message_Peer-to-Peer">Short
  1177.      * Message Peer to Peer</a>).
  1178.      *
  1179.      * <p>
  1180.      * This builder allows to configure how Data Coding Scheme value is
  1181.      * determined:
  1182.      * <ul>
  1183.      * <li>Use automatic mode base on interface version (see
  1184.      * {@link #interfaceVersion(InterfaceVersion)} and
  1185.      * {@link #interfaceVersion(Byte)}) and charset encoding (see
  1186.      * {@link #encoder()}) used to encode the message ("User Data")</li>
  1187.      * <li>Use a fixed value used for every message</li>
  1188.      * <li>Use a custom implementation</li>
  1189.      * </ul>
  1190.      *
  1191.      * <p>
  1192.      * Examples of usage:
  1193.      *
  1194.      * <pre>
  1195.      * {@code
  1196.      * .dataCodingScheme()
  1197.      *   .auto("${ogham.sms.cloudhopper.data-coding-scheme.auto.enable}", "${ogham.sms.smpp.data-coding-scheme.auto.enable}")
  1198.      *   .value("${ogham.sms.cloudhopper.data-coding-scheme.value}", "${ogham.sms.smpp.data-coding-scheme.value}")
  1199.      *   .custom(new MyCustomDataCodingProvider())
  1200.      * }
  1201.      * </pre>
  1202.      *
  1203.      * See {@link DataCodingSchemeBuilder#auto(Boolean)},
  1204.      * {@link DataCodingSchemeBuilder#value(Byte)} and
  1205.      * {@link DataCodingSchemeBuilder#custom(DataCodingProvider)} for more
  1206.      * information.
  1207.      *
  1208.      *
  1209.      * @return the builder to configure how to determine Data Coding Scheme
  1210.      *         value
  1211.      */
  1212.     public DataCodingSchemeBuilder dataCodingScheme() {
  1213.         if (dataCodingBuilder == null) {
  1214.             dataCodingBuilder = new DataCodingSchemeBuilder(this, buildContext, this::getInterfaceVersion);
  1215.         }
  1216.         return dataCodingBuilder;
  1217.     }

  1218.     @Override
  1219.     public CloudhopperSMPPSender build() {
  1220.         CloudhopperSessionOptions sessionOpts = buildSessionOpts();
  1221.         ExtendedSmppSessionConfiguration configuration = buildSessionConfiguration(sessionOpts);
  1222.         if (configuration.getHost() == null || configuration.getPort() == 0) {
  1223.             return null;
  1224.         }
  1225.         LOG.info("Sending SMS using Cloudhopper is registered");
  1226.         LOG.debug("SMPP server address: {}:{}", configuration.getHost(), configuration.getPort());
  1227.         SessionHandlingStrategy sessionHandler = buildSessionHandlingStrategy(configuration);
  1228.         return buildContext.register(new CloudhopperSMPPSender(configuration, sessionHandler, buildPreparator()));
  1229.     }

  1230.     private CloudhopperSessionOptions buildSessionOpts() {
  1231.         if (sessionBuilder != null) {
  1232.             return sessionBuilder.build();
  1233.         }
  1234.         CloudhopperSessionOptions cloudhopperSessionOptions = buildContext.register(new CloudhopperSessionOptions());
  1235.         cloudhopperSessionOptions.setConnectRetry(buildConnectRetry(cloudhopperSessionOptions));
  1236.         return cloudhopperSessionOptions;
  1237.     }

  1238.     private SessionHandlingStrategy buildSessionHandlingStrategy(ExtendedSmppSessionConfiguration configuration) {
  1239.         if (configuration.getKeepAlive() != null && configuration.getKeepAlive().isEnable(false)) {
  1240.             return buildKeepAliveHandler(configuration);
  1241.         }
  1242.         if (configuration.getReuseSession() != null && configuration.getReuseSession().isEnable(false)) {
  1243.             return buildReuseSessionHandler(configuration);
  1244.         }
  1245.         return buildAlwaysNewSessionHandler(configuration);
  1246.     }

  1247.     private SessionHandlingStrategy buildKeepAliveHandler(ExtendedSmppSessionConfiguration configuration) {
  1248.         return new KeepSessionAliveStrategy(configuration, buildClientSupplier(), buildSmppSessionHandler(), configuration.getConnectRetry(), configuration.getKeepAlive().getExecutor(), buildKeepAliveErrorAnalyzer(configuration.getKeepAlive()), buildReconnectionErrorHandler());
  1249.     }
  1250.    
  1251.     private ErrorAnalyzer buildKeepAliveErrorAnalyzer(KeepAliveOptions options) {
  1252.         return new DefaultErrorAnalyzer(options.getMaxConsecutiveTimeouts());
  1253.     }
  1254.    
  1255.     private ErrorHandler buildReconnectionErrorHandler() {
  1256.         // TODO: make this configurable ?
  1257.         return new LogErrorHandler("Failed to reconnect", Level.ERROR);
  1258.     }

  1259.     private SessionHandlingStrategy buildReuseSessionHandler(ExtendedSmppSessionConfiguration configuration) {
  1260.         return new MayReuseSessionStrategy(configuration, buildClientSupplier(), buildSmppSessionHandler(), configuration.getConnectRetry(), buildReuseSessionErrorAnalyzer());
  1261.     }
  1262.    
  1263.     private ErrorAnalyzer buildReuseSessionErrorAnalyzer() {
  1264.         return new DefaultErrorAnalyzer(1);
  1265.     }

  1266.     private SessionHandlingStrategy buildAlwaysNewSessionHandler(ExtendedSmppSessionConfiguration configuration) {
  1267.         return new AlwaysNewSessionStrategy(configuration, buildClientSupplier(), buildSmppSessionHandler(), configuration.getConnectRetry());
  1268.     }

  1269.     private RetryExecutor buildConnectRetry(CloudhopperSessionOptions sessionOpts) {
  1270.         if (sessionOpts.getConnectRetry() == null) {
  1271.             return noRetry();
  1272.         }
  1273.         return sessionOpts.getConnectRetry();
  1274.     }
  1275.    
  1276.     private SimpleRetryExecutor noRetry() {
  1277.         return buildContext.register(new SimpleRetryExecutor(() -> null, buildContext.register(new ThreadSleepAwaiter())));
  1278.     }

  1279.     private MessagePreparator buildPreparator() {
  1280.         if (preparator != null) {
  1281.             return preparator;
  1282.         }
  1283.         if (userDataBuilder != null) {
  1284.             UserDataPropValues values = userDataBuilder.build();
  1285.             if (values.isUseShortMessage()) {
  1286.                 return buildShortMessagePreparator();
  1287.             }
  1288.             if (values.isUseTlvMessagePayload()) {
  1289.                 return buildTlvMessagePayloadMessagePreparator();
  1290.             }
  1291.         }
  1292.         return buildShortMessagePreparator();
  1293.     }

  1294.     private MessagePreparator buildShortMessagePreparator() {
  1295.         return buildContext.register(new ShortMessagePreparator(buildSplitter(buildEncoder()), buildDataCodingProvider(), buildPhoneNumberTranslator()));
  1296.     }

  1297.     private MessagePreparator buildTlvMessagePayloadMessagePreparator() {
  1298.         return buildContext.register(new TlvMessagePayloadMessagePreparator(buildSplitter(buildEncoder()), buildDataCodingProvider(), buildPhoneNumberTranslator()));
  1299.     }

  1300.     private Encoder buildEncoder() {
  1301.         if (encoderBuilder == null) {
  1302.             return buildContext.register(new CloudhopperCharsetSupportingEncoder(NamedCharset.from(DEFAULT_CHARSET)));
  1303.         }
  1304.         return encoderBuilder.build();
  1305.     }

  1306.     private DataCodingProvider buildDataCodingProvider() {
  1307.         if (dataCodingBuilder == null) {
  1308.             return buildContext.register(new CharsetMapToCharacterEncodingGroupDataCodingProvider(true));
  1309.         }
  1310.         return dataCodingBuilder.build();
  1311.     }

  1312.     private MessageSplitter buildSplitter(Encoder encoder) {
  1313.         if (messageSplitterBuilder == null) {
  1314.             return buildContext.register(new NoSplitMessageSplitter(encoder));
  1315.         }
  1316.         MessageSplitter splitter = messageSplitterBuilder.build();
  1317.         if (splitter != null) {
  1318.             return splitter;
  1319.         }
  1320.         return buildContext.register(new NoSplitMessageSplitter(encoder));
  1321.     }

  1322.     private SmppClientSupplier buildClientSupplier() {
  1323.         if (clientSupplier == null) {
  1324.             return buildContext.register(DefaultSmppClient::new);
  1325.         }
  1326.         return clientSupplier;
  1327.     }

  1328.     private SmppSessionHandlerSupplier buildSmppSessionHandler() {
  1329.         if (smppSessionHandler == null) {
  1330.             return defaultSmppSessionHandlerSupplier();
  1331.         }
  1332.         return smppSessionHandler;
  1333.     }
  1334.    
  1335.     private SmppSessionHandlerSupplier defaultSmppSessionHandlerSupplier() {
  1336.         return () -> new RespondToEnquireLinkRequestHandler(new RespondToDeliveryReceiptHandler(new DefaultSmppSessionHandler()));
  1337.     }

  1338.     private PhoneNumberTranslator buildPhoneNumberTranslator() {
  1339.         // TODO: allow configuration of fallback phone number translator
  1340.         return buildContext.register(new CompositePhoneNumberTranslator(buildContext.register(new DefaultHandler())));
  1341.     }

  1342.     private ExtendedSmppSessionConfiguration buildSessionConfiguration(CloudhopperSessionOptions sessionOpts) {
  1343.         ExtendedSmppSessionConfiguration session = buildContext.register(new ExtendedSmppSessionConfiguration());
  1344.         ExtendedSmppSessionConfiguration manual = sessionConfiguration == null ? new ExtendedSmppSessionConfiguration() : sessionConfiguration;
  1345.         // @formatter:off
  1346.         merge(session::setType,                    bindTypeValueBuilder::getValue,            manual::getType,                                           () -> DEFAULT_BIND_TYPE);
  1347.         merge(session::setHost,                    hostValueBuilder::getValue,                manual::getHost);
  1348.         merge(session::setPort,                    portValueBuilder::getValue,                manual::getPort,                                           () -> 0);
  1349.         merge(session::setSystemId,                systemIdValueBuilder::getValue,            manual::getSystemId);
  1350.         merge(session::setPassword,                passwordValueBuilder::getValue,            manual::getPassword);
  1351.         merge(session::setSystemType,              systemTypeValueBuilder::getValue,          manual::getSystemType);
  1352.         merge(session::setBindTimeout,             sessionOpts::getBindTimeout,               considerZeroAsDefault(manual::getBindTimeout));
  1353.         merge(session::setConnectTimeout,          sessionOpts::getConnectTimeout,            considerZeroAsDefault(manual::getConnectTimeout));
  1354.         merge(session::setInterfaceVersion,        this::getInterfaceVersion,                 manual::getInterfaceVersion);
  1355.         merge(session::setName,                    sessionOpts::getSessionName,               manual::getName);
  1356.         merge(session::setRequestExpiryTimeout,    sessionOpts::getRequestExpiryTimeout,      considerZeroAsDefault(manual::getRequestExpiryTimeout));
  1357.         merge(session::setWindowMonitorInterval,   sessionOpts::getWindowMonitorInterval,     considerZeroAsDefault(manual::getWindowMonitorInterval));
  1358.         merge(session::setWindowSize,              sessionOpts::getWindowSize,                considerZeroAsDefault(manual::getWindowSize));
  1359.         merge(session::setWindowWaitTimeout,       sessionOpts::getWindowWaitTimeout,         considerZeroAsDefault(manual::getWindowWaitTimeout));
  1360.         merge(session::setWriteTimeout,            sessionOpts::getWriteTimeout,              considerZeroAsDefault(manual::getWriteTimeout));
  1361.         merge(session::setResponseTimeout,         sessionOpts::getResponseTimeout,           considerZeroAsDefault(manual::getResponseTimeout),         () -> DEFAULT_RESPONSE_TIMEOUT);
  1362.         merge(session::setUnbindTimeout,           sessionOpts::getUnbindTimeout,             considerZeroAsDefault(manual::getUnbindTimeout),           () -> DEFAULT_UNBIND_TIMEOUT);
  1363.         merge(session::setReuseSession,            sessionOpts::getReuseSession,              manual::getReuseSession);
  1364.         merge(session::setAddressRange,            () -> addressRange,                        manual::getAddressRange);
  1365.         merge(session::setKeepAlive,               sessionOpts::getKeepAlive,                 manual::getKeepAlive);
  1366.         merge(session::setConnectRetry,            () -> buildConnectRetry(sessionOpts),      manual::getConnectRetry);
  1367.         // @formatter:on
  1368.         configureSsl(session);
  1369.         configureLogs(session);
  1370.         return session;
  1371.     }

  1372.     private static <T> Supplier<T> considerZeroAsDefault(Supplier<T> getter) {
  1373.         T value = getter.get();
  1374.         if (value instanceof Long) {
  1375.             return () -> (Long) value == 0 ? null : value;
  1376.         }
  1377.         if (value instanceof Integer) {
  1378.             return () -> (Integer) value == 0 ? null : value;
  1379.         }
  1380.         return getter;
  1381.     }
  1382.    
  1383.     @SafeVarargs
  1384.     private static <T> void merge(Consumer<T> setter, Supplier<T>... getters) {
  1385.         T value = null;
  1386.         for (Supplier<T> getter : getters) {
  1387.             value = getter.get();
  1388.             if (value != null) {
  1389.                 break;
  1390.             }
  1391.         }
  1392.         // if there is a value, set it
  1393.         if (value != null) {
  1394.             setter.accept(value);
  1395.         }
  1396.     }

  1397.     private void configureLogs(SmppSessionConfiguration session) {
  1398.         if (loggingBuilder == null) {
  1399.             return;
  1400.         }
  1401.         LoggingOptions options = loggingBuilder.build();
  1402.         if (options != null) {
  1403.             session.setLoggingOptions(options);
  1404.         }
  1405.     }

  1406.     private void configureSsl(SmppSessionConfiguration session) {
  1407.         if (sslBuilder == null) {
  1408.             return;
  1409.         }
  1410.         SslConfiguration sslConfiguration = sslBuilder.build();
  1411.         session.setUseSsl(sslConfiguration != null);
  1412.         if (sslConfiguration != null) {
  1413.             session.setSslConfiguration(sslConfiguration);
  1414.         }
  1415.     }


  1416.     private Byte getInterfaceVersion() {
  1417.         InterfaceVersion version = interfaceVersionValueBuilder.getValue(DEFAULT_INTERFACE_VERSION);
  1418.         return version.value();
  1419.     }

  1420. }