How to marshall java.lang.Exception using JAXB Marshaller? - java

I am trying to marshall java.lang.Exception, but without success. Here's my code -
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
public class JAXBTester
{
public static void main(String[] args) throws Exception
{
TestReport report = new TestReport();
report.setReportLog("Tests successful.");
File file = new File("TestReport.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(TestReport.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(report, file);
}
}
This is the class I want to marshall -
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class TestReport
{
private String reportLog;
private Exception exception;
#XmlElement
public void setReportLog(String reportLog) { this.reportLog = reportLog; }
public String getReportLog() { return reportLog; }
#XmlElement
public void setException(Exception exception) { this.exception = exception; }
public Exception getException() { return exception; }
}
I get the following exception -
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
java.lang.StackTraceElement does not have a no-arg default constructor.
this problem is related to the following location:
at java.lang.StackTraceElement
at public java.lang.StackTraceElement[] java.lang.Throwable.getStackTrace()
at java.lang.Throwable
at java.lang.Exception
at public java.lang.Exception TestReport.getException()
at TestReport
at com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:91)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:451)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:283)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:126)
at com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1142)
at com.sun.xml.internal.bind.v2.ContextFactory.createContext(ContextFactory.java:130)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:248)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:235)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:445)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:637)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:584)
at JAXBTester.main(JAXBTester.java:14)
This is because I am trying to marshall java.lang.Exception. How to solve this problem?

