Az esetek többségében a Jasper Studio használata a vizuális felületen való kattintgatásban kimerül, de speciális esetekben szükség lehet a forráskód mélyebb ismeretére is. Ilyen eset lehet a listák kezelése kapcsán használandó JsonDataSource osztály kódja vagy a hibakezelés is.
A forráskódot a https://sourceforge.net/projects/jasperreports/files/jasperreports oldalról tölthetjük le, ha kiválasztjuk a szükséges verziót (pl: 6.2.2), majd a "jasperreports-x.x.x-project.zip" elemet választva. A letöltött fájl kicsomagolása után importálhatjuk az Eclipse-be Maven projektként.
A JsonDataSource kódjának részlete:
public class JsonDataSource extends JRAbstractTextDataSource implements JsonData {
... @Override public Object getFieldValue(JRField jrField) throws JRException { if (currentJsonNode == null) { return null; } String expression = jrField.getDescription(); if (expression == null || expression.length() == 0) { expression = jrField.getName(); if (expression == null || expression.length() == 0) { return null; } } Object value = null; Class<?> valueClass = jrField.getValueClass(); JsonNode selectedObject = getJsonData(currentJsonNode, expression); if (Object.class != valueClass) { boolean hasValue = selectedObject != null && !selectedObject.isMissingNode() && !selectedObject.isNull(); if (hasValue) { try { if (valueClass.equals(String.class)) { if (selectedObject.isArray()) { value = selectedObject.toString(); } else { value = selectedObject.asText(); } } else if (valueClass.equals(Boolean.class)) { value = selectedObject.booleanValue(); } else if (Number.class.isAssignableFrom(valueClass)) { value = convertStringValue(selectedObject.asText(), valueClass); } else if (Date.class.isAssignableFrom(valueClass)) { value = convertStringValue(selectedObject.asText(), valueClass); } else { throw new JRException(EXCEPTION_MESSAGE_KEY_CANNOT_CONVERT_FIELD_TYPE, new Object[] { jrField.getName(), valueClass.getName() }); } } catch (Exception e) { throw new JRException(EXCEPTION_MESSAGE_KEY_JSON_FIELD_VALUE_NOT_RETRIEVED, new Object[] { jrField.getName(), valueClass.getName() }, e); } } } else { value = selectedObject; } return value; }
... }
List helyett ArrayNode
A fenti kódban látszik, hogy a mező visszatérési értéke csak String, Number, Boolean vagy Date lehet. Ha egy lista elemeit szeretnénk visszakapni java.util.List típusként, akkor csalódnunk kell. Ebben az esetben csak java.lang.Object-ként kérhetjük el, de még így sem cast-olhatjuk List-re, hanem csak JsonNode lehet (selectedObject). Látható az is, hogy ha tömböt String-ként kérünk el, akkor azt ugyan visszakapjuk, de már csak String-ként dolgozhatunk vele (["", "", ""]). A tömb elemeinek számát például nem kérdezhetjük le így (csak csűrés-csavarással: replace + Arrays.asList).
Hogyan tudjuk meg egy adott lista elemeinek számát?
A megoldást a kódban továbbugrálva kaphatjuk meg: getJsonData - goDownPathWithAttribute - goDownPath:
protected JsonNode goDownPath(JsonNode rootNode, String simplePath) { if(rootNode != null && !rootNode.isMissingNode()) { JsonNode result = null; if (rootNode.isObject()) { result = rootNode.path(simplePath); } else if (rootNode.isArray()) { result = mapper.createArrayNode(); for (JsonNode node: rootNode) { JsonNode deeperNode = node.path(simplePath); if (!deeperNode.isMissingNode()) { if (deeperNode.isArray()) { for(JsonNode arrayNode: deeperNode) { ((ArrayNode)result).add(arrayNode); } } else { ((ArrayNode)result).add(deeperNode); } } } } return result; } return rootNode; }
A createArrayNode() visszatérési értéke com.fasterxml.jackson.databind.node.ArrayNode, amelynek már van size() metódusa, így megkaphatjuk az adott lista elemeinek számát is.
Visszatérési érték bővítése List-tel
A fenti getFieldValue() metódust bővíthetjük a következő kódrészlettel:
@Override public Object getFieldValue(JRField jrField) throws JRException { ... if (Object.class != valueClass) { boolean hasValue = selectedObject != null && !selectedObject.isMissingNode() && !selectedObject.isNull(); if (hasValue) { try { if (valueClass.equals(String.class)) { ... } else if (List.class.equals(valueClass)) { List<Object> result = new ArrayList<Object>(); if (!selectedObject.isMissingNode() && selectedObject.isArray()) { value = this.convertToList(selectedObject, result); } else { value = result; } } ... } catch (Exception e) { ... } } } ... return value; }
A listát összepakoló metódus szöveges elemként teszi be a listaelemeket, függetlenül azok típusától (a jelenlegi cél a lista elemszámának kiderítése). A listába ágyazott mélységi listák felderítését a rekurzivitás biztosítja.
private List<Object> convertToList(JsonNode selectedObject, List<Object> result) { for (JsonNode node : selectedObject) { if (!node.isMissingNode() && node.isArray()) { List<Object> innerResult = new ArrayList<Object>(); result.add(innerResult); this.convertToList(node, innerResult); } else { // don't care about type result.add(node.asText()); } } return result; }
A módosított kód használata
A maven install után a target mappában találjuk a "jasperreports-6.2.2.jar" fájlt. A Jasper Studio telepített mappájában keressünk rá a "jasperreports*.jar"-ra és a 6.2.2-es verziót tartalmazzó mappában írjuk felül a legenerált jar fájllal az ottani jart (a mappa helye a frissítésektől függ). Ez a fájl nálam a következő helyen található:
..\configuration\org.eclipse.osgi\506\0\.cp\lib\jasperreports-6.2.2-20160505.150232-15.jar
Az $F{issues} mező class property-jét Object-ről java.util.List-re állítjuk. Így, ha egy jármű bejegyzéseinek számára vagyunk kíváncsiak, akkor a korábban használt kifejezés helyett az $V{issueSize} változóban egy egyszerűbbet használhatunk (nem kell a MissingNode miatti feltétel és nem kell cast-olni sem):
//($F{issues} instanceof ArrayNode) ? ((ArrayNode)$F{issues}).size() : 0; $F{issues}.size()