package er.jasperreports; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import org.apache.commons.lang.exception.NestableRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eocontrol.EOEditingContext; import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.eocontrol.EOFetchSpecification; import com.webobjects.eocontrol.EOQualifier; import com.webobjects.foundation.NSArray; import er.extensions.appserver.ERXApplication; import er.extensions.concurrency.IERXPercentComplete; import er.extensions.eof.ERXEC; import er.extensions.eof.ERXEOAccessUtilities; import er.extensions.foundation.ERXAssert; /** * A background task class that creates a JasperReports report in the context * of a WebObjects application. Sensible defaults are used. * * A convenient Builder pattern inner class is provided too. * * @author kieran */ public class ERJRFetchSpecificationReportTask implements Callable<File>, IERXPercentComplete { private static final Logger log = LoggerFactory.getLogger(ERJRFetchSpecificationReportTask.class); private File reportFile; private final String frameworkName; private final EOFetchSpecification fetchSpecification; private final String jasperCompiledReportFileName; private Map<String, Object> parameters; // iVar so we can get percentage complete private ERJRFoundationDataSource jrDataSource; public ERJRFetchSpecificationReportTask(EOFetchSpecification fetchSpecification, String jasperCompiledReportFileName) { this(fetchSpecification, jasperCompiledReportFileName, null, null); } public ERJRFetchSpecificationReportTask(EOFetchSpecification fetchSpecification, String jasperCompiledReportFileName, HashMap<String, Object> parameters) { this(fetchSpecification, jasperCompiledReportFileName, null, parameters); } public ERJRFetchSpecificationReportTask(EOFetchSpecification fetchSpecification, String jasperCompiledReportFileName, String frameworkName, HashMap<String, Object> parameters) { ERXAssert.PRE.notNull(fetchSpecification); ERXAssert.PRE.notNull(jasperCompiledReportFileName); // Since it is likely the fetch spec will be used in a different // background thread during report generation, we need to ensure we have a schema-based // qualifier instead of a memory-based qualifier that might have references to EOs // in an RR-locked editing context EOFetchSpecification fs = null; EOEditingContext ec = ERXEC.newEditingContext(); ec.lock(); try { fs = schemaBasedFetchSpecification(fetchSpecification); } catch (Exception e) { throw new RuntimeException("Failed to convert fetchSpecification to schema-based", e); } finally { ec.unlock(); } this.fetchSpecification = fs; this.jasperCompiledReportFileName = jasperCompiledReportFileName; this.frameworkName = frameworkName; this.parameters = parameters; if (this.parameters == null) { this.parameters = new HashMap<>(); } } /** * Callable interface implementation * * @throws Exception */ public File call() throws Exception { ERXApplication._startRequest(); try { return _call(); } catch (Exception e) { log.error("Error in JR task", e); throw e; } finally { // Unlocks any locked editing contexts ERXApplication._endRequest(); } } private File _call() { // If development if (ERXApplication.isDevelopmentModeSafe()) { parameters.put("_isDevelopmentMode", Boolean.TRUE); } else { parameters.put("_isDevelopmentMode", Boolean.FALSE ); } reportFile = null; log.debug("Starting JasperReportTask: {}", this); EOEditingContext ec = ERXEC.newEditingContext(); ec.lock(); try { @SuppressWarnings("unchecked") NSArray<EOEnterpriseObject> objects = ec.objectsWithFetchSpecification(fetchSpecification); jrDataSource = new ERJRFoundationDataSource(objects); if (jasperCompiledReportFileName != null) { reportFile = ERJRUtilities.runCompiledReportToPDFFile(jasperCompiledReportFileName, frameworkName, parameters, jrDataSource); } } catch (Exception e) { throw new NestableRuntimeException(e); } finally { ec.unlock(); } return reportFile; } public File file() { return reportFile; } /* (non-Javadoc) * @see er.extensions.concurrency.IERXPercentComplete#percentComplete() * * Some whacky logic just so the user can be comfortable that we are making some progress. */ public Double percentComplete() { if (jrDataSource == null) { return Double.valueOf(0.1); } else { double percent = 0.1 + jrDataSource.percentProcessed() * 0.8; return Double.valueOf(percent); } } /** * Ensures we don't have references to EOs before using this in the background thread. * * @param fs * @return a clone of the fetchSpecification with the EOQualifier converted to a schema-based qualifier or the same {@link EOFetchSpecification} * if there is no qualifier. */ private EOFetchSpecification schemaBasedFetchSpecification(EOFetchSpecification fetchSpecification) { EOQualifier q = fetchSpecification.qualifier(); if (q != null) { // Clone the fetchSpec fetchSpecification = (EOFetchSpecification) fetchSpecification.clone(); EOEditingContext ec = ERXEC.newEditingContext(); ec.lock(); try { EOEntity entity = ERXEOAccessUtilities.entityMatchingString(ec, fetchSpecification.entityName()); // Convert the qualifier to a schema-based qualifier q = entity.schemaBasedQualifier(q); fetchSpecification.setQualifier(q); } finally { ec.unlock(); } } //~ if (q != null) return fetchSpecification; } }