There are other ways of doing it, such as the one referenced by the commenter. But here's another way...
The Throwable class in Java (superclass of Exception) is serializable in the sense of java.io.Serializable. This means that you can write it to a byte stream and then recompose it later from those bytes. (It's possible that your application might have poorly-written non-serializable subclasses of Throwable. If that's the case, the following won't work.)
So one way to deal with this is to write a custom adapter that serializes the Throwable (or Exception) to bytes. And in the XML, you see the hex for those bytes. Then on the receiving end you can un-serialize and then work with (an exact replica of) the Throwable you started with.
The bad part about this way is that the Exception is not human-readable inside the XML. The good part is that it's really simple. On your TestReport class, put this annotation on your Exception getter:
#XmlJavaTypeAdapter(ThrowableAdapter.class)
public Exception getException() { return exception; }
public void setException(Exception exception) { this.exception = exception; }
And then add this adapter class to your project:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class ThrowableAdapter extends XmlAdapter<String, Throwable> {
private HexBinaryAdapter hexAdapter = new HexBinaryAdapter();
#Override
public String marshal(Throwable v) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(v);
oos.close();
byte[] serializedBytes = baos.toByteArray();
return hexAdapter.marshal(serializedBytes);
}
#Override
public Throwable unmarshal(String v) throws Exception {
byte[] serializedBytes = hexAdapter.unmarshal(v);
ByteArrayInputStream bais = new ByteArrayInputStream(serializedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
Throwable result = (Throwable) ois.readObject();
return result;
}
}
Then your XML will contain an element like this:
<exception>AED...</exception>
except in instead of ... you'll see a huge hex string. When it's un-marshalled on the other side, it'll be just like the original.

Do you use JRE 1.7? I do not get this exception if I use JAXB version that is distributed with the JRE. However, if I include explicitly JAXB v. 2.1.7, I run into this problem. Therefore, I'd recommend getting rid of all the jaxb instances from your classpath and use the one that is included in the Java runtime.
JRE 6 seems to use 2.1.x, while JRE 7 2.2.x, which probably handles the change in Throwable implementation correctly.
The JAXB version included in your JDK can be found by running
<JDK_HOME>/bin/xjc -version

Related

Byte-buddy: generate classes with cyclic types

I'm trying to generate classes with a cyclic class dependency, similar to this question: Byte Buddy - Handling cyclic references in generated classes
As a minimal example, the kind of classes I want to generate have dependencies like this:
//class A depends on class B, and vice-versa
final class A { B theB; }
final class B { A theA; }
The accepted answer in the link above did not provide enough information for me to get this to work. This is what I tried:
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.jar.asm.Opcodes;
public class ByteBuddyHello {
public static void main(String[] args) {
try {
final ByteBuddy bb = new ByteBuddy();
final TypeDescription.Latent typeDescrA = new TypeDescription.Latent("A", 0, null, null);
final TypeDescription.Latent typeDescrB = new TypeDescription.Latent("B", 0, null, null);
final DynamicType.Unloaded<Object> madeA = bb
.subclass(Object.class)
.name("A")
.defineField("theB", typeDescrB, Opcodes.ACC_PUBLIC)
.make(); // exception thrown here!
final DynamicType.Unloaded<Object> madeB = bb.subclass(Object.class)
.name("B")
.defineField("theA", typeDescrA, Opcodes.ACC_PUBLIC)
.make();
Object a = madeA
.include(madeB)
.load(ByteBuddyHello.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded().newInstance();
System.out.println(a.toString());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
When I run this, I get Exception in thread "main" java.lang.IllegalStateException: Cannot resolve declared type of a latent type description: class B in the marked line.
The answer to the referenced question above says to "Make sure that you do not load a type before defining the latent type properly", and I'm guessing that this might be my issue. I don't know how to define a latent type though :-(
Edit: made classes A and B above final (as this would be the ideal solution)
Edit: Add stack trace
Exception in thread "main" java.lang.IllegalStateException: Cannot resolve declared type of a latent type description: class B
at net.bytebuddy.description.type.TypeDescription$Latent.getDeclaringType(TypeDescription.java:7613)
at net.bytebuddy.description.type.TypeDescription$AbstractBase.getSegmentCount(TypeDescription.java:6833)
at net.bytebuddy.implementation.attribute.AnnotationAppender$ForTypeAnnotations.onNonGenericType(AnnotationAppender.java:617)
at net.bytebuddy.implementation.attribute.AnnotationAppender$ForTypeAnnotations.onNonGenericType(AnnotationAppender.java:333)
at net.bytebuddy.description.type.TypeDescription$Generic$OfNonGenericType.accept(TypeDescription.java:3364)
at net.bytebuddy.implementation.attribute.FieldAttributeAppender$ForInstrumentedField.apply(FieldAttributeAppender.java:122)
at net.bytebuddy.dynamic.scaffold.TypeWriter$FieldPool$Record$ForExplicitField.apply(TypeWriter.java:270)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation.create(TypeWriter.java:4156)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1633)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:174)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:155)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:2559)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:2661)
at my.package.playground.ByteBuddyHello.main(ByteBuddyHello.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
This is a bug in Byte Buddy; the resolver for type annotations needs to know the depth of any type description and therefore resolves the type path of any type description. For latent types, this depth is always 0 but the default implementation applies a more complex solution.
This will be fixed in the next release. In the meantime, subclass the latent type description and override the method to return 0.
I decided not to change the TypeDescription.Latent type but rather make the InstrumentedType.Default implementation more accessible. Use the latter type which allows you to define features of the cyclic type which will be visible to any user. This way, you can for example specify existing fields and methods if you want to define Implementations that validate against this feature.
This is a working solution, following the accepted answer:
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.modifier.ModifierContributor;
import net.bytebuddy.description.modifier.TypeManifestation;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.jar.asm.Opcodes;
import java.io.File;
import java.io.IOException;
import java.util.List;
class TypeDescrFix extends TypeDescription.Latent {
TypeDescrFix(final String name, final int modifiers, final Generic superClass, final List<? extends Generic> interfaces) {
super(name, modifiers, superClass, interfaces);
}
#Override
public int getSegmentCount() {
return 0;
}
#Override
public AnnotationList getDeclaredAnnotations() {
return new AnnotationList.Empty();
}
}
public class ByteBuddyHello {
public static void main(String[] args) {
try {
final ByteBuddy bb = new ByteBuddy();
final TypeDescription.Latent typeDescrA = new TypeDescrFix("A", 0, null, null);
final TypeDescription.Latent typeDescrB = new TypeDescrFix("B", 0, null, null);
final DynamicType.Unloaded<Object> madeA = bb
.subclass(Object.class)
.name("A")
.modifiers(ModifierContributor.Resolver.of(Visibility.PUBLIC, TypeManifestation.FINAL).resolve())
.defineField("theB", typeDescrB, Opcodes.ACC_PUBLIC)
.make();
final DynamicType.Unloaded<Object> madeB = bb.subclass(Object.class)
.name("B")
.modifiers(ModifierContributor.Resolver.of(Visibility.PUBLIC, TypeManifestation.FINAL).resolve())
.defineField("theA", typeDescrA, Opcodes.ACC_PUBLIC)
.make();
Object a = madeA
.include(madeB)
.load(ByteBuddyHello.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded().newInstance();
System.out.println(a.toString());
final File folder = new File("/tmp/ByteBuddyHello");
madeA.saveIn(folder);
madeB.saveIn(folder);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

OutputStream class is used for writing into files. How is it possible?

The below code is quoted from : http://examples.javacodegeeks.com/core-java/io/fileoutputstream/java-io-fileoutputstream-example/
Although the OutputStream is an abstract method, at the below code, OutputStream object is used for writing into the file.
Files.newOutputStream(filepath)) returns OutputStream. Then, the type of out is OutputStream, and out references OutputStream.
How can this be possible while OutputStream is an abstract class?
package com.javacodegeeks.core.io.outputstream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class FileOutputStreamExample {
private static final String OUTPUT_FILE = "C:\\Users\\nikos\\Desktop\\TestFiles\\testFile.txt";
public static void main(String[] args) {
String content = "Hello Java Code Geeks";
byte[] bytes = content.getBytes();
Path filepath = Paths.get(OUTPUT_FILE);
try ( OutputStream out = Files.newOutputStream(filepath)) {
out.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Just because the declared type is OutputStream, that doesn't mean the implementation doesn't create an instance of a concrete subclass of OutputStream. You see this all the time with interfaces. For example:
public List<String> getList() {
return new ArrayList<String>();
}
Basically you need to distinguish between the API exposed (which uses the abstract class) and the implementation (which can choose to use any subclass it wants).
So Files.newOutputStream could be implemented as:
public static OutputStream newOutputStream(Path path)
throws IOException {
return new FileOutputStream(path.toFile());
}

Java - System.setOut does not save an exception message

It seems that System.setOut() does not work in this test case.
Here are problem description.
test0 executes System.setOut(new PrintStream(byteBuffer)) so that it stores standard output.
test0 invokes AddChild1_wy_v1.main.
In the AddChild1_wy_v1.main, xml.addChild(null) generates an exception message.
The exception message should be stored in byteBuffer, but it seems it wasn't.. JVM stops running the test case once the exception message pops up. And the remaining code after AddChild1_wy_v1.main are not executed.
Is there a way for jvm to execute the remaining code in test0?
NanoAddChild1_wy_v1Tests.java
package tests;
import junit.framework.TestCase;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import net.n3.nanoxml.*;
public class NanoAddChild1_wy_v1Tests extends TestCase {
public void test0() throws Exception { //addchild1.out
String result;
ByteArrayOutputStream byteBuffer;
byteBuffer = new ByteArrayOutputStream();
System.setOut(new PrintStream(byteBuffer));
AddChild1_wy_v1.main(new String[] {"/home/junghyun/Dev/nanoxml/inputs/simple.xml"});
result = new String(byteBuffer.toByteArray());
assertEquals(result, "Exception in thread \"main\" java.lang.IllegalArgumentException: child must not be null\n\tat net.n3.nanoxml.XMLElement.addChild(XMLElement.java:165)\n\tat AddChild1_wy_v1.main(AddChild1_wy_v1.java:47)\n");
}
}
AddChild1_wy_v1.java
package tests;
import net.n3.nanoxml.IXMLParser;
import net.n3.nanoxml.IXMLReader;
import net.n3.nanoxml.StdXMLReader;
import net.n3.nanoxml.XMLElement;
import net.n3.nanoxml.XMLParserFactory;
import net.n3.nanoxml.XMLWriter;
public class AddChild1_wy_v1
{
public static void main(String args[])
throws Exception
{
if (args.length == 0) {
System.err.println("Usage: java DumpXML file.xml");
Runtime.getRuntime().exit(1);
}
IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
IXMLReader reader = StdXMLReader.fileReader(args[0]);
parser.setReader(reader);
XMLElement xml = (XMLElement) parser.parse();
xml.addChild (null);
(new XMLWriter(System.out)).write(xml);
}
}
There's 3 default streams:
System.in : InputStream
System.out :PrintStream
System.err :PrintStream
So to set each one there is 3 methods:
public static void setIn(InputStream in) {...}
public static void setOut(PrintStream out) {...}
public static void setErr(PrintStream err) {...}
To set System.err you must use System.setErr(yourStream);
For another question: you just need to use
try {
//throwing exception
} catch (Exception e) {
//act on exception
}
It seems to me that you never write that Exception at all.
You just throw it upwards. Try catch it and have ex.printStackTrace();
Also that will go to standard error, unless you specifically say otherwise.
As by your request I will leave the test0 method unaltered, you can use it the way it is.
in AddChild1_wy_v1.java:
public class AddChild1_wy_v1 {
public static void main(String args[]) // note that I don't throw the Exception.
{
try {
if (args.length == 0) {
System.err.println("Usage: java DumpXML file.xml");
Runtime.getRuntime().exit(1);
}
IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
IXMLReader reader = StdXMLReader.fileReader(args[0]);
parser.setReader(reader);
XMLElement xml = (XMLElement) parser.parse();
xml.addChild (null);
(new XMLWriter(System.out)).write(xml);
} catch (Exception any) {
any.printStackTrace(System.out); // note that I send the Stack Trace to standard out here.
}
}
}
Wrap your method call in a try-catch to continue past the exception:
try {
AddChild1_wy_v1.main(...);
} catch(Throwable t) {
t.printStackTrace();
}
// the rest of your code will execute
Exceptions are printed to standard error, not standard output. Try System.setErr.
Never post images of your code.

create multiple files do loop with java and store in a drive , how to?

my first java program ..
so I'm trying to create a file and store in my pc using java
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class createfile {
public static void main(String[] args) throws IOException {
int[] numbers = {1,2,3};
for (int item : numbers) {
String key = "file" + item;
File file = File.createTempFile("c:\\",key,".txt");
Writer writer = new OutputStreamWriter(new FileOutputStream(file));
writer.write("abcdefghijklmnopqrstuvwxyz\n");
writer.write("01234567890112345678901234\n");
writer.write("!##$%^&*()-=[]{};':',.<>/?\n");
writer.write("01234567890112345678901234\n");
writer.write("abcdefghijklmnopqrstuvwxyz\n");
writer.close();
}
return file;
}
}
what am I missing here .. I coudln't figured it out. everything seem to follow along the book.
Thanks
===========update ===========
after I took of
- return file ;
- throws IOException ;
- and change to File file = File.createTempFile(key,".txt",new File("c:\\"));
I still get this error
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
Unhandled exception type IOException
Unhandled exception type FileNotFoundException
Unhandled exception type IOException
Unhandled exception type IOException
Unhandled exception type IOException
Unhandled exception type IOException
Unhandled exception type IOException
Unhandled exception type IOException
you have some mistakes in java syntax:
When you declare method as void (here public static void main(....)) it means that method has no return value - so line "return file;" not needed here.
Use use wrong signature (wrong parameters types in File.createTempFile function.
Possible usages are:
createTempFile(String prefix, String suffix)
createTempFile(String prefix, String suffix, File directory)
For additional information about File class use this link: http://docs.oracle.com/javase/6/docs/api/java/io/File.html
Following possible version of working code:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class createfile
{
public static void main(String[] args) throws IOException
{
int[] numbers = {1,2,3};
for (int item : numbers)
{
String key = "file" + item;
File file = File.createTempFile(key,".txt",new File("c:\\"));
Writer writer = new OutputStreamWriter(new FileOutputStream(file));
writer.write("abcdefghijklmnopqrstuvwxyz\n");
writer.write("01234567890112345678901234\n");
writer.write("!##$%^&*()-=[]{};':',.<>/?\n");
writer.write("01234567890112345678901234\n");
writer.write("abcdefghijklmnopqrstuvwxyz\n");
writer.close();
}
}
}
You can also see another sample how to write text to file: http://www.homeandlearn.co.uk/java/write_to_textfile.html. This link use NetBeans as Java Tool for writing code. I strongly suggest to use some IDE (Eclipse,NetBeans) to write code in java.It will mark your compile mistakes and will suggest corrections.
NetBeans site:https://netbeans.org/
Welcome to Java world
public static void main(String[] args) throws IOException { doesn't return anything, so the return file statement is not required
File.createTempFile either takes String, String, File or String, String so File file = File.createTempFile("c:\\", key, ".txt"); won't compile.
Something like, File file = File.createTempFile(key, ".txt", new File("c:\\")); might be a better idea, but is depended on what you want to achieve.
The JavaDocs state that the prefix must be at least three characters long, so you'll need to pad the key value to meet these requirements.
You MAY find using something like...
File file = new File("C:\\" + key + ".txt");
more managable...

Can JAXB parse large XML files in chunks

I need to parse potentially large XML files, of which the schema is already provided to me in several XSD files, so XML binding is highly favored. I'd like to know if I can use JAXB to parse the file in chunks and if so, how.
Because code matters, here is a PartialUnmarshaller who reads a big file into chunks. It can be used that way 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();
}
}
This is detailed in the user guide. The JAXB download from http://jaxb.java.net/ includes an example of how to parse one chunk at a time.
When a document is large, it's
usually because there's repetitive
parts in it. Perhaps it's a purchase
order with a large list of line items,
or perhaps it's an XML log file with
large number of log entries.
This kind of XML is suitable for
chunk-processing; the main idea is to
use the StAX API, run a loop, and
unmarshal individual chunks
separately. Your program acts on a
single chunk, and then throws it away.
In this way, you'll be only keeping at
most one chunk in memory, which allows
you to process large documents.
See the streaming-unmarshalling
example and the partial-unmarshalling
example in the JAXB RI distribution
for more about how to do this. The
streaming-unmarshalling example has an
advantage that it can handle chunks at
arbitrary nest level, yet it requires
you to deal with the push model ---
JAXB unmarshaller will "push" new
chunk to you and you'll need to
process them right there.
In contrast, the partial-unmarshalling
example works in a pull model (which
usually makes the processing easier),
but this approach has some limitations
in databinding portions other than the
repeated part.
Yves Amsellem's answer is pretty good, but only works if all elements are of exactly the same type. Otherwise your unmarshall will throw an exception, but the reader will have already consumed the bytes, so you would be unable to recover. Instead, we should follow Skaffman's advice and look at the sample from the JAXB jar.
To explain how it works:
Create a JAXB unmarshaller.
Add a listener to the unmarshaller for intercepting the appropriate elements. This is done by "hacking" the ArrayList to ensure the elements are not stored in memory after being unmarshalled.
Create a SAX parser. This is where the streaming happens.
Use the unmarshaller to generate a handler for the SAX parser.
Stream!
I modified the solution to be generic*. However, it required some reflection. If this is not OK, please look at the code samples in the JAXB jars.
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);
}
}
}
*This code has not been tested and is for illustrative purposes only.
I wrote a small library (available on Maven Central) to help to read big XML files and process them by chunks. Please note it can only be applied for files with a unique container having a list of data (even from different types). In other words, your file has to follow the structure:
<container>
<type1>...</type1>
<type2>...</type2>
<type1>...</type1>
...
</container>
Here is an example where Type1, Type2, ... are the JAXB representation of the repeating data in the file:
try (StreamingUnmarshaller unmarshaller = new StreamingUnmarshaller(Type1.class, Type2.class, ...)) {
unmarshaller.open(new FileInputStream(fileName));
unmarshaller.iterate((type, element) -> doWhatYouWant(element));
}
You can find more information with detailed examples on the GitHub page of the library.

Categories

Resources