KeepAliveBuilder.java
package fr.sii.ogham.sms.builder.cloudhopper;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import com.cloudhopper.smpp.pdu.EnquireLink;
import fr.sii.ogham.core.builder.Builder;
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.fluent.AbstractParent;
import fr.sii.ogham.sms.sender.impl.cloudhopper.KeepAliveOptions;
/**
* Builder to configure how keep alive session management should behave.
*
* <p>
* Keep alive actively maintains the session opened by sending regularly
* {@link EnquireLink} messages.
*
* This builder let you configure:
* <ul>
* <li>Enable/disable active keep alive management</li>
* <li>The time to wait between two {@link EnquireLink} messages</li>
* <li>The maximum time to wait for a response from the server for
* {@link EnquireLink} request</li>
* </ul>
*
* @author Aurélien Baudet
*
*/
public class KeepAliveBuilder extends AbstractParent<SessionBuilder> implements Builder<KeepAliveOptions> {
private static final AtomicInteger enquireLinkThreadCounter = new AtomicInteger();
private final ConfigurationValueBuilderHelper<KeepAliveBuilder, Boolean> enableValueBuilder;
private final ConfigurationValueBuilderHelper<KeepAliveBuilder, Long> enquireLinkIntervalValueBuilder;
private final ConfigurationValueBuilderHelper<KeepAliveBuilder, Long> enquireLinkTimeoutValueBuilder;
private final ConfigurationValueBuilderHelper<KeepAliveBuilder, Boolean> connectAtStartupValueBuilder;
private final ConfigurationValueBuilderHelper<KeepAliveBuilder, Integer> maxConsecutiveTimeoutsValueBuilder;
private Supplier<ScheduledExecutorService> executorFactory;
public KeepAliveBuilder(SessionBuilder parent, BuildContext buildContext) {
super(parent);
this.enableValueBuilder = buildContext.newConfigurationValueBuilder(this, Boolean.class);
this.enquireLinkIntervalValueBuilder = buildContext.newConfigurationValueBuilder(this, Long.class);
this.enquireLinkTimeoutValueBuilder = buildContext.newConfigurationValueBuilder(this, Long.class);
this.connectAtStartupValueBuilder = buildContext.newConfigurationValueBuilder(this, Boolean.class);
this.maxConsecutiveTimeoutsValueBuilder = buildContext.newConfigurationValueBuilder(this, Integer.class);
}
/**
* Enable or disable sending of {@link EnquireLink} messages to keep the
* session alive.
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #enable()}.
*
* <pre>
* .enable(true)
* .enable()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(false)
* </pre>
*
* <pre>
* .enable(true)
* .enable()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(false)
* </pre>
*
* In both cases, {@code enable(true)} 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 enable
* true to enable sending requests to keep the session alive
* @return this instance for fluent chaining
*/
public KeepAliveBuilder enable(Boolean enable) {
enableValueBuilder.setValue(enable);
return this;
}
/**
* Enable or disable sending of {@link EnquireLink} messages to keep the
* session alive.
*
* <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>
* .enable()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(false)
* </pre>
*
* <p>
* Non-null value set using {@link #enable(Boolean)} takes precedence over
* property values and default value.
*
* <pre>
* .enable(true)
* .enable()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(false)
* </pre>
*
* The value {@code true} 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<KeepAliveBuilder, Boolean> enable() {
return enableValueBuilder;
}
/**
* The fixed delay (in milliseconds) between two {@link EnquireLink}
* messages.
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #interval()}.
*
* <pre>
* .interval(60000L)
* .interval()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(30000L)
* </pre>
*
* <pre>
* .interval(60000L)
* .interval()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(30000L)
* </pre>
*
* In both cases, {@code interval(60000L)} 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 delay
* the amount of time to wait between two {@link EnquireLink}
* messages
* @return this instance for fluent chaining
*/
public KeepAliveBuilder interval(Long delay) {
enquireLinkIntervalValueBuilder.setValue(delay);
return this;
}
/**
* The fixed delay (in milliseconds) between two {@link EnquireLink}
* messages.
*
* <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>
* .interval()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(30000L)
* </pre>
*
* <p>
* Non-null value set using {@link #interval(Long)} takes precedence over
* property values and default value.
*
* <pre>
* .interval(60000L)
* .interval()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(30000L)
* </pre>
*
* The value {@code 60000L} 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<KeepAliveBuilder, Long> interval() {
return enquireLinkIntervalValueBuilder;
}
/**
* The maximum amount of time (in milliseconds) to wait for receiving a
* response from the server to an {@link EnquireLink} request.
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #responseTimeout()}.
*
* <pre>
* .responseTimeout(5000L)
* .responseTimeout()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(10000L)
* </pre>
*
* <pre>
* .responseTimeout(5000L)
* .responseTimeout()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(10000L)
* </pre>
*
* In both cases, {@code responseTimeout(5000L)} 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 timeout
* the maximum amount of time to wait for the response
* @return this instance for fluent chaining
*/
public KeepAliveBuilder responseTimeout(Long timeout) {
enquireLinkTimeoutValueBuilder.setValue(timeout);
return this;
}
/**
* The maximum amount of time (in milliseconds) to wait for receiving a
* response from the server to an {@link EnquireLink} request.
*
* <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>
* .responseTimeout()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(10000L)
* </pre>
*
* <p>
* Non-null value set using {@link #responseTimeout(Long)} takes precedence
* over property values and default value.
*
* <pre>
* .responseTimeout(5000L)
* .responseTimeout()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(10000L)
* </pre>
*
* The value {@code 5000L} 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<KeepAliveBuilder, Long> responseTimeout() {
return enquireLinkTimeoutValueBuilder;
}
/**
* Connect to the server directly when the client is ready (if true).
* Otherwise, the connection is done when the first message is sent.
*
* This may be useful to avoid a latency for the first message.
*
* If connection fails at startup, then a new attempt is done when first
* message is sent.
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #connectAtStartup()}.
*
* <pre>
* .connectAtStartup(true)
* .connectAtStartup()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(false)
* </pre>
*
* <pre>
* .connectAtStartup(true)
* .connectAtStartup()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(false)
* </pre>
*
* In both cases, {@code connectAtStartup(true)} 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 connectAtStartup
* try to connect when client is ready
* @return this instance for fluent chaining
*/
public KeepAliveBuilder connectAtStartup(Boolean connectAtStartup) {
connectAtStartupValueBuilder.setValue(connectAtStartup);
return this;
}
/**
* Connect to the server directly when the client is ready (if true).
* Otherwise, the connection is done when the first message is sent.
*
* This may be useful to avoid a latency for the first message.
*
* If connection fails at startup, then a new attempt is done when first
* message is sent.
*
* <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>
* .connectAtStartup()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(false)
* </pre>
*
* <p>
* Non-null value set using {@link #connectAtStartup(Boolean)} takes
* precedence over property values and default value.
*
* <pre>
* .connectAtStartup(true)
* .connectAtStartup()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(false)
* </pre>
*
* The value {@code true} 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<KeepAliveBuilder, Boolean> connectAtStartup() {
return connectAtStartupValueBuilder;
}
/**
* Provides a factory that creates an {@link ScheduledExecutorService}. The
* created executor is then used to schedule the task that sends
* {@link EnquireLink} requests.
*
* The factory should use one of:
* <ul>
* <li>{@link Executors#newSingleThreadScheduledExecutor()}</li>
* <li>{@link Executors#newSingleThreadScheduledExecutor(java.util.concurrent.ThreadFactory)}</li>
* <li>{@link Executors#newScheduledThreadPool(int)}</li>
* <li>{@link Executors#newScheduledThreadPool(int, java.util.concurrent.ThreadFactory)}</li>
* </ul>
*
* <p>
* If this method is called several times, only the last value is used.
*
* @param executorFactory
* the factory that creates an executor to use
* @return this instance for fluent chaining
*/
public KeepAliveBuilder executor(Supplier<ScheduledExecutorService> executorFactory) {
this.executorFactory = executorFactory;
return this;
}
/**
* Provides an {@link ScheduledExecutorService}. The executor is then used
* to schedule the task that sends {@link EnquireLink} requests.
*
* <p>
* This method is a shortcut to {@code executor(() -> executor)}.
*
* <p>
* If this method is called several times, only the last value is used.
*
* @param executor
* the executor to use
* @return this instance for fluent chaining
*/
public KeepAliveBuilder executor(ScheduledExecutorService executor) {
return executor(() -> executor);
}
/**
* The maximum number of consecutive timeouts to {@link EnquireLink}
* requests to consider that a new session is required.
*
* <p>
* The value set using this method takes precedence over any property and
* default value configured using {@link #maxConsecutiveTimeouts()}.
*
* <pre>
* .maxConsecutiveTimeouts(5)
* .maxConsecutiveTimeouts()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(3)
* </pre>
*
* <pre>
* .maxConsecutiveTimeouts(5)
* .maxConsecutiveTimeouts()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(3)
* </pre>
*
* In both cases, {@code maxConsecutiveTimeouts(5)} 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 max
* the maximum consecutive timeouts
* @return this instance for fluent chaining
*/
public KeepAliveBuilder maxConsecutiveTimeouts(Integer max) {
maxConsecutiveTimeoutsValueBuilder.setValue(max);
return this;
}
/**
* The maximum number of consecutive timeouts to {@link EnquireLink}
* requests to consider that a new session is required.
*
* <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>
* .maxConsecutiveTimeouts()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(3)
* </pre>
*
* <p>
* Non-null value set using {@link #maxConsecutiveTimeouts(Integer)} takes
* precedence over property values and default value.
*
* <pre>
* .maxConsecutiveTimeouts(5)
* .maxConsecutiveTimeouts()
* .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
* .defaultValue(3)
* </pre>
*
* The value {@code 5} 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<KeepAliveBuilder, Integer> maxConsecutiveTimeouts() {
return maxConsecutiveTimeoutsValueBuilder;
}
@Override
public KeepAliveOptions build() {
KeepAliveOptions keepAliveOptions = new KeepAliveOptions();
keepAliveOptions.setEnable(enableValueBuilder.getValue());
keepAliveOptions.setEnquireLinkInterval(enquireLinkIntervalValueBuilder.getValue());
keepAliveOptions.setEnquireLinkTimeout(enquireLinkTimeoutValueBuilder.getValue());
keepAliveOptions.setConnectAtStartup(connectAtStartupValueBuilder.getValue());
keepAliveOptions.setExecutor(executorFactory != null ? executorFactory : defaultEnquireLinkTimerFactory());
keepAliveOptions.setMaxConsecutiveTimeouts(maxConsecutiveTimeoutsValueBuilder.getValue());
return keepAliveOptions;
}
/**
* Default factory that provides a {@link ScheduledExecutorService}
* executor. As only one thread is needed (only one task) to regularly send
* {@link EnquireLink}s, it uses
* {@link Executors#newSingleThreadScheduledExecutor(ThreadFactory)}.
*
* <p>
* The thread is named {@code EnquireLink-<generated number>}.
*
* @return the factory
*/
public static Supplier<ScheduledExecutorService> defaultEnquireLinkTimerFactory() {
return () -> Executors.newSingleThreadScheduledExecutor(KeepAliveBuilder::newThread);
}
private static Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable);
thread.setName("EnquireLink-" + enquireLinkThreadCounter.incrementAndGet());
return thread;
}
}