Merge pull request 'health_check' (#21) from health_check into master

Reviewed-on: #21
This commit is contained in:
Francesco Mangiacrapa 2024-10-28 15:51:22 +01:00
commit 3ec3738784
21 changed files with 664 additions and 109 deletions

View File

@ -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

View File

@ -21,28 +21,31 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserUtils {
public static List<String> DEFAULT_ROLES=new ArrayList<>();
public static List<String> 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<String> roles = new HashSet<>();
org.gcube.common.authorization.utils.user.User user = cm.getUser();
log.info("Identified caller {} in context {}",user.getUsername(),context);
Set<String> 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;
}

View File

@ -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]

View File

@ -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">
<api-classes>
<!-- Use patterns to exclude classes...
e.g. for URI-Resolver
-->
<!-- Use patterns to exclude classes... -->
<exclude pattern="org.gcube.application.geoportal.service.rest.DocsGenerator" />
<exclude pattern="org.gcube.application.geoportal.service.rest.ConcessioniOverMongo" />
<exclude pattern="org.gcube.application.geoportal.service.rest.GeoportalHealth" />
</api-classes>
<modules>
<gwt-json-overlay disabled="true" />

View File

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<application mode='online'>
<name>${project.artifactId}</name>
<group>${project.groupId}</group>
<version>${project.version}</version>
<description>${project.description}</description>
<exclude>/srv/docs/*</exclude>
<exclude>/srv/api-docs/*</exclude>
<name>${project.artifactId}</name>
<group>${project.groupId}</group>
<version>${project.version}</version>
<description>${project.description}</description>
<!-- <exclude>/srv/docs/*</exclude> -->
<!-- <exclude>/srv/api-docs/*</exclude> -->
<exclude>/srv/health</exclude>
<exclude>/srv/health/*</exclude>
</application>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.gcube.application</groupId>
<artifactId>geoportal-service</artifactId>
<version>1.2.1</version>
<version>1.2.2</version>
<name>Geoportal Service</name>
<packaging>war</packaging>
@ -155,6 +155,17 @@
</dependency> <dependency> <groupId>javax.xml.ws</groupId> <artifactId>jaxws-api</artifactId>
<scope>test</scope> </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.eclipse.microprofile.health</groupId> -->
<!-- <artifactId>microprofile-health-api</artifactId> -->
<!-- <version>2.2</version> -->
<!-- </dependency> -->
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-health</artifactId>
<version>2.2.3</version>
</dependency>
<!-- Plugins related tests -->

View File

@ -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 <T> Map<Engine<T>,Class<T>> customImplementations(){
public <T> Map<Engine<T>, Class<T>> 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<Engine<Object>, Class<Object>> entry : customImplementations().entrySet()){
log.warn("LOADING CUSTOM ENGINE : {} serving {}",entry.getKey(),entry.getValue());
for (Map.Entry<Engine<Object>, Class<Object>> 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();
}
}

View File

@ -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";
}

View File

@ -29,7 +29,7 @@ public class MongoClientProvider extends AbstractScopedMap<Mongo> {
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);

View File

@ -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);
}
}

View File

@ -19,18 +19,15 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class GuardedMethod<T> {
private static List<Runnable> preoperations=new ArrayList<>();
private static List<Runnable> 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<T> execute() throws WebApplicationException{
public GuardedMethod<T> execute() throws WebApplicationException {
try {
if (!preoperations.isEmpty()) {
log.trace("Running preops (size : {} )", preoperations.size());
@ -40,41 +37,41 @@ public abstract class GuardedMethod<T> {
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;
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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<HealthCheckResponse> {
@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<String, Object> 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();
}
}

View File

@ -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<String> 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;
}
}

View File

@ -1,11 +1,13 @@
<web-app>
<servlet>
<servlet-name>org.gcube.application.geoportal.service.GeoPortalService</servlet-name>
<servlet-name>org.gcube.application.geoportal.service.GeoPortalService
</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.gcube.application.geoportal.service.GeoPortalService</param-value>
<param-value>org.gcube.application.geoportal.service.GeoPortalService
</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
@ -20,16 +22,17 @@
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/docs/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/api-docs/*</url-pattern>
<servlet-name>default</servlet-name>
<url-pattern>/docs/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>org.gcube.application.geoportal.service.GeoPortalService</servlet-name>
<servlet-name>default</servlet-name>
<url-pattern>/api-docs/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>org.gcube.application.geoportal.service.GeoPortalService
</servlet-name>
<url-pattern>/srv/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -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);
}
}

View File

@ -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]

View File

@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>sdi-plugins</artifactId>
<version>1.1.4</version>
<version>1.1.4-SNAPSHOT</version>
<name>gCube CMS - SDI Plugins</name>
<parent>

View File

@ -23,7 +23,11 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class SDIAbstractPlugin extends AbstractPlugin implements InitializablePlugin {
protected static AbstractScopedMap<SDIManagerWrapper> sdiCache;
public static final String POSTGIS_CREDENTIALS = "POSTGIS-CREDENTIALS";
public static final String SDI_CACHE = "SDI-CACHE";
protected static AbstractScopedMap<SDIManagerWrapper> sdiCache;
protected static AbstractScopedMap<DatabaseConnection> 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<SDIManagerWrapper>("SDI-CACHE") {
sdiCache = new AbstractScopedMap<SDIManagerWrapper>(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<DatabaseConnection>("POSTGIS-CREDENTIALS") {
postgisCache = new AbstractScopedMap<DatabaseConnection>(POSTGIS_CREDENTIALS) {
@Override
protected DatabaseConnection retrieveObject(String context) throws ConfigurationException {
try {

View File

@ -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());