diff --git a/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java b/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java index 230d425..13b47e2 100644 --- a/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java +++ b/src/main/java/com/eactive/apim/portal/apps/ReadinessController.java @@ -5,6 +5,7 @@ import java.sql.SQLException; import javax.sql.DataSource; +import org.jboss.logging.Logger; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -12,19 +13,28 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.thymeleaf.spring5.SpringTemplateEngine; +import com.google.common.util.concurrent.Futures; + /** * Readiness probe. * *

Distinct from {@link HealthCheckController} (liveness — servlet alive?). * This endpoint validates both EMS and Gateway datasources via JDBC - * {@code Connection.isValid(timeout)}, and the Thymeleaf {@link SpringTemplateEngine} - * configuration, to confirm the app is ready to serve real page requests. + * {@code Connection.isValid(timeout)}, plus three libraries that WebLogic's + * oracle_common bundles at a different version from this app (see weblogic.xml + * {@code prefer-application-packages}): Thymeleaf's {@link SpringTemplateEngine} + * (jackson), jboss-logging, and Guava. * - *

The template-engine check exists because this controller bypasses view - * resolution entirely (plain {@code @RestController} JSON) — a broken - * {@code TemplateEngine.getConfiguration()} (e.g. classpath split causing - * {@link java.util.ServiceConfigurationError} during dialect/module discovery) - * previously left every real page returning 500 while this probe still reported 200. + *

These exist because this controller bypasses view resolution entirely + * (plain {@code @RestController} JSON) — a classpath split between the app's + * copy and WebLogic's bundled copy of a preferred package can throw + * {@link LinkageError}/{@link java.util.ServiceConfigurationError} only on the + * real code paths that touch it, while this probe kept reporting 200. That + * already happened once for jackson (Thymeleaf's dialect/module discovery + * failed on every real page while /health/ready stayed green) — jboss-logging + * and Guava carry the same risk (confirmed version mismatch against + * oracle_common, not yet observed failing in production) so they get the same + * kind of forced-touch check here. * *

HTTP 200 + JSON when all checks pass. *

HTTP 503 + JSON when any check fails — body still includes the per-component @@ -50,16 +60,21 @@ public class ReadinessController { @GetMapping(value = "/health/ready", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity ready() { - String ems = check(portalDataSource); - String gw = check(gatewayDataSource); - String view = checkTemplateEngine(); - boolean ok = "UP".equals(ems) && "UP".equals(gw) && "UP".equals(view); + String ems = check(portalDataSource); + String gw = check(gatewayDataSource); + String view = checkTemplateEngine(); + String logging = checkJbossLogging(); + String guava = checkGuava(); + boolean ok = "UP".equals(ems) && "UP".equals(gw) && "UP".equals(view) + && "UP".equals(logging) && "UP".equals(guava); String body = "{" + "\"status\":\"" + (ok ? "UP" : "DOWN") + "\"," - + "\"ems\":\"" + ems + "\"," - + "\"gateway\":\"" + gw + "\"," - + "\"view\":\"" + view + "\"" + + "\"ems\":\"" + ems + "\"," + + "\"gateway\":\"" + gw + "\"," + + "\"view\":\"" + view + "\"," + + "\"logging\":\"" + logging + "\"," + + "\"guava\":\"" + guava + "\"" + "}"; return ResponseEntity.status(ok ? 200 : 503) @@ -76,11 +91,40 @@ public class ReadinessController { templateEngine.getConfiguration(); return "UP"; } catch (Throwable t) { - String msg = t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage(); - return "DOWN:" + msg.replace('"', '\'').replace('\n', ' ').replace('\r', ' '); + return down(t); } } + /** Forces jboss-logging's {@code Logger} + its bound provider (LogManager/JBoss/JUL + * discovery) to load and actually run a log-level check, not just resolve the class. */ + private static String checkJbossLogging() { + try { + Logger log = Logger.getLogger(ReadinessController.class); + log.isDebugEnabled(); + return "UP"; + } catch (Throwable t) { + return down(t); + } + } + + /** {@code Futures.immediateFuture} touches Guava's {@code util.concurrent} machinery + * (AbstractFuture/InternalFutureFailureAccess) — the part backed by the separate + * {@code failureaccess} jar, which is exactly where an app/WebLogic version mismatch + * would split across classloaders. */ + private static String checkGuava() { + try { + Futures.immediateFuture(Boolean.TRUE).isDone(); + return "UP"; + } catch (Throwable t) { + return down(t); + } + } + + private static String down(Throwable t) { + String msg = t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage(); + return "DOWN:" + msg.replace('"', '\'').replace('\n', ' ').replace('\r', ' '); + } + private static String check(DataSource ds) { if (ds == null) return "DOWN:NO_DATASOURCE"; try (Connection c = ds.getConnection()) {