diff --git a/appserver/distributions/glassfish/pom.xml b/appserver/distributions/glassfish/pom.xml index b810e0456a7..e16477302ae 100644 --- a/appserver/distributions/glassfish/pom.xml +++ b/appserver/distributions/glassfish/pom.xml @@ -141,6 +141,10 @@ basedir="${patches}/microprofile-rest-client-api" includes="META-INF/MANIFEST.MF" destfile="${glassfish.modules}/microprofile-rest-client-api.jar" /> + + + org.eclipse.microprofile.health + microprofile-health-api + + + * + * + + + + + org.glassfish.main.microprofile + microprofile-health + ${project.version} + + + * + * + + + + + org.glassfish.main.microprofile + health-glassfish + ${project.version} + + + * + * + + + + org.eclipse.microprofile.jwt diff --git a/appserver/microprofile/health-glassfish/pom.xml b/appserver/microprofile/health-glassfish/pom.xml new file mode 100644 index 00000000000..c52dd917137 --- /dev/null +++ b/appserver/microprofile/health-glassfish/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + + org.glassfish.main.microprofile + microprofile-parent + 7.0.21-SNAPSHOT + + + health-glassfish + glassfish-jar + + + UTF-8 + 17 + + + + + org.glassfish.main.microprofile + microprofile-health + ${project.version} + provided + + + jakarta.json.bind + jakarta.json.bind-api + provided + + + jakarta.enterprise + jakarta.enterprise.cdi-api + provided + + + + + + + maven-compiler-plugin + + 17 + + + + org.apache.felix + maven-bundle-plugin + + + glassfish-jar + + + + + org.glassfish.microprofile.impl; + + + jakarta.enterprise.context.spi; + jakarta.enterprise.event; + jakarta.enterprise.inject; + jakarta.enterprise.inject.spi; + jakarta.json.bind; + jakarta.servlet; + jakarta.servlet.http; + org.eclipse.microprofile.health; + org.glassfish.internal.api; + org.glassfish.microprofile.health; + org.glassfish.hk2.api; + org.glassfish.hk2.utilities; + + + + + + osgi-bundle + package + + bundle + + + + + + + + \ No newline at end of file diff --git a/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/CollectHealthChecksExtension.java b/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/CollectHealthChecksExtension.java new file mode 100644 index 00000000000..7c95b2f2453 --- /dev/null +++ b/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/CollectHealthChecksExtension.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.impl; + +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AfterDeploymentValidation; +import jakarta.enterprise.inject.spi.Annotated; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.ProcessBean; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.Liveness; +import org.eclipse.microprofile.health.Readiness; +import org.eclipse.microprofile.health.Startup; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.utilities.ServiceLocatorUtilities; +import org.glassfish.internal.api.Globals; +import org.glassfish.microprofile.health.HealthReporter; + + +public class CollectHealthChecksExtension implements Extension { + + private final HealthReporter service; + private final Set> healthChecks = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public CollectHealthChecksExtension() { + ServiceLocator defaultBaseServiceLocator = Globals.getDefaultBaseServiceLocator(); + HealthReporter healthReporterService = defaultBaseServiceLocator.getService(HealthReporter.class); + if (healthReporterService == null) { + ServiceLocatorUtilities.addClasses(defaultBaseServiceLocator, true, HealthReporter.class); + healthReporterService = defaultBaseServiceLocator.getService(HealthReporter.class); + } + service = healthReporterService; + } + public void processBeans(@Observes ProcessBean beans) { + Annotated annotated = beans.getAnnotated(); + if (annotated.isAnnotationPresent(Liveness.class) || + annotated.isAnnotationPresent(Readiness.class) || + annotated.isAnnotationPresent(Startup.class)) { + if (beans.getBean().getTypes().contains(HealthCheck.class)) { + healthChecks.add((Bean) beans.getBean()); + } + } + } + + public void afterDeploymentValidation(@Observes AfterDeploymentValidation adv, BeanManager beanManager) { + healthChecks.forEach(bean -> { + CreationalContext creationalContext = beanManager.createCreationalContext(bean); + service.addHealthCheck("", bean.create(creationalContext)); + }); + } + + + +} diff --git a/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthService.java b/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthService.java new file mode 100644 index 00000000000..355d2c02f1d --- /dev/null +++ b/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthService.java @@ -0,0 +1,31 @@ +package org.glassfish.microprofile.impl; + + +import org.glassfish.api.StartupRunLevel; +import org.glassfish.api.event.EventListener; +import org.glassfish.hk2.runlevel.RunLevel; +import org.glassfish.internal.api.Globals; +import org.glassfish.internal.data.ApplicationInfo; +import org.glassfish.internal.deployment.Deployment; +import org.glassfish.microprofile.health.HealthReporter; +import org.jvnet.hk2.annotations.Service; + +@Service(name = "healthcheck-service") +@RunLevel(StartupRunLevel.VAL) +public class HealthService implements EventListener { + + + @Override + public void event(Event event) { + + HealthReporter service = Globals.getDefaultHabitat().getService(HealthReporter.class); + + if (event.is(Deployment.APPLICATION_UNLOADED) && event.hook() instanceof ApplicationInfo appInfo) { + service.removeAllHealthChecksFrom(appInfo.getName()); + } + + if (event.is(Deployment.APPLICATION_STARTED) && event.hook() instanceof ApplicationInfo appInfo) { + + } + } +} diff --git a/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthServlet.java b/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthServlet.java new file mode 100644 index 00000000000..d3c3a533714 --- /dev/null +++ b/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthServlet.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.impl; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.glassfish.internal.api.Globals; +import org.glassfish.microprofile.health.HealthReport; +import org.glassfish.microprofile.health.HealthReporter; + +public class HealthServlet extends HttpServlet { + + private static final Logger LOGGER = Logger.getLogger(HealthServlet.class.getName()); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + HealthReport healthReport; + try { + healthReport = Globals.getDefaultHabitat().getService(HealthReporter.class) + .getReport(getReportKind(req.getRequestURI())); + + int httpStatus = switch (healthReport.status()) { + case UP -> HttpServletResponse.SC_OK; + case DOWN -> HttpServletResponse.SC_SERVICE_UNAVAILABLE; + }; + resp.setStatus(httpStatus); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Unable to fetch health check status", e); + healthReport = new HealthReport(HealthCheckResponse.Status.DOWN, List.of()); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + + Jsonb jsonb = JsonbBuilder.create(); + String json = jsonb.toJson(healthReport); + resp.setCharacterEncoding("UTF-8"); + resp.setContentType("application/json"); + resp.getWriter().println(json); + } + + private static HealthReporter.ReportKind getReportKind(String path) { + if (path.endsWith("health/live")) { + return HealthReporter.ReportKind.LIVE; + } else if (path.endsWith("health/ready")) { + return HealthReporter.ReportKind.READY; + } else if (path.endsWith("health/started")) { + return HealthReporter.ReportKind.STARTED; + } else { + return HealthReporter.ReportKind.ALL; + } + } +} diff --git a/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthServletInitializer.java b/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthServletInitializer.java new file mode 100644 index 00000000000..97512331836 --- /dev/null +++ b/appserver/microprofile/health-glassfish/src/main/java/org/glassfish/microprofile/impl/HealthServletInitializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.impl; + +import jakarta.servlet.ServletContainerInitializer; +import jakarta.servlet.ServletContext; + +import java.util.Set; +import java.util.logging.Logger; + +public class HealthServletInitializer implements ServletContainerInitializer { + + private static final String MICROPROFILE_HEALTH_SERVLET = "microprofile-health-servlet"; + private static final String MICROPROFILE_HEALTH_ENABLED = "org.glassfish.microprofile.health.enabled"; + private static final Logger LOGGER = Logger.getLogger(HealthServletInitializer.class.getName()); + + @Override + public void onStartup(Set> set, ServletContext servletContext) { + if (!servletContext.getContextPath().isEmpty()) { + return; + } + + if (!Boolean.parseBoolean(System.getProperty(MICROPROFILE_HEALTH_ENABLED, "true"))) { + LOGGER.info("MicroProfile Health is disabled"); + return; + } + + servletContext.addServlet(MICROPROFILE_HEALTH_SERVLET, HealthServlet.class) + .addMapping("/health", "/health/live", "/health/ready", "/health/started"); + } +} diff --git a/appserver/microprofile/health-glassfish/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension b/appserver/microprofile/health-glassfish/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension new file mode 100644 index 00000000000..9231f525d1b --- /dev/null +++ b/appserver/microprofile/health-glassfish/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension @@ -0,0 +1 @@ +org.glassfish.microprofile.impl.CollectHealthChecksExtension \ No newline at end of file diff --git a/appserver/microprofile/health-glassfish/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer b/appserver/microprofile/health-glassfish/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer new file mode 100644 index 00000000000..16a5f16529e --- /dev/null +++ b/appserver/microprofile/health-glassfish/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +org.glassfish.microprofile.impl.HealthServletInitializer diff --git a/appserver/microprofile/health/pom.xml b/appserver/microprofile/health/pom.xml new file mode 100644 index 00000000000..4967748940a --- /dev/null +++ b/appserver/microprofile/health/pom.xml @@ -0,0 +1,96 @@ + + + + + 4.0.0 + + + org.glassfish.main.microprofile + microprofile-parent + 7.0.21-SNAPSHOT + + + microprofile-health + glassfish-jar + + GlassFish MicroProfile Health + + + + + org.eclipse.microprofile.health + microprofile-health-api + + + org.glassfish.main.microprofile + microprofile-config + ${project.version} + + + jakarta.enterprise + jakarta.enterprise.cdi-api + provided + + + + + + + maven-compiler-plugin + + 17 + + + + org.apache.felix + maven-bundle-plugin + + + glassfish-jar + + + + + org.glassfish.microprofile.health; + + + org.eclipse.microprofile.config; + org.eclipse.microprofile.health.spi; + org.eclipse.microprofile.health; + jakarta.inject; + jakarta.enterprise.context; + jakarta.enterprise.inject; + jakarta.enterprise.inject.spi; + + + + + + osgi-bundle + package + + bundle + + + + + + + + diff --git a/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/GlassFishHealthCheckResponseBuilder.java b/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/GlassFishHealthCheckResponseBuilder.java new file mode 100644 index 00000000000..d55b63918a5 --- /dev/null +++ b/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/GlassFishHealthCheckResponseBuilder.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; + +public class GlassFishHealthCheckResponseBuilder extends HealthCheckResponseBuilder { + + private final Map data = new HashMap<>(); + private String name; + private HealthCheckResponse.Status status; + + @Override + public HealthCheckResponseBuilder name(String name) { + this.name = name; + return this; + } + + @Override + public HealthCheckResponseBuilder withData(String key, String value) { + data.put(key, value); + return this; + } + + @Override + public HealthCheckResponseBuilder withData(String key, long value) { + data.put(key, value); + return this; + } + + @Override + public HealthCheckResponseBuilder withData(String key, boolean value) { + data.put(key, value); + return this; + } + + @Override + public HealthCheckResponseBuilder up() { + return status(true); + } + + @Override + public HealthCheckResponseBuilder down() { + return status(false); + } + + @Override + public HealthCheckResponseBuilder status(boolean up) { + status = up ? HealthCheckResponse.Status.UP : HealthCheckResponse.Status.DOWN; + return this; + } + + @Override + public HealthCheckResponse build() { + return new HealthCheckResponse(name, status, data.isEmpty() ? null : Optional.of(data)); + } +} diff --git a/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/GlassFishHealthCheckResponseProvider.java b/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/GlassFishHealthCheckResponseProvider.java new file mode 100644 index 00000000000..43089bf3959 --- /dev/null +++ b/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/GlassFishHealthCheckResponseProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health; + +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.spi.HealthCheckResponseProvider; + +public class GlassFishHealthCheckResponseProvider implements HealthCheckResponseProvider { + @Override + public HealthCheckResponseBuilder createResponseBuilder() { + return new GlassFishHealthCheckResponseBuilder(); + } +} diff --git a/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/HealthReport.java b/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/HealthReport.java new file mode 100644 index 00000000000..b8c8f82242d --- /dev/null +++ b/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/HealthReport.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health; + +import java.util.List; + +import org.eclipse.microprofile.health.HealthCheckResponse; + +public record HealthReport(HealthCheckResponse.Status status, List checks) { +} diff --git a/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/HealthReporter.java b/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/HealthReporter.java new file mode 100644 index 00000000000..388b491eb77 --- /dev/null +++ b/appserver/microprofile/health/src/main/java/org/glassfish/microprofile/health/HealthReporter.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.CDI; +import jakarta.inject.Singleton; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Liveness; +import org.eclipse.microprofile.health.Readiness; +import org.eclipse.microprofile.health.Startup; + +@Singleton +public class HealthReporter { + + private static final String MP_DEFAULT_STARTUP_EMPTY_RESPONSE = "mp.health.default.startup.empty.response"; + private static final String MP_DEFAULT_READINESS_EMPTY_RESPONSE = "mp.health.default.readiness.empty.response"; + private static final Logger LOGGER = Logger.getLogger(HealthReporter.class.getName()); + + private final Map> applicationHealthChecks = new ConcurrentHashMap<>(); + + private static HealthCheckResponse callHealthCheck(HealthCheck healthCheck) { + try { + return healthCheck.call(); + } catch (RuntimeException e) { + LOGGER.log(Level.SEVERE, "Health check failed", e); + return buildHealthCheckResponse(healthCheck.getClass().getName(), e); + } + } + + private static HealthCheckResponse buildHealthCheckResponse(String name, Exception e) { + return HealthCheckResponse.builder() + .down() + .name(name) + .withData("rootCause", e.getMessage()) + .build(); + } + + public enum ReportKind { + LIVE, + READY, + STARTED, + ALL; + + private Stream healthChecks() { + Instance select = CDI.current().select(HealthCheck.class); + return switch (this) { + case LIVE -> select.select(Liveness.Literal.INSTANCE).stream(); + case READY -> select.select(Readiness.Literal.INSTANCE).stream(); + case STARTED -> select.select(Startup.Literal.INSTANCE).stream(); + case ALL -> Stream.of( + select.select(Liveness.Literal.INSTANCE).stream(), + select.select(Readiness.Literal.INSTANCE).stream(), + select.select(Startup.Literal.INSTANCE).stream() + ).flatMap(s -> s); + }; + } + + private HealthCheckResponse.Status getEmptyResponse() { + return switch (this) { + case LIVE -> getValue(MP_DEFAULT_STARTUP_EMPTY_RESPONSE) + .orElse(HealthCheckResponse.Status.UP); + case READY -> getValue(MP_DEFAULT_READINESS_EMPTY_RESPONSE) + .orElse(HealthCheckResponse.Status.UP); + case STARTED, ALL -> HealthCheckResponse.Status.UP; + }; + } + } + + public HealthReport getReport(ReportKind reportKind) { + HealthCheckResponse.Status emptyResponse = HealthCheckResponse.Status.UP; + + Stream healthChecks = applicationHealthChecks.values() + .stream() + .flatMap(Collection::stream); +// Stream.concat(reportKind.healthChecks(), applicationHealthChecks.stream()); + + List healthCheckResults = healthChecks + .map(HealthReporter::callHealthCheck) + .toList(); + + HealthCheckResponse.Status overallStatus; + if (healthCheckResults.isEmpty()) { + overallStatus = emptyResponse; + } else { + overallStatus = healthCheckResults.stream() + .map(HealthCheckResponse::getStatus) + .filter(HealthCheckResponse.Status.DOWN::equals) + .findFirst() + .orElse(HealthCheckResponse.Status.UP); + } + return new HealthReport(overallStatus, healthCheckResults); + + } + + + public void addHealthCheck(String contextPath, HealthCheck healthCheck) { + applicationHealthChecks.computeIfAbsent(contextPath, k -> new CopyOnWriteArrayList<>()) + .add(healthCheck); +// applicationHealthChecks.add(healthCheck); + } + + public void removeHealthCheck(String contextPath, HealthCheck healthCheck) { + applicationHealthChecks.get(contextPath).remove(healthCheck); + } + + public void removeAllHealthChecksFrom(String contextPath) { + applicationHealthChecks.remove(contextPath); + } + + private static Optional getValue(String value) { + try { + return ConfigProvider.getConfig() + .getOptionalValue(value, String.class) + .map(HealthCheckResponse.Status::valueOf); + } catch (IllegalStateException e) { + // Microprofile Config is not enabled for this application + return Optional.empty(); + } + } + +} diff --git a/appserver/microprofile/health/src/main/resources/META-INF/services/org.eclipse.microprofile.health.spi.HealthCheckResponseProvider b/appserver/microprofile/health/src/main/resources/META-INF/services/org.eclipse.microprofile.health.spi.HealthCheckResponseProvider new file mode 100644 index 00000000000..667885a3511 --- /dev/null +++ b/appserver/microprofile/health/src/main/resources/META-INF/services/org.eclipse.microprofile.health.spi.HealthCheckResponseProvider @@ -0,0 +1 @@ +org.glassfish.microprofile.health.GlassFishHealthCheckResponseProvider diff --git a/appserver/microprofile/pom.xml b/appserver/microprofile/pom.xml index 1f8adcc7213..00b0b6a97d1 100644 --- a/appserver/microprofile/pom.xml +++ b/appserver/microprofile/pom.xml @@ -1,7 +1,7 @@ + 4.0.1 + 2.1 2.0.2 @@ -654,6 +658,14 @@ ${helidon-config.version} + + + org.eclipse.microprofile.health + microprofile-health-api + ${microprofile.health-api.version} + + + org.eclipse.microprofile.jwt diff --git a/appserver/tests/tck/microprofile/health/pom.xml b/appserver/tests/tck/microprofile/health/pom.xml new file mode 100644 index 00000000000..8196030ec3b --- /dev/null +++ b/appserver/tests/tck/microprofile/health/pom.xml @@ -0,0 +1,78 @@ + + + + + 4.0.0 + + + org.glassfish.main.tests.tck + glassfish-external-tck-microprofile + 7.0.21-SNAPSHOT + + + glassfish-external-tck-microprofile-health + jar + + TCK: MicroProfile Health + + + + + org.jboss.weld + weld-core-impl + + + + org.eclipse.microprofile.health + microprofile-health-tck + 4.0.1 + + + org.eclipse.microprofile.health + microprofile-health-api + provided + + + org.hamcrest + hamcrest + + + + + + + maven-dependency-plugin + + + + unpack-glassfish + + + + + maven-failsafe-plugin + + + tck-suite.xml + + + + + + diff --git a/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/HealthArquillianExtension.java b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/HealthArquillianExtension.java new file mode 100644 index 00000000000..93cfa62f457 --- /dev/null +++ b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/HealthArquillianExtension.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health.tck; + +import java.net.URI; +import java.util.logging.Logger; + +import org.glassfish.microprofile.health.tck.client.BeansXmlTransformer; +import org.glassfish.microprofile.health.tck.client.ConfigDeploymentExceptionTransformer; +import org.glassfish.microprofile.health.tck.client.MicroProfileConfigPropertiesTransformer; +import org.glassfish.microprofile.health.tck.client.RootResourceProvider; +import org.jboss.arquillian.container.spi.client.container.DeploymentExceptionTransformer; +import org.jboss.arquillian.container.test.impl.enricher.resource.URIResourceProvider; +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.core.spi.LoadableExtension; +import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider; + +public class HealthArquillianExtension implements LoadableExtension { + + private static final Logger LOGGER = Logger.getLogger(HealthArquillianExtension.class.getName()); + + /** + * Register this object as an Arquillian extension + * @param extensionBuilder a context object for the extension + */ + @Override + public void register(ExtensionBuilder extensionBuilder) { + + LOGGER.info("Client Arquillian extension registered"); + + extensionBuilder.service(ApplicationArchiveProcessor.class, BeansXmlTransformer.class); + extensionBuilder.service(ApplicationArchiveProcessor.class, MicroProfileConfigPropertiesTransformer.class); + extensionBuilder.service(DeploymentExceptionTransformer.class, ConfigDeploymentExceptionTransformer.class); + extensionBuilder.override(ResourceProvider.class, URIResourceProvider.class, RootResourceProvider.class); + } +} diff --git a/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/BeansXmlTransformer.java b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/BeansXmlTransformer.java new file mode 100644 index 00000000000..a6624d7c4ce --- /dev/null +++ b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/BeansXmlTransformer.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health.tck.client; + +import org.jboss.arquillian.container.spi.event.container.BeforeDeploy; +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.core.api.annotation.Observes; +import org.jboss.arquillian.test.spi.TestClass; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ArchivePath; +import org.jboss.shrinkwrap.api.Node; +import org.jboss.shrinkwrap.api.asset.ArchiveAsset; +import org.jboss.shrinkwrap.api.asset.UrlAsset; + +import java.net.URL; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.logging.Logger; + +import static java.lang.String.format; + +/** + * This extension replaces beans.xml files with ones declaring the 'all' bean discovery type. + * This is because version 3.0.1 of the TCK still deploys an empty beans.xml due to a faulty assumption that + * CDI < 4 is still defaulting to the 'all' type. + */ +public class BeansXmlTransformer implements ApplicationArchiveProcessor { + + private static final Logger LOGGER = Logger.getLogger(BeansXmlTransformer.class.getName()); + + private static final String LIB_DIR_PATH = format("WEB-INF%slib", ArchivePath.SEPARATOR); + private static final String[] BEANS_XML_PATHS = { + format("/META-INF%sbeans.xml", ArchivePath.SEPARATOR), + format("/WEB-INF%sbeans.xml", ArchivePath.SEPARATOR), + }; + + private final URL beansXmlResource; + + public BeansXmlTransformer() { + this.beansXmlResource = getClass().getClassLoader().getResource("beans.xml"); + if (beansXmlResource == null) { + throw new IllegalStateException("Unable to find beans.xml resource in test dir"); + } + } + + /** + * Listen for and process non-testable deployments. This is required as, by default, ShouldThrowException annotated + * deployments aren't processed by ApplicationArchiveProcessors, but the beans xml may still need fixing. + * @param event + */ + protected void onEvent(@Observes BeforeDeploy event) { + final var deployment = event.getDeployment(); + + if (!deployment.testable()) { + new BeansXmlTransformer().process(deployment.getArchive()); + } + } + + @Override + public void process(Archive archive, TestClass testClass) { + process(archive); + } + + public void process(Archive archive) { + findBeansXml(archive) + .ifPresent(beansXml -> { + LOGGER.info(() -> format("Replacing beans.xml in archive [%s]", archive.getName())); + archive.add(new UrlAsset(beansXmlResource), beansXml.getPath()); + }); + processLibraries(archive, this::process); + } + + private static Optional findBeansXml(Archive archive) { + for (String beansXmlPath : BEANS_XML_PATHS) { + final var node = archive.get(beansXmlPath); + if (node != null) { + LOGGER.info(() -> format("Discovered beans.xml at path [%s]", node.getPath())); + return Optional.of(node); + } + } + return Optional.empty(); + } + + private static void processLibraries(Archive archive, Consumer> consumer) { + final var libDir = archive.get(LIB_DIR_PATH); + + if (libDir != null) { + for (var node : libDir.getChildren()) { + final var asset = node.getAsset(); + if (asset instanceof ArchiveAsset) { + LOGGER.info(() -> format("Processing subarchive [%s]", node.getPath())); + consumer.accept(((ArchiveAsset) asset).getArchive()); + } + } + } + } +} diff --git a/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/ConfigDeploymentExceptionTransformer.java b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/ConfigDeploymentExceptionTransformer.java new file mode 100644 index 00000000000..e61c135b0fe --- /dev/null +++ b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/ConfigDeploymentExceptionTransformer.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health.tck.client; + +import jakarta.enterprise.inject.spi.DeploymentException; + +import org.jboss.arquillian.container.spi.client.container.DeploymentExceptionTransformer; + +/** + * The common GlassFishClientCommon handler will always throw a GlassfishClientException, + * with a message from the response. Deployment errors can safely be converted to the expected + * exception type. + */ +public class ConfigDeploymentExceptionTransformer implements DeploymentExceptionTransformer { + + @Override + public Throwable transform(Throwable throwable) { + if (throwable != null && throwable.getMessage().contains("Error occurred during deployment")) { + return new DeploymentException(throwable); + } + return throwable; + } + +} diff --git a/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/MicroProfileConfigPropertiesTransformer.java b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/MicroProfileConfigPropertiesTransformer.java new file mode 100644 index 00000000000..9d74d7ff564 --- /dev/null +++ b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/MicroProfileConfigPropertiesTransformer.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health.tck.client; + +import java.util.logging.Logger; + +import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; +import org.jboss.arquillian.test.spi.TestClass; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.Node; + +import static java.lang.String.format; +/** + * This class addresses a discrepancy in MicroProfile specifications regarding the location of + * the `microprofile-config.properties` file. Originally, MicroProfile Config specifies that + * `microprofile-config.properties` should be located at `META-INF/` within the `classes` directory + * of a WAR (i.e., `WEB-INF/classes/META-INF/microprofile-config.properties`). However, some implementations + * (such as those supporting MP OpenAPI) also recognize the top-level `META-INF/microprofile-config.properties`. + *

+ * The MicroProfile Health TCK, which does not explicitly require OpenAPI, still expects the file at + * the top-level `META-INF/` location, creating inconsistency in the configuration support across + * implementations. This divergence means implementations supporting only MP Config are restricted + * to `WEB-INF/classes/META-INF`, while those supporting both Config and OpenAPI may support both + * locations. + *

+ * To address this in GlassFish and ensure compatibility with the Health TCK, this class leverages + * an Arquillian `ApplicationArchiveProcessor` to move the file from the unsupported top-level + * `META-INF` to the standard `WEB-INF/classes/META-INF` in the archive, ensuring that the tests + * align with the expected location for all base MP APIs. + */ +public class MicroProfileConfigPropertiesTransformer implements ApplicationArchiveProcessor { + private static final Logger LOGGER = Logger.getLogger(MicroProfileConfigPropertiesTransformer.class.getName()); + private static final String ORIGINAL_META_INF_CONFIG = "/META-INF/microprofile-config.properties"; + private static final String TARGET_META_INF_CONFIG = "/WEB-INF/classes" + ORIGINAL_META_INF_CONFIG; + + @Override + public void process(Archive archive, TestClass testClass) { + Node node = archive.get(ORIGINAL_META_INF_CONFIG); + if (node != null) { + LOGGER.info(() -> format("Moving %s to %s in archive [%s]", ORIGINAL_META_INF_CONFIG, TARGET_META_INF_CONFIG, archive.getName())); + archive.delete(ORIGINAL_META_INF_CONFIG); + archive.add(node.getAsset(), TARGET_META_INF_CONFIG); + } + } + + +} \ No newline at end of file diff --git a/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/RootResourceProvider.java b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/RootResourceProvider.java new file mode 100644 index 00000000000..54fb7d3d819 --- /dev/null +++ b/appserver/tests/tck/microprofile/health/src/main/java/org/glassfish/microprofile/health/tck/client/RootResourceProvider.java @@ -0,0 +1,26 @@ +package org.glassfish.microprofile.health.tck.client; + +import org.jboss.arquillian.container.test.impl.enricher.resource.URIResourceProvider; +import org.jboss.arquillian.test.api.ArquillianResource; + +import java.lang.annotation.Annotation; +import java.net.URI; +import java.net.URISyntaxException; + +public class RootResourceProvider extends URIResourceProvider { + + @Override + public Object lookup(ArquillianResource arquillianResource, Annotation... annotations) { + Object lookup = super.lookup(arquillianResource, annotations); + // remove the context path from the URI + if (lookup instanceof URI) { + URI uri = (URI) lookup; + try { + return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), "", uri.getQuery(), uri.getFragment()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + return null; + } +} diff --git a/appserver/tests/tck/microprofile/health/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/appserver/tests/tck/microprofile/health/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 00000000000..9df6669abeb --- /dev/null +++ b/appserver/tests/tck/microprofile/health/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +org.glassfish.microprofile.health.tck.HealthArquillianExtension \ No newline at end of file diff --git a/appserver/tests/tck/microprofile/health/src/main/resources/beans.xml b/appserver/tests/tck/microprofile/health/src/main/resources/beans.xml new file mode 100644 index 00000000000..eea1bce2d91 --- /dev/null +++ b/appserver/tests/tck/microprofile/health/src/main/resources/beans.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/appserver/tests/tck/microprofile/health/src/test/java/org/glassfish/microprofile/health/tck/ContainerSetup.java b/appserver/tests/tck/microprofile/health/src/test/java/org/glassfish/microprofile/health/tck/ContainerSetup.java new file mode 100644 index 00000000000..a3984367c39 --- /dev/null +++ b/appserver/tests/tck/microprofile/health/src/test/java/org/glassfish/microprofile/health/tck/ContainerSetup.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Contributors to Eclipse Foundation. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ +package org.glassfish.microprofile.health.tck; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.testng.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.testng.annotations.Test; + +/** + * Setup the container via a test that runs before the TCK + */ +public class ContainerSetup extends Arquillian { + + @Deployment + public static Archive deployment() { + return ShrinkWrap.create(WebArchive.class, "container-setup.war"); + } + + @Test + public void setup() { + // Setup TCK required system properties + System.setProperty("mp.health.disable-default-procedures", "true"); + } +} diff --git a/appserver/tests/tck/microprofile/health/tck-suite.xml b/appserver/tests/tck/microprofile/health/tck-suite.xml new file mode 100644 index 00000000000..ce1b99b477c --- /dev/null +++ b/appserver/tests/tck/microprofile/health/tck-suite.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/appserver/tests/tck/microprofile/pom.xml b/appserver/tests/tck/microprofile/pom.xml index a903078e860..9bd2d023f85 100644 --- a/appserver/tests/tck/microprofile/pom.xml +++ b/appserver/tests/tck/microprofile/pom.xml @@ -1,7 +1,7 @@