package com.til.tcc.manager.mongo.service;
import com.til.tcc.manager.constants.Constants;
import com.til.tcc.manager.mongo.entity.AufsConfigTranslationEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@Slf4j
public class AufsConfigTranslationService {
@Autowired
private BaseMongoService baseMongoService;
@Autowired
private MongoTemplate mongoTemplate;
public Map<String, Map<Integer, List<AufsConfigTranslationEntity>>> getTranslationConfigs(String pc) {
if (pc == null) {
throw new IllegalArgumentException("Parameter 'pc' cannot be null");
}
try {
if(StringUtils.equalsIgnoreCase(pc, "eis")){
pc = Constants.pc.es.name();
}else if (StringUtils.equalsIgnoreCase(pc, "tml")){
pc = Constants.pc.tamilsamayam.name();
}else if (StringUtils.equalsIgnoreCase(pc, "mly")){
pc = Constants.pc.malayalamsamayam.name();
}else if (StringUtils.equalsIgnoreCase(pc, "tlg")){
pc = Constants.pc.telugusamayam.name();
}
Query query = new Query();
query.addCriteria(Criteria.where(AufsConfigTranslationEntity.PC).is(pc));
query.addCriteria(Criteria.where(AufsConfigTranslationEntity.ENABLE).is(true));
List<AufsConfigTranslationEntity> list = baseMongoService.find(query, AufsConfigTranslationEntity.class);
list.forEach(x -> x.setRootTrans(null));
return list.stream()
.collect(Collectors.groupingBy(AufsConfigTranslationEntity::getPfm,
Collectors.groupingBy(AufsConfigTranslationEntity::getFv, Collectors.toList())));
} catch (Exception e) {
log.error("Exception occurred for pc: {} ", pc, e);
throw new RuntimeException("Failed to retrieve translation configs", e);
}
}
public AufsConfigTranslationEntity getById(String id) {
try {
Query query = new Query();
query.addCriteria(Criteria.where(AufsConfigTranslationEntity.ID).is(id));
return baseMongoService.findOne(query, AufsConfigTranslationEntity.class);
} catch (Exception e) {
log.error("Exception occurred for {} ", id, e);
throw new RuntimeException("Failed to retrieve translation config", e);
}
}
public int updateById(String id, AufsConfigTranslationEntity updatedEntity) {
try {
Query query = new Query(Criteria.where("_id").is(id));
Update update = new Update();
if(StringUtils.isNotBlank(updatedEntity.getPfm())){
update.set("pfm", updatedEntity.getPfm());
}
if(StringUtils.isNotBlank(updatedEntity.getPc())){
update.set("pc", updatedEntity.getPc());
}
if(Objects.nonNull(updatedEntity.getFv())){
update.set("fv", updatedEntity.getFv());
}
if(Objects.nonNull(updatedEntity.getEnable())){
update.set("enable", updatedEntity.getEnable());
}
if(updatedEntity.getRootTrans() != null){
update.set("rootTrans", updatedEntity.getRootTrans());
}
return baseMongoService.updateOne(query, update, AufsConfigTranslationEntity.class);
} catch (Exception e) {
log.error("Exception occurred for {} ", id, e);
throw new RuntimeException("Failed to retrieve translation config", e);
}
}
public void updateTranslations(String client, String pfm, Integer fv,
Map<String, Object> updates, Map<String, Object> removals) {
// 1. Get target feed version (use specified or latest)
Integer targetFv = (fv != null) ? fv : getLatestFv(client, pfm);
if (targetFv == null) {
throw new RuntimeException("No translations found for client: " + client + ", platform: " + pfm);
}
// 2. Update the specified document
updateSpecificDocument(client, pfm, targetFv, updates, removals);
// 3. Update other language documents (pc ≠ "toi")
updateOtherLanguageDocuments(client, pfm, updates, removals);
}
private void updateSpecificDocument(String client, String pfm, Integer fv,
Map<String, Object> updates, Map<String, Object> removals) {
Query query = Query.query(Criteria.where("client").is(client)
.and("pfm").is(pfm)
.and("fv").is(fv));
AufsConfigTranslationEntity doc = mongoTemplate.findOne(query, AufsConfigTranslationEntity.class);
if (doc == null) {
doc = new AufsConfigTranslationEntity();
doc.setClient(client);
doc.setPfm(pfm);
doc.setFv(fv);
doc.setRootTrans(new HashMap<>());
}
Map<String, Object> rootTrans = doc.getRootTrans() != null
? new HashMap<>(doc.getRootTrans())
: new HashMap<>();
// Apply updates and removals with deep merge
applyUpdatesAndRemovals(rootTrans, updates, removals);
doc.setRootTrans(rootTrans);
mongoTemplate.save(doc);
}
private void updateOtherLanguageDocuments(String client, String pfm,
Map<String, Object> updates, Map<String, Object> removals) {
Query query = new Query(Criteria.where("client").is(client)
.and("pfm").is(pfm)
.and("pc").ne("toi"));
query.with(new Sort(Sort.Direction.DESC, "fv"));
List<AufsConfigTranslationEntity> allLangDocs = mongoTemplate.find(query, AufsConfigTranslationEntity.class);
// Keep only latest fv per unique pc
Map<String, AufsConfigTranslationEntity> latestPerPc = allLangDocs.stream()
.collect(Collectors.toMap(
AufsConfigTranslationEntity::getPc,
Function.identity(),
(doc1, doc2) -> doc1.getFv() >= doc2.getFv() ? doc1 : doc2
));
int updateCount = 0;
for (AufsConfigTranslationEntity doc : latestPerPc.values()) {
Map<String, Object> rootTrans = doc.getRootTrans() != null
? new HashMap<>(doc.getRootTrans())
: new HashMap<>();
// Apply updates and removals with deep merge
applyUpdatesAndRemovals(rootTrans, updates, removals);
doc.setRootTrans(rootTrans);
mongoTemplate.save(doc);
updateCount++;
}
System.out.println("Updated " + updateCount + " other language documents.");
}
private Integer getLatestFv(String client, String pfm) {
Query query = new Query(Criteria.where("client").is(client)
.and("pfm").is(pfm))
.with(new Sort(Sort.Direction.DESC, "fv"))
.limit(1);
AufsConfigTranslationEntity doc = mongoTemplate.findOne(query, AufsConfigTranslationEntity.class);
return doc != null ? doc.getFv() : null;
}
//Apply updates and then apply removals to the root translation map with deep merge support
@SuppressWarnings("unchecked")
private void applyUpdatesAndRemovals(Map<String, Object> rootTrans,
Map<String, Object> updates,
Map<String, Object> removals) {
// Apply updates with deep merge
if (updates != null && !updates.isEmpty()) {
// First unflatten dotted keys into a nested structure
Map<String, Object> structuredUpdates = unflattenUpdates(updates);
// Then merge with existing data
deepMerge(rootTrans, structuredUpdates);
}
// Apply removals
if (removals != null && !removals.isEmpty()) {
for (Map.Entry<String, Object> entry : removals.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// Handle removal based on key structure
if (key.contains(".")) {
// For dot notation keys, handle nested removal
removeNestedMapValue(rootTrans, key, value);
} else {
// For top-level keys
if (value == null) {
// Remove entire key if value is null
rootTrans.remove(key);
} else if (value instanceof Map) {
// Handle nested map removal
handleNestedMapRemoval(rootTrans, key, (Map<String, Object>) value);
} else {
// Remove key only if value matches exactly
Object currentValue = rootTrans.get(key);
if (currentValue != null && currentValue.equals(value)) {
rootTrans.remove(key);
}
}
}
}
}
}
//Convert dotted notation keys to nested structure
@SuppressWarnings("unchecked")
private Map<String, Object> unflattenUpdates(Map<String, Object> updates) {
Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, Object> entry : updates.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (key.contains(".")) {
String[] parts = key.split("\\.");
Map<String, Object> current = result;
// Navigate to the correct level
for (int i = 0; i < parts.length - 1; i++) {
if (!current.containsKey(parts[i])) {
current.put(parts[i], new HashMap<>());
}
Object next = current.get(parts[i]);
if (!(next instanceof Map)) {
// Override if not a map
Map<String, Object> newMap = new HashMap<>();
current.put(parts[i], newMap);
current = newMap;
} else {
current = (Map<String, Object>) next;
}
}
// Set the value at the deepest level
current.put(parts[parts.length - 1], value);
} else {
// Handle top-level keys normally
result.put(key, value);
}
}
return result;
}
//Deep merge two maps - preserves existing values when not explicitly overwritten
@SuppressWarnings("unchecked")
private void deepMerge(Map<String, Object> target, Map<String, Object> source) {
if (source == null) {
return;
}
for (Map.Entry<String, Object> entry : source.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// If both have a map for the same key, merge them
if (value instanceof Map && target.containsKey(key) && target.get(key) instanceof Map) {
deepMerge((Map<String, Object>) target.get(key), (Map<String, Object>) value);
} else {
// Otherwise just overwrite/add the value
target.put(key, value);
}
}
}
//Handle removal of nested values in a map structure
@SuppressWarnings("unchecked")
private void handleNestedMapRemoval(Map<String, Object> rootMap, String key, Map<String, Object> valueToRemove) {
// If the key doesn't exist in root map, nothing to remove
if (!rootMap.containsKey(key)) {
return;
}
Object currentValue = rootMap.get(key);
// If current value is not a map, but removal specifies a map, something is wrong
if (!(currentValue instanceof Map)) {
return;
}
Map<String, Object> currentMap = (Map<String, Object>) currentValue;
// If removal map is empty, remove the entire key
if (valueToRemove.isEmpty()) {
rootMap.remove(key);
return;
}
// For each entry in the removal map, apply removal logic
for (Map.Entry<String, Object> entry : valueToRemove.entrySet()) {
String nestedKey = entry.getKey();
Object nestedValue = entry.getValue();
if (!currentMap.containsKey(nestedKey)) {
continue;
}
Object existingNestedValue = currentMap.get(nestedKey);
if (nestedValue == null) {
// Remove entire nested key if value is null
currentMap.remove(nestedKey);
} else if (nestedValue instanceof Map && existingNestedValue instanceof Map) {
// Recursively handle nested map removal
handleNestedMapRemoval(currentMap, nestedKey, (Map<String, Object>) nestedValue);
// If the nested map is now empty, remove it
if (((Map<?, ?>) existingNestedValue).isEmpty()) {
currentMap.remove(nestedKey);
}
} else if (existingNestedValue != null && existingNestedValue.equals(nestedValue)) {
// Remove if values match exactly
currentMap.remove(nestedKey);
}
}
// If the map is now empty after removals, remove the parent key
if (currentMap.isEmpty()) {
rootMap.remove(key);
}
}
//Remove a value from a nested map structure using dot notation
@SuppressWarnings("unchecked")
private void removeNestedMapValue(Map<String, Object> rootMap, String dottedKey, Object valueToRemove) {
String[] keyParts = dottedKey.split("\\.");
// Navigate to the parent map that contains the final key
Map<String, Object> currentMap = rootMap;
for (int i = 0; i < keyParts.length - 1; i++) {
Object value = currentMap.get(keyParts[i]);
if (!(value instanceof Map)) {
// Path doesn't exist, nothing to remove
return;
}
currentMap = (Map<String, Object>) value;
}
String finalKey = keyParts[keyParts.length - 1];
// Handle removal based on value type
if (!currentMap.containsKey(finalKey)) {
return;
}
Object currentValue = currentMap.get(finalKey);
if (valueToRemove == null) {
// Remove entire key if value to remove is null
currentMap.remove(finalKey);
} else if (valueToRemove instanceof Map && currentValue instanceof Map) {
// Handle nested map removal
handleNestedMapRemoval(currentMap, finalKey, (Map<String, Object>) valueToRemove);
} else if (currentValue != null && currentValue.equals(valueToRemove)) {
// Remove if values match exactly
currentMap.remove(finalKey);
}
// Check if parent maps should be removed (if they're empty)
if (keyParts.length > 1 && currentMap.isEmpty()) {
// Remove empty parent maps recursively
removeEmptyParentMaps(rootMap, keyParts, 0, keyParts.length - 1);
}
}
//Recursively remove empty parent maps
@SuppressWarnings("unchecked")
private void removeEmptyParentMaps(Map<String, Object> rootMap, String[] keyParts, int startIdx, int endIdx) {
if (startIdx >= endIdx) {
return;
}
// Build the path to the current map
Map<String, Object> currentMap = rootMap;
for (int i = 0; i < endIdx - 1; i++) {
Object value = currentMap.get(keyParts[i]);
if (!(value instanceof Map)) {
return;
}
currentMap = (Map<String, Object>) value;
}
// Check if the map at the path is empty
Object value = currentMap.get(keyParts[endIdx - 1]);
if (value instanceof Map && ((Map<?, ?>) value).isEmpty()) {
currentMap.remove(keyParts[endIdx - 1]);
//Recursively check parent
removeEmptyParentMaps(rootMap, keyParts, startIdx, endIdx - 1);
}
}
}
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter