diff --git a/src/main/java/com/banesco/common/infraestructure/helpers/XmlHelper.java b/src/main/java/com/banesco/common/infraestructure/helpers/XmlHelper.java index b9e7470..c78e673 100644 --- a/src/main/java/com/banesco/common/infraestructure/helpers/XmlHelper.java +++ b/src/main/java/com/banesco/common/infraestructure/helpers/XmlHelper.java @@ -2,8 +2,11 @@ package com.banesco.common.infraestructure.helpers; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -12,10 +15,28 @@ import jakarta.xml.bind.JAXBElement; import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Marshaller; +import javax.xml.namespace.QName; + +/** + * Clase de utilidad para trabajar con XML utilizando JAXB. + */ public class XmlHelper { private static final Logger logger = Logger.getLogger(XmlHelper.class.getName()); - /** + // Cache para las instancias de JAXBContext. + // La creación de JAXBContext es una operación costosa, por lo que cachearlas + // mejora significativamente el rendimiento en aplicaciones que procesan XML frecuentemente. + // Se utiliza ConcurrentHashMap para garantizar la seguridad en entornos con múltiples hilos (thread-safe). + private static final ConcurrentMap, JAXBContext> JAXB_CONTEXT_CACHE = new ConcurrentHashMap<>(); + + /** + * Constructor privado para evitar la instanciación de esta clase de utilidad. + */ + private XmlHelper() { + throw new UnsupportedOperationException("Esta es una clase de utilidad y no puede ser instanciada."); + } + + /** * Imprime el contenido XML de un objeto JAXBElement en el log. * Utiliza JAXB para serializar el objeto a XML y lo formatea para una mejor legibilidad. * @@ -68,4 +89,81 @@ public class XmlHelper { } } + /** + * Crea un JAXBElement y lo convierte a un String XML en un solo paso. + *

+ * Este es un método de conveniencia de alto nivel que simplifica la generación de XML + * al encargarse de la creación del QName y del JAXBElement internamente. + * + * @param namespaceUri El URI del namespace para el elemento raíz XML. + * @param localPart El nombre local del elemento raíz XML (ej: "readCustomerProducts_Rq"). + * @param declaredType El .class del objeto JAXB que se está serializando. + * @param value El objeto de datos JAXB (payload) que se va a convertir. + * @param El tipo del objeto de datos. + * @return Un String con la representación XML formateada del objeto. + * @throws JAXBException Si ocurre algún error durante la serialización. + */ + public static String toXml(String namespaceUri, String localPart, Class declaredType, T value) throws JAXBException { + // 1. Crear el QName (Qualified Name) a partir del namespace y el nombre local. + QName qName = new QName(namespaceUri, localPart); + + // 2. Crear el JAXBElement, que envuelve el objeto de datos con su información de esquema XML. + JAXBElement jaxbElement = new JAXBElement<>(qName, declaredType, value); + + // 3. Reutilizar el método original para hacer la conversión a String. + return toXml(jaxbElement); + } + + /** + * Convierte un objeto JAXBElement genérico en una representación de String con formato XML. + *

+ * Este método es genérico y puede manejar cualquier tipo de objeto contenido dentro de un JAXBElement. + * Utiliza una caché para las instancias de JAXBContext, optimizando el rendimiento al evitar + * la recreación costosa de estos objetos. + * + * @param element El objeto JAXBElement que se va a convertir a XML. No puede ser nulo. + * @param El tipo del valor contenido en el JAXBElement. + * @return Un String que representa el objeto en formato XML, indentado para fácil lectura. + * @throws JAXBException Si ocurre un error durante el proceso de marshalling (conversión de objeto a XML). + * Esto puede suceder si el contexto JAXB no se puede crear para la clase dada + * o si hay un problema durante la serialización. + */ + public static String toXml(JAXBElement element) throws JAXBException { + if (element == null) { + // Es preferible devolver un string vacío o lanzar una excepción si el elemento es nulo. + // Para un helper, lanzar IllegalArgumentException es una buena práctica. + throw new IllegalArgumentException("El JAXBElement de entrada no puede ser nulo."); + } + + // Obtiene la clase del objeto contenido en el JAXBElement (por ejemplo, ReadCustomerProductsRq.class). + Class objectClass = element.getDeclaredType(); + + // Obtiene el JAXBContext de la caché o lo crea si no existe. + // computeIfAbsent es una operación atómica que asegura que el contexto se cree solo una vez por clase. + JAXBContext context = JAXB_CONTEXT_CACHE.computeIfAbsent(objectClass, clazz -> { + try { + return JAXBContext.newInstance(clazz); + } catch (JAXBException e) { + // Envuelve la excepción comprobada en una excepción de runtime para usarla dentro de la lambda. + throw new RuntimeException("Error al crear la instancia de JAXBContext para la clase: " + clazz.getName(), e); + } + }); + + // Crea un Marshaller a partir del contexto. El Marshaller es el que realiza la conversión. + Marshaller marshaller = context.createMarshaller(); + + // Configura la propiedad para que la salida XML esté formateada con indentación y saltos de línea. + // Esto es clave para que el XML sea legible en los logs. + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + + // Se utiliza StringWriter para escribir la salida XML en un String. + StringWriter stringWriter = new StringWriter(); + + // Realiza la serialización del objeto JAXBElement y escribe el resultado en el StringWriter. + marshaller.marshal(element, stringWriter); + + // Devuelve el contenido del StringWriter como un String. + return stringWriter.toString(); + } + } \ No newline at end of file