168 lines
8.7 KiB
Java
168 lines
8.7 KiB
Java
package com.banesco.common.infraestructure.helpers;
|
|
|
|
import jakarta.xml.bind.JAXBContext;
|
|
import jakarta.xml.bind.JAXBElement;
|
|
import jakarta.xml.bind.JAXBException;
|
|
import jakarta.xml.bind.Marshaller;
|
|
|
|
import javax.xml.namespace.QName;
|
|
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;
|
|
|
|
/**
|
|
* 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<Class<?>, 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.
|
|
*
|
|
* @param element El JAXBElement que contiene el objeto a serializar a XML.
|
|
* @param traceId requestID.
|
|
* @throws JAXBException Si ocurre un error durante la serialización JAXB.
|
|
*/
|
|
public static void printXml(JAXBElement<?> element, String traceId) throws JAXBException {
|
|
// Verifica si el elemento es nulo para evitar NullPointerException
|
|
Objects.requireNonNull(element, "El JAXBElement no puede ser nulo.");
|
|
|
|
// Obtiene el tipo declarado del elemento para crear el contexto JAXB.
|
|
// Esto asegura que JAXB conozca la clase raíz del objeto que se va a serializar.
|
|
Class<?> declaredType = element.getDeclaredType();
|
|
|
|
// Crea una instancia de JAXBContext para el tipo declarado.
|
|
// JAXBContext es costoso de crear, en aplicaciones de alto rendimiento
|
|
// se suele cachear o reutilizar. Para este caso de log, es aceptable crearlo aquí.
|
|
JAXBContext ctx = JAXBContext.newInstance(declaredType);
|
|
|
|
// Crea un Marshaller, que es responsable de convertir objetos Java a XML.
|
|
Marshaller marshaller = ctx.createMarshaller();
|
|
|
|
// Configura la propiedad para que el XML de salida esté formateado (indentado).
|
|
// Esto hace que el XML sea mucho más legible en los logs.
|
|
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
|
|
|
|
// Configura la propiedad para que el XML no incluya la declaración XML (<?xml version="1.0" encoding="UTF-8" standalone="yes"?>).
|
|
// Esto puede ser útil si solo quieres el contenido XML puro en el log,
|
|
// pero a menudo es mejor dejarlo si quieres un XML completo y válido.
|
|
// marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
|
|
|
|
// Usa ByteArrayOutputStream para capturar la salida XML en memoria.
|
|
// Esto es necesario porque Marshaller escribe en un OutputStream,
|
|
// y necesitamos obtener el String resultante para el log.
|
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
|
// Realiza la serialización del JAXBElement al OutputStream.
|
|
marshaller.marshal(element, outputStream);
|
|
|
|
// Convierte el contenido del ByteArrayOutputStream a un String.
|
|
// Es crucial especificar la codificación (UTF-8 es lo más común)
|
|
// para asegurar que los caracteres se interpreten correctamente.
|
|
String xmlString = outputStream.toString(StandardCharsets.UTF_8.name());
|
|
|
|
// Loggea el XML generado.
|
|
logger.log(Level.INFO, traceId + " - XML del servicio SOAP:\n{0}", xmlString);
|
|
} catch (IOException e) {
|
|
// Manejo de excepción si hay un problema con el ByteArrayOutputStream (poco probable en este caso).
|
|
logger.log(Level.SEVERE, "Error al procesar el OutputStream para el XML", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Crea un JAXBElement y lo convierte a un String XML en un solo paso.
|
|
* <p>
|
|
* 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 <T> 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 <T> String toXml(String namespaceUri, String localPart, Class<T> 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<T> 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.
|
|
* <p>
|
|
* 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 <T> 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 <T> String toXml(JAXBElement<T> 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();
|
|
}
|
|
|
|
} |