package er.jasperreports; import java.util.Enumeration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.sf.jasperreports.engine.JRDataSource; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRField; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSLog; import com.webobjects.foundation.NSMutableDictionary; import er.extensions.foundation.ERXAssert; import er.extensions.foundation.ERXProperties; /** * Takes an an NSArray of {@link NSKeyValueCodingAdditions} (think keypaths) objects * **/ public class ERJRFoundationDataSource implements JRDataSource { public final static String REPORT_KEYPATH_SEPARATOR = ERXProperties.stringForKeyWithDefault("er.jasperreports.keyPathSeparator", "_"); private final static String WEBOBJECTS_KEYPATH_SEPARATOR = "."; private static final Logger log = LoggerFactory.getLogger(ERJRFoundationDataSource.class); /** * Sometimes we want might want a reference to the current row, perhaps to pass to a custom function in a scriptlet. * This functionality allows us to specify a field name that returns the current row object itself * rather than an attribute of the current row. * * The default fieldname is "_currentRow", however this can be overridden in system properties using: * er.jasperreports.currentRow.fieldName=myCustomCurrentRowFieldname **/ private static final String FIELD_NAME_FOR_CURRENT_ROW = ERXProperties.stringForKeyWithDefault("er.jasperreports.currentRow.fieldName", "_currentRow"); /** * Private NSArray that contains the database values we wish to use in the report. * */ protected NSKeyValueCodingAdditions currRow; protected Enumeration<? extends NSKeyValueCodingAdditions> e; protected boolean filterNulls = true; protected NSMutableDictionary<String, Object> debugRow; private int processedCount = 0; private int totalCount = 100; //prevent divide by zero public ERJRFoundationDataSource(NSArray<? extends NSKeyValueCodingAdditions> arr) { e = arr.objectEnumerator(); totalCount = arr.count(); } public ERJRFoundationDataSource(Enumeration<? extends NSKeyValueCodingAdditions> enumeration, int itemCount) { ERXAssert.PRE.notNull(enumeration); e = enumeration; totalCount = itemCount; } /* (non-Javadoc) * @see net.sf.jasperreports.engine.JRDataSource#next() */ public boolean next() throws JRException { if (e.hasMoreElements()) { currRow = e.nextElement(); processedCount++; if (log.isInfoEnabled()) { if (debugRow != null) { log.info("DetailRow: {}", debugRow); } //~ if (debugRow != null) debugRow = new NSMutableDictionary<>(); if (currRow instanceof EOEnterpriseObject) { EOEnterpriseObject eo = (EOEnterpriseObject) currRow; debugRow.takeValueForKey(eo.editingContext().globalIDForObject(eo), "_globalID"); } //~ if (currRow instanceof EOEnterpriseObject) } //~ if (log.isDebugEnabled()) return true; } return false; } public boolean getFilterNulls() { return filterNulls; } public void setFilterNulls(boolean filters) { filterNulls = filters; } /* (non-Javadoc) * @see net.sf.jasperreports.engine.JRDataSource#getFieldValue(net.sf.jasperreports.engine.JRField) */ public Object getFieldValue(JRField jrField) throws JRException { // Check for special field names if (jrField.getName().equals(FIELD_NAME_FOR_CURRENT_ROW)) { return currRow; } // Fields in JasperReports become Java variables in .java files when // the report is compiled. Because we are using key-path coding, // we would normally have periods in the field name. However, this makes // Jasper Reports very unhappy, and it will not compile the report. // Therefore, the field names as entered in Jasper Reports use // underscores instead of periods. This also means that to get the value // we need to replace the underscores in the field name to periods so that // key-path coding will work and so that the report will compile. // This has the obvious caveat that the key-path cannot contain an underscore // or this conversion will mess things up // // boolean isStringValueClass = jrField.getValueClass().equals(String.class); Object fieldValue = null; try { fieldValue = (currRow).valueForKeyPath(jrField.getName().replaceAll(REPORT_KEYPATH_SEPARATOR, WEBOBJECTS_KEYPATH_SEPARATOR)); if (log.isDebugEnabled()) log.debug("value = {}; jrField = {}", fieldValue, (jrField == null ? "null" : ERJRUtilities.toString(jrField))); // Allow for implied toString methods if (isStringValueClass) { // At least let's do toString by default when JasperReports expects a String return fieldValue = (fieldValue == null ? null : fieldValue.toString()); } } catch (Exception ex) { NSLog.err.appendln("Error while retrieving value" + jrField.getName().replaceAll("_", ".")); NSLog.err.appendln(ex); } if (filterNulls && fieldValue == null && isStringValueClass) { fieldValue = ""; } if (log.isInfoEnabled()) { debugRow.takeValueForKey(fieldValue == null ? "null" : fieldValue, jrField.getName()); } //~ if (log.isDebugEnabled()) return fieldValue; } public double percentProcessed() { return (double)processedCount / (double)totalCount; } }