AuditOperationDescriber.java
package com.licensis.notaire.audit;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Locale;
/**
* Builds the human-readable Spanish description stored in
* {@code registro_auditoria.detalle_operacion} from controller method
* metadata.
*
* <p>The descriptions are intentionally written in plain, non-technical
* Spanish so end users can read the audit trail without needing any
* implementation knowledge.
*/
public final class AuditOperationDescriber {
private static final String GENERIC_OBJECT = "registro";
private AuditOperationDescriber() {
// Utility class.
}
/**
* Builds the operation description.
*
* @param method the invoked controller method
* @param args the runtime arguments passed to the method
* @param module the resolved business module name
* @return a Spanish sentence describing the user action
*/
public static String describe(Method method, Object[] args, String module) {
String resourceSingular = toSingularLower(module);
String resourcePlural;
if (module == null || module.isBlank()) {
resourcePlural = GENERIC_OBJECT;
} else {
resourcePlural = module.toLowerCase(Locale.ROOT);
}
String methodName = method == null ? "" : method.getName().toLowerCase(Locale.ROOT);
if (methodName.contains("login")) {
return "Inicio de sesión del usuario";
}
if (methodName.contains("logout")) {
return "Cierre de sesión del usuario";
}
HttpVerb verb = detectHttpVerb(method);
String idText = findIdArgument(args);
switch (verb) {
case POST:
return "Creación de nuevo " + resourceSingular;
case PUT:
case PATCH:
return idText != null
? "Actualización de " + resourceSingular + " con ID " + idText
: "Actualización de " + resourceSingular;
case DELETE:
return idText != null
? "Eliminación de " + resourceSingular + " con ID " + idText
: "Eliminación de " + resourceSingular;
case GET:
default:
if (idText != null) {
return "Consulta de " + resourceSingular + " con ID " + idText;
}
return "Consulta de listado de " + resourcePlural;
}
}
private static HttpVerb detectHttpVerb(Method method) {
if (method == null) {
return HttpVerb.GET;
}
for (Annotation annotation : method.getAnnotations()) {
if (annotation instanceof GetMapping) {
return HttpVerb.GET;
}
if (annotation instanceof PostMapping) {
return HttpVerb.POST;
}
if (annotation instanceof PutMapping) {
return HttpVerb.PUT;
}
if (annotation instanceof PatchMapping) {
return HttpVerb.PATCH;
}
if (annotation instanceof DeleteMapping) {
return HttpVerb.DELETE;
}
}
return HttpVerb.GET;
}
private static String findIdArgument(Object[] args) {
if (args == null) {
return null;
}
for (Object arg : args) {
if (arg instanceof Integer || arg instanceof Long || arg instanceof String) {
String value = String.valueOf(arg);
if (!value.isBlank() && !value.equals("null")) {
return value;
}
}
}
return null;
}
private static String toSingularLower(String module) {
if (module == null || module.isBlank()) {
return GENERIC_OBJECT;
}
String lower = module.toLowerCase(Locale.ROOT);
if (lower.endsWith("nes")) {
// gestiones -> gestion
return lower.substring(0, lower.length() - 2);
}
if (lower.endsWith("es")) {
// tramites -> tramite, copies -> copy (we only deal with Spanish here)
return lower.substring(0, lower.length() - 1);
}
if (lower.endsWith("s")) {
// escrituras -> escritura, personas -> persona
return lower.substring(0, lower.length() - 1);
}
return lower;
}
private enum HttpVerb {
GET, POST, PUT, PATCH, DELETE
}
}