CloudhopperBuilder.java
package fr.sii.ogham.sms.builder.cloudhopper;
import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_BIND_TYPE;
import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_CHARSET;
import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_INTERFACE_VERSION;
import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_RESPONSE_TIMEOUT;
import static fr.sii.ogham.sms.CloudhopperConstants.DEFAULT_UNBIND_TIMEOUT;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import com.cloudhopper.commons.charset.Charset;
import com.cloudhopper.smpp.SmppBindType;
import com.cloudhopper.smpp.SmppClient;
import com.cloudhopper.smpp.SmppConstants;
import com.cloudhopper.smpp.SmppSessionConfiguration;
import com.cloudhopper.smpp.SmppSessionHandler;
import com.cloudhopper.smpp.impl.DefaultSmppClient;
import com.cloudhopper.smpp.impl.DefaultSmppSessionHandler;
import com.cloudhopper.smpp.pdu.Pdu;
import com.cloudhopper.smpp.pdu.SubmitSm;
import com.cloudhopper.smpp.ssl.SslConfiguration;
import com.cloudhopper.smpp.type.Address;
import com.cloudhopper.smpp.type.LoggingOptions;
import fr.sii.ogham.core.async.ThreadSleepAwaiter;
import fr.sii.ogham.core.builder.Builder;
import fr.sii.ogham.core.builder.MessagingBuilder;
import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilder;
import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilderHelper;
import fr.sii.ogham.core.builder.configurer.Configurer;
import fr.sii.ogham.core.builder.context.BuildContext;
import fr.sii.ogham.core.builder.context.DefaultBuildContext;
import fr.sii.ogham.core.fluent.AbstractParent;
import fr.sii.ogham.core.retry.RetryExecutor;
import fr.sii.ogham.core.retry.SimpleRetryExecutor;
import fr.sii.ogham.sms.builder.SmsBuilder;
import fr.sii.ogham.sms.builder.cloudhopper.UserDataBuilder.UserDataPropValues;
import fr.sii.ogham.sms.encoder.Encoder;
import fr.sii.ogham.sms.message.Sms;
import fr.sii.ogham.sms.message.addressing.translator.CompositePhoneNumberTranslator;
import fr.sii.ogham.sms.message.addressing.translator.DefaultHandler;
import fr.sii.ogham.sms.message.addressing.translator.PhoneNumberTranslator;
import fr.sii.ogham.sms.sender.impl.CloudhopperSMPPSender;
import fr.sii.ogham.sms.sender.impl.cloudhopper.ExtendedSmppSessionConfiguration;
import fr.sii.ogham.sms.sender.impl.cloudhopper.KeepAliveOptions;
import fr.sii.ogham.sms.sender.impl.cloudhopper.encoder.CloudhopperCharsetSupportingEncoder;
import fr.sii.ogham.sms.sender.impl.cloudhopper.encoder.NamedCharset;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.CharsetMapToCharacterEncodingGroupDataCodingProvider;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.DataCodingProvider;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.MessagePreparator;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.ShortMessagePreparator;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.TlvMessagePayloadMessagePreparator;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.AlwaysNewSessionStrategy;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.DefaultErrorAnalyzer;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.ErrorAnalyzer;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.ErrorHandler;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.KeepSessionAliveStrategy;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.LogErrorHandler;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.MayReuseSessionStrategy;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.RespondToDeliveryReceiptHandler;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.RespondToEnquireLinkRequestHandler;
import fr.sii.ogham.sms.sender.impl.cloudhopper.session.SessionHandlingStrategy;
import fr.sii.ogham.sms.splitter.GsmMessageSplitter;
import fr.sii.ogham.sms.splitter.MessageSplitter;
import fr.sii.ogham.sms.splitter.NoSplitMessageSplitter;
import fr.sii.ogham.sms.splitter.ReferenceNumberGenerator;
/**
* Configures Cloudhopper:
*
* <ul>
* <li>SMPP protocol parameters (host, port, systemId, password,
* version...)</li>
* <li>Session management (name, bind, timeouts, retry...)</li>
* <li>SSL configuration</li>
* <li>Logging options</li>
* </ul>
*
* <p>
* To send {@link Sms} using Cloudhopper, you need to register this builder into
* a {@link MessagingBuilder} like this:
*
* <pre>
* <code>
* MessagingBuilder msgBuilder = ...
* msgBuilder.sms()
* .sender(CloudhopperBuilder.class) // registers the builder and accesses to that builder for configuring it
* </code>
* </pre>
*
* Once the builder is registered, sending sms through Cloudhopper requires at
* least host of the SMPP server. You can define it using:
*
* <pre>
* <code>
* msgBuilder.sms()
* .sender(CloudhopperBuilder.class) // registers the builder and accesses to that builder for configuring it
* .host("localhost")
* </code>
* </pre>
*
* Or you can also use property keys (using interpolation):
*
* <pre>
* <code>
* msgBuilder
* .environment()
* .properties()
* .set("custom.property.for.host", "localhost")
* .and()
* .and()
* .sms()
* .sender(CloudhopperBuilder.class) // registers the builder and accesses to that builder for configuring it
* .host()
* .properties("${custom.property.for.host}")
* </code>
* </pre>
*
* You can do the same with port of the SMPP server.
*
*
* <p>
* SMPP server may require authentication. In most cases, authentication is done
* using system_id/password. You can use this builder to quickly provide your
* system_id and password:
*
* <pre>
* <code>
* .sender(CloudhopperBuilder.class)
* .systemId("foo")
* .password("bar")
* </code>
* </pre>
*
*
* @author Aurélien Baudet
*/
public class CloudhopperBuilder extends AbstractParent<SmsBuilder> implements Builder<CloudhopperSMPPSender> {
private static final Logger LOG = LoggerFactory.getLogger(CloudhopperBuilder.class);
private final ReadableEncoderBuilder sharedEncoderBuilder;
private BuildContext buildContext;
private final ConfigurationValueBuilderHelper<CloudhopperBuilder, String> systemIdValueBuilder;
private final ConfigurationValueBuilderHelper<CloudhopperBuilder, String> passwordValueBuilder;
private final ConfigurationValueBuilderHelper<CloudhopperBuilder, String> hostValueBuilder;
private final ConfigurationValueBuilderHelper<CloudhopperBuilder, Integer> portValueBuilder;
private final ConfigurationValueBuilderHelper<CloudhopperBuilder, String> systemTypeValueBuilder;
private final ConfigurationValueBuilderHelper<CloudhopperBuilder, InterfaceVersion> interfaceVersionValueBuilder;
private final ConfigurationValueBuilderHelper<CloudhopperBuilder, SmppBindType> bindTypeValueBuilder;
private SessionBuilder sessionBuilder;
private ExtendedSmppSessionConfiguration sessionConfiguration;
private Address addressRange;
private SslBuilder sslBuilder;
private LoggingBuilder loggingBuilder;
private SmppClientSupplier clientSupplier;
private SmppSessionHandlerSupplier smppSessionHandler;
private MessageSplitterBuilder messageSplitterBuilder;
private EncoderBuilder encoderBuilder;
private UserDataBuilder userDataBuilder;
private DataCodingSchemeBuilder dataCodingBuilder;
private MessagePreparator preparator;
/**
* Default constructor when using without all Ogham work.
*
* <strong>WARNING: use is only if you know what you are doing !</strong>
*/
public CloudhopperBuilder() {
this(null, new DefaultBuildContext());
}
/**
* Constructor that is called when using Ogham builder:
*
* <pre>
* MessagingBuilder msgBuilder = ...
* msgBuilder
* .sms()
* .sender(CloudhopperBuilder.class)
* </pre>
*
* @param parent
* the parent builder instance for fluent chaining
* @param buildContext
* for registering instances and property evaluation
*/
public CloudhopperBuilder(SmsBuilder parent, BuildContext buildContext) {
super(parent);
this.buildContext = buildContext;
sharedEncoderBuilder = new ReadableEncoderBuilder(buildContext);
systemIdValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
passwordValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
hostValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
portValueBuilder = buildContext.newConfigurationValueBuilder(this, Integer.class);
interfaceVersionValueBuilder = buildContext.newConfigurationValueBuilder(this, InterfaceVersion.class);
systemTypeValueBuilder = buildContext.newConfigurationValueBuilder(this, String.class);
bindTypeValueBuilder = buildContext.newConfigurationValueBuilder(this, SmppBindType.class);
}
/**
* The system_id parameter is used to identify an ESME ( External Short
* Message Entity) or an SMSC (Short Message Service Centre) at bind time.
* An ESME system_id identifies the ESME or ESME agent to the SMSC. The SMSC
* system_id provides an identification of the SMSC to the ESME.
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #systemId()}.
*
* <pre>
* .systemId("my-system-id")
* .systemId()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-system-id")
* </pre>
*
* <pre>
* .systemId("my-system-id")
* .systemId()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-system-id")
* </pre>
*
* In both cases, {@code systemId("my-system-id")} is used.
*
* <p>
* If this method is called several times, only the last value is used.
*
* <p>
* If {@code null} value is set, it is like not setting a value at all. The
* property/default value configuration is applied.
*
* @param systemId
* the system_id value
* @return this instance for fluent chaining
*/
public CloudhopperBuilder systemId(String systemId) {
systemIdValueBuilder.setValue(systemId);
return this;
}
/**
* The system_id parameter is used to identify an ESME ( External Short
* Message Entity) or an SMSC (Short Message Service Centre) at bind time.
* An ESME system_id identifies the ESME or ESME agent to the SMSC. The SMSC
* system_id provides an identification of the SMSC to the ESME.
*
* <p>
* This method is mainly used by {@link Configurer}s to register some
* property keys and/or a default value. The aim is to let developer be able
* to externalize its configuration (using system properties, configuration
* file or anything else). If the developer doesn't configure any value for
* the registered properties, the default value is used (if set).
*
* <pre>
* .systemId()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-system-id")
* </pre>
*
* <p>
* Non-null value set using {@link #systemId(String)} takes precedence over
* property values and default value.
*
* <pre>
* .systemId("my-system-id")
* .systemId()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-system-id")
* </pre>
*
* The value {@code "my-system-id"} is used regardless of the value of the
* properties and default value.
*
* <p>
* See {@link ConfigurationValueBuilder} for more information.
*
*
* @return the builder to configure property keys/default value
*/
public ConfigurationValueBuilder<CloudhopperBuilder, String> systemId() {
return systemIdValueBuilder;
}
/**
* The system_type parameter is used to categorize the type of ESME that is
* binding to the SMSC. Examples include “VMS” (voice mail system) and “OTA”
* (over-the-air activation system). Specification of the system_type is
* optional - some SMSC’s may not require ESME’s to provide this detail. In
* this case, the ESME can set the system_type to NULL. The system_type
* (optional) may be used to categorize the system, e.g., “EMAIL”, “WWW”,
* etc.
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #systemType()}.
*
* <pre>
* .systemType("my-system-type")
* .systemType()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-system-type")
* </pre>
*
* <pre>
* .systemType("my-system-type")
* .systemType()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-system-type")
* </pre>
*
* In both cases, {@code systemType("my-system-type")} is used.
*
* <p>
* If this method is called several times, only the last value is used.
*
* <p>
* If {@code null} value is set, it is like not setting a value at all. The
* property/default value configuration is applied.
*
* @param systemType
* the system type value
* @return this instance for fluent chaining
*/
public CloudhopperBuilder systemType(String systemType) {
systemTypeValueBuilder.setValue(systemType);
return this;
}
/**
* The system_type parameter is used to categorize the type of ESME that is
* binding to the SMSC. Examples include “VMS” (voice mail system) and “OTA”
* (over-the-air activation system). Specification of the system_type is
* optional - some SMSC’s may not require ESME’s to provide this detail. In
* this case, the ESME can set the system_type to NULL. The system_type
* (optional) may be used to categorize the system, e.g., “EMAIL”, “WWW”,
* etc.
*
* <p>
* This method is mainly used by {@link Configurer}s to register some
* property keys and/or a default value. The aim is to let developer be able
* to externalize its configuration (using system properties, configuration
* file or anything else). If the developer doesn't configure any value for
* the registered properties, the default value is used (if set).
*
* <pre>
* .systemType()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("defaut-system-type")
* </pre>
*
* <p>
* Non-null value set using {@link #systemType(String)} takes precedence
* over property values and default value.
*
* <pre>
* .systemType("my-system-type")
* .systemType()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("defaut-system-type")
* </pre>
*
* The value {@code "my-system-type"} is used regardless of the value of the
* properties and default value.
*
* <p>
* See {@link ConfigurationValueBuilder} for more information.
*
*
* @return the builder to configure property keys/default value
*/
public ConfigurationValueBuilder<CloudhopperBuilder, String> systemType() {
return systemTypeValueBuilder;
}
/**
* The password parameter is used by the SMSC to authenticate the identity
* of the binding ESME. The Service Provider may require ESME’s to provide a
* password when binding to the SMSC. This password is normally issued by
* the SMSC system administrator. The password parameter may also be used by
* the ESME to authenticate the identity of the binding SMSC (e.g. in the
* case of the outbind operation).
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #password()}.
*
* <pre>
* .password("my-password")
* .password()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-password")
* </pre>
*
* <pre>
* .password("my-password")
* .password()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-password")
* </pre>
*
* In both cases, {@code password("my-password")} is used.
*
* <p>
* If this method is called several times, only the last value is used.
*
* <p>
* If {@code null} value is set, it is like not setting a value at all. The
* property/default value configuration is applied.
*
* @param password
* the password used to authenticate
* @return this instance for fluent chaining
*/
public CloudhopperBuilder password(String password) {
passwordValueBuilder.setValue(password);
return this;
}
/**
* The password parameter is used by the SMSC to authenticate the identity
* of the binding ESME. The Service Provider may require ESME’s to provide a
* password when binding to the SMSC. This password is normally issued by
* the SMSC system administrator. The password parameter may also be used by
* the ESME to authenticate the identity of the binding SMSC (e.g. in the
* case of the outbind operation).
*
* <p>
* This method is mainly used by {@link Configurer}s to register some
* property keys and/or a default value. The aim is to let developer be able
* to externalize its configuration (using system properties, configuration
* file or anything else). If the developer doesn't configure any value for
* the registered properties, the default value is used (if set).
*
* <pre>
* .password()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-password")
* </pre>
*
* <p>
* Non-null value set using {@link #password(String)} takes precedence over
* property values and default value.
*
* <pre>
* .password("my-password")
* .password()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-password")
* </pre>
*
* The value {@code "my-password"} is used regardless of the value of the
* properties and default value.
*
* <p>
* See {@link ConfigurationValueBuilder} for more information.
*
*
* @return the builder to configure property keys/default value
*/
public ConfigurationValueBuilder<CloudhopperBuilder, String> password() {
return passwordValueBuilder;
}
/**
* The SMPP server host (IP or address).
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #host()}.
*
* <pre>
* .host("localhost")
* .host()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-host")
* </pre>
*
* <pre>
* .host("localhost")
* .host()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-host")
* </pre>
*
* In both cases, {@code host("localhost")} is used.
*
* <p>
* If this method is called several times, only the last value is used.
*
* <p>
* If {@code null} value is set, it is like not setting a value at all. The
* property/default value configuration is applied.
*
* @param host
* the host address
* @return this instance for fluent chaining
*/
public CloudhopperBuilder host(String host) {
hostValueBuilder.setValue(host);
return this;
}
/**
* The SMPP server host (IP or address).
*
* <p>
* This method is mainly used by {@link Configurer}s to register some
* property keys and/or a default value. The aim is to let developer be able
* to externalize its configuration (using system properties, configuration
* file or anything else). If the developer doesn't configure any value for
* the registered properties, the default value is used (if set).
*
* <pre>
* .host()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-host")
* </pre>
*
* <p>
* Non-null value set using {@link #host(String)} takes precedence over
* property values and default value.
*
* <pre>
* .host("localhost")
* .host()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue("default-host")
* </pre>
*
* The value {@code "localhost"} is used regardless of the value of the
* properties and default value.
*
* <p>
* See {@link ConfigurationValueBuilder} for more information.
*
*
* @return the builder to configure property keys/default value
*/
public ConfigurationValueBuilder<CloudhopperBuilder, String> host() {
return hostValueBuilder;
}
/**
* Set the SMPP server port.
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #port()}.
*
* <pre>
* .port(2775)
* .port()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(1775)
* </pre>
*
* <pre>
* .port(2775)
* .port()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(1775)
* </pre>
*
* In both cases, {@code port(2775)} is used.
*
* <p>
* If this method is called several times, only the last value is used.
*
* <p>
* If {@code null} value is set, it is like not setting a value at all. The
* property/default value configuration is applied.
*
* @param port
* the SMPP server port
* @return this instance for fluent chaining
*/
public CloudhopperBuilder port(Integer port) {
portValueBuilder.setValue(port);
return this;
}
/**
* Set the SMPP server port.
*
* <p>
* This method is mainly used by {@link Configurer}s to register some
* property keys and/or a default value. The aim is to let developer be able
* to externalize its configuration (using system properties, configuration
* file or anything else). If the developer doesn't configure any value for
* the registered properties, the default value is used (if set).
*
* <pre>
* .port()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(1775)
* </pre>
*
* <p>
* Non-null value set using {@link #port(Integer)} takes precedence over
* property values and default value.
*
* <pre>
* .port(2775)
* .port()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(1775)
* </pre>
*
* The value {@code 2775} is used regardless of the value of the properties
* and default value.
*
* <p>
* See {@link ConfigurationValueBuilder} for more information.
*
*
* @return the builder to configure property keys/default value
*/
public ConfigurationValueBuilder<CloudhopperBuilder, Integer> port() {
return portValueBuilder;
}
/**
* The SMPP protocol version (one of {@link InterfaceVersion#VERSION_3_3},
* {@link InterfaceVersion#VERSION_3_4},
* {@link InterfaceVersion#VERSION_5_0}).
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #interfaceVersion()}.
*
* <pre>
* .interfaceVersion(InterfaceVersion.VERSION_5_0)
* .interfaceVersion()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(InterfaceVersion.VERSION_3_4)
* </pre>
*
* <pre>
* .interfaceVersion(InterfaceVersion.VERSION_5_0)
* .interfaceVersion()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(InterfaceVersion.VERSION_3_4)
* </pre>
*
* In both cases, {@code interfaceVersion(InterfaceVersion.VERSION_5_0)} is
* used.
*
* <p>
* If this method is called several times, only the last value is used.
*
* <p>
* If {@code null} value is set, it is like not setting a value at all. The
* property/default value configuration is applied.
*
* @param version
* the version of the SMPP protocol
* @return this instance for fluent chaining
*/
public CloudhopperBuilder interfaceVersion(InterfaceVersion version) {
interfaceVersionValueBuilder.setValue(version);
return this;
}
/**
* The SMPP protocol version (one of {@link SmppConstants#VERSION_3_3},
* {@link SmppConstants#VERSION_3_4}, {@link SmppConstants#VERSION_5_0}).
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #interfaceVersion()}.
*
* <pre>
* .interfaceVersion(SmppConstants.VERSION_5_0)
* .interfaceVersion()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(SmppConstants.VERSION_3_4)
* </pre>
*
* <pre>
* .interfaceVersion(SmppConstants.VERSION_5_0)
* .interfaceVersion()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(SmppConstants.VERSION_3_4)
* </pre>
*
* In both cases, {@code interfaceVersion(SmppConstants.VERSION_5_0)} is
* used.
*
* <p>
* If this method is called several times, only the last value is used.
*
* <p>
* If {@code null} value is set, it is like not setting a value at all. The
* property/default value configuration is applied.
*
* @param version
* the version of the SMPP protocol
* @return this instance for fluent chaining
*/
public CloudhopperBuilder interfaceVersion(Byte version) {
interfaceVersionValueBuilder.setValue(InterfaceVersion.fromValue(version));
return this;
}
/**
* The SMPP protocol version (one of {@link InterfaceVersion#VERSION_3_3},
* {@link InterfaceVersion#VERSION_3_4},
* {@link InterfaceVersion#VERSION_5_0}).
*
*
* <p>
* This method is mainly used by {@link Configurer}s to register some
* property keys and/or a default value. The aim is to let developer be able
* to externalize its configuration (using system properties, configuration
* file or anything else). If the developer doesn't configure any value for
* the registered properties, the default value is used (if set).
*
* <pre>
* .interfaceVersion()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(InterfaceVersion.VERSION_3_4)
* </pre>
*
* <p>
* Non-null value set using {@link #interfaceVersion(InterfaceVersion)}
* takes precedence over property values and default value.
*
* <pre>
* .interfaceVersion(InterfaceVersion.VERSION_5_0)
* .interfaceVersion()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(InterfaceVersion.VERSION_3_4)
* </pre>
*
* The value {@code InterfaceVersion.VERSION_5_0} is used regardless of the
* value of the properties and default value.
*
* <p>
* See {@link ConfigurationValueBuilder} for more information.
*
*
* @return the builder to configure property keys/default value
*/
public ConfigurationValueBuilder<CloudhopperBuilder, InterfaceVersion> interfaceVersion() {
return interfaceVersionValueBuilder;
}
/**
* The bind command type (see {@link SmppBindType}).
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #bindType()}.
*
* <pre>
* .bindType(SmppBindType.TRANSCEIVER)
* .bindType()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(SmppBindType.RECEIVER)
* </pre>
*
* <pre>
* .bindType(SmppBindType.TRANSCEIVER)
* .bindType()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(SmppBindType.RECEIVER)
* </pre>
*
* In both cases, {@code bindType(SmppBindType.TRANSCEIVER)} is used.
*
* <p>
* If this method is called several times, only the last value is used.
*
* <p>
* If {@code null} value is set, it is like not setting a value at all. The
* property/default value configuration is applied.
*
* @param bindType
* the bind type
* @return this instance for fluent chaining
*/
public CloudhopperBuilder bindType(SmppBindType bindType) {
bindTypeValueBuilder.setValue(bindType);
return this;
}
/**
* The bind command type (see {@link SmppBindType}).
*
* <p>
* This method is mainly used by {@link Configurer}s to register some
* property keys and/or a default value. The aim is to let developer be able
* to externalize its configuration (using system properties, configuration
* file or anything else). If the developer doesn't configure any value for
* the registered properties, the default value is used (if set).
*
* <pre>
* .bindType()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(SmppBindType.RECEIVER)
* </pre>
*
* <p>
* Non-null value set using {@link #bindType(SmppBindType)} takes precedence
* over property values and default value.
*
* <pre>
* .bindType(SmppBindType.TRANSCEIVER)
* .bindType()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(SmppBindType.RECEIVER)
* </pre>
*
* The value {@code SmppBindType.TRANSCEIVER} is used regardless of the
* value of the properties and default value.
*
* <p>
* See {@link ConfigurationValueBuilder} for more information.
*
*
* @return the builder to configure property keys/default value
*/
public ConfigurationValueBuilder<CloudhopperBuilder, SmppBindType> bindType() {
return bindTypeValueBuilder;
}
/**
* Configures how Cloudhopper will encode SMS messages. Charsets defined by
* the SMPP protocol may be different from NIO charsets.
*
* <p>
* The encoder will be used to transform Java {@link String} into a byte
* array that is understandable by SMPP servers.
*
* <p>
* This builder configures encoders for both messages that are split and
* message that are not split.
*
* <p>
* This builder allows to configure:
* <ul>
* <li>Enable/disable the standard GSM encoders (GSM 7-bit, GSM 8-bit and
* UCS-2) as defined in
* <a href="https://en.wikipedia.org/wiki/GSM_03.38">GSM 03.38
* specification</a>. It also allows to define different priority order</li>
* <li>Enable/disable automatic guessing of encoding (based on previously
* registered priorities).</li>
* <li>Define a fallback encoder based on {@link Charset}</li>
* <li>Provide custom {@link Encoder}s</li>
* </ul>
*
* <pre>
* {@code
* .encoder()
* .gsm7()
* .properties("${ogham.sms.cloudhopper.encoder.gsm7bit-packed.priority}", "${ogham.sms.smpp.encoder.gsm7bit-packed.priority}")
* .defaultValue(100000)
* .and()
* .gsm8()
* .properties("${ogham.sms.cloudhopper.encoder.gsm8bit.priority}", "${ogham.sms.smpp.encoder.gsm8bit.priority}")
* .defaultValue(99000)
* .and()
* .ucs2()
* .properties("${ogham.sms.cloudhopper.encoder.ucs2.priority}", "${ogham.sms.smpp.encoder.ucs2.priority}")
* .defaultValue(98000)
* .and()
* .autoGuess()
* .properties("${ogham.sms.cloudhopper.encoder.auto-guess.enable}", "${ogham.sms.smpp.encoder.auto-guess.enable}")
* .defaultValue(true)
* .and()
* .fallback()
* .properties("${ogham.sms.cloudhopper.encoder.default-charset}", "${ogham.sms.smpp.encoder.default-charset}")
* .defaultValue(CharsetUtil.NAME_GSM)
* .and()
* .customEncoder(new MyCustomEncoder(), 50000)
* }
* </pre>
*
* @return the builder to configure the encoder
*/
public EncoderBuilder encoder() {
if (encoderBuilder == null) {
encoderBuilder = new EncoderBuilder(this, buildContext);
sharedEncoderBuilder.update(encoderBuilder);
}
return encoderBuilder;
}
/**
* Configures how Cloudhopper will split messages.
*
* <p>
* The splitter will check if the whole message can fit in a single segment.
* If not the splitter will split the whole message in several segments with
* a header to indicate splitting information such as number of segments,
* reference number and current segment number.
*
* <p>
* {@link Encoder} configured using {@link #encoder()} is used to encode
* each segment.
*
* <p>
* If automatic guessing of best standard encoder is enabled for
* {@link Encoder} (using {@code encoder().autoGuess(true)}), and message
* splitting is enabled, then standard message splitting is configured such
* as:
* <ul>
* <li>If GSM 7-bit encoder is enabled, {@link GsmMessageSplitter} is used
* to split messages that support this encoding. If whole message can fit in
* a single segment of 160 characters. Longer message is split into segments
* of either 153 characters or 152 characters (depending on reference number
* generation, see {@link ReferenceNumberGenerator})</li>
* <li>If GSM 8-bit encoder is enabled, {@link GsmMessageSplitter} is used
* to split messages that support this encoding. If whole message can fit in
* a single segment of 140 characters. Longer message is split into segments
* of either 134 characters or 133 characters (depending on reference number
* generation, see {@link ReferenceNumberGenerator})</li>
* <li>If UCS-2 encoder is enabled, {@link GsmMessageSplitter} is used to
* split messages that support this encoding. If whole message can fit in a
* single segment of 70 characters. Longer message is split into segments of
* either 67 characters or 66 characters (depending on reference number
* generation, see {@link ReferenceNumberGenerator})</li>
* </ul>
*
* Each registered splitter uses the same priority as associated
* {@link Encoder}.
*
* If you don't want standard message splitting based on supported
* {@link Encoder}s, you can either disable message splitting or provide a
* custom splitter with higher priority.
*
* <p>
* This builder allows to configure:
* <ul>
* <li>Enable/disable message splitting</li>
* <li>Provide a custom split strategy</li>
* <li>Choose strategy for reference number generation</li>
* </ul>
*
* <p>
* Examples of usage:
*
* <pre>
* {@code
* .splitter()
* .enable()
* .properties("${ogham.sms.cloudhopper.split.enable}", "${ogham.sms.smpp.split.enable}", "${ogham.sms.split.enable}")
* .defaultValue(true)
* .and()
* .customSplitter(new MyCustomSplitter(), 100000)
* .referenceNumber()
* .random()
* .random(new Random())
* .generator(new MyCustomReferenceNumberGenerator())
* }
* </pre>
*
* @return the builder to configure message splitting
*/
public MessageSplitterBuilder splitter() {
if (messageSplitterBuilder == null) {
messageSplitterBuilder = new MessageSplitterBuilder(this, buildContext, sharedEncoderBuilder);
}
return messageSplitterBuilder;
}
/**
* Configures Cloudhopper session management (timeouts, retry, session
* name...).
*
* @return the builder to configure the session management
*/
public SessionBuilder session() {
if (sessionBuilder == null) {
sessionBuilder = new SessionBuilder(this, buildContext);
}
return sessionBuilder;
}
/**
* Overrides any previously defined Cloudhopper parameters to use the
* provided session.
*
* <p>
* If this method is called several times, only the last session is used.
*
* @param session
* the Cloudhopper session to use
* @return this instance for fluent chaining
*/
public CloudhopperBuilder session(ExtendedSmppSessionConfiguration session) {
this.sessionConfiguration = session;
return this;
}
/**
* The address_range parameter is used in the bind_receiver and
* bind_transceiver command to specify a set of SME addresses serviced by
* the ESME client. A single SME address may also be specified in the
* address_range parameter. UNIX Regular Expression notation should be used
* to specify a range of addresses. Messages addressed to any destination in
* this range shall be routed to the ESME.
*
* Default to {@code null}.
*
* Note: For IP addresses, it is only possible to specify a single IP
* address. A range of IP addresses are not allowed. IP version 6.0 is not
* currently supported in this version of the protocol.
*
* Note: It is likely that the addr_range field is not supported or
* deliberately ignored on most Message Centres. The reason for this is that
* most carriers will not allow an ESME control the message routing as this
* can carry the risk of mis-routing messages. In such circumstances, the
* ESME will be requested to set the field to NULL.
*
* @param range
* the address range
* @return this instance for fluent chaining
*/
public CloudhopperBuilder addressRange(Address range) {
this.addressRange = range;
return this;
}
/**
* Enable or disable SSL configuration and configure how SSL is handled.
*
* See <a href=
* "https://github.com/fizzed/cloudhopper-smpp/blob/master/SSL.md">How to
* use SSL with cloudhopper-smpp</a>
*
* @return the builder to configure SSL
*/
public SslBuilder ssl() {
if (sslBuilder == null) {
sslBuilder = new SslBuilder(this, buildContext);
}
return sslBuilder;
}
/**
* Configure logs:
* <ul>
* <li>Enable/disable log of {@link Pdu}s</li>
* <li>Enable/disable log of bytes</li>
* </ul>
*
* @return the builder to enable/disable some logs
*/
public LoggingBuilder logging() {
if (loggingBuilder == null) {
loggingBuilder = new LoggingBuilder(this);
}
return loggingBuilder;
}
/**
* By default, {@link CloudhopperSMPPSender} uses {@link DefaultSmppClient}
* client. This option provides a way to use another {@link SmppClient}.
*
* @param supplier
* an implementation that provides an instance of a
* {@link SmppClient}
* @return this instance for fluent chaining
*/
public CloudhopperBuilder clientSupplier(SmppClientSupplier supplier) {
this.clientSupplier = supplier;
return this;
}
/**
* By default, {@link CloudhopperSMPPSender} uses
* {@link DefaultSmppSessionHandler}. This option provides a way to use
* another {@link SmppSessionHandler}.
*
* @param supplier
* an implementation that provides an instance of a
* {@link SmppSessionHandler}
* @return this instance for fluent chaining
*/
public CloudhopperBuilder smppSessionHandlerSupplier(SmppSessionHandlerSupplier supplier) {
this.smppSessionHandler = supplier;
return this;
}
/**
* {@link Sms} message is converted to {@link SubmitSm}(s) using a
* {@link MessagePreparator}.
*
* <p>
* You can provide a custom {@link MessagePreparator} instance if the
* default behavior doesn't fit your needs.
* </p>
*
* <p>
* If a custom {@link MessagePreparator} is set, any other preparator (using
* {@link #userData()}) is not used.
* </p>
*
* <p>
* If this method is called several times, only the last value is used.
* </p>
*
* <p>
* If {@code null} value is provided, then custom {@link MessagePreparator}
* is disabled. Other configured preparators are used (using
* {@link #userData()}).
* </p>
*
* @param preparator
* the custom preprator instance
* @return this instance for fluent chaining
* @see #userData()
*/
public CloudhopperBuilder messagePreparator(MessagePreparator preparator) {
this.preparator = preparator;
return this;
}
/**
* SMS message (named "User Data" in SMPP specification) can be transmitted
* using:
* <ul>
* <li>Either {@code short_message} field (standard field for "User
* Data").</li>
* <li>Or {@code message_payload} optional parameter.</li>
* </ul>
*
* <p>
* This builder allow to configure which strategy to use for sending
* message:
* <ul>
* <li>Either use {@code short_message} field</li>
* <li>Or use {@code message_payload} field</li>
* </ul>
*
* <p>
* The result of {@link #userData()} configuration affects the message
* preparation strategy.
* </p>
*
* <p>
* Examples of usage:
*
* <pre>
* {@code
* .userData()
* .useShortMessage()
* .properties("${ogham.sms.cloudhopper.user-data.use-short-message}", "${ogham.sms.smpp.user-data.use-short-message}")
* .defaultValue(true)
* .and()
* .useTlvMessagePayload()
* .properties("${ogham.sms.cloudhopper.user-data.use-tlv-message-payload}", "${ogham.sms.smpp.user-data.use-tlv-message-payload}")
* .defaultValue(false)
* }
* </pre>
*
* If any of {@code ogham.sms.cloudhopper.user-data.use-short-message}
* property or {@code ogham.sms.user-data.use-short-message} property is set
* to true, it uses {@code short_message} field.
*
* If any of {@code ogham.sms.cloudhopper.user-data.use-tlv-message-payload}
* property or {@code ogham.sms.user-data.use-tlv-message-payload} property
* is set to true, it uses {@code message_payload} field.
*
* If none of the above properties is set, it uses {@code short_message}
* field is used (last value of {@code shortMessage} is set to
* {@code "true"}).
*
* <p>
* If {@link #userData()} is not configured at all, then default behavior is
* used ({@code short_message} field is used).
* </p>
*
* @return the builder to configure how the "User Data" is sent
*/
public UserDataBuilder userData() {
if (userDataBuilder == null) {
userDataBuilder = new UserDataBuilder(this, buildContext);
}
return userDataBuilder;
}
/**
* Data Coding Scheme is a one-octet field in Short Messages (SM) and Cell
* Broadcast Messages (CB) which carries a basic information how the
* recipient handset should process the received message. The information
* includes:
* <ul>
* <li>the character set or message coding which determines the encoding of
* the message user data</li>
* <li>the message class which determines to which component of the Mobile
* Station (MS) or User Equipment (UE) should be the message delivered</li>
* <li>the request to automatically delete the message after reading</li>
* <li>the state of flags indicating presence of unread voicemail, fax,
* e-mail or other messages</li>
* <li>the indication that the message content is compressed</li>
* <li>the language of the cell broadcast message</li>
* </ul>
* The field is described in 3GPP 23.040 and 3GPP 23.038 under the name
* TP-DCS (see <a href=
* "https://en.wikipedia.org/wiki/Data_Coding_Scheme#SMS_Data_Coding_Scheme">SMS
* Data Coding Scheme</a>).
*
* SMPP 3.4 introduced a new list of {@code data_coding} values (see
* <a href="https://en.wikipedia.org/wiki/Short_Message_Peer-to-Peer">Short
* Message Peer to Peer</a>).
*
* <p>
* This builder allows to configure how Data Coding Scheme value is
* determined:
* <ul>
* <li>Use automatic mode base on interface version (see
* {@link #interfaceVersion(InterfaceVersion)} and
* {@link #interfaceVersion(Byte)}) and charset encoding (see
* {@link #encoder()}) used to encode the message ("User Data")</li>
* <li>Use a fixed value used for every message</li>
* <li>Use a custom implementation</li>
* </ul>
*
* <p>
* Examples of usage:
*
* <pre>
* {@code
* .dataCodingScheme()
* .auto("${ogham.sms.cloudhopper.data-coding-scheme.auto.enable}", "${ogham.sms.smpp.data-coding-scheme.auto.enable}")
* .value("${ogham.sms.cloudhopper.data-coding-scheme.value}", "${ogham.sms.smpp.data-coding-scheme.value}")
* .custom(new MyCustomDataCodingProvider())
* }
* </pre>
*
* See {@link DataCodingSchemeBuilder#auto(Boolean)},
* {@link DataCodingSchemeBuilder#value(Byte)} and
* {@link DataCodingSchemeBuilder#custom(DataCodingProvider)} for more
* information.
*
*
* @return the builder to configure how to determine Data Coding Scheme
* value
*/
public DataCodingSchemeBuilder dataCodingScheme() {
if (dataCodingBuilder == null) {
dataCodingBuilder = new DataCodingSchemeBuilder(this, buildContext, this::getInterfaceVersion);
}
return dataCodingBuilder;
}
@Override
public CloudhopperSMPPSender build() {
CloudhopperSessionOptions sessionOpts = buildSessionOpts();
ExtendedSmppSessionConfiguration configuration = buildSessionConfiguration(sessionOpts);
if (configuration.getHost() == null || configuration.getPort() == 0) {
return null;
}
LOG.info("Sending SMS using Cloudhopper is registered");
LOG.debug("SMPP server address: {}:{}", configuration.getHost(), configuration.getPort());
SessionHandlingStrategy sessionHandler = buildSessionHandlingStrategy(configuration);
return buildContext.register(new CloudhopperSMPPSender(configuration, sessionHandler, buildPreparator()));
}
private CloudhopperSessionOptions buildSessionOpts() {
if (sessionBuilder != null) {
return sessionBuilder.build();
}
CloudhopperSessionOptions cloudhopperSessionOptions = buildContext.register(new CloudhopperSessionOptions());
cloudhopperSessionOptions.setConnectRetry(buildConnectRetry(cloudhopperSessionOptions));
return cloudhopperSessionOptions;
}
private SessionHandlingStrategy buildSessionHandlingStrategy(ExtendedSmppSessionConfiguration configuration) {
if (configuration.getKeepAlive() != null && configuration.getKeepAlive().isEnable(false)) {
return buildKeepAliveHandler(configuration);
}
if (configuration.getReuseSession() != null && configuration.getReuseSession().isEnable(false)) {
return buildReuseSessionHandler(configuration);
}
return buildAlwaysNewSessionHandler(configuration);
}
private SessionHandlingStrategy buildKeepAliveHandler(ExtendedSmppSessionConfiguration configuration) {
return new KeepSessionAliveStrategy(configuration, buildClientSupplier(), buildSmppSessionHandler(), configuration.getConnectRetry(), configuration.getKeepAlive().getExecutor(), buildKeepAliveErrorAnalyzer(configuration.getKeepAlive()), buildReconnectionErrorHandler());
}
private ErrorAnalyzer buildKeepAliveErrorAnalyzer(KeepAliveOptions options) {
return new DefaultErrorAnalyzer(options.getMaxConsecutiveTimeouts());
}
private ErrorHandler buildReconnectionErrorHandler() {
// TODO: make this configurable ?
return new LogErrorHandler("Failed to reconnect", Level.ERROR);
}
private SessionHandlingStrategy buildReuseSessionHandler(ExtendedSmppSessionConfiguration configuration) {
return new MayReuseSessionStrategy(configuration, buildClientSupplier(), buildSmppSessionHandler(), configuration.getConnectRetry(), buildReuseSessionErrorAnalyzer());
}
private ErrorAnalyzer buildReuseSessionErrorAnalyzer() {
return new DefaultErrorAnalyzer(1);
}
private SessionHandlingStrategy buildAlwaysNewSessionHandler(ExtendedSmppSessionConfiguration configuration) {
return new AlwaysNewSessionStrategy(configuration, buildClientSupplier(), buildSmppSessionHandler(), configuration.getConnectRetry());
}
private RetryExecutor buildConnectRetry(CloudhopperSessionOptions sessionOpts) {
if (sessionOpts.getConnectRetry() == null) {
return noRetry();
}
return sessionOpts.getConnectRetry();
}
private SimpleRetryExecutor noRetry() {
return buildContext.register(new SimpleRetryExecutor(() -> null, buildContext.register(new ThreadSleepAwaiter())));
}
private MessagePreparator buildPreparator() {
if (preparator != null) {
return preparator;
}
if (userDataBuilder != null) {
UserDataPropValues values = userDataBuilder.build();
if (values.isUseShortMessage()) {
return buildShortMessagePreparator();
}
if (values.isUseTlvMessagePayload()) {
return buildTlvMessagePayloadMessagePreparator();
}
}
return buildShortMessagePreparator();
}
private MessagePreparator buildShortMessagePreparator() {
return buildContext.register(new ShortMessagePreparator(buildSplitter(buildEncoder()), buildDataCodingProvider(), buildPhoneNumberTranslator()));
}
private MessagePreparator buildTlvMessagePayloadMessagePreparator() {
return buildContext.register(new TlvMessagePayloadMessagePreparator(buildSplitter(buildEncoder()), buildDataCodingProvider(), buildPhoneNumberTranslator()));
}
private Encoder buildEncoder() {
if (encoderBuilder == null) {
return buildContext.register(new CloudhopperCharsetSupportingEncoder(NamedCharset.from(DEFAULT_CHARSET)));
}
return encoderBuilder.build();
}
private DataCodingProvider buildDataCodingProvider() {
if (dataCodingBuilder == null) {
return buildContext.register(new CharsetMapToCharacterEncodingGroupDataCodingProvider(true));
}
return dataCodingBuilder.build();
}
private MessageSplitter buildSplitter(Encoder encoder) {
if (messageSplitterBuilder == null) {
return buildContext.register(new NoSplitMessageSplitter(encoder));
}
MessageSplitter splitter = messageSplitterBuilder.build();
if (splitter != null) {
return splitter;
}
return buildContext.register(new NoSplitMessageSplitter(encoder));
}
private SmppClientSupplier buildClientSupplier() {
if (clientSupplier == null) {
return buildContext.register(DefaultSmppClient::new);
}
return clientSupplier;
}
private SmppSessionHandlerSupplier buildSmppSessionHandler() {
if (smppSessionHandler == null) {
return defaultSmppSessionHandlerSupplier();
}
return smppSessionHandler;
}
private SmppSessionHandlerSupplier defaultSmppSessionHandlerSupplier() {
return () -> new RespondToEnquireLinkRequestHandler(new RespondToDeliveryReceiptHandler(new DefaultSmppSessionHandler()));
}
private PhoneNumberTranslator buildPhoneNumberTranslator() {
// TODO: allow configuration of fallback phone number translator
return buildContext.register(new CompositePhoneNumberTranslator(buildContext.register(new DefaultHandler())));
}
private ExtendedSmppSessionConfiguration buildSessionConfiguration(CloudhopperSessionOptions sessionOpts) {
ExtendedSmppSessionConfiguration session = buildContext.register(new ExtendedSmppSessionConfiguration());
ExtendedSmppSessionConfiguration manual = sessionConfiguration == null ? new ExtendedSmppSessionConfiguration() : sessionConfiguration;
// @formatter:off
merge(session::setType, bindTypeValueBuilder::getValue, manual::getType, () -> DEFAULT_BIND_TYPE);
merge(session::setHost, hostValueBuilder::getValue, manual::getHost);
merge(session::setPort, portValueBuilder::getValue, manual::getPort, () -> 0);
merge(session::setSystemId, systemIdValueBuilder::getValue, manual::getSystemId);
merge(session::setPassword, passwordValueBuilder::getValue, manual::getPassword);
merge(session::setSystemType, systemTypeValueBuilder::getValue, manual::getSystemType);
merge(session::setBindTimeout, sessionOpts::getBindTimeout, considerZeroAsDefault(manual::getBindTimeout));
merge(session::setConnectTimeout, sessionOpts::getConnectTimeout, considerZeroAsDefault(manual::getConnectTimeout));
merge(session::setInterfaceVersion, this::getInterfaceVersion, manual::getInterfaceVersion);
merge(session::setName, sessionOpts::getSessionName, manual::getName);
merge(session::setRequestExpiryTimeout, sessionOpts::getRequestExpiryTimeout, considerZeroAsDefault(manual::getRequestExpiryTimeout));
merge(session::setWindowMonitorInterval, sessionOpts::getWindowMonitorInterval, considerZeroAsDefault(manual::getWindowMonitorInterval));
merge(session::setWindowSize, sessionOpts::getWindowSize, considerZeroAsDefault(manual::getWindowSize));
merge(session::setWindowWaitTimeout, sessionOpts::getWindowWaitTimeout, considerZeroAsDefault(manual::getWindowWaitTimeout));
merge(session::setWriteTimeout, sessionOpts::getWriteTimeout, considerZeroAsDefault(manual::getWriteTimeout));
merge(session::setResponseTimeout, sessionOpts::getResponseTimeout, considerZeroAsDefault(manual::getResponseTimeout), () -> DEFAULT_RESPONSE_TIMEOUT);
merge(session::setUnbindTimeout, sessionOpts::getUnbindTimeout, considerZeroAsDefault(manual::getUnbindTimeout), () -> DEFAULT_UNBIND_TIMEOUT);
merge(session::setReuseSession, sessionOpts::getReuseSession, manual::getReuseSession);
merge(session::setAddressRange, () -> addressRange, manual::getAddressRange);
merge(session::setKeepAlive, sessionOpts::getKeepAlive, manual::getKeepAlive);
merge(session::setConnectRetry, () -> buildConnectRetry(sessionOpts), manual::getConnectRetry);
// @formatter:on
configureSsl(session);
configureLogs(session);
return session;
}
private static <T> Supplier<T> considerZeroAsDefault(Supplier<T> getter) {
T value = getter.get();
if (value instanceof Long) {
return () -> (Long) value == 0 ? null : value;
}
if (value instanceof Integer) {
return () -> (Integer) value == 0 ? null : value;
}
return getter;
}
@SafeVarargs
private static <T> void merge(Consumer<T> setter, Supplier<T>... getters) {
T value = null;
for (Supplier<T> getter : getters) {
value = getter.get();
if (value != null) {
break;
}
}
// if there is a value, set it
if (value != null) {
setter.accept(value);
}
}
private void configureLogs(SmppSessionConfiguration session) {
if (loggingBuilder == null) {
return;
}
LoggingOptions options = loggingBuilder.build();
if (options != null) {
session.setLoggingOptions(options);
}
}
private void configureSsl(SmppSessionConfiguration session) {
if (sslBuilder == null) {
return;
}
SslConfiguration sslConfiguration = sslBuilder.build();
session.setUseSsl(sslConfiguration != null);
if (sslConfiguration != null) {
session.setSslConfiguration(sslConfiguration);
}
}
private Byte getInterfaceVersion() {
InterfaceVersion version = interfaceVersionValueBuilder.getValue(DEFAULT_INTERFACE_VERSION);
return version.value();
}
}