Initial Python rewrite

This commit is contained in:
2026-02-19 17:50:14 +00:00
parent da95a64fb7
commit faa04a0d01
158 changed files with 5122 additions and 1144 deletions

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.util;
import com.example.repository.FirestoreBaseRepository;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.cloud.firestore.DocumentReference;
import com.google.cloud.firestore.DocumentSnapshot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
public class FirestoreDataImporter {
private static final Logger logger = LoggerFactory.getLogger(FirestoreDataImporter.class);
private static final String QUICK_REPLIES_COLLECTION_PATH_FORMAT = "artifacts/%s/quick-replies";
private final FirestoreBaseRepository firestoreBaseRepository;
private final ObjectMapper objectMapper;
private final boolean isImporterEnabled;
public FirestoreDataImporter(FirestoreBaseRepository firestoreBaseRepository, ObjectMapper objectMapper) {
this.firestoreBaseRepository = firestoreBaseRepository;
this.objectMapper = objectMapper;
this.isImporterEnabled = Boolean.parseBoolean(System.getProperty("firestore.data.importer.enabled"));
}
public void runImport() {
if (isImporterEnabled) {
try {
importQuickReplies();
} catch (Exception e) {
logger.error("Failed to import data to Firestore on startup", e);
}
}
}
private void importQuickReplies() throws IOException, ExecutionException, InterruptedException {
String collectionPath = String.format(QUICK_REPLIES_COLLECTION_PATH_FORMAT, firestoreBaseRepository.getAppId());
importJson(collectionPath, "home");
importJson(collectionPath, "pagos");
importJson(collectionPath, "finanzas");
importJson(collectionPath, "lealtad");
importJson(collectionPath, "descubre");
importJson(collectionPath, "detalle-tdc");
importJson(collectionPath, "detalle-tdd");
importJson(collectionPath, "transferencia");
importJson(collectionPath, "retiro-sin-tarjeta");
importJson(collectionPath, "capsulas");
importJson(collectionPath, "inversiones");
importJson(collectionPath, "prestamos");
logger.info("All JSON files were imported successfully.");
}
private void importJson(String collectionPath, String documentId) throws IOException, ExecutionException, InterruptedException {
String resourcePath = "/quick-replies/" + documentId + ".json";
try (InputStream inputStream = getClass().getResourceAsStream(resourcePath)) {
if (inputStream == null) {
logger.warn("Resource not found: {}", resourcePath);
return;
}
Map<String, Object> localData = objectMapper.readValue(inputStream, new TypeReference<Map<String, Object>>() {});
DocumentReference docRef = firestoreBaseRepository.getDocumentReference(collectionPath, documentId);
if (firestoreBaseRepository.documentExists(docRef)) {
DocumentSnapshot documentSnapshot = firestoreBaseRepository.getDocumentSnapshot(docRef);
Map<String, Object> firestoreData = documentSnapshot.getData();
if (!Objects.equals(localData, firestoreData)) {
firestoreBaseRepository.setDocument(docRef, localData);
logger.info("Successfully updated {} in Firestore.", documentId);
}
} else {
firestoreBaseRepository.setDocument(docRef, localData);
logger.info("Successfully imported {} to Firestore.", documentId);
}
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.util;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.google.cloud.Timestamp;
import java.io.IOException;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Custom Jackson Deserializer for com.google.cloud.Timestamp.
* Handles deserialization from embedded objects (direct Timestamp instances),
* ISO 8601 strings, and JSON objects with "seconds" and "nanos" fields.
*/
public class FirestoreTimestampDeserializer extends JsonDeserializer<Timestamp> {
private static final Logger logger = LoggerFactory.getLogger(FirestoreTimestampDeserializer.class);
@Override
public Timestamp deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonToken token = p.getCurrentToken();
if (token == JsonToken.VALUE_EMBEDDED_OBJECT) {
// This is the ideal case when ObjectMapper.convertValue gets a direct Timestamp object
Object embedded = p.getEmbeddedObject();
if (embedded instanceof Timestamp) {
logger.debug("FirestoreTimestampDeserializer: Deserializing from embedded Timestamp object: {}", embedded);
return (Timestamp) embedded;
}
} else if (token == JsonToken.VALUE_STRING) {
// Handles cases where the timestamp is represented as an ISO 8601 string
String timestampString = p.getText();
try {
logger.debug("FirestoreTimestampDeserializer: Deserializing from String: {}", timestampString);
return Timestamp.parseTimestamp(timestampString);
} catch (IllegalArgumentException e) {
logger.error("FirestoreTimestampDeserializer: Failed to parse timestamp string: '{}'", timestampString, e);
throw new IOException("Failed to parse timestamp string: " + timestampString, e);
}
} else if (token == JsonToken.START_OBJECT) {
// This is crucial for handling the "Cannot deserialize ... from Object value (token JsonToken.START_OBJECT)" error.
// It assumes the object represents { "seconds": X, "nanos": Y }
logger.debug("FirestoreTimestampDeserializer: Deserializing from JSON object.");
// Suppress the unchecked warning here, as we expect a Map<String, Number>
@SuppressWarnings("unchecked")
Map<String, Number> map = p.readValueAs(Map.class);
if (map != null && map.containsKey("seconds") && map.containsKey("nanos")) {
Number secondsNum = map.get("seconds");
Number nanosNum = map.get("nanos");
if (secondsNum != null && nanosNum != null) {
Long seconds = secondsNum.longValue();
Integer nanos = nanosNum.intValue();
return Timestamp.ofTimeSecondsAndNanos(seconds, nanos);
}
}
logger.error("FirestoreTimestampDeserializer: JSON object missing 'seconds' or 'nanos' fields, or fields are not numbers.");
}
// If none of the above formats match, log an error and delegate to default handling
logger.error("FirestoreTimestampDeserializer: Unexpected token type for Timestamp deserialization. Expected Embedded Object, String, or START_OBJECT. Got: {}", token);
// This will likely re-throw an error indicating inability to deserialize.
return (Timestamp) ctxt.handleUnexpectedToken(Timestamp.class, p);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.util;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.cloud.Timestamp;
import java.io.IOException;
/**
* Custom Jackson Serializer for com.google.cloud.Timestamp.
* This is crucial for ObjectMapper.convertValue to correctly handle Timestamp objects
* when they are encountered in a Map<String, Object> and need to be internally
* serialized before deserialization into a DTO. It converts Timestamp into a
* simple JSON object with "seconds" and "nanos" fields.
*/
public class FirestoreTimestampSerializer extends JsonSerializer<Timestamp> {
@Override
public void serialize(Timestamp value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value == null) {
gen.writeNull();
} else {
// Write Timestamp as a JSON object with seconds and nanos
gen.writeStartObject();
gen.writeNumberField("seconds", value.getSeconds());
gen.writeNumberField("nanos", value.getNanos());
gen.writeEndObject();
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Supplier;
/**
* A utility class to measure and log the execution time of a given operation.
* It uses the Supplier functional interface to wrap the code block to be timed.
*/
public class PerformanceTimer {
private static final Logger logger = LoggerFactory.getLogger(PerformanceTimer.class);
public static <T> T timeExecution(String operationName, Supplier<T> operation) {
long startTime = System.nanoTime();
try {
T result = operation.get();
long endTime = System.nanoTime();
long durationNanos = endTime - startTime;
double durationMillis = durationNanos / 1_000_000.0;
logger.info("Operation '{}' completed in {} ms.", operationName, String.format("%.2f", durationMillis));
return result;
} catch (Exception e) {
long endTime = System.nanoTime();
long durationNanos = endTime - startTime;
double durationMillis = durationNanos / 1_000_000.0;
logger.error("Operation '{}' failed in {} ms: {}", operationName, String.format("%.2f", durationMillis), e.getMessage(), e);
throw new RuntimeException("Error during timed operation: " + operationName, e);
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.util;
import com.google.protobuf.ListValue;
import com.google.protobuf.NullValue;
import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ProtobufUtil {
private static final Logger logger = LoggerFactory.getLogger(ProtobufUtil.class);
/**
* Converts a Java Object to a Protobuf Value.
* Supports primitive types, String, Map, and List.
* Maps will be converted to Protobuf Structs.
* Lists will be converted to Protobuf ListValues.
*/
@SuppressWarnings("rawtypes")
public static Value convertJavaObjectToProtobufValue(Object obj) {
if (obj == null) {
return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
} else if (obj instanceof Boolean) {
return Value.newBuilder().setBoolValue((Boolean) obj).build();
} else if (obj instanceof Integer) {
return Value.newBuilder().setNumberValue(((Integer) obj).doubleValue()).build();
} else if (obj instanceof Long) {
return Value.newBuilder().setNumberValue(((Long) obj).doubleValue()).build();
} else if (obj instanceof Double) {
return Value.newBuilder().setNumberValue((Double) obj).build();
} else if (obj instanceof String) {
return Value.newBuilder().setStringValue((String) obj).build();
} else if (obj instanceof Enum) {
return Value.newBuilder().setStringValue(((Enum) obj).name()).build();
} else if (obj instanceof Map) {
Struct.Builder structBuilder = Struct.newBuilder();
((Map<?, ?>) obj).forEach((key, val) ->
structBuilder.putFields(String.valueOf(key), convertJavaObjectToProtobufValue(val))
);
return Value.newBuilder().setStructValue(structBuilder.build()).build();
} else if (obj instanceof List) {
ListValue.Builder listValueBuilder = ListValue.newBuilder();
((List<?>) obj).forEach(item ->
listValueBuilder.addValues(convertJavaObjectToProtobufValue(item))
);
return Value.newBuilder().setListValue(listValueBuilder.build()).build();
}
logger.warn("Unsupported type for Protobuf conversion: {}. Converting to String.", obj.getClass().getName());
return Value.newBuilder().setStringValue(obj.toString()).build();
}
/**
* Converts a Protobuf Value to a Java Object.
* Supports Null, Boolean, Number, String, Struct (to Map), and ListValue (to List).
*/
public static Object convertProtobufValueToJavaObject(Value protobufValue) {
return switch (protobufValue.getKindCase()) {
case NULL_VALUE -> null;
case BOOL_VALUE -> protobufValue.getBoolValue();
case NUMBER_VALUE -> protobufValue.getNumberValue();
case STRING_VALUE -> protobufValue.getStringValue();
case STRUCT_VALUE -> protobufValue.getStructValue().getFieldsMap().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> convertProtobufValueToJavaObject(entry.getValue()),
(oldValue, newValue) -> oldValue,
LinkedHashMap::new
));
case LIST_VALUE -> protobufValue.getListValue().getValuesList().stream()
.map(ProtobufUtil::convertProtobufValueToJavaObject) // Use static method reference
.collect(Collectors.toList());
default -> {
logger.warn("Unsupported Protobuf Value type: {}. Returning null.", protobufValue.getKindCase());
yield null;
}
};
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.util;
import java.util.UUID;
import java.util.Base64;
/**
* A utility class for generating consistent and formatted session IDs.
* Centralizing ID generation ensures all parts of the application use the same
* logic and format.
*/
public final class SessionIdGenerator {
// Private constructor to prevent instantiation of the utility class.
private SessionIdGenerator() {}
/**
* Generates a standard, version 4 (random) UUID as a string.
* This is the most common and robust approach for general-purpose unique IDs.
* The UUID is a 36-character string with hyphens (e.g., "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").
*
* @return a new, randomly generated UUID string.
*/
public static String generateStandardSessionId() {
return UUID.randomUUID().toString();
}
/**
* Generates a more compact session ID by removing the hyphens from a standard UUID.
* This is useful for contexts where a shorter or URL-friendly ID is needed.
*
* @return a 32-character UUID string without hyphens.
*/
public static String generateCompactSessionId() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* Generates a base64-encoded, URL-safe session ID from a UUID.
* This provides a very compact, yet robust, representation of the UUID.
* It's ideal for use in URLs, cookies, or other contexts where size matters.
*
* @return a new, base64-encoded UUID string.
*/
public static String generateUrlSafeSessionId() {
UUID uuid = UUID.randomUUID();
byte[] uuidBytes = toBytes(uuid);
return Base64.getUrlEncoder().withoutPadding().encodeToString(uuidBytes);
}
// Helper method to convert UUID to a byte array
private static byte[] toBytes(UUID uuid) {
long mostSignificantBits = uuid.getMostSignificantBits();
long leastSignificantBits = uuid.getLeastSignificantBits();
byte[] bytes = new byte[16];
for (int i = 0; i < 8; i++) {
bytes[i] = (byte) (mostSignificantBits >>> (8 * (7 - i)));
}
for (int i = 0; i < 8; i++) {
bytes[8 + i] = (byte) (leastSignificantBits >>> (8 * (7 - i)));
}
return bytes;
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2025 Google. This software is provided as-is, without warranty or representation for any use or purpose.
* Your use of it is subject to your agreement with Google.
*/
package com.example.util;
import java.util.Comparator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.privacy.dlp.v2.Finding;
import com.google.privacy.dlp.v2.InspectContentResponse;
public class TextObfuscator {
private static final Logger logger = LoggerFactory.getLogger(TextObfuscator.class);
public static String obfuscate(InspectContentResponse response, String textToInspect) {
List<Finding> findings = response.getResult().getFindingsList().stream()
.filter(finding -> finding.getLikelihoodValue() > 3)
.sorted(Comparator.comparing(Finding::getLikelihoodValue).reversed())
.peek(finding -> logger.info("InfoType: {} | Likelihood: {}", finding.getInfoType().getName(),
finding.getLikelihoodValue()))
.toList();
for (Finding finding : findings) {
String quote = finding.getQuote();
switch (finding.getInfoType().getName()) {
case "CREDIT_CARD_NUMBER":
textToInspect = textToInspect.replace(quote, "**** **** **** " + getLast4(quote));
break;
case "CREDIT_CARD_EXPIRATION_DATE":
case "FECHA_VENCIMIENTO":
textToInspect = textToInspect.replace(quote, "[FECHA_VENCIMIENTO_TARJETA]");
break;
case "CVV_NUMBER":
case "CVV":
textToInspect = textToInspect.replace(quote, "[CVV]");
break;
case "EMAIL_ADDRESS":
textToInspect = textToInspect.replace(quote, "[CORREO]");
break;
case "PERSON_NAME":
textToInspect = textToInspect.replace(quote, "[NOMBRE]");
break;
case "PHONE_NUMBER":
textToInspect = textToInspect.replace(quote, "[TELEFONO]");
break;
case "DIRECCION":
case "DIR_COLONIA":
case "DIR_DEL_MUN":
case "DIR_INTERIOR":
case "DIR_ESQUINA":
case "DIR_CIUDAD_EDO":
case "DIR_CP":
textToInspect = textToInspect.replace(quote, "[DIRECCION]");
break;
case "CLABE_INTERBANCARIA":
textToInspect = textToInspect.replace(quote, "[CLABE]");
break;
case "CLAVE_RASTREO_SPEI":
textToInspect = textToInspect.replace(quote, "[CLAVE_RASTREO]");
break;
case "NIP":
textToInspect = textToInspect.replace(quote, "[NIP]");
break;
case "SALDO":
textToInspect = textToInspect.replace(quote, "[SALDO]");
break;
case "CUENTA":
textToInspect = textToInspect.replace(quote, "**************" + getLast4(quote));
break;
case "NUM_ACLARACION":
textToInspect = textToInspect.replace(quote, "[NUM_ACLARACION]");
break;
}
}
textToInspect = cleanDireccion(textToInspect);
return textToInspect;
}
private static String getLast4(String quote) {
char[] last4 = new char[4];
String cleanQuote = quote.trim();
cleanQuote = cleanQuote.replace(" ", "");
cleanQuote.getChars(cleanQuote.length() - 4, cleanQuote.length(), last4, 0);
return new String(last4);
}
private static String cleanDireccion(String quote) {
String output = quote.replaceAll("\\[DIRECCION\\](?:(?:,\\s*|\\s+)\\[DIRECCION\\])*", "[DIRECCION]");
return output.trim();
}
}