Skip to content

Commit 0cda784

Browse files
MOSIP-45087 - Updated the regex for fetching OTP from SMTP and added retry mechanism for smtp websocket connection
Signed-off-by: Mohanachandran S <mohanachandran.s@technoforte.co.in>
1 parent 4b4651a commit 0cda784

3 files changed

Lines changed: 108 additions & 10 deletions

File tree

apitest-commons/src/main/java/io/mosip/testrig/apirig/testrunner/OTPListener.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
import java.net.http.HttpClient;
55
import java.net.http.WebSocket;
66
import java.net.http.WebSocket.Listener;
7+
import java.nio.ByteBuffer;
78
import java.util.concurrent.CompletionStage;
9+
import java.util.concurrent.Executors;
10+
import java.util.concurrent.ScheduledExecutorService;
811
import java.util.concurrent.TimeUnit;
12+
import java.util.concurrent.ConcurrentLinkedQueue;
13+
import java.util.concurrent.atomic.AtomicBoolean;
914

1015
import org.apache.log4j.Level;
1116
import org.apache.log4j.Logger;
@@ -25,6 +30,10 @@ public class OTPListener {
2530

2631
public static volatile boolean bTerminate = false;
2732

33+
private static final AtomicBoolean reconnecting = new AtomicBoolean(false);
34+
35+
public static final ConcurrentLinkedQueue<String> reconnectEvents = new ConcurrentLinkedQueue<>();
36+
2837
public OTPListener() {
2938
if (ConfigManager.IsDebugEnabled()) {
3039
logger.setLevel(Level.ALL);
@@ -75,13 +84,36 @@ private static class WebSocketClient implements WebSocket.Listener {
7584

7685
private final ObjectMapper objectMapper = new ObjectMapper();
7786
private final StringBuilder messageBuffer = new StringBuilder();
87+
private ScheduledExecutorService pingScheduler;
7888

7989
@Override
8090
public void onOpen(WebSocket webSocket) {
8191
logger.info("OTP WebSocket connection opened.");
92+
startPing(webSocket);
8293
Listener.super.onOpen(webSocket);
8394
}
8495

96+
private void startPing(WebSocket webSocket) {
97+
pingScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
98+
Thread t = new Thread(r, "ws-ping");
99+
t.setDaemon(true);
100+
return t;
101+
});
102+
pingScheduler.scheduleAtFixedRate(() -> {
103+
if (bTerminate) {
104+
pingScheduler.shutdown();
105+
return;
106+
}
107+
webSocket.sendPing(ByteBuffer.wrap("ping".getBytes()));
108+
}, 30, 30, TimeUnit.SECONDS);
109+
}
110+
111+
private void stopPing() {
112+
if (pingScheduler != null && !pingScheduler.isShutdown()) {
113+
pingScheduler.shutdownNow();
114+
}
115+
}
116+
85117
@Override
86118
public synchronized CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
87119

@@ -172,13 +204,70 @@ public synchronized CompletionStage<?> onText(WebSocket webSocket, CharSequence
172204
@Override
173205
public void onError(WebSocket webSocket, Throwable error) {
174206
logger.error("WebSocket error occurred", error);
207+
stopPing();
208+
scheduleReconnect("error");
175209
Listener.super.onError(webSocket, error);
176210
}
177211

178212
@Override
179213
public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
180214
logger.warn("WebSocket closed. Status: " + statusCode + " Reason: " + reason);
215+
stopPing();
216+
if (statusCode != 1000) {
217+
scheduleReconnect("abnormal close (status=" + statusCode + ")");
218+
}
181219
return Listener.super.onClose(webSocket, statusCode, reason);
182220
}
221+
222+
private void scheduleReconnect(String trigger) {
223+
if (bTerminate) return;
224+
if (!reconnecting.compareAndSet(false, true)) {
225+
logger.info("Reconnect already in progress, ignoring trigger: " + trigger);
226+
return;
227+
}
228+
logger.info("Scheduling WebSocket reconnect after " + trigger);
229+
Thread t = new Thread(() -> {
230+
try {
231+
int attempt = 0;
232+
while (!bTerminate) {
233+
attempt++;
234+
long delayMs = Math.min(3000L * attempt, 60000L);
235+
try {
236+
Thread.sleep(delayMs);
237+
} catch (InterruptedException e) {
238+
Thread.currentThread().interrupt();
239+
return;
240+
}
241+
if (bTerminate) return;
242+
try {
243+
URI iamUri = URI.create(ConfigManager.getIAMUrl());
244+
String host = iamUri.getHost();
245+
int firstDot = host.indexOf('.');
246+
if (firstDot < 0 || firstDot == host.length() - 1) return;
247+
String domain = host.substring(firstDot + 1);
248+
String websocketUrl = "wss://smtp." + domain + "/mocksmtp/websocket";
249+
logger.info("Reconnecting WebSocket (attempt " + attempt + "): " + websocketUrl);
250+
HTTP_CLIENT.newWebSocketBuilder()
251+
.buildAsync(URI.create(websocketUrl), new WebSocketClient())
252+
.get(30, TimeUnit.SECONDS);
253+
logger.info("WebSocket reconnected successfully on attempt " + attempt);
254+
reconnectEvents.add("WebSocket reconnected successfully on attempt "
255+
+ attempt + " at " + java.time.LocalDateTime.now());
256+
return;
257+
} catch (Exception e) {
258+
String failMsg = "WebSocket reconnect attempt " + attempt + " failed: "
259+
+ e.getMessage() + " at " + java.time.LocalDateTime.now();
260+
logger.warn(failMsg + ". Retrying in "
261+
+ Math.min(3000L * (attempt + 1), 60000L) + "ms...");
262+
reconnectEvents.add(failMsg);
263+
}
264+
}
265+
} finally {
266+
reconnecting.set(false);
267+
}
268+
}, "ws-reconnect");
269+
t.setDaemon(true);
270+
t.start();
271+
}
183272
}
184273
}

apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/NotificationListener.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public final class NotificationListener {
1818

1919
private static final int MAX_QUEUE_SIZE = 50;
2020
private static final long INACTIVE_EXPIRY_MS = Long.parseLong(ConfigManager.getproperty("otp_queue_inactive_expiry_time")) * 60 * 1000; // 15 mins
21-
private static final Pattern OTP_PATTERN = Pattern.compile("(?i)\\bis\\s+(\\d{6})\\s+and\\s+is\\s+valid");
21+
private static final Pattern OTP_PATTERN = Pattern.compile(
22+
"(?i)(?:\\bis\\s+(\\d{6})\\s+and\\s+is\\s+valid|\\buse\\s+(?:otp\\s+)?(\\d{6})\\b)");
2223
private static final ConcurrentHashMap<String, EmailQueue> otpQueues = new ConcurrentHashMap<>();
2324

2425
private static final ConcurrentHashMap<String, EmailQueue> notificationQueues = new ConcurrentHashMap<>();
@@ -193,12 +194,12 @@ public static String parseOtp(String message) {
193194
}
194195
Matcher matcher = OTP_PATTERN.matcher(message);
195196
if (matcher.find()) {
196-
197-
String otp = matcher.group(1);
198-
199-
logger.info("Extracted OTP=" + otp + " from message");
200-
201-
return otp;
197+
for (int i = 1; i <= matcher.groupCount(); i++) {
198+
if (matcher.group(i) != null) {
199+
logger.info("Extracted OTP=" + matcher.group(i) + " from message");
200+
return matcher.group(i);
201+
}
202+
}
202203
}
203204

204205
logger.info("OTP not found in message");

apitest-commons/src/main/java/io/mosip/testrig/apirig/utils/ReportUtil.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import io.mosip.testrig.apirig.dto.OutputValidationDto;
88
import io.mosip.testrig.apirig.testrunner.JsonPrecondtion;
9+
import io.mosip.testrig.apirig.testrunner.OTPListener;
910

1011
/**
1112
* Class to show the result in table and text area format in testng report
@@ -38,6 +39,13 @@ public static String getTextAreaForHeaders(String headers) {
3839
* @return html table
3940
*/
4041
public static String getOutputValidationReport(Map<String, List<OutputValidationDto>> outputresult) {
42+
StringBuilder reconnectWarnings = new StringBuilder();
43+
String reconnectEvent;
44+
while ((reconnectEvent = OTPListener.reconnectEvents.poll()) != null) {
45+
reconnectWarnings.append("<div style='background-color:#FFF3CD; border-left:4px solid #FFA500; padding:6px; margin:4px 0;'>")
46+
.append("<b>&#9888; WebSocket Reconnect:</b> ").append(reconnectEvent).append("</div>");
47+
}
48+
4149
String htmlforReport = "<table width='100%' charset='UTF8'>\r\n" + " <tr style='background-color: #d3d3d3;'>\r\n" + " <th>FieldName</th>\r\n"
4250
+ " <th>Expected Value</th> \r\n" + " <th>Actual Value</th>\r\n" + " <th>Status</th>\r\n"
4351
+ " </tr>\r\n";
@@ -70,10 +78,10 @@ public static String getOutputValidationReport(Map<String, List<OutputValidation
7078
}
7179
}
7280
if (!outputValidationDone) {
73-
return "<b style=\"background-color: #0A0;\">Marking test case as passed. As Output validation not performed and no errors in the response</b><br>\n";
81+
return reconnectWarnings + "<b style=\"background-color: #0A0;\">Marking test case as passed. As Output validation not performed and no errors in the response</b><br>\n";
7482
}
75-
76-
htmlforReport = temp + htmlforReport + "</table>";
83+
84+
htmlforReport = reconnectWarnings + temp + htmlforReport + "</table>";
7785
return htmlforReport;
7886
}
7987

0 commit comments

Comments
 (0)