Groovy: Create a Map with JAX-B Object's specific attributes
I have a sample LEDES XML file https://codebeautify.org/xmlviewer/cbdc79e7
Generted Ledesxmlebilling21 class using JDK's xjc
as below and Ledes21.xsd schema https://codebeautify.org/xmlviewer/cb974a2e
xjc -d src ledes21.xsd
And I am converting the XML to Java object using JAX-B as below
Ledesxmlebilling21 XMLtoObject(InputStream fis) throws Exception {
JAXBContext context = JAXBContext.newInstance(Ledesxmlebilling21.class)
Unmarshaller um = context.createUnmarshaller()
Ledesxmlebilling21 ledes = (Ledesxmlebilling21) um.unmarshal(fis)
return ledes
}
And am trying to create a Map with Invoice object's invId
attribute value as Key and the Values as list of all of Invoice object's nested attribute's fileItemNbr
values as below
['Invoice 31' : [10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33]
'Invoice 32' : [50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73]
]
can someone please help me with it?
Update With The Solution
def extractFileItemNbr(input, List<Integer> extracted) {
input.properties.each { prop, val -> //LedesXmlRuleProcessor.groovy:82)
if (prop in ["metaClass", "class"]) return
if (prop == 'file_item_nbr') {
extracted << val
} else {
extractFileItemNbr(val, extracted) //LedesXmlRuleProcessor.groovy:87)
}
}
}
def extractFileItemNbr(List input, List<Integer> extracted) {
input.each {
extractFileItemNbr(it, extracted)
}
}
void testExtract(Ledesxmlebilling21 ledesxmlebilling21) {
def xmlInvoices = ledesxmlebilling21.firm.client.invoice.flatten()
Map<String, List<Integer>> extracted = [:]
println "invoices -- "+xmlInvoices
for (Invoice invoice : xmlInvoices) {
def accuList = []
extractFileItemNbr(invoice, accuList)
extracted.put(invoice.invId, accuList)
}
println("extracted file_item_nbr "+ extracted)
}
I am getting below exception with the actual Ledesxmlebilling21
object
Disconnected from the target VM, address: '127.0.0.1:59759', transport: 'socket'
2017-12-11 11:04:06 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause
java.lang.StackOverflowError: null
at org.codehaus.groovy.util.AbstractConcurrentMap.getOrPut(AbstractConcurrentMap.java:37)
at org.codehaus.groovy.reflection.GroovyClassValuePreJava7.get(GroovyClassValuePreJava7.java:94)
at org.codehaus.groovy.reflection.ClassInfo.getClassInfo(ClassInfo.java:143)
at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:265)
at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:879)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.createPojoMetaClassGetPropertySite(AbstractCallSite.java:351)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.createGetPropertySite(AbstractCallSite.java:327)
at org.codehaus.groovy.runtime.callsite.GetEffectivePojoPropertySite.acceptGetProperty(GetEffectivePojoPropertySite.java:56)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGetProperty(AbstractCallSite.java:296)
at com.validation.rule.processor.impl.LedesXmlRuleProcessor.extractFileItemNbr(LedesXmlRuleProcessor.groovy:82)
at sun.reflect.GeneratedMethodAccessor82.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:384)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1027)
at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:174)
at com.validation.rule.processor.impl.LedesXmlRuleProcessor$_extractFileItemNbr_closure2.doCall(LedesXmlRuleProcessor.groovy:87)
Solution 1:
My guess would be that the schema will have a cyclic property. Have a look here perhaps: JAXB Mapping cyclic references to XML
Solution 2:
I believe what you want is to recursively iterate over groovy properties.
I skip over the JAX-B parsing since you have that solved already, and use my own classes. The groovy code is not idiomatic and could be shortened
class LedesStatementTest extends GroovyTestCase {
// Recursive function adding file_item_nbr to given list
def extractFileItemNbr(input, List<Integer> extracted) {
input.properties.each { prop, val ->
if (prop in ["metaClass", "class"]) return
if (prop == 'file_item_nbr') {
// println(" $prop : $val")
extracted << val
} else {
extractFileItemNbr(val, extracted)
}
}
}
// deal with list fields
def extractFileItemNbr(List input, List<Integer> extracted) {
input.each {
extractFileItemNbr(it, extracted)
}
}
void testExtract() {
List<LedesInvoice> invoices = [new LedesInvoice([inv_id: 'Invoice 31',
file_item_nbr: 10,
statement: new LedesStatement([file_item_nbr: 11]),
summary: [new LedesTaxSummary([file_item_nbr: 12]), new LedesTaxSummary([file_item_nbr: 13])]]),
new LedesInvoice([inv_id: 'Invoice 32',
file_item_nbr: 50,
statement: new LedesStatement([file_item_nbr: 51]),
summary: [new LedesTaxSummary([file_item_nbr: 52]),
new LedesTaxSummary([file_item_nbr: 53])]])
]
Map<String, List<Integer>> extracted = [:]
for (LedesInvoice invoice : invoices) {
def accuList = []
extractFileItemNbr(invoice, accuList)
extracted.put(invoice.inv_id, accuList)
}
println(extracted)
}
// data classes, similar to Ledes XML, simplified
static class LedesInvoice {
String inv_id;
int file_item_nbr;
LedesStatement statement;
List<LedesTaxSummary> summary;
}
static class LedesStatement {
int file_item_nbr;
}
static class LedesTaxSummary {
int file_item_nbr;
}
}
Output:
[Invoice 31:[12, 13, 11, 10], Invoice 32:[52, 53, 51, 50]]
Update:
In case of cycles, don't just pass around a List<Integer> extracted
of extracted ints, but also a a Set of visited inputs, and in each extract method check if the given input is already in the list.