diff --git a/common-utitlities/src/main/java/org/eea/security/jwt/configuration/CacheClientSecurityConfiguration.java b/common-utitlities/src/main/java/org/eea/security/jwt/configuration/CacheClientSecurityConfiguration.java index e8800fd53a..6928e24c2a 100644 --- a/common-utitlities/src/main/java/org/eea/security/jwt/configuration/CacheClientSecurityConfiguration.java +++ b/common-utitlities/src/main/java/org/eea/security/jwt/configuration/CacheClientSecurityConfiguration.java @@ -1,17 +1,24 @@ package org.eea.security.jwt.configuration; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import org.eea.security.jwt.data.CacheTokenVO; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import redis.clients.jedis.JedisPoolConfig; @@ -40,6 +47,8 @@ public class CacheClientSecurityConfiguration { private Integer minEvitableIdleTimeMillis; @Value("${spring.redis.jedis.pool.max-wait}") private Integer maxWaitMillis; + @Value("${cache.datasetSchemaId.ttl:120}") + private long datasetSchemaIdTtl; @Value("${spring.cloud.consul.discovery.instanceId}") private String serviceInstanceId; @@ -111,4 +120,64 @@ private JedisPoolConfig createPoolConfig() { return poolConfig; } + /** + * Redis Cache Manager for local profile. + * Uses a standalone Redis connection to manage caches for + * {@code datasetSchemaId}. + * TTLs are configurable via Consul keys {@code cache.datasetSchemaId.ttl} + * and {@code cache.uniqueConstraints.ttl} (in minutes). + * + * @param jedisConnectionFactory the standalone Redis connection factory + * @return the Redis cache manager + */ + @Bean + @Profile("local") + public RedisCacheManager localCacheManager(JedisConnectionFactory jedisConnectionFactory) { + RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer())); + + Map cacheConfigs = new HashMap<>(); + cacheConfigs.put("datasetSchemaId", defaultConfig.entryTtl(Duration.ofMinutes(datasetSchemaIdTtl))); + + return RedisCacheManager.builder(jedisConnectionFactory) + .cacheDefaults(defaultConfig) + .withInitialCacheConfigurations(cacheConfigs) + .build(); + } + + /** + * Redis Cache Manager for non-local profiles (dev, staging, production). + * Uses a Redis Sentinel connection for high availability to manage caches for + * {@code datasetSchemaId}. + * TTLs are configurable via Consul keys {@code cache.datasetSchemaId.ttl} + * and {@code cache.uniqueConstraints.ttl} (in minutes). + * + * @param jedisSentinelConnectionFactory the Redis Sentinel connection factory + * @return the Redis cache manager + */ + @Bean + @Profile("!local") + public RedisCacheManager sentinelCacheManager(JedisConnectionFactory jedisSentinelConnectionFactory) { + RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer())); + + Map cacheConfigs = new HashMap<>(); + cacheConfigs.put("datasetSchemaId", defaultConfig.entryTtl(Duration.ofMinutes(datasetSchemaIdTtl))); + + return RedisCacheManager.builder(jedisSentinelConnectionFactory) + .cacheDefaults(defaultConfig) + .withInitialCacheConfigurations(cacheConfigs) + .build(); + } + } diff --git a/dataset-service/src/main/java/org/eea/dataset/service/impl/DataschemaServiceImpl.java b/dataset-service/src/main/java/org/eea/dataset/service/impl/DataschemaServiceImpl.java index 6a4c2a89f1..6ae627ae82 100644 --- a/dataset-service/src/main/java/org/eea/dataset/service/impl/DataschemaServiceImpl.java +++ b/dataset-service/src/main/java/org/eea/dataset/service/impl/DataschemaServiceImpl.java @@ -73,6 +73,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.context.SecurityContextHolder; @@ -92,6 +95,18 @@ @Service("dataschemaService") public class DataschemaServiceImpl implements DatasetSchemaService { + /** + * Self-referencing proxy of this service bean, injected to enable Spring AOP features + * (e.g., {@link Cacheable}, {@link CacheEvict}) on internal method calls within the same class. + * Direct {@code this.method()} calls bypass the Spring proxy and would not trigger cache + * interception; calling {@code self.method()} routes through the proxy instead. + * + * The {@link Lazy} annotation prevents circular dependency issues during bean initialization. + */ + @Autowired + @Lazy + private DatasetSchemaService self; + /** The Constant REGEX_NAME: {@value}. */ private static final String REGEX_NAME = "[a-zA-Z0-9\\s_-]+"; @@ -333,21 +348,6 @@ public ObjectId createEmptyDataSetSchema(Long dataflowId) throws EEAException { return idDataSetSchema; } - /** - * Delete group and remove user. - * - * @param datasetId the dataset id - * @param resourceTypeEnum the resource type enum - */ - @Override - public void deleteGroup(Long datasetId, ResourceTypeEnum resourceTypeEnum) { - // We find all types of data of this schema and delete it - List resourceCustodian = resourceManagementControllerZull - .getGroupsByIdResourceType(datasetId, ResourceTypeEnum.DATA_SCHEMA); - resourceManagementControllerZull.deleteResource(resourceCustodian); - LOG.info("Deleted group for datasetId {}", datasetId); - } - /** * Gets the data schema by id. * @@ -370,6 +370,21 @@ public DataSetSchemaVO getDataSchemaById(String dataschemaId) { return dataSchemaVO; } + /** + * Delete group and remove user. + * + * @param datasetId the dataset id + * @param resourceTypeEnum the resource type enum + */ + @Override + public void deleteGroup(Long datasetId, ResourceTypeEnum resourceTypeEnum) { + // We find all types of data of this schema and delete it + List resourceCustodian = resourceManagementControllerZull + .getGroupsByIdResourceType(datasetId, ResourceTypeEnum.DATA_SCHEMA); + resourceManagementControllerZull.deleteResource(resourceCustodian); + LOG.info("Deleted group for datasetId {}", datasetId); + } + /** * Find the dataschema per idDataFlow. * @@ -420,6 +435,7 @@ private void setNameSchema(String schemaId, DataSetSchemaVO dataschemaVO) { * @throws EEAException the EEA exception */ @Override + @Cacheable(value = "datasetSchemaId", key = "#datasetId") public String getDatasetSchemaId(Long datasetId) throws EEAException { return obtainDatasetMetabase(datasetId).getDatasetSchema(); } @@ -450,6 +466,7 @@ private DataSetMetabase obtainDatasetMetabase(final Long datasetId) throws EEAEx */ @Override @Transactional + @CacheEvict(value = "datasetSchemaId", key = "#datasetId") public void deleteDatasetSchema(String schemaId, Long datasetId) { // we delete the integrity rules associated with this dataset and delete the integrity in mongo rulesControllerZuul.deleteDatasetRuleAndIntegrityByDatasetSchemaId(schemaId, datasetId); @@ -568,7 +585,7 @@ public TableSchemaVO createTableSchema(String id, TableSchemaVO tableSchemaVO, L @Override public void updateTableSchema(Long datasetId, TableSchemaVO tableSchemaVO, Boolean updateMaterializedViews) throws EEAException { - String datasetSchemaId = getDatasetSchemaId(datasetId); + String datasetSchemaId = self.getDatasetSchemaId(datasetId); try { Document tableSchema = @@ -777,6 +794,7 @@ public String createFieldSchema(String datasetSchemaId, FieldSchemaVO fieldSchem * @throws EEAException the EEA exception */ @Override + @Transactional public DataType updateFieldSchema(String datasetSchemaId, FieldSchemaVO fieldSchemaVO, Long datasetId, boolean cloningOrImporting) throws EEAException { @@ -1156,6 +1174,7 @@ public void updateDatasetSchemaDescription(String datasetSchemaId, String descri * @param availableInPublic the available in public */ @Override + @Transactional public void updateDatasetSchemaExportable(String datasetSchemaId, boolean availableInPublic) { schemasRepository.updateDatasetSchemaExportable(datasetSchemaId, availableInPublic); } @@ -2152,7 +2171,7 @@ public void copyUniqueConstraintsCatalogue(List originDatasetSchemaIds, */ @Override public SimpleDatasetSchemaVO getSimpleSchema(Long datasetId) throws EEAException { - String schemaId = getDatasetSchemaId(datasetId); + String schemaId = self.getDatasetSchemaId(datasetId); if (schemaId != null) { LOG.info("Getting schema from id {}", schemaId); Optional designDataset = @@ -2249,7 +2268,7 @@ public Boolean checkClearAttachments(Long datasetId, String datasetSchemaId, */ private void createNotEmptyRule(String tableSchemaId, Long datasetId) throws EEAException { // retieve default level error if any - String datasetSchemaId = getDatasetSchemaId(datasetId); + String datasetSchemaId = self.getDatasetSchemaId(datasetId); RulesSchema rulesSchema = rulesRepository.findByIdDatasetSchema(new ObjectId(datasetSchemaId)); ErrorTypeEnum automaticQCDefaultLevelError = @@ -2570,7 +2589,7 @@ public void importSchemas(Long dataflowId, InputStream is, String fileName) */ @Override public List getTableSchemasIds(Long datasetId) throws EEAException { - String datasetschemaId = getDatasetSchemaId(datasetId); + String datasetschemaId = self.getDatasetSchemaId(datasetId); DataSetSchema schema = schemasRepository.findByIdDataSetSchema(new ObjectId(datasetschemaId)); List tableSchemasVOList = new ArrayList<>(); for (TableSchema table : schema.getTableSchemas()) { @@ -2588,6 +2607,7 @@ public List getTableSchemasIds(Long datasetId) throws EEAEx * @param referenceDataset the reference dataset */ @Override + @Transactional public void updateReferenceDataset(Long datasetId, String datasetSchemaId, boolean referenceDataset) { @@ -2982,7 +3002,7 @@ private void updateImportFieldSchema(FieldSchemaVO fieldSchemaVO, List dictionaryOriginTargetObje // update all the stuff // related to the PK/FK try { - String datasetSchemaId = getDatasetSchemaId(datasetId); + String datasetSchemaId = self.getDatasetSchemaId(datasetId); updateForeignRelation(datasetId, fieldSchemaNoRulesMapper.entityToClass(field), datasetSchemaId); - DataType type = updateFieldSchema(datasetSchemaId, + DataType type = self.updateFieldSchema(datasetSchemaId, fieldSchemaNoRulesMapper.entityToClass(field), datasetId, true); propagateRulesAfterUpdateSchema(datasetSchemaId, fieldSchemaNoRulesMapper.entityToClass(field), type, datasetId); @@ -3633,7 +3653,7 @@ public String getFieldName(String datasetSchemaId, String tableSchemaId, List tableSchemaIds = getTableSchemasIds(datasetId); for(TableSchemaIdNameVO tableSchemaIdNameVO: tableSchemaIds){ TableSchemaVO tableSchemaVO = getTableSchemaVO(tableSchemaIdNameVO.getIdTableSchema(), datasetSchemaId);