com.evento.application.bus.EventoSocketConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of evento-bundle Show documentation
Show all versions of evento-bundle Show documentation
Evento Framework - Bundle. The library to build a RECQ System based on Evento Server
The newest version!
package com.evento.application.bus;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.evento.common.messaging.bus.SendFailedException;
import com.evento.common.modeling.exceptions.ExceptionWrapper;
import com.evento.common.modeling.messaging.message.internal.DisableMessage;
import com.evento.common.modeling.messaging.message.internal.EnableMessage;
import com.evento.common.modeling.messaging.message.internal.EventoRequest;
import com.evento.common.modeling.messaging.message.internal.EventoResponse;
import com.evento.common.modeling.messaging.message.internal.discovery.BundleRegistration;
import com.evento.common.utils.Sleep;
import java.io.*;
import java.net.Socket;
import java.util.HashSet;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* Represents a socket connection for communication with an Evento server.
*/
public class EventoSocketConnection {
private static final Logger logger = LogManager.getLogger(EventoSocketConnection.class);
// Configuration parameters
private final String serverAddress;
private final int serverPort;
private final int maxReconnectAttempts;
private final long reconnectDelayMillis;
// Bundle registration information
private final BundleRegistration bundleRegistration;
// Message handler for processing incoming messages
private final MessageHandler handler;
// Connection state variables
private int reconnectAttempt = 0;
private final AtomicReference out = new AtomicReference<>();
private Socket socket;
private boolean enabled;
private static final AtomicInteger instanceCounter = new AtomicInteger(0);
private final int conn = instanceCounter.incrementAndGet();
private boolean isClosed = false;
private final HashSet pendingCorrelations = new HashSet<>();
private final Executor threadPerRequestExecutor = Executors.newVirtualThreadPerTaskExecutor();
/**
* Private constructor to create an EventoSocketConnection instance.
*
* @param serverAddress The address of the Evento server.
* @param serverPort The port on which the server is listening.
* @param maxReconnectAttempts The maximum number of attempts to reconnect in case of connection failure.
* @param reconnectDelayMillis The delay between reconnection attempts in milliseconds.
* @param bundleRegistration The registration information for the connection.
* @param handler The message handler for processing incoming messages.
*/
private EventoSocketConnection(
String serverAddress,
int serverPort,
int maxReconnectAttempts,
long reconnectDelayMillis,
BundleRegistration bundleRegistration,
MessageHandler handler) {
// Initialization of parameters
this.serverAddress = serverAddress;
this.serverPort = serverPort;
this.maxReconnectAttempts = maxReconnectAttempts;
this.reconnectDelayMillis = reconnectDelayMillis;
this.handler = handler;
this.bundleRegistration = bundleRegistration;
}
/**
* Sends a serializable message over the socket connection.
*
* @param message The message to be sent.
* @throws SendFailedException Thrown if the message fails to be sent.
*/
public synchronized void send(Serializable message) throws SendFailedException {
if (message instanceof EventoRequest r) {
this.pendingCorrelations.add(r.getCorrelationId());
}
try {
var o = out.get();
synchronized (o) {
o.writeObject(message);
}
} catch (Exception e) {
throw new SendFailedException(e);
}
}
/**
* Starts the socket connection and listens for incoming messages.
*
* @throws InterruptedException Thrown if the thread is interrupted during execution.
*/
private void start() throws InterruptedException {
// Semaphore for signaling when the connection is ready
var connectionReady = new Semaphore(0);
var t = new Thread(() -> {
// Loop until the connection is closed or the maximum reconnect attempts are reached
while (!isClosed && (maxReconnectAttempts < 0 || reconnectAttempt < maxReconnectAttempts)) {
// Log the current attempt to connect
logger.info("Socket Connection #{} to {}:{} attempt {}",
conn,
serverAddress,
serverPort,
reconnectAttempt + 1);
try (Socket socket = new Socket(serverAddress, serverPort)) {
// Initialize the output stream for sending messages
var dataOutputStream = new ObjectOutputStream(socket.getOutputStream());
this.socket = socket;
logger.info("Connected to {}:{}", serverAddress, serverPort);
// Reset the reconnect attempt count
reconnectAttempt = 0;
// Send the bundle registration information to the server
dataOutputStream.writeObject(bundleRegistration);
logger.info("Registration message sent");
// Initialize the input stream for receiving messages
var dataInputStream = new ObjectInputStream(socket.getInputStream());
var ok = dataInputStream.readObject();
if (!((boolean) ok)) {
throw new IllegalStateException("Bundle registration failed");
}
out.set(dataOutputStream);
// If the connection is enabled, send an enable message
if (enabled) {
enable();
}
// Signal that the connection is ready
connectionReady.release();
// Continuously listen for incoming messages
while (true) {
try {
var data = dataInputStream.readObject();
if (data instanceof EventoResponse r) {
// Remove correlation ID from pending set on receiving a response
this.pendingCorrelations.remove(r.getCorrelationId());
}
// Process the incoming message in a new thread using the message handler
threadPerRequestExecutor.execute(() -> handler.handle((Serializable) data, this::send));
} catch (OptionalDataException ignored) {
}
}
} catch (Exception e) {
// Log connection error and handle pending correlations
logger.error("Connection error %s:%d".formatted(reconnectAttempt, serverPort), e);
for (String pendingCorrelation : pendingCorrelations) {
var resp = new EventoResponse();
resp.setCorrelationId(pendingCorrelation);
resp.setBody(new ExceptionWrapper(e));
try {
handler.handle(resp, this::send);
} catch (Exception ignored) {
}
}
pendingCorrelations.clear();
// Sleep before attempting to reconnect
Sleep.apply(reconnectDelayMillis);
// Increment the reconnect attempt count
reconnectAttempt++;
}
}
// Signal that the connection is ready (even if it failed to connect)
connectionReady.release();
// Log an error if the server is unreachable after maximum attempts
logger.error("Server unreachable after {} attempts. Dead socket.", reconnectAttempt);
isClosed = true;
});
t.setName("EventoConnection - " + serverAddress + ":" + serverPort);
t.start();
// Wait for the connection to be ready (or for the maximum attempts to be reached)
connectionReady.acquire();
}
/**
* Checks if the socket connection is closed.
*
* @return {@code true} if the socket connection is closed, {@code false} otherwise
*/
public boolean isClosed() {
return isClosed;
}
/**
* Enables the socket connection.
*/
public void enable() {
enabled = true;
logger.info("Enabling connection #{}", conn);
try {
var o = out.get();
synchronized (o) {
o.writeObject(new EnableMessage());
}
} catch (Exception e) {
enabled = false;
logger.error("Enabling failed", e);
}
}
/**
* Disables the socket connection.
*/
public void disable() {
enabled = false;
logger.info("Disabling connection #{}", conn);
try {
var o = out.get();
synchronized (o) {
o.writeObject(new DisableMessage());
}
} catch (Exception e) {
logger.error("Disabling failed", e);
}
}
/**
* Closes the socket connection.
*/
public void close() {
isClosed = true;
try {
socket.close();
} catch (IOException e) {
if (socket != null && !socket.isClosed()) {
throw new RuntimeException(e);
}
}
}
/**
* Builder class for constructing EventoSocketConnection instances.
*/
public static class Builder {
private final String serverAddress;
private final int serverPort;
private final BundleRegistration bundleRegistration;
private final MessageHandler handler;
// Optional parameters with default values
private int maxReconnectAttempts = -1;
private long reconnectDelayMillis = 2000;
/**
* Constructs a Builder instance with required parameters.
*
* @param serverAddress The address of the Evento server.
* @param serverPort The port on which the server is listening.
* @param bundleRegistration The registration information for the connection.
* @param handler The message handler for processing incoming messages.
*/
public Builder(String serverAddress, int serverPort,
BundleRegistration bundleRegistration,
MessageHandler handler) {
this.serverAddress = serverAddress;
this.serverPort = serverPort;
this.bundleRegistration = bundleRegistration;
this.handler = handler;
}
/**
* Sets the maximum number of reconnect attempts.
*
* @param maxReconnectAttempts The maximum number of reconnect attempts.
* @return The Builder instance for method chaining.
*/
public Builder setMaxReconnectAttempts(int maxReconnectAttempts) {
this.maxReconnectAttempts = maxReconnectAttempts;
return this;
}
/**
* Sets the delay between reconnection attempts.
*
* @param reconnectDelayMillis The delay between reconnection attempts in milliseconds.
* @return The Builder instance for method chaining.
*/
public Builder setReconnectDelayMillis(long reconnectDelayMillis) {
this.reconnectDelayMillis = reconnectDelayMillis;
return this;
}
/**
* Connects and initializes an EventoSocketConnection instance.
*
* @return The constructed EventoSocketConnection instance.
* @throws InterruptedException Thrown if the thread is interrupted during execution.
*/
public EventoSocketConnection connect() throws InterruptedException {
var s = new EventoSocketConnection(serverAddress,
serverPort,
maxReconnectAttempts,
reconnectDelayMillis,
bundleRegistration,
handler);
s.start();
return s;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy