java - parse - ¿JAXB puede analizar grandes archivos XML en fragmentos?
parse xml java jaxb (3)
Necesito analizar archivos XML potencialmente grandes, cuyo esquema ya se me proporciona en varios archivos XSD, por lo que el enlace XML es muy favorecido. Me gustaría saber si puedo usar JAXB para analizar el archivo en fragmentos y, de ser así, cómo.
Esto se detalla en la guía del usuario . La descarga de JAXB desde http://jaxb.java.net/ incluye un ejemplo de cómo analizar un fragmento a la vez.
Cuando un documento es grande, generalmente se debe a que contiene partes repetitivas. Quizás sea una orden de compra con una gran lista de líneas de pedido, o tal vez sea un archivo de registro XML con una gran cantidad de entradas de registro.
Este tipo de XML es adecuado para el procesamiento de fragmentos; la idea principal es usar la API de StAX, ejecutar un ciclo y desempatar fragmentos individuales por separado. Su programa actúa en un solo fragmento y luego lo descarta. De esta forma, solo mantendrá como máximo un fragmento en la memoria, lo que le permitirá procesar documentos grandes.
Consulte el ejemplo de desasignación de transmisión y el ejemplo de desasignación parcial en la distribución JAXB RI para obtener más información acerca de cómo hacer esto. El ejemplo de streaming-unmarshalling tiene la ventaja de que puede manejar fragmentos en un nivel de nicho arbitrario, pero requiere que se ocupe del modelo push --- JAXB unmarshaller le "empujará" un nuevo fragmento y tendrá que procesarlo correctamente ahí.
Por el contrario, el ejemplo de desmallamiento parcial funciona en un modelo de extracción (lo que generalmente facilita el procesamiento), pero este enfoque tiene algunas limitaciones en las partes de enlace de datos distintas de la parte repetida.
Debido a que el código es importante, aquí hay un PartialUnmarshaller
que lee un gran archivo en partes. Se puede usar de esa manera, new PartialUnmarshaller<YourClass>(stream, YourClass.class)
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.*;
import java.io.InputStream;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static javax.xml.stream.XMLStreamConstants.*;
public class PartialUnmarshaller<T> {
XMLStreamReader reader;
Class<T> clazz;
Unmarshaller unmarshaller;
public PartialUnmarshaller(InputStream stream, Class<T> clazz) throws XMLStreamException, FactoryConfigurationError, JAXBException {
this.clazz = clazz;
this.unmarshaller = JAXBContext.newInstance(clazz).createUnmarshaller();
this.reader = XMLInputFactory.newInstance().createXMLStreamReader(stream);
/* ignore headers */
skipElements(START_DOCUMENT, DTD);
/* ignore root element */
reader.nextTag();
/* if there''s no tag, ignore root element''s end */
skipElements(END_ELEMENT);
}
public T next() throws XMLStreamException, JAXBException {
if (!hasNext())
throw new NoSuchElementException();
T value = unmarshaller.unmarshal(reader, clazz).getValue();
skipElements(CHARACTERS, END_ELEMENT);
return value;
}
public boolean hasNext() throws XMLStreamException {
return reader.hasNext();
}
public void close() throws XMLStreamException {
reader.close();
}
void skipElements(int... elements) throws XMLStreamException {
int eventType = reader.getEventType();
List<Integer> types = asList(elements);
while (types.contains(eventType))
eventType = reader.next();
}
}
La respuesta de Yves Amsellem es bastante buena, pero solo funciona si todos los elementos son exactamente del mismo tipo. De lo contrario, tu unmarshall emitirá una excepción, pero el lector ya habrá consumido los bytes, por lo que no podrás recuperarlos. En cambio, debemos seguir el consejo de Skaffman y mirar la muestra del frasco JAXB.
Para explicar cómo funciona:
- Crea un unmarshaller JAXB.
- Agregue un oyente al unmarshaller para interceptar los elementos apropiados. Esto se hace mediante la "piratería" de ArrayList para garantizar que los elementos no se almacenen en la memoria después de desasociarse.
- Crea un analizador SAX. Aquí es donde ocurre la transmisión.
- Use el unmarshaller para generar un controlador para el analizador SAX.
- ¡Corriente!
Modifiqué la solución para que sea genérica *. Sin embargo, requirió un poco de reflexión. Si esto no está bien, mire los ejemplos de código en los jar JAXB.
ArrayListAddInterceptor.java
import java.lang.reflect.Field;
import java.util.ArrayList;
public class ArrayListAddInterceptor<T> extends ArrayList<T> {
private static final long serialVersionUID = 1L;
private AddInterceptor<T> interceptor;
public ArrayListAddInterceptor(AddInterceptor<T> interceptor) {
this.interceptor = interceptor;
}
@Override
public boolean add(T t) {
interceptor.intercept(t);
return false;
}
public static interface AddInterceptor<T> {
public void intercept(T t);
}
public static void apply(AddInterceptor<?> interceptor, Object o, String property) {
try {
Field field = o.getClass().getDeclaredField(property);
field.setAccessible(true);
field.set(o, new ArrayListAddInterceptor(interceptor));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Main.java
public class Main {
public void parsePurchaseOrders(AddInterceptor<PurchaseOrder> interceptor, List<File> files) {
try {
// create JAXBContext for the primer.xsd
JAXBContext context = JAXBContext.newInstance("primer");
Unmarshaller unmarshaller = context.createUnmarshaller();
// install the callback on all PurchaseOrders instances
unmarshaller.setListener(new Unmarshaller.Listener() {
public void beforeUnmarshal(Object target, Object parent) {
if (target instanceof PurchaseOrders) {
ArrayListAddInterceptor.apply(interceptor, target, "purchaseOrder");
}
}
});
// create a new XML parser
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
XMLReader reader = factory.newSAXParser().getXMLReader();
reader.setContentHandler(unmarshaller.getUnmarshallerHandler());
for (File file : files) {
reader.parse(new InputSource(new FileInputStream(file)));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
* Este código no ha sido probado y es solo para fines ilustrativos.