diff --git a/cms-plugin-framework/CHANGELOG.md b/cms-plugin-framework/CHANGELOG.md index f936503..b51dd93 100644 --- a/cms-plugin-framework/CHANGELOG.md +++ b/cms-plugin-framework/CHANGELOG.md @@ -3,6 +3,7 @@ ## [v1.0.6] - 2024-10-01 - Included the file size to reduce/optimize the time to upload files to the storage hub [#28150] +- Checked if the user is `null` in the `UserUtils` class [#28301] ## [v1.0.5] - 2024-07-03 diff --git a/cms-plugin-framework/src/main/java/org/gcube/application/cms/implementations/utils/UserUtils.java b/cms-plugin-framework/src/main/java/org/gcube/application/cms/implementations/utils/UserUtils.java index 86806ff..a168533 100644 --- a/cms-plugin-framework/src/main/java/org/gcube/application/cms/implementations/utils/UserUtils.java +++ b/cms-plugin-framework/src/main/java/org/gcube/application/cms/implementations/utils/UserUtils.java @@ -21,28 +21,31 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class UserUtils { - public static List DEFAULT_ROLES=new ArrayList<>(); + public static List DEFAULT_ROLES = new ArrayList<>(); public static AuthenticatedUser getCurrent() throws SecurityException { log.debug("Loading caller info.."); SecretManager cm = SecretManagerProvider.instance.get(); String context = cm.getContext(); - if(context==null) throw new SecurityException("Cannot determine context"); + if (context == null) + throw new SecurityException("Cannot determine context"); + Set roles = new HashSet<>(); org.gcube.common.authorization.utils.user.User user = cm.getUser(); - log.info("Identified caller {} in context {}",user.getUsername(),context); - Set roles=new HashSet<>(); - roles.addAll(user.getRoles()); + if (user == null) { + log.warn("No user found in the session work, context is {}", context); + } else { + log.info("Identified caller {} in context {}", user.getUsername(), context); + roles.addAll(user.getRoles()); + } + AuthenticatedUser toReturn = new AuthenticatedUser(user, roles, AccessTokenProvider.instance.get(), + SecurityTokenProvider.instance.get(), context); - AuthenticatedUser toReturn = - new AuthenticatedUser(user,roles, AccessTokenProvider.instance.get(),SecurityTokenProvider.instance.get(),context); - - log.info("Current User is {} ",toReturn); + log.info("Current User is {} ", toReturn); return toReturn; } - @AllArgsConstructor @Getter public static class AuthenticatedUser { @@ -63,10 +66,10 @@ public class UserUtils { builder.append("User [user="); builder.append(user); builder.append(", uma_token="); - builder.append(uma_token==null?uma_token:"***"); + builder.append(uma_token == null ? uma_token : "***"); builder.append(", gcube_token="); - builder.append(gcube_token==null?gcube_token:"***"); + builder.append(gcube_token == null ? gcube_token : "***"); builder.append(", roles="); builder.append(roles); @@ -77,14 +80,14 @@ public class UserUtils { return builder.toString(); } - public AccountingInfo asInfo(){ - AccountingInfo info=new AccountingInfo(); + public AccountingInfo asInfo() { + AccountingInfo info = new AccountingInfo(); User user = new User(); - try{ + try { user.setUsername(this.getUser().getUsername()); user.setRoles(roles); - }catch(Exception e){ - log.warn("Unable to determine user id, using FAKE",e); + } catch (Exception e) { + log.warn("Unable to determine user id, using FAKE", e); user.setUsername("FAKE"); user.setRoles(new HashSet<>()); user.getRoles().addAll(DEFAULT_ROLES); @@ -92,9 +95,9 @@ public class UserUtils { info.setUser(user); info.setInstant(LocalDateTime.now()); - Context c=new Context(); + Context c = new Context(); c.setId(this.context); - c.setName(context.contains("/")?context.substring(context.lastIndexOf("/")):context); + c.setName(context.contains("/") ? context.substring(context.lastIndexOf("/")) : context); info.setContext(c); return info; } diff --git a/geoportal-service/CHANGELOG.md b/geoportal-service/CHANGELOG.md index 3ac8b7c..fe472bd 100644 --- a/geoportal-service/CHANGELOG.md +++ b/geoportal-service/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog for org.gcube.application.geoportal-service +## [v1.2.2] - 2024-10-16 + +- Included the health check at https://{geoporta_endpoint}/health via `smallrye-health` (that implements the https://microprofile.io/specifications/microprofile-health/) [#28301] + ## [v1.2.1] - 2024-10-02 - Included the file size to reduce/optimize the time to upload files to the storage hub [#28150] diff --git a/geoportal-service/enunciate.xml b/geoportal-service/enunciate.xml index 3c3f8bb..3bf4cf7 100644 --- a/geoportal-service/enunciate.xml +++ b/geoportal-service/enunciate.xml @@ -3,13 +3,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://enunciate.webcohesion.com/schemas/enunciate-2.14.0.xsd"> - + + diff --git a/geoportal-service/gcube/extra-resources/WEB-INF/gcube-app.xml b/geoportal-service/gcube/extra-resources/WEB-INF/gcube-app.xml index 907178e..a417488 100644 --- a/geoportal-service/gcube/extra-resources/WEB-INF/gcube-app.xml +++ b/geoportal-service/gcube/extra-resources/WEB-INF/gcube-app.xml @@ -1,11 +1,13 @@ - ${project.artifactId} - ${project.groupId} - ${project.version} - ${project.description} - /srv/docs/* - /srv/api-docs/* + ${project.artifactId} + ${project.groupId} + ${project.version} + ${project.description} + + + /srv/health + /srv/health/* diff --git a/geoportal-service/pom.xml b/geoportal-service/pom.xml index fc18c4b..987298b 100644 --- a/geoportal-service/pom.xml +++ b/geoportal-service/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.gcube.application geoportal-service - 1.2.1 + 1.2.2 Geoportal Service war @@ -155,6 +155,17 @@ javax.xml.ws jaxws-api test --> + + + + + + + + io.smallrye + smallrye-health + 2.2.3 + diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java index 7d9466e..7d7e531 100644 --- a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java @@ -21,6 +21,7 @@ import org.gcube.application.geoportal.service.engine.providers.ucd.ProfileMap; import org.gcube.application.geoportal.service.engine.providers.ucd.SingleISResourceUCDProvider; import org.gcube.application.geoportal.service.engine.providers.ucd.UCDManager; import org.gcube.application.geoportal.service.model.internal.db.Mongo; +import org.gcube.application.geoportal.service.rest.GeoportalHealth; import org.gcube.application.geoportal.service.rest.Plugins; import org.gcube.application.geoportal.service.rest.ProfiledDocuments; import org.gcube.application.geoportal.service.rest.UseCaseDescriptors; @@ -32,28 +33,28 @@ import lombok.extern.slf4j.Slf4j; @ApplicationPath(InterfaceConstants.APPLICATION_PATH) @Slf4j -public class GeoPortalService extends ResourceConfig{ +public class GeoPortalService extends ResourceConfig { - public Map,Class> customImplementations(){ + public Map, Class> customImplementations() { return Collections.EMPTY_MAP; } - + public GeoPortalService() { super(); - //Register interrfaces + // Register interfaces log.info("Initializing serialization"); JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(); provider.setMapper(Serialization.mapper); register(provider); - registerClasses(RequestFilter.class); registerClasses(ProfiledDocuments.class); registerClasses(UseCaseDescriptors.class); registerClasses(Plugins.class); - //registerClasses(DocsGenerator.class); + registerClasses(GeoportalHealth.class); + // registerClasses(DocsGenerator.class); log.info("Setting implementations .. "); @@ -62,25 +63,19 @@ public class GeoPortalService extends ResourceConfig{ ImplementationProvider.get().setEngine(new StorageClientProvider(), StorageUtils.class); ImplementationProvider.get().setEngine(new SingleISResourceUCDProvider(), ProfileMap.class); ImplementationProvider.get().setEngine(new PluginManager(), PluginManager.PluginMap.class); - ImplementationProvider.get().setEngine(new UCDManager(),UCDManagerI.class); + ImplementationProvider.get().setEngine(new UCDManager(), UCDManagerI.class); ImplementationProvider.get().setEngine(new ConfigurationCache(), ConfigurationCache.ConfigurationMap.class); - - for(Map.Entry, Class> entry : customImplementations().entrySet()){ - log.warn("LOADING CUSTOM ENGINE : {} serving {}",entry.getKey(),entry.getValue()); + for (Map.Entry, Class> entry : customImplementations().entrySet()) { + log.warn("LOADING CUSTOM ENGINE : {} serving {}", entry.getKey(), entry.getValue()); ImplementationProvider.get().setEngine(entry.getKey(), entry.getValue()); } log.debug("ENGINES ARE : "); - ImplementationProvider.get().getManagerList().forEach( - (aClass, s) -> log.debug("{} serving {} ",aClass,s)); + ImplementationProvider.get().getManagerList().forEach((aClass, s) -> log.debug("{} serving {} ", aClass, s)); ImplementationProvider.get().initEngines(); - } - - - } diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/ServiceConstants.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/ServiceConstants.java index 8b46015..3c7ada0 100644 --- a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/ServiceConstants.java +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/ServiceConstants.java @@ -1,15 +1,17 @@ package org.gcube.application.geoportal.service; - public class ServiceConstants { - public static final String SE_GNA_DB_FLAG="GNA_DB"; - public static final String SE_GNA_DB_CATEGORY="Database"; - - public static final String MONGO_SE_PLATFORM="mongodb"; - public static final String MONGO_SE_GNA_FLAG="internal-db"; - - - - + // SE DB flagName + public static final String SE_GNA_DB_FLAGNAME = "GNA_DB"; + // SE DB flagValue + public static final String SE_GNA_DB_FLAGVALUE = "Concessioni"; + // SE DB category + public static final String SE_GNA_DB_CATEGORY = "Database"; + // SE DB platform + public static final String SE_GNA_DB_PLATFORM = "postgis"; + + public static final String MONGO_SE_PLATFORM = "mongodb"; + public static final String MONGO_SE_GNA_FLAG = "internal-db"; + } diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoClientProvider.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoClientProvider.java index bc368e7..64fcbb4 100644 --- a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoClientProvider.java +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/engine/providers/MongoClientProvider.java @@ -29,7 +29,7 @@ public class MongoClientProvider extends AbstractScopedMap { getProvidedObjectByClass(ISInterface.class), ServiceConstants.SE_GNA_DB_CATEGORY, ServiceConstants.MONGO_SE_PLATFORM, - ServiceConstants.SE_GNA_DB_FLAG, + ServiceConstants.SE_GNA_DB_FLAGNAME, ServiceConstants.MONGO_SE_GNA_FLAG); log.debug("Connecting to "+conn); diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GeoportalHealth.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GeoportalHealth.java new file mode 100644 index 0000000..6df4bb4 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GeoportalHealth.java @@ -0,0 +1,144 @@ +package org.gcube.application.geoportal.service.rest; + +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponse.State; +import org.gcube.application.geoportal.service.rest.health.DatabaseHealthCheck; +import org.gcube.application.geoportal.service.rest.health.GeoportalHealthCheck; +import org.gcube.application.geoportal.service.rest.health.HealthCheckResponseSerializer; +import org.gcube.application.geoportal.service.rest.health.MongoHealthCheck; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import lombok.extern.slf4j.Slf4j; + +/** + * The Class GeoportalHealth. + * + * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it + * + * Oct 22, 2024 + */ +@Path("/health") +@Slf4j +public class GeoportalHealth { + + private ObjectMapper mapper = new ObjectMapper(); + + /** + * Instantiates a new geoportal health. + */ + public GeoportalHealth() { + SimpleModule module = new SimpleModule(); + module.addSerializer(HealthCheckResponse.class, new HealthCheckResponseSerializer()); + mapper.registerModule(module); + } + + /** + * Service check. + * + * @return the response compliant to `microprofile-health` specification. 200 if + * is OK. Otherwise it fails. + * @throws JsonProcessingException the json processing exception + */ + @GET + @Path("") + @Produces({ MediaType.APPLICATION_JSON }) + public Response serviceCheck() throws JsonProcessingException { + log.debug("serviceCheck called"); + HealthCheckResponse response = new GeoportalHealthCheck().call(); + String json = healthCheckSerializer(response); + log.info("serviceCheck response is {}", json); + return Response.ok().entity(json).build(); + } + + /** + * Mongo check. + * + * @param context the gcube context + * @param include_collections if the check has to include the mongo collections + * in the response + * @return the response compliant to `microprofile-health` specification + * @throws JsonProcessingException the json processing exception + */ + @GET + @Path("/mongo") + @Produces({ MediaType.APPLICATION_JSON }) + public Response mongoCheck(@QueryParam("context") String context, + @DefaultValue("false") @QueryParam("include_collections") Boolean includeCollections) + throws JsonProcessingException { + log.debug("mongoCheck called in the context {}, includeCollections {}", context, includeCollections); + if (context == null) { + HealthCheckResponse response = HealthCheckResponse.named(MongoHealthCheck.SERVICE_NAME) + .withData("context", "is required parameter (e.g. context=/gcube/devsec/devVRE)").down().build(); + String json = healthCheckSerializer(response); + log.info("mongoCheck error response is {}", json); + // Bad request + return Response.status(400).entity(json).build(); + } + + HealthCheckResponse response = new MongoHealthCheck(context, includeCollections).call(); + ResponseBuilder responseBuilder = Response.ok(); + if (response.getState().equals(State.DOWN)) { + responseBuilder = responseBuilder.status(503); + } + String json = healthCheckSerializer(response); + log.info("mongoCheck response is {}", json); + return responseBuilder.entity(json).build(); + } + + /** + * Database check. + * + * @param context the gcube context + * @return the response compliant to `microprofile-health` specification + * @throws JsonProcessingException the json processing exception + */ + @GET + @Path("/database") + @Produces({ MediaType.APPLICATION_JSON }) + public Response databaseCheck(@QueryParam("context") String context) throws JsonProcessingException { + log.debug("databaseCheck called in the context {}", context); + if (context == null) { + HealthCheckResponse response = HealthCheckResponse.named(DatabaseHealthCheck.SERVICE_NAME) + .withData("context", "is required parameter (e.g. context=/gcube/devsec/devVRE)").down().build(); + String json = healthCheckSerializer(response); + log.info("databaseCheck error response is {}", json); + // Bad request + return Response.status(400).entity(json).build(); + } + + HealthCheckResponse response = new DatabaseHealthCheck(context).call(); + ResponseBuilder responseBuilder = Response.ok(); + if (response.getState().equals(State.DOWN)) { + responseBuilder = responseBuilder.status(503); + } + String json = healthCheckSerializer(response); + log.info("databaseCheck response is {}", json); + return responseBuilder.entity(json).build(); + } + + /** + * Health check serializer. + * + * @param response the response + * @return the string + * @throws JsonProcessingException the json processing exception + */ + private String healthCheckSerializer(HealthCheckResponse response) throws JsonProcessingException { + // Serializes HealthCheckResponse in JSON with custom + // HealthCheckResponseSerializer + return mapper.writeValueAsString(response); + } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java index 49bb9ce..bffc3ca 100644 --- a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/GuardedMethod.java @@ -19,18 +19,15 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public abstract class GuardedMethod { - private static List preoperations=new ArrayList<>(); + private static List preoperations = new ArrayList<>(); - public static void addPreoperation(Runnable preoperation){ + public static void addPreoperation(Runnable preoperation) { preoperations.add(preoperation); } + private T result = null; - - - private T result=null; - - public GuardedMethod execute() throws WebApplicationException{ + public GuardedMethod execute() throws WebApplicationException { try { if (!preoperations.isEmpty()) { log.trace("Running preops (size : {} )", preoperations.size()); @@ -40,41 +37,41 @@ public abstract class GuardedMethod { log.trace("Executing actual method.."); result = run(); return this; - }catch (InvalidUserRoleException e){ - log.error("Returning exception ",e); - throw new WebApplicationException("Invalid Step ID ", e,Status.FORBIDDEN); - }catch (UnrecognizedStepException e){ - log.error("Returning exception ",e); - throw new WebApplicationException("Invalid Step ID ", e,Status.BAD_REQUEST); - }catch (ConfigurationException e){ - log.error("Returning exception ",e); - throw new WebApplicationException("Environment is not properly configured", e,Status.EXPECTATION_FAILED); - }catch (InsufficientPrivileges e){ - log.error("Returning exception ",e); - throw new WebApplicationException("User has insufficient privileges for requested action", e,Status.FORBIDDEN); - }catch(WebApplicationException e) { + } catch (InvalidUserRoleException e) { + log.error("Returning exception ", e); + throw new WebApplicationException("Invalid Step ID ", e, Status.FORBIDDEN); + } catch (UnrecognizedStepException e) { + log.error("Returning exception ", e); + throw new WebApplicationException("Invalid Step ID ", e, Status.BAD_REQUEST); + } catch (ConfigurationException e) { + log.error("Returning exception ", e); + throw new WebApplicationException("Environment is not properly configured", e, Status.EXPECTATION_FAILED); + } catch (InsufficientPrivileges e) { + log.error("Returning exception ", e); + throw new WebApplicationException("User has insufficient privileges for requested action", e, + Status.FORBIDDEN); + } catch (WebApplicationException e) { log.error("Throwing Web Application Exception ", e); throw e; - }catch(ProjectNotFoundException e){ - log.error("Returning exception ",e); - throw new WebApplicationException("Project not found", e,Status.NOT_FOUND); - }catch(ProjectLockedException e){ - log.error("Returning exception ",e); - throw new WebApplicationException("Project is currently locked", e,Status.PRECONDITION_FAILED); - }catch(InvalidLockException e){ - log.error("Lock exception ",e); - throw new WebApplicationException("Conflicts found in locks", e,Status.CONFLICT); - }catch(Throwable t) { - log.error("Unexpected error ",t); - throw new WebApplicationException("Unexpected internal error", t,Status.INTERNAL_SERVER_ERROR); + } catch (ProjectNotFoundException e) { + log.error("Returning exception ", e); + throw new WebApplicationException("Project not found", e, Status.NOT_FOUND); + } catch (ProjectLockedException e) { + log.error("Returning exception ", e); + throw new WebApplicationException("Project is currently locked", e, Status.PRECONDITION_FAILED); + } catch (InvalidLockException e) { + log.error("Lock exception ", e); + throw new WebApplicationException("Conflicts found in locks", e, Status.CONFLICT); + } catch (Throwable t) { + log.error("Unexpected error ", t); + throw new WebApplicationException("Unexpected internal error", t, Status.INTERNAL_SERVER_ERROR); } - + } - + public T getResult() { return result; } - - - protected abstract T run() throws Exception,WebApplicationException; + + protected abstract T run() throws Exception, WebApplicationException; } diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/DatabaseHealthCheck.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/DatabaseHealthCheck.java new file mode 100644 index 0000000..343de34 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/DatabaseHealthCheck.java @@ -0,0 +1,161 @@ +package org.gcube.application.geoportal.service.rest.health; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Liveness; +import org.eclipse.microprofile.health.Readiness; +import org.gcube.application.cms.implementations.ISInterface; +import org.gcube.application.cms.implementations.ImplementationProvider; +import org.gcube.application.geoportal.common.model.rest.ConfigurationException; +import org.gcube.application.geoportal.common.model.rest.DatabaseConnection; +import org.gcube.application.geoportal.service.ServiceConstants; +import org.gcube.common.scope.api.ScopeProvider; + +import lombok.extern.slf4j.Slf4j; + +/** + * The Class DatabaseHealthCheck. + * + * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it + * + * Oct 23, 2024 + */ +@Readiness +@Liveness +@Slf4j +public class DatabaseHealthCheck implements HealthCheck { + + private String context; + public static final String SERVICE_NAME = "database"; + + private static final int CONNECTION_TIMEOUT = 30; + + /** + * Call. + * + * @return the health check response + */ + @Override + public HealthCheckResponse call() { + return checkDatabase(context); + } + + /** + * Instantiates a new database health check. + * + * @param context the context + */ + public DatabaseHealthCheck(String context) { + this.context = context; + } + + /** + * Check database. + * + * @param context the context + * @return the health check response + */ + private HealthCheckResponse checkDatabase(String context) { + log.debug("checkMongo in the context: {}", context); + HealthCheckResponseBuilder buildHCRBuilder = HealthCheckResponse.named(SERVICE_NAME); + + ScopeProvider.instance.set(context); + + try { + + DatabaseConnection databaseConnection = null; + ISInterface isInterface = ImplementationProvider.get().getProvidedObjectByClass(ISInterface.class); + + try { + if (isInterface == null) + throw new Exception(ISInterface.class.getSimpleName() + " configuration is null for " + + DatabaseConnection.class.getSimpleName()); + + databaseConnection = isInterface.queryForDatabase(ServiceConstants.SE_GNA_DB_CATEGORY, + ServiceConstants.SE_GNA_DB_PLATFORM, ServiceConstants.SE_GNA_DB_FLAGNAME, + ServiceConstants.SE_GNA_DB_FLAGVALUE); + + if (databaseConnection == null) + throw new Exception(DatabaseConnection.class.getSimpleName() + " configuration is null"); + } catch (Exception e) { + log.error("Error on checking DB configuration: ", e); + buildHCRBuilder.state(false); + return buildHCRBuilder.build(); + } + + boolean connectionStatus = checkDatabaseConnection(databaseConnection); + buildHCRBuilder = appendDBInfo(buildHCRBuilder, databaseConnection); + buildHCRBuilder.state(connectionStatus); + log.info("checkDatabase is OK in the context: {}. State is {}", context, connectionStatus); + return buildHCRBuilder.build(); + + } catch (Exception e) { + log.error("Error on checkDatabase: ", e); + log.warn("checkDatabase is KO in the context: {}", context); + buildHCRBuilder.state(false); + return buildHCRBuilder.build(); + } finally { + ScopeProvider.instance.reset(); + } + } + + /** + * Append DB info. + * + * @param buildHCRBuilder the build HCR builder + * @param connection the connection + * @return the health check response builder + */ + private HealthCheckResponseBuilder appendDBInfo(HealthCheckResponseBuilder buildHCRBuilder, + DatabaseConnection connection) { + buildHCRBuilder.withData("host", connection.getUrl() + ""); + + // anonymize the DB username + String userNotClear = "***"; + if (connection.getUser() != null && connection.getUser().length() > 3) { + userNotClear = connection.getUser().substring(0, 3) + userNotClear; + } + buildHCRBuilder.withData("user ", userNotClear); + buildHCRBuilder.withData("pwd ", "****"); + return buildHCRBuilder; + } + + /** + * Check database connection. + * + * @param connectionParameters the connection parameters + * @return true, if successful + */ + private boolean checkDatabaseConnection(DatabaseConnection connectionParameters) { + + try { + + if (connectionParameters == null) + throw new ConfigurationException("connectionParameters is null"); + + // Getting connection + Connection connection = DriverManager.getConnection(connectionParameters.getUrl(), + connectionParameters.getUser(), connectionParameters.getPwd()); + // Check if the connection is valid (timeout 30 seconds) + if (connection != null && connection.isValid(CONNECTION_TIMEOUT)) { + log.debug("Connection to DB " + connectionParameters.getUrl() + " is OK!"); + return true; + } else { + log.debug("Connection to DB " + connectionParameters.getUrl() + " is KO!"); + return false; + } + } catch (SQLException e) { + log.warn("Error on connecting to DB: " + connectionParameters, e); + return false; + } catch (ConfigurationException e1) { + log.warn("Error on reading connection configuration: " + connectionParameters, e1); + return false; + } + } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/GeoportalHealthCheck.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/GeoportalHealthCheck.java new file mode 100644 index 0000000..7257afe --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/GeoportalHealthCheck.java @@ -0,0 +1,37 @@ +package org.gcube.application.geoportal.service.rest.health; + +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 lombok.extern.slf4j.Slf4j; + + +/** + * The Class GeoportalHealthCheck. + * + * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it + * + * Oct 22, 2024 + */ +@Readiness +@Liveness +@Slf4j +public class GeoportalHealthCheck implements HealthCheck { + + private static final String SERVICE_NAME = "geooportal-service"; + + /** + * Call. + * + * @return the health check response + */ + @Override + public HealthCheckResponse call() { + log.info(GeoportalHealthCheck.class.getSimpleName() + " call"); + HealthCheckResponse response = HealthCheckResponse.named(SERVICE_NAME).state(true).build(); + return response; + } + +} diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/HealthCheckResponseSerializer.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/HealthCheckResponseSerializer.java new file mode 100644 index 0000000..fbb4972 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/HealthCheckResponseSerializer.java @@ -0,0 +1,40 @@ +package org.gcube.application.geoportal.service.rest.health; + +import java.io.IOException; +import java.util.Map; + +import org.eclipse.microprofile.health.HealthCheckResponse; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HealthCheckResponseSerializer extends JsonSerializer { + + @Override + public void serialize(HealthCheckResponse response, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeStartObject(); + if (response.getName() != null) + gen.writeStringField("name", response.getName()); + if (response.getState() != null) + gen.writeStringField("state", response.getState().toString()); + + response.getData().ifPresent(data -> { + try { + gen.writeObjectFieldStart("data"); + for (Map.Entry entry : data.entrySet()) { + gen.writeObjectField(entry.getKey(), entry.getValue()); + } + gen.writeEndObject(); + } catch (IOException e) { + log.warn("Error on serializing the data field", e); + } + }); + + gen.writeEndObject(); + } +} \ No newline at end of file diff --git a/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/MongoHealthCheck.java b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/MongoHealthCheck.java new file mode 100644 index 0000000..be7cf23 --- /dev/null +++ b/geoportal-service/src/main/java/org/gcube/application/geoportal/service/rest/health/MongoHealthCheck.java @@ -0,0 +1,117 @@ +package org.gcube.application.geoportal.service.rest.health; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Liveness; +import org.eclipse.microprofile.health.Readiness; +import org.gcube.application.cms.implementations.ImplementationProvider; +import org.gcube.application.geoportal.common.model.configuration.MongoConnection; +import org.gcube.application.geoportal.service.model.internal.db.Mongo; +import org.gcube.common.scope.api.ScopeProvider; + +import com.mongodb.client.MongoIterable; + +import lombok.extern.slf4j.Slf4j; + + +/** + * The Class MongoHealthCheck. + * + * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it + * + * Oct 22, 2024 + */ +@Readiness +@Liveness +@Slf4j +public class MongoHealthCheck implements HealthCheck { + + private String context; + private Boolean includeCollections = false; + public static final String SERVICE_NAME = "mongo"; + + /** + * Call. + * + * @return the health check response + */ + @Override + public HealthCheckResponse call() { + return checkMongo(context); + } + + /** + * Instantiates a new mongo health check. + * + * @param context the context + * @param includeCollections the include collections + */ + public MongoHealthCheck(String context, Boolean includeCollections) { + this.context = context; + this.includeCollections = includeCollections; + } + + /** + * Check mongo. + * + * @param context the context + * @return the health check response + */ + private HealthCheckResponse checkMongo(String context) { + log.debug("checkMongo in the context: {}", context); + HealthCheckResponseBuilder buildHCRBuilder = HealthCheckResponse.named(SERVICE_NAME); + Mongo mongo = null; + try { + ScopeProvider.instance.set(context); + mongo = ImplementationProvider.get().getProvidedObjectByClass(Mongo.class); + buildHCRBuilder = appendMongoInfo(buildHCRBuilder, mongo.getConnection()); + buildHCRBuilder.state(true); + if (includeCollections!=null && includeCollections) { + MongoIterable collections = mongo.getTheClient() + .getDatabase(mongo.getConnection().getDatabase()).listCollectionNames(); + log.info("listCollectionNames is null: {}", collections == null); + int i = 1; + for (String coll : collections) { + log.debug("adding collection: {}", coll); + buildHCRBuilder.withData("collection_" + i, coll); + i++; + } + } + log.info("checkMongo is OK in the context: {}", context); + return buildHCRBuilder.build(); + } catch (Exception e) { + log.error("Error on checkMongo: ", e); + log.warn("checkMongo is KO in the context: {}", context); + buildHCRBuilder.state(false); + if (mongo != null) { + MongoConnection connection = null; + try { + connection = mongo.getConnection(); + buildHCRBuilder = appendMongoInfo(buildHCRBuilder, connection); + } catch (Exception e1) { + buildHCRBuilder.withData("hosts", connection.getHosts() + ""); + } + + } + return buildHCRBuilder.build(); + } finally { + ScopeProvider.instance.reset(); + } + } + + /** + * Append mongo info. + * + * @param buildHCRBuilder the build HCR builder + * @param connection the connection + * @return the health check response builder + */ + private HealthCheckResponseBuilder appendMongoInfo(HealthCheckResponseBuilder buildHCRBuilder, + MongoConnection connection) { + buildHCRBuilder.withData("hosts", connection.getHosts() + ""); + buildHCRBuilder.withData("db_name ", connection.getDatabase()); + return buildHCRBuilder; + } + +} diff --git a/geoportal-service/src/main/webapp/WEB-INF/web.xml b/geoportal-service/src/main/webapp/WEB-INF/web.xml index b387ccd..ef51265 100644 --- a/geoportal-service/src/main/webapp/WEB-INF/web.xml +++ b/geoportal-service/src/main/webapp/WEB-INF/web.xml @@ -1,11 +1,13 @@ - org.gcube.application.geoportal.service.GeoPortalService + org.gcube.application.geoportal.service.GeoPortalService + org.glassfish.jersey.servlet.ServletContainer javax.ws.rs.Application - org.gcube.application.geoportal.service.GeoPortalService + org.gcube.application.geoportal.service.GeoPortalService + jersey.config.server.provider.classnames @@ -20,16 +22,17 @@ - default - /docs/* - - - default - /api-docs/* + default + /docs/* - - org.gcube.application.geoportal.service.GeoPortalService + default + /api-docs/* + + + + org.gcube.application.geoportal.service.GeoPortalService + /srv/* \ No newline at end of file diff --git a/geoportal-service/src/test/java/org/gcube/application/geoportal/service/health/HealthCheckSerializer.java b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/health/HealthCheckSerializer.java new file mode 100644 index 0000000..44a1101 --- /dev/null +++ b/geoportal-service/src/test/java/org/gcube/application/geoportal/service/health/HealthCheckSerializer.java @@ -0,0 +1,32 @@ +package org.gcube.application.geoportal.service.health; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.gcube.application.geoportal.service.rest.health.HealthCheckResponseSerializer; + +public class HealthCheckSerializer { + + public static void main(String[] args) throws Exception { + HealthCheckResponse response = HealthCheckResponse.named("geooportal-service") + .state(true) + .withData("status", "healthy") + .build(); + + // Configura ObjectMapper con il serializer personalizzato + ObjectMapper mapper = new ObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addSerializer(HealthCheckResponse.class, new HealthCheckResponseSerializer()); + mapper.registerModule(module); + + // Serializza l'oggetto HealthCheckResponse in JSON + String json = mapper.writeValueAsString(response); + + // Stampa il JSON serializzato + System.out.println(json); + } + + + +} \ No newline at end of file diff --git a/sdi-plugins/CHANGELOG.md b/sdi-plugins/CHANGELOG.md index 0fb028b..2abd5f0 100644 --- a/sdi-plugins/CHANGELOG.md +++ b/sdi-plugins/CHANGELOG.md @@ -3,6 +3,7 @@ ## [v1.1.4] - Improved logs - Added fallback on /geoserver/rest path [#28150#note-8] +- Improved some business logics ## [v1.1.3] - Added apply regex business logic [#26322] diff --git a/sdi-plugins/pom.xml b/sdi-plugins/pom.xml index 71ec477..8811cf9 100644 --- a/sdi-plugins/pom.xml +++ b/sdi-plugins/pom.xml @@ -5,7 +5,7 @@ 4.0.0 sdi-plugins - 1.1.4 + 1.1.4-SNAPSHOT gCube CMS - SDI Plugins diff --git a/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIAbstractPlugin.java b/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIAbstractPlugin.java index 2a3aa34..90aa669 100644 --- a/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIAbstractPlugin.java +++ b/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIAbstractPlugin.java @@ -23,7 +23,11 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public abstract class SDIAbstractPlugin extends AbstractPlugin implements InitializablePlugin { - protected static AbstractScopedMap sdiCache; + public static final String POSTGIS_CREDENTIALS = "POSTGIS-CREDENTIALS"; + + public static final String SDI_CACHE = "SDI-CACHE"; + + protected static AbstractScopedMap sdiCache; protected static AbstractScopedMap postgisCache; @@ -31,7 +35,7 @@ public abstract class SDIAbstractPlugin extends AbstractPlugin implements Initia private static void initCache(){ if(sdiCache==null) { log.info("Creating internal caches.. "); - sdiCache = new AbstractScopedMap("SDI-CACHE") { + sdiCache = new AbstractScopedMap(SDI_CACHE) { @Override protected SDIManagerWrapper retrieveObject(String context) throws ConfigurationException { try { @@ -44,7 +48,7 @@ public abstract class SDIAbstractPlugin extends AbstractPlugin implements Initia sdiCache.setTTL(Duration.of(10, ChronoUnit.MINUTES)); } if(postgisCache==null) { - postgisCache = new AbstractScopedMap("POSTGIS-CREDENTIALS") { + postgisCache = new AbstractScopedMap(POSTGIS_CREDENTIALS) { @Override protected DatabaseConnection retrieveObject(String context) throws ConfigurationException { try { diff --git a/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIIndexerPlugin.java b/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIIndexerPlugin.java index 4a41e74..4a67e59 100644 --- a/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIIndexerPlugin.java +++ b/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIIndexerPlugin.java @@ -463,6 +463,10 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin DatabaseConnection connectionParameters = null; try { connectionParameters = postgisCache.getObject(); + + if(connectionParameters==null) + throw new ConfigurationException("connectionParameters is null"); + // Getting connection Connection connection = DriverManager.getConnection(connectionParameters.getUrl(), connectionParameters.getUser(), connectionParameters.getPwd());