001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import java.io.PrintWriter;
023import java.io.StringWriter;
024import java.net.InetSocketAddress;
025import java.time.Duration;
026import java.util.List;
027
028/**
029 * Read-only hook methods for observing system and request lifecycle events.
030 * <p>
031 * Note: some of these methods are "fail-fast" - exceptions thrown will bubble out and stop execution - and for others,
032 * Soklet will catch exceptions and surface separately via {@link #didReceiveLogEvent(LogEvent)}.
033 * <p>
034 * A standard threadsafe implementation can be acquired via the {@link #defaultInstance()} factory method.
035 * <p>
036 * Full documentation is available at <a href="https://www.soklet.com/docs/request-lifecycle">https://www.soklet.com/docs/request-lifecycle</a>.
037 *
038 * @author <a href="https://www.revetkn.com">Mark Allen</a>
039 */
040public interface LifecycleObserver {
041        /**
042         * Called before a {@link Soklet} instance starts.
043         */
044        default void willStartSoklet(@NonNull Soklet soklet) {
045                // No-op by default
046        }
047
048        /**
049         * Called after a {@link Soklet} instance starts.
050         */
051        default void didStartSoklet(@NonNull Soklet soklet) {
052                // No-op by default
053        }
054
055        /**
056         * Called after a {@link Soklet} instance was asked to start, but failed due to an exception.
057         */
058        default void didFailToStartSoklet(@NonNull Soklet soklet,
059                                                                                                                                                @NonNull Throwable throwable) {
060                // No-op by default
061        }
062
063        /**
064         * Called before a {@link Soklet} instance stops.
065         */
066        default void willStopSoklet(@NonNull Soklet soklet) {
067                // No-op by default
068        }
069
070        /**
071         * Called after a {@link Soklet} instance stops.
072         */
073        default void didStopSoklet(@NonNull Soklet soklet) {
074                // No-op by default
075        }
076
077        /**
078         * Called after a {@link Soklet} instance was asked to stop, but failed due to an exception.
079         */
080        default void didFailToStopSoklet(@NonNull Soklet soklet,
081                                                                                                                                         @NonNull Throwable throwable) {
082                // No-op by default
083        }
084
085        /**
086         * Called before the server starts.
087         */
088        default void willStartHttpServer(@NonNull HttpServer httpServer) {
089                // No-op by default
090        }
091
092        /**
093         * Called after the server starts.
094         */
095        default void didStartHttpServer(@NonNull HttpServer httpServer) {
096                // No-op by default
097        }
098
099        /**
100         * Called after a {@link HttpServer} instance was asked to start, but failed due to an exception.
101         */
102        default void didFailToStartHttpServer(@NonNull HttpServer httpServer,
103                                                                                                                                                @NonNull Throwable throwable) {
104                // No-op by default
105        }
106
107        /**
108         * Called before the server stops.
109         */
110        default void willStopHttpServer(@NonNull HttpServer httpServer) {
111                // No-op by default
112        }
113
114        /**
115         * Called after the server stops.
116         */
117        default void didStopHttpServer(@NonNull HttpServer httpServer) {
118                // No-op by default
119        }
120
121        /**
122         * Called after a {@link HttpServer} instance was asked to stop, but failed due to an exception.
123         */
124        default void didFailToStopHttpServer(@NonNull HttpServer httpServer,
125                                                                                                                                         @NonNull Throwable throwable) {
126                // No-op by default
127        }
128
129        /**
130         * Called when a server is about to accept a new TCP connection.
131         *
132         * @param serverType    the server type that is accepting the connection
133         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
134         */
135        default void willAcceptConnection(@NonNull ServerType serverType,
136                                                                                                                                                @Nullable InetSocketAddress remoteAddress) {
137                // No-op by default
138        }
139
140        /**
141         * Called after a server accepts a new TCP connection.
142         *
143         * @param serverType    the server type that accepted the connection
144         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
145         */
146        default void didAcceptConnection(@NonNull ServerType serverType,
147                                                                                                                                         @Nullable InetSocketAddress remoteAddress) {
148                // No-op by default
149        }
150
151        /**
152         * Called after a server fails to accept a new TCP connection.
153         *
154         * @param serverType    the server type that failed to accept the connection
155         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
156         * @param reason        the failure reason
157         * @param throwable     an optional underlying cause, or {@code null} if not applicable
158         */
159        default void didFailToAcceptConnection(@NonNull ServerType serverType,
160                                                                                                                                                                 @Nullable InetSocketAddress remoteAddress,
161                                                                                                                                                                 @NonNull ConnectionRejectionReason reason,
162                                                                                                                                                                 @Nullable Throwable throwable) {
163                // No-op by default
164        }
165
166        /**
167         * Called when a request is about to be accepted for application-level handling.
168         *
169         * @param serverType    the server type that received the request
170         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
171         * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable
172         */
173        default void willAcceptRequest(@NonNull ServerType serverType,
174                                                                                                                                 @Nullable InetSocketAddress remoteAddress,
175                                                                                                                                 @Nullable String requestTarget) {
176                // No-op by default
177        }
178
179        /**
180         * Called after a request is accepted for application-level handling.
181         *
182         * @param serverType    the server type that received the request
183         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
184         * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable
185         */
186        default void didAcceptRequest(@NonNull ServerType serverType,
187                                                                                                                                @Nullable InetSocketAddress remoteAddress,
188                                                                                                                                @Nullable String requestTarget) {
189                // No-op by default
190        }
191
192        /**
193         * Called when a request fails to be accepted before application-level handling begins.
194         *
195         * @param serverType    the server type that received the request
196         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
197         * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable
198         * @param reason        the rejection reason
199         * @param throwable     an optional underlying cause, or {@code null} if not applicable
200         */
201        default void didFailToAcceptRequest(@NonNull ServerType serverType,
202                                                                                                                                                        @Nullable InetSocketAddress remoteAddress,
203                                                                                                                                                        @Nullable String requestTarget,
204                                                                                                                                                        @NonNull RequestRejectionReason reason,
205                                                                                                                                                        @Nullable Throwable throwable) {
206                // No-op by default
207        }
208
209        /**
210         * Called when Soklet is about to read or parse a request into a valid {@link Request}.
211         *
212         * @param serverType    the server type that received the request
213         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
214         * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable
215         */
216        default void willReadRequest(@NonNull ServerType serverType,
217                                                                                                                         @Nullable InetSocketAddress remoteAddress,
218                                                                                                                         @Nullable String requestTarget) {
219                // No-op by default
220        }
221
222        /**
223         * Called when a request was successfully read or parsed into a valid {@link Request}.
224         *
225         * @param serverType    the server type that received the request
226         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
227         * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable
228         */
229        default void didReadRequest(@NonNull ServerType serverType,
230                                                                                                                        @Nullable InetSocketAddress remoteAddress,
231                                                                                                                        @Nullable String requestTarget) {
232                // No-op by default
233        }
234
235        /**
236         * Called when a request could not be read or parsed into a valid {@link Request}.
237         *
238         * @param serverType    the server type that received the request
239         * @param remoteAddress the best-effort remote address, or {@code null} if unavailable
240         * @param requestTarget the raw request target (path + query) if known, or {@code null} if unavailable
241         * @param reason        the failure reason
242         * @param throwable     an optional underlying cause, or {@code null} if not applicable
243         */
244        default void didFailToReadRequest(@NonNull ServerType serverType,
245                                                                                                                                                @Nullable InetSocketAddress remoteAddress,
246                                                                                                                                                @Nullable String requestTarget,
247                                                                                                                                                @NonNull RequestReadFailureReason reason,
248                                                                                                                                                @Nullable Throwable throwable) {
249                // No-op by default
250        }
251
252        /**
253         * Called as soon as a request is received and a <em>Resource Method</em> has been resolved to handle it.
254         *
255         * @param serverType the server type that received the request
256         */
257        default void didStartRequestHandling(@NonNull ServerType serverType,
258                                                                                                                                                         @NonNull Request request,
259                                                                                                                                                         @Nullable ResourceMethod resourceMethod) {
260                // No-op by default
261        }
262
263        /**
264         * Called after a request finishes processing.
265         */
266        default void didFinishRequestHandling(@NonNull ServerType serverType,
267                                                                                                                                                                @NonNull Request request,
268                                                                                                                                                                @Nullable ResourceMethod resourceMethod,
269                                                                                                                                                                @NonNull MarshaledResponse marshaledResponse,
270                                                                                                                                                                @NonNull Duration duration,
271                                                                                                                                                                @NonNull List<@NonNull Throwable> throwables) {
272                // No-op by default
273        }
274
275        /**
276         * Called before response data is written.
277         */
278        default void willWriteResponse(@NonNull ServerType serverType,
279                                                                                                                                 @NonNull Request request,
280                                                                                                                                 @Nullable ResourceMethod resourceMethod,
281                                                                                                                                 @NonNull MarshaledResponse marshaledResponse) {
282                // No-op by default
283        }
284
285        /**
286         * Called after response data is written.
287         */
288        default void didWriteResponse(@NonNull ServerType serverType,
289                                                                                                                                @NonNull Request request,
290                                                                                                                                @Nullable ResourceMethod resourceMethod,
291                                                                                                                                @NonNull MarshaledResponse marshaledResponse,
292                                                                                                                                @NonNull Duration responseWriteDuration) {
293                // No-op by default
294        }
295
296        /**
297         * Called after response data fails to write.
298         */
299        default void didFailToWriteResponse(@NonNull ServerType serverType,
300                                                                                                                                                        @NonNull Request request,
301                                                                                                                                                        @Nullable ResourceMethod resourceMethod,
302                                                                                                                                                        @NonNull MarshaledResponse marshaledResponse,
303                                                                                                                                                        @NonNull Duration responseWriteDuration,
304                                                                                                                                                        @NonNull Throwable throwable) {
305                // No-op by default
306        }
307
308        /**
309         * Called before the MCP server starts.
310         */
311        default void willStartMcpServer(@NonNull McpServer mcpServer) {
312                // No-op by default
313        }
314
315        /**
316         * Called after the MCP server starts.
317         */
318        default void didStartMcpServer(@NonNull McpServer mcpServer) {
319                // No-op by default
320        }
321
322        /**
323         * Called after an {@link McpServer} instance was asked to start, but failed due to an exception.
324         */
325        default void didFailToStartMcpServer(@NonNull McpServer mcpServer,
326                                                                                                                                                         @NonNull Throwable throwable) {
327                // No-op by default
328        }
329
330        /**
331         * Called before the MCP server stops.
332         */
333        default void willStopMcpServer(@NonNull McpServer mcpServer) {
334                // No-op by default
335        }
336
337        /**
338         * Called after the MCP server stops.
339         */
340        default void didStopMcpServer(@NonNull McpServer mcpServer) {
341                // No-op by default
342        }
343
344        /**
345         * Called after an {@link McpServer} instance was asked to stop, but failed due to an exception.
346         */
347        default void didFailToStopMcpServer(@NonNull McpServer mcpServer,
348                                                                                                                                                        @NonNull Throwable throwable) {
349                // No-op by default
350        }
351
352        /**
353         * Called after an MCP session is durably created.
354         */
355        default void didCreateMcpSession(@NonNull Request request,
356                                                                                                                                         @NonNull Class<? extends McpEndpoint> endpointClass,
357                                                                                                                                         @NonNull String sessionId) {
358                // No-op by default
359        }
360
361        /**
362         * Called after an MCP session is terminated.
363         */
364        default void didTerminateMcpSession(@NonNull Class<? extends McpEndpoint> endpointClass,
365                                                                                                                                                        @NonNull String sessionId,
366                                                                                                                                                        @NonNull Duration sessionDuration,
367                                                                                                                                                        @NonNull McpSessionTerminationReason terminationReason,
368                                                                                                                                                        @Nullable Throwable throwable) {
369                // No-op by default
370        }
371
372        /**
373         * Called after a valid MCP JSON-RPC request begins handling.
374         */
375        default void didStartMcpRequestHandling(@NonNull Request request,
376                                                                                                                                                                        @NonNull Class<? extends McpEndpoint> endpointClass,
377                                                                                                                                                                        @Nullable String sessionId,
378                                                                                                                                                                        @NonNull String jsonRpcMethod,
379                                                                                                                                                                        @Nullable McpJsonRpcRequestId jsonRpcRequestId) {
380                // No-op by default
381        }
382
383        /**
384         * Called after MCP JSON-RPC request handling finishes.
385         */
386        default void didFinishMcpRequestHandling(@NonNull Request request,
387                                                                                                                                                                         @NonNull Class<? extends McpEndpoint> endpointClass,
388                                                                                                                                                                         @Nullable String sessionId,
389                                                                                                                                                                         @NonNull String jsonRpcMethod,
390                                                                                                                                                                         @Nullable McpJsonRpcRequestId jsonRpcRequestId,
391                                                                                                                                                                         @NonNull McpRequestOutcome requestOutcome,
392                                                                                                                                                                         @Nullable McpJsonRpcError jsonRpcError,
393                                                                                                                                                                         @NonNull Duration duration,
394                                                                                                                                                                         @NonNull List<@NonNull Throwable> throwables) {
395                // No-op by default
396        }
397
398        /**
399         * Called after an MCP GET stream is established.
400         */
401        default void didEstablishMcpSseStream(@NonNull Request request,
402                                                                                                                                                                                                                @NonNull Class<? extends McpEndpoint> endpointClass,
403                                                                                                                                                                                                                @NonNull String sessionId) {
404                // No-op by default
405        }
406
407        /**
408         * Called before an MCP GET stream is terminated.
409         */
410        default void willTerminateMcpSseStream(@NonNull Request request,
411                                                                                                                                                                                                                 @NonNull Class<? extends McpEndpoint> endpointClass,
412                                                                                                                                                                                                                 @NonNull String sessionId,
413                                                                                                                                                                                                                 @NonNull McpStreamTerminationReason terminationReason,
414                                                                                                                                                                                                                 @Nullable Throwable throwable) {
415                // No-op by default
416        }
417
418        /**
419         * Called after an MCP GET stream is terminated.
420         */
421        default void didTerminateMcpSseStream(@NonNull Request request,
422                                                                                                                                                                                                                @NonNull Class<? extends McpEndpoint> endpointClass,
423                                                                                                                                                                                                                @NonNull String sessionId,
424                                                                                                                                                                                                                @NonNull Duration connectionDuration,
425                                                                                                                                                                                                                @NonNull McpStreamTerminationReason terminationReason,
426                                                                                                                                                                                                                @Nullable Throwable throwable) {
427                // No-op by default
428        }
429
430        /**
431         * Called before the SSE server starts.
432         */
433        default void willStartSseServer(@NonNull SseServer sseServer) {
434                // No-op by default
435        }
436
437        /**
438         * Called after the SSE server starts.
439         */
440        default void didStartSseServer(@NonNull SseServer sseServer) {
441                // No-op by default
442        }
443
444        /**
445         * Called after a {@link SseServer} instance was asked to start, but failed due to an exception.
446         */
447        default void didFailToStartSseServer(@NonNull SseServer sseServer,
448                                                                                                                                                                                                         @NonNull Throwable throwable) {
449                // No-op by default
450        }
451
452        /**
453         * Called before the SSE server stops.
454         */
455        default void willStopSseServer(@NonNull SseServer sseServer) {
456                // No-op by default
457        }
458
459        /**
460         * Called after the SSE server stops.
461         */
462        default void didStopSseServer(@NonNull SseServer sseServer) {
463                // No-op by default
464        }
465
466        /**
467         * Called after a {@link SseServer} instance was asked to stop, but failed due to an exception.
468         */
469        default void didFailToStopSseServer(@NonNull SseServer sseServer,
470                                                                                                                                                                                                        @NonNull Throwable throwable) {
471                // No-op by default
472        }
473
474        /**
475         * Called before an SSE connection is established.
476         */
477        default void willEstablishSseConnection(@NonNull Request request,
478                                                                                                                                                                                                                        @Nullable ResourceMethod resourceMethod) {
479                // No-op by default
480        }
481
482        /**
483         * Called after an SSE connection is established.
484         */
485        default void didEstablishSseConnection(@NonNull SseConnection sseConnection) {
486                // No-op by default
487        }
488
489        /**
490         * Called if an SSE connection fails to establish.
491         *
492         * @param reason    the handshake failure reason
493         * @param throwable an optional underlying cause, or {@code null} if not applicable
494         */
495        default void didFailToEstablishSseConnection(@NonNull Request request,
496                                                                                                                                                                                                                                         @Nullable ResourceMethod resourceMethod,
497                                                                                                                                                                                                                                         SseConnection.@NonNull HandshakeFailureReason reason,
498                                                                                                                                                                                                                                         @Nullable Throwable throwable) {
499                // No-op by default
500        }
501
502        /**
503         * Called before an SSE connection is terminated.
504         */
505        default void willTerminateSseConnection(@NonNull SseConnection sseConnection,
506                                                                                                                                                                                                                        SseConnection.@NonNull TerminationReason terminationReason,
507                                                                                                                                                                                                                        @Nullable Throwable throwable) {
508                // No-op by default
509        }
510
511        /**
512         * Called after an SSE connection is terminated.
513         */
514        default void didTerminateSseConnection(@NonNull SseConnection sseConnection,
515                                                                                                                                                                                                                 @NonNull Duration connectionDuration,
516                                                                                                                                                                                                                 SseConnection.@NonNull TerminationReason terminationReason,
517                                                                                                                                                                                                                 @Nullable Throwable throwable) {
518                // No-op by default
519        }
520
521        /**
522         * Called before an SSE event is written.
523         */
524        default void willWriteSseEvent(@NonNull SseConnection sseConnection,
525                                                                                                                                                                @NonNull SseEvent sseEvent) {
526                // No-op by default
527        }
528
529        /**
530         * Called after an SSE event is written.
531         */
532        default void didWriteSseEvent(@NonNull SseConnection sseConnection,
533                                                                                                                                                         @NonNull SseEvent sseEvent,
534                                                                                                                                                         @NonNull Duration writeDuration) {
535                // No-op by default
536        }
537
538        /**
539         * Called after an SSE event fails to write.
540         */
541        default void didFailToWriteSseEvent(@NonNull SseConnection sseConnection,
542                                                                                                                                                                                 @NonNull SseEvent sseEvent,
543                                                                                                                                                                                 @NonNull Duration writeDuration,
544                                                                                                                                                                                 @NonNull Throwable throwable) {
545                // No-op by default
546        }
547
548        /**
549         * Called before an SSE comment is written.
550         */
551        default void willWriteSseComment(@NonNull SseConnection sseConnection,
552                                                                                                                                                                                         @NonNull SseComment sseComment) {
553                // No-op by default
554        }
555
556        /**
557         * Called after an SSE comment is written.
558         */
559        default void didWriteSseComment(@NonNull SseConnection sseConnection,
560                                                                                                                                                                                        @NonNull SseComment sseComment,
561                                                                                                                                                                                        @NonNull Duration writeDuration) {
562                // No-op by default
563        }
564
565        /**
566         * Called after an SSE comment fails to write.
567         */
568        default void didFailToWriteSseComment(@NonNull SseConnection sseConnection,
569                                                                                                                                                                                                                @NonNull SseComment sseComment,
570                                                                                                                                                                                                                @NonNull Duration writeDuration,
571                                                                                                                                                                                                                @NonNull Throwable throwable) {
572                // No-op by default
573        }
574
575        /**
576         * Called when Soklet emits a log event.
577         */
578        default void didReceiveLogEvent(@NonNull LogEvent logEvent) {
579                String message = logEvent.getMessage();
580                Throwable throwable = logEvent.getThrowable().orElse(null);
581
582                if (throwable == null) {
583                        System.err.printf("%s::didReceiveLogEvent [%s]: %s", LifecycleObserver.class.getSimpleName(), logEvent.getLogEventType().name(), message);
584                } else {
585                        StringWriter stringWriter = new StringWriter();
586                        PrintWriter printWriter = new PrintWriter(stringWriter);
587                        throwable.printStackTrace(printWriter);
588                        String throwableWithStackTrace = stringWriter.toString();
589
590                        System.err.printf("%s::didReceiveLogEvent [%s]: %s\n%s\n", LifecycleObserver.class.getSimpleName(), logEvent.getLogEventType().name(), message, throwableWithStackTrace);
591                }
592        }
593
594        /**
595         * Acquires a threadsafe {@link LifecycleObserver} instance with sensible defaults.
596         *
597         * @return a {@code LifecycleObserver} with default settings
598         */
599        @NonNull
600        static LifecycleObserver defaultInstance() {
601                return DefaultLifecycleObserver.defaultInstance();
602        }
603}