All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.sii.ogham.sms.builder.cloudhopper.CloudhopperBuilder Maven / Gradle / Ivy

The newest version!
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:
 * 
 * 
    *
  • SMPP protocol parameters (host, port, systemId, password, * version...)
  • *
  • Session management (name, bind, timeouts, retry...)
  • *
  • SSL configuration
  • *
  • Logging options
  • *
* *

* To send {@link Sms} using Cloudhopper, you need to register this builder into * a {@link MessagingBuilder} like this: * *

 * 
 * MessagingBuilder msgBuilder = ...
 * msgBuilder.sms()
 *    .sender(CloudhopperBuilder.class)    // registers the builder and accesses to that builder for configuring it
 * 
 * 
* * Once the builder is registered, sending sms through Cloudhopper requires at * least host of the SMPP server. You can define it using: * *
 * 
 * msgBuilder.sms()
 *    .sender(CloudhopperBuilder.class)    // registers the builder and accesses to that builder for configuring it
 *       .host("localhost")
 * 
 * 
* * Or you can also use property keys (using interpolation): * *
 * 
 * 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}")
 * 
 * 
* * You can do the same with port of the SMPP server. * * *

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

 * 
 * .sender(CloudhopperBuilder.class)
 *        .systemId("foo")
 *        .password("bar")
 * 
 * 
* * * @author Aurélien Baudet */ public class CloudhopperBuilder extends AbstractParent implements Builder { private static final Logger LOG = LoggerFactory.getLogger(CloudhopperBuilder.class); private final ReadableEncoderBuilder sharedEncoderBuilder; private BuildContext buildContext; private final ConfigurationValueBuilderHelper systemIdValueBuilder; private final ConfigurationValueBuilderHelper passwordValueBuilder; private final ConfigurationValueBuilderHelper hostValueBuilder; private final ConfigurationValueBuilderHelper portValueBuilder; private final ConfigurationValueBuilderHelper systemTypeValueBuilder; private final ConfigurationValueBuilderHelper interfaceVersionValueBuilder; private final ConfigurationValueBuilderHelper 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. * * WARNING: use is only if you know what you are doing ! */ public CloudhopperBuilder() { this(null, new DefaultBuildContext()); } /** * Constructor that is called when using Ogham builder: * *
	 * MessagingBuilder msgBuilder = ...
	 * msgBuilder
	 * .sms()
	 *    .sender(CloudhopperBuilder.class)
	 * 
* * @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. * *

* The value set using this method takes precedence over any property and * default value configured using {@link #systemId()}. * *

	 * .systemId("my-system-id")
	 * .systemId()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-system-id")
	 * 
* *
	 * .systemId("my-system-id")
	 * .systemId()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-system-id")
	 * 
* * In both cases, {@code systemId("my-system-id")} is used. * *

* If this method is called several times, only the last value is used. * *

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

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

	 * .systemId()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-system-id")
	 * 
* *

* Non-null value set using {@link #systemId(String)} takes precedence over * property values and default value. * *

	 * .systemId("my-system-id")
	 * .systemId()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-system-id")
	 * 
* * The value {@code "my-system-id"} is used regardless of the value of the * properties and default value. * *

* See {@link ConfigurationValueBuilder} for more information. * * * @return the builder to configure property keys/default value */ public ConfigurationValueBuilder 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. * *

* The value set using this method takes precedence over any property and * default value configured using {@link #systemType()}. * *

	 * .systemType("my-system-type")
	 * .systemType()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-system-type")
	 * 
* *
	 * .systemType("my-system-type")
	 * .systemType()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-system-type")
	 * 
* * In both cases, {@code systemType("my-system-type")} is used. * *

* If this method is called several times, only the last value is used. * *

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

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

	 * .systemType()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("defaut-system-type")
	 * 
* *

* Non-null value set using {@link #systemType(String)} takes precedence * over property values and default value. * *

	 * .systemType("my-system-type")
	 * .systemType()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("defaut-system-type")
	 * 
* * The value {@code "my-system-type"} is used regardless of the value of the * properties and default value. * *

* See {@link ConfigurationValueBuilder} for more information. * * * @return the builder to configure property keys/default value */ public ConfigurationValueBuilder 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). * *

* The value set using this method takes precedence over any property and * default value configured using {@link #password()}. * *

	 * .password("my-password")
	 * .password()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-password")
	 * 
* *
	 * .password("my-password")
	 * .password()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-password")
	 * 
* * In both cases, {@code password("my-password")} is used. * *

* If this method is called several times, only the last value is used. * *

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

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

	 * .password()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-password")
	 * 
* *

* Non-null value set using {@link #password(String)} takes precedence over * property values and default value. * *

	 * .password("my-password")
	 * .password()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-password")
	 * 
* * The value {@code "my-password"} is used regardless of the value of the * properties and default value. * *

* See {@link ConfigurationValueBuilder} for more information. * * * @return the builder to configure property keys/default value */ public ConfigurationValueBuilder password() { return passwordValueBuilder; } /** * The SMPP server host (IP or address). * *

* The value set using this method takes precedence over any property and * default value configured using {@link #host()}. * *

	 * .host("localhost")
	 * .host()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-host")
	 * 
* *
	 * .host("localhost")
	 * .host()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-host")
	 * 
* * In both cases, {@code host("localhost")} is used. * *

* If this method is called several times, only the last value is used. * *

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

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

	 * .host()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-host")
	 * 
* *

* Non-null value set using {@link #host(String)} takes precedence over * property values and default value. * *

	 * .host("localhost")
	 * .host()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue("default-host")
	 * 
* * The value {@code "localhost"} is used regardless of the value of the * properties and default value. * *

* See {@link ConfigurationValueBuilder} for more information. * * * @return the builder to configure property keys/default value */ public ConfigurationValueBuilder host() { return hostValueBuilder; } /** * Set the SMPP server port. * *

* The value set using this method takes precedence over any property and * default value configured using {@link #port()}. * *

	 * .port(2775)
	 * .port()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(1775)
	 * 
* *
	 * .port(2775)
	 * .port()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(1775)
	 * 
* * In both cases, {@code port(2775)} is used. * *

* If this method is called several times, only the last value is used. * *

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

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

	 * .port()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(1775)
	 * 
* *

* Non-null value set using {@link #port(Integer)} takes precedence over * property values and default value. * *

	 * .port(2775)
	 * .port()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(1775)
	 * 
* * The value {@code 2775} is used regardless of the value of the properties * and default value. * *

* See {@link ConfigurationValueBuilder} for more information. * * * @return the builder to configure property keys/default value */ public ConfigurationValueBuilder port() { return portValueBuilder; } /** * The SMPP protocol version (one of {@link InterfaceVersion#VERSION_3_3}, * {@link InterfaceVersion#VERSION_3_4}, * {@link InterfaceVersion#VERSION_5_0}). * *

* The value set using this method takes precedence over any property and * default value configured using {@link #interfaceVersion()}. * *

	 * .interfaceVersion(InterfaceVersion.VERSION_5_0)
	 * .interfaceVersion()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(InterfaceVersion.VERSION_3_4)
	 * 
* *
	 * .interfaceVersion(InterfaceVersion.VERSION_5_0)
	 * .interfaceVersion()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(InterfaceVersion.VERSION_3_4)
	 * 
* * In both cases, {@code interfaceVersion(InterfaceVersion.VERSION_5_0)} is * used. * *

* If this method is called several times, only the last value is used. * *

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

* The value set using this method takes precedence over any property and * default value configured using {@link #interfaceVersion()}. * *

	 * .interfaceVersion(SmppConstants.VERSION_5_0)
	 * .interfaceVersion()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(SmppConstants.VERSION_3_4)
	 * 
* *
	 * .interfaceVersion(SmppConstants.VERSION_5_0)
	 * .interfaceVersion()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(SmppConstants.VERSION_3_4)
	 * 
* * In both cases, {@code interfaceVersion(SmppConstants.VERSION_5_0)} is * used. * *

* If this method is called several times, only the last value is used. * *

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

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

	 * .interfaceVersion()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(InterfaceVersion.VERSION_3_4)
	 * 
* *

* Non-null value set using {@link #interfaceVersion(InterfaceVersion)} * takes precedence over property values and default value. * *

	 * .interfaceVersion(InterfaceVersion.VERSION_5_0)
	 * .interfaceVersion()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(InterfaceVersion.VERSION_3_4)
	 * 
* * The value {@code InterfaceVersion.VERSION_5_0} is used regardless of the * value of the properties and default value. * *

* See {@link ConfigurationValueBuilder} for more information. * * * @return the builder to configure property keys/default value */ public ConfigurationValueBuilder interfaceVersion() { return interfaceVersionValueBuilder; } /** * The bind command type (see {@link SmppBindType}). * *

* The value set using this method takes precedence over any property and * default value configured using {@link #bindType()}. * *

	 * .bindType(SmppBindType.TRANSCEIVER)
	 * .bindType()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(SmppBindType.RECEIVER)
	 * 
* *
	 * .bindType(SmppBindType.TRANSCEIVER)
	 * .bindType()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(SmppBindType.RECEIVER)
	 * 
* * In both cases, {@code bindType(SmppBindType.TRANSCEIVER)} is used. * *

* If this method is called several times, only the last value is used. * *

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

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

	 * .bindType()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(SmppBindType.RECEIVER)
	 * 
* *

* Non-null value set using {@link #bindType(SmppBindType)} takes precedence * over property values and default value. * *

	 * .bindType(SmppBindType.TRANSCEIVER)
	 * .bindType()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(SmppBindType.RECEIVER)
	 * 
* * The value {@code SmppBindType.TRANSCEIVER} is used regardless of the * value of the properties and default value. * *

* See {@link ConfigurationValueBuilder} for more information. * * * @return the builder to configure property keys/default value */ public ConfigurationValueBuilder bindType() { return bindTypeValueBuilder; } /** * Configures how Cloudhopper will encode SMS messages. Charsets defined by * the SMPP protocol may be different from NIO charsets. * *

* The encoder will be used to transform Java {@link String} into a byte * array that is understandable by SMPP servers. * *

* This builder configures encoders for both messages that are split and * message that are not split. * *

* This builder allows to configure: *

    *
  • Enable/disable the standard GSM encoders (GSM 7-bit, GSM 8-bit and * UCS-2) as defined in * GSM 03.38 * specification. It also allows to define different priority order
  • *
  • Enable/disable automatic guessing of encoding (based on previously * registered priorities).
  • *
  • Define a fallback encoder based on {@link Charset}
  • *
  • Provide custom {@link Encoder}s
  • *
* *
	 * {@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)
	 * }
	 * 
* * @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. * *

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

* {@link Encoder} configured using {@link #encoder()} is used to encode * each segment. * *

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

    *
  • 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})
  • *
  • 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})
  • *
  • 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})
  • *
* * 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. * *

* This builder allows to configure: *

    *
  • Enable/disable message splitting
  • *
  • Provide a custom split strategy
  • *
  • Choose strategy for reference number generation
  • *
* *

* Examples of usage: * *

	 * {@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())
	 * }
	 * 
* * @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. * *

* 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 How to * use SSL with cloudhopper-smpp * * @return the builder to configure SSL */ public SslBuilder ssl() { if (sslBuilder == null) { sslBuilder = new SslBuilder(this, buildContext); } return sslBuilder; } /** * Configure logs: *

    *
  • Enable/disable log of {@link Pdu}s
  • *
  • Enable/disable log of bytes
  • *
* * @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}. * *

* You can provide a custom {@link MessagePreparator} instance if the * default behavior doesn't fit your needs. *

* *

* If a custom {@link MessagePreparator} is set, any other preparator (using * {@link #userData()}) is not used. *

* *

* If this method is called several times, only the last value is used. *

* *

* If {@code null} value is provided, then custom {@link MessagePreparator} * is disabled. Other configured preparators are used (using * {@link #userData()}). *

* * @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: *
    *
  • Either {@code short_message} field (standard field for "User * Data").
  • *
  • Or {@code message_payload} optional parameter.
  • *
* *

* This builder allow to configure which strategy to use for sending * message: *

    *
  • Either use {@code short_message} field
  • *
  • Or use {@code message_payload} field
  • *
* *

* The result of {@link #userData()} configuration affects the message * preparation strategy. *

* *

* Examples of usage: * *

	 * {@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)
	 * }
	 * 
* * 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"}). * *

* If {@link #userData()} is not configured at all, then default behavior is * used ({@code short_message} field is used). *

* * @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: *
    *
  • the character set or message coding which determines the encoding of * the message user data
  • *
  • the message class which determines to which component of the Mobile * Station (MS) or User Equipment (UE) should be the message delivered
  • *
  • the request to automatically delete the message after reading
  • *
  • the state of flags indicating presence of unread voicemail, fax, * e-mail or other messages
  • *
  • the indication that the message content is compressed
  • *
  • the language of the cell broadcast message
  • *
* The field is described in 3GPP 23.040 and 3GPP 23.038 under the name * TP-DCS (see SMS * Data Coding Scheme). * * SMPP 3.4 introduced a new list of {@code data_coding} values (see * Short * Message Peer to Peer). * *

* This builder allows to configure how Data Coding Scheme value is * determined: *

    *
  • 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")
  • *
  • Use a fixed value used for every message
  • *
  • Use a custom implementation
  • *
* *

* Examples of usage: * *

	 * {@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())
	 * }
	 * 
* * 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 Supplier considerZeroAsDefault(Supplier 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 void merge(Consumer setter, Supplier... getters) { T value = null; for (Supplier 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(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy