package com.evolveum.midpoint.report.impl;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRTemplate;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.export.JRCsvExporter;
import net.sf.jasperreports.engine.export.JRRtfExporter;
import net.sf.jasperreports.engine.export.JRXlsExporter;
import net.sf.jasperreports.engine.export.oasis.JROdsExporter;
import net.sf.jasperreports.engine.export.oasis.JROdtExporter;
import net.sf.jasperreports.engine.export.ooxml.JRDocxExporter;
import net.sf.jasperreports.engine.export.ooxml.JRPptxExporter;
import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
import net.sf.jasperreports.engine.xml.JRXmlTemplateLoader;
import net.sf.jasperreports.export.Exporter;
import net.sf.jasperreports.export.ExporterInput;
import net.sf.jasperreports.export.ExporterOutput;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;
import net.sf.jasperreports.export.SimpleWriterExporterOutput;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.report.api.ReportConstants;
import com.evolveum.midpoint.report.api.ReportService;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.schema.util.ReportTypeUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskCategory;
import com.evolveum.midpoint.task.api.TaskHandler;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.task.api.TaskRunResult;
import com.evolveum.midpoint.task.api.TaskRunResult.TaskRunResultStatus;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExportType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportOutputType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportParameterType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SubreportType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.fill.JRAbstractLRUVirtualizer;
import net.sf.jasperreports.engine.fill.JRFileVirtualizer;
import net.sf.jasperreports.engine.fill.JRGzipVirtualizer;
import net.sf.jasperreports.engine.fill.JRSwapFileVirtualizer;
import net.sf.jasperreports.engine.util.JRSwapFile;
import net.sf.jasperreports.governors.MaxPagesGovernor;
import net.sf.jasperreports.governors.TimeoutGovernor;
@Component
public class ReportCreateTaskHandler implements TaskHandler {
public static final String REPORT_CREATE_TASK_URI = "http://midpoint.evolveum.com/xml/ns/public/report/create/handler-3";
private static final Trace LOGGER = TraceManager.getTrace(ReportCreateTaskHandler.class);
private static String PARAMETER_TEMPLATE_STYLES = "baseTemplateStyles";
private static String PARAMETER_REPORT_OID = "reportOid";
private static String PARAMETER_OPERATION_RESULT = "operationResult";
private static String MIDPOINT_HOME = System.getProperty("midpoint.home");
private static String EXPORT_DIR = MIDPOINT_HOME + "export/";
private static String TEMP_DIR = MIDPOINT_HOME + "tmp/";
private static String JASPER_VIRTUALIZER_PKG = "net.sf.jasperreports.engine.fill";
@Autowired
private TaskManager taskManager;
@Autowired
private ModelService modelService;
@Autowired
private PrismContext prismContext;
//
//
//
//
// @Autowired(required=true)
// private ExpressionFactory expressionFactory;
//
// @Autowired(required=true)
// private AuditService auditService;
@Autowired(required = true)
private ReportService reportService;
@Autowired(required = true)
private ObjectResolver objectResolver;
@PostConstruct
private void initialize() {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Registering with taskManager as a handler for " + REPORT_CREATE_TASK_URI);
}
taskManager.registerHandler(REPORT_CREATE_TASK_URI, this);
}
@Override
public TaskRunResult run(Task task) {
// TODO Auto-generated method stub
OperationResult parentResult = task.getResult();
OperationResult result = parentResult.createSubresult(ReportCreateTaskHandler.class.getSimpleName() + ".run");
TaskRunResult runResult = new TaskRunResult();
runResult.setOperationResult(result);
recordProgress(task, 0, result);
long progress = task.getProgress();
JRSwapFile swapFile = null;
JRAbstractLRUVirtualizer virtualizer = null; // http://community.jaspersoft.com/wiki/virtualizers-jasperreports
try {
ReportType parentReport = objectResolver.resolve(task.getObjectRef(), ReportType.class, null, "resolving report", task, result);
Map<String, Object> parameters = completeReport(parentReport, task, result);
JasperReport jasperReport = ReportTypeUtil.loadJasperReport(parentReport);
LOGGER.trace("compile jasper design, create jasper report : {}", jasperReport);
PrismContainer<ReportParameterType> reportParams = (PrismContainer) task.getExtensionItem(ReportConstants.REPORT_PARAMS_PROPERTY_NAME);
if (reportParams != null) {
PrismContainerValue<ReportParameterType> reportParamsValues = reportParams.getValue();
List<Item<?, ?>> items = reportParamsValues.getItems();
if (items != null) {
for (Item item : items) {
PrismProperty pp = (PrismProperty) item;
String paramName = ItemPath.getName(pp.getPath().lastNamed()).getLocalPart();
Object value = null;
if (isSingleValue(paramName, jasperReport.getParameters())) {
value = pp.getRealValues().iterator().next();
} else {
value = pp.getRealValues();
}
parameters.put(paramName, value);
}
}
}
String virtualizerS = parentReport.getVirtualizer();
Integer virtualizerKickOn = parentReport.getVirtualizerKickOn();
Integer maxPages = parentReport.getMaxPages();
Integer timeout = parentReport.getTimeout();
if (maxPages != null && maxPages > 0) {
LOGGER.trace("Setting hardlimit on number of report pages: " + maxPages);
jasperReport.setProperty(MaxPagesGovernor.PROPERTY_MAX_PAGES_ENABLED, Boolean.TRUE.toString());
jasperReport.setProperty(MaxPagesGovernor.PROPERTY_MAX_PAGES, String.valueOf(maxPages));
}
if (timeout != null && timeout > 0) {
LOGGER.trace("Setting timeout on report execution [ms]: " + timeout);
jasperReport.setProperty(TimeoutGovernor.PROPERTY_TIMEOUT_ENABLED, Boolean.TRUE.toString());
jasperReport.setProperty(TimeoutGovernor.PROPERTY_TIMEOUT, String.valueOf(timeout));
}
if (virtualizerS != null && virtualizerKickOn != null && virtualizerKickOn > 0) {
String virtualizerClassName = JASPER_VIRTUALIZER_PKG + "." + virtualizerS;
try {
Class<?> clazz = Class.forName(virtualizerClassName);
if (clazz.equals(JRSwapFileVirtualizer.class)) {
swapFile = new JRSwapFile(TEMP_DIR, 4096, 200);
virtualizer = new JRSwapFileVirtualizer(virtualizerKickOn, swapFile);
} else if (clazz.equals(JRGzipVirtualizer.class)) {
virtualizer = new JRGzipVirtualizer(virtualizerKickOn);
} else if (clazz.equals(JRFileVirtualizer.class)) {
virtualizer = new JRFileVirtualizer(virtualizerKickOn, TEMP_DIR);
} else {
throw new ClassNotFoundException("No support for virtualizer class: " + clazz.getName());
}
LOGGER.trace("Setting explicit Jasper virtualizer: " + virtualizer);
virtualizer.setReadOnly(false);
parameters.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);
} catch (ClassNotFoundException e) {
LOGGER.error("Cannot find Jasper virtualizer: " + e.getMessage());
}
}
LOGGER.trace("All Report parameters : {}", parameters);
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters);
LOGGER.trace("fill report : {}", jasperPrint);
String reportFilePath = generateReport(parentReport, jasperPrint);
LOGGER.trace("generate report : {}", reportFilePath);
saveReportOutputType(reportFilePath, parentReport, task, result);
LOGGER.trace("create report output type : {}", reportFilePath);
result.computeStatus();
} catch (Exception ex) {
LOGGER.error("CreateReport: {}", ex.getMessage(), ex);
result.recordFatalError(ex.getMessage(), ex);
runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR);
runResult.setProgress(progress);
return runResult;
} finally {
if (swapFile != null) {
swapFile.dispose();
}
if (virtualizer != null) {
virtualizer.cleanup();
}
}
// This "run" is finished. But the task goes on ...
runResult.setRunResultStatus(TaskRunResultStatus.FINISHED);
runResult.setProgress(progress);
LOGGER.trace("CreateReportTaskHandler.run stopping");
return runResult;
}
private boolean isSingleValue(String paramName, JRParameter[] jrParams) {
JRParameter param = Arrays.stream(jrParams).filter(p -> p.getName().equals(paramName)).findAny().get();
return !List.class.isAssignableFrom(param.getValueClass());
}
private Map<String, Object> completeReport(ReportType parentReport, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException {
return completeReport(parentReport, null, null, task, result);
}
private Map<String, Object> completeReport(ReportType parentReport, JasperReport subReport, String subReportName, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException {
Map<String, Object> params = new HashMap<String, Object>();
if (subReport != null && StringUtils.isNotBlank(subReportName)) {
params.put(subReportName, subReport);
}
Map<String, Object> parameters = prepareReportParameters(parentReport, result);
params.putAll(parameters);
LOGGER.trace("create report params : {}", parameters);
Map<String, Object> subreportParameters = processSubreportParameters(parentReport, task, result);
params.putAll(subreportParameters);
return params;
}
// private JasperReport loadJasperReport(ReportType reportType) throws SchemaException{
//
// if (reportType.getTemplate() == null) {
// throw new IllegalStateException("Could not create report. No jasper template defined.");
// }
// try {
// byte[] reportTemplate = Base64.decodeBase64(reportType.getTemplate());
//
// InputStream inputStreamJRXML = new ByteArrayInputStream(reportTemplate);
// JasperDesign jasperDesign = JRXmlLoader.load(inputStreamJRXML);
// LOGGER.trace("load jasper design : {}", jasperDesign);
//
// if (reportType.getTemplateStyle() != null){
// JRDesignReportTemplate templateStyle = new JRDesignReportTemplate(new JRDesignExpression("$P{" + PARAMETER_TEMPLATE_STYLES + "}"));
// jasperDesign.addTemplate(templateStyle);
// JRDesignParameter parameter = new JRDesignParameter();
// parameter.setName(PARAMETER_TEMPLATE_STYLES);
// parameter.setValueClass(JRTemplate.class);
// parameter.setForPrompting(false);
// jasperDesign.addParameter(parameter);
// }
// JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
// return jasperReport;
// } catch (JRException ex){
// LOGGER.error("Couldn't create jasper report design {}", ex.getMessage());
// throw new SchemaException(ex.getMessage(), ex.getCause());
// }
//
//
// }
private Map<String, Object> prepareReportParameters(ReportType reportType, OperationResult parentResult) {
Map<String, Object> params = new HashMap<String, Object>();
if (reportType.getTemplateStyle() != null) {
byte[] reportTemplateStyleBase64 = reportType.getTemplateStyle();
byte[] reportTemplateStyle = Base64.decodeBase64(reportTemplateStyleBase64);
try {
LOGGER.trace("Style template string {}", new String(reportTemplateStyle));
InputStream inputStreamJRTX = new ByteArrayInputStream(reportTemplateStyle);
JRTemplate templateStyle = JRXmlTemplateLoader.load(inputStreamJRTX);
params.put(PARAMETER_TEMPLATE_STYLES, templateStyle);
LOGGER.trace("Style template parameter {}", templateStyle);
} catch (Exception ex) {
LOGGER.error("Error create style template parameter {}", ex.getMessage());
throw new SystemException(ex);
}
}
// for our special datasource
params.put(PARAMETER_REPORT_OID, reportType.getOid());
params.put(PARAMETER_OPERATION_RESULT, parentResult);
params.put(ReportService.PARAMETER_REPORT_SERVICE, reportService);
return params;
}
private Map<String, Object> processSubreportParameters(ReportType reportType, Task task, OperationResult subreportResult) throws SchemaException, ObjectNotFoundException {
Map<String, Object> subreportParameters = new HashMap<String, Object>();
for (SubreportType subreport : reportType.getSubreport()) {
Map<String, Object> subreportParam = getSubreportParameters(subreport, task, subreportResult);
LOGGER.trace("create subreport params : {}", subreportParam);
subreportParameters.putAll(subreportParam);
}
return subreportParameters;
}
private Map<String, Object> getSubreportParameters(SubreportType subreportType, Task task, OperationResult subResult)
throws SchemaException, ObjectNotFoundException {
Map<String, Object> reportParams = new HashMap<String, Object>();
ReportType reportType = objectResolver.resolve(subreportType.getReportRef(), ReportType.class, null,
"resolve subreport", task, subResult);
Map<String, Object> parameters = prepareReportParameters(reportType, subResult);
reportParams.putAll(parameters);
JasperReport jasperReport = ReportTypeUtil.loadJasperReport(reportType);
reportParams.put(subreportType.getName(), jasperReport);
Map<String, Object> subReportParams = processSubreportParameters(reportType, task, subResult);
reportParams.putAll(subReportParams);
return reportParams;
}
private void recordProgress(Task task, long progress, OperationResult opResult) {
try {
task.setProgressImmediate(progress, opResult);
} catch (ObjectNotFoundException e) { // these exceptions are of so little probability and harmless, so we just log them and do not report higher
LoggingUtils.logException(LOGGER, "Couldn't record progress to task {}, probably because the task does not exist anymore", e, task);
} catch (SchemaException e) {
LoggingUtils.logException(LOGGER, "Couldn't record progress to task {}, due to unexpected schema exception", e, task);
}
}
private String generateReport(ReportType reportType, JasperPrint jasperPrint) throws JRException {
String destinationFileName = getDestinationFileName(reportType);
switch (reportType.getExport()) {
case PDF:
JasperExportManager.exportReportToPdfFile(jasperPrint, destinationFileName);
break;
case XML:
JasperExportManager.exportReportToXmlFile(jasperPrint, destinationFileName, true);
break;
case XML_EMBED:
JasperExportManager.exportReportToXmlFile(jasperPrint, destinationFileName, true);
break;
case XHTML:
case HTML:
JasperExportManager.exportReportToHtmlFile(jasperPrint, destinationFileName);
break;
case CSV:
JRCsvExporter csvExporter = new JRCsvExporter();
csvExporter.setExporterInput(new SimpleExporterInput(jasperPrint));
csvExporter.setExporterOutput(new SimpleWriterExporterOutput(destinationFileName));
csvExporter.exportReport();
break;
case RTF:
case XLS:
case ODT:
case ODS:
case DOCX:
case XLSX:
case PPTX:
case JXL:
ExporterInput input = new SimpleExporterInput(jasperPrint);
ExporterOutput output = new SimpleOutputStreamExporterOutput(destinationFileName);
Exporter exporter = createExporter(reportType.getExport());
if (exporter == null) {
break;
}
exporter.setExporterInput(input);
exporter.setExporterOutput(output);
exporter.exportReport();
break;
default:
break;
}
return destinationFileName;
}
private Exporter createExporter(ExportType type) {
switch (type) {
case CSV:
return new JRCsvExporter();
case RTF:
return new JRRtfExporter();
case XLS:
return new JRXlsExporter();
case ODT:
return new JROdtExporter();
case ODS:
return new JROdsExporter();
case DOCX:
return new JRDocxExporter();
case XLSX:
return new JRXlsxExporter();
case PPTX:
return new JRPptxExporter();
default:
return null;
}
}
public static String getDateTime() {
Date createDate = new Date(System.currentTimeMillis());
SimpleDateFormat formatDate = new SimpleDateFormat("dd-MM-yyyy hh-mm-ss");
return formatDate.format(createDate);
}
private static String getDestinationFileName(ReportType reportType) {
File exportFolder = new File(EXPORT_DIR);
if (!exportFolder.exists() || !exportFolder.isDirectory()) {
exportFolder.mkdir();
}
String output = EXPORT_DIR + reportType.getName().getOrig() + " " + getDateTime();
if (reportType.getExport() == ExportType.XML_EMBED) {
return output + "_embed.xml";
}
return output + "." + reportType.getExport().value();
}
private void saveReportOutputType(String filePath, ReportType reportType, Task task, OperationResult parentResult) throws Exception {
String fileName = FilenameUtils.getBaseName(filePath);
String reportOutputName = fileName + " - " + reportType.getExport().value();
ReportOutputType reportOutputType = new ReportOutputType();
prismContext.adopt(reportOutputType);
reportOutputType.setFilePath(filePath);
reportOutputType.setReportRef(MiscSchemaUtil.createObjectReference(reportType.getOid(), ReportType.COMPLEX_TYPE));
reportOutputType.setName(new PolyStringType(reportOutputName));
reportOutputType.setDescription(reportType.getDescription() + " - " + reportType.getExport().value());
reportOutputType.setExportType(reportType.getExport());
ObjectDelta<ReportOutputType> objectDelta = null;
Collection<ObjectDelta<? extends ObjectType>> deltas = new ArrayList<ObjectDelta<? extends ObjectType>>();
OperationResult subResult = null;
objectDelta = ObjectDelta.createAddDelta((PrismObject<ReportOutputType>) reportOutputType.asPrismObject());
deltas.add(objectDelta);
subResult = parentResult.createSubresult(ReportCreateTaskHandler.class.getName() + "createRepourtOutput");
modelService.executeChanges(deltas, null, task, subResult);
String outputOid = objectDelta.getOid();
LOGGER.debug("Created report output with OID {}", outputOid);
PrismProperty<String> outputOidProperty = prismContext.getSchemaRegistry().findPropertyDefinitionByElementName(ReportConstants.REPORT_OUTPUT_OID_PROPERTY_NAME).instantiate();
outputOidProperty.setRealValue(outputOid);
task.setExtensionPropertyImmediate(outputOidProperty, subResult);
subResult.computeStatus();
}
@Override
public Long heartbeat(Task task) {
// TODO Auto-generated method stub
return null;
}
@Override
public void refreshStatus(Task task) {
// TODO Auto-generated method stub
}
@Override
public String getCategoryName(Task task) {
return TaskCategory.REPORT;
}
@Override
public List<String> getCategoryNames() {
// TODO Auto-generated method stub
return null;
}
}