/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.module.reporting.web.reports.renderers;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.WebDataBinder;
import org.openmrs.Cohort;
import org.openmrs.api.context.Context;
import org.openmrs.module.htmlwidgets.web.WidgetUtil;
import org.openmrs.module.reporting.cohort.definition.CohortDefinition;
import org.openmrs.module.reporting.cohort.definition.service.CohortDefinitionService;
import org.openmrs.module.reporting.common.ObjectUtil;
import org.openmrs.module.reporting.evaluation.EvaluationContext;
import org.openmrs.module.reporting.evaluation.EvaluationException;
import org.openmrs.module.reporting.evaluation.EvaluationUtil;
import org.openmrs.module.reporting.evaluation.parameter.Mapped;
import org.openmrs.module.reporting.evaluation.parameter.Parameter;
import org.openmrs.module.reporting.propertyeditor.MappedEditor;
import org.openmrs.module.reporting.report.definition.ReportDefinition;
import org.openmrs.module.reporting.report.definition.service.ReportDefinitionService;
import org.openmrs.module.reporting.report.renderer.ReportTemplateRenderer;
import org.openmrs.module.reporting.report.renderer.TextTemplateRenderer;
import org.openmrs.module.reporting.report.renderer.template.TemplateEngineManager;
import org.openmrs.module.reporting.report.ReportData;
import org.openmrs.module.reporting.report.ReportDesign;
import org.openmrs.module.reporting.report.ReportDesignResource;
import org.openmrs.module.reporting.report.service.ReportService;
import org.openmrs.web.WebConstants;
@Controller
public class TextTemplateFormController {
protected static Log log = LogFactory.getLog(DelimitedTextReportRendererFormController.class);
/**
* Default Constructor
*/
public TextTemplateFormController() { }
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Mapped.class, new MappedEditor());
}
/**
* prepares a new form for the a TextTemplateRenderer
* @throws UnsupportedEncodingException
*/
@RequestMapping("/module/reporting/reports/renderers/textTemplateReportRenderer")
public void textTemplateReportRenderer(ModelMap model,
@RequestParam(required=false, value="reportDesignUuid") String reportDesignUuid,
@RequestParam(required=false, value="reportDefinitionUuid") String reportDefinitionUuid,
@RequestParam(required=true, value="type") Class<? extends TextTemplateRenderer> type,
@RequestParam(required=false, value="successUrl") String successUrl) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException, InstantiationException, ClassNotFoundException, UnsupportedEncodingException {
ReportService rs = Context.getService(ReportService.class);
ReportDesign design = null;
if (StringUtils.isNotEmpty(reportDesignUuid)) {
design = rs.getReportDesignByUuid(reportDesignUuid);
} else {
design = new ReportDesign();
design.setRendererType(type);
if (StringUtils.isNotEmpty(reportDefinitionUuid)) {
design.setReportDefinition(Context.getService(ReportDefinitionService.class).getDefinitionByUuid(reportDefinitionUuid));
}
}
ReportDesignResource resource = design.getResourceByName("template");
if (resource != null) {
model.addAttribute("script", new String(resource.getContents(), "UTF-8"));
}
String pathToRemove = "/" + WebConstants.WEBAPP_NAME;
if (StringUtils.isEmpty(successUrl)) {
successUrl = "/module/reporting/reports/manageReportDesigns.form";
}
else if (successUrl.startsWith(pathToRemove)) {
successUrl = successUrl.substring(pathToRemove.length());
}
model.addAttribute("design", design );
model.addAttribute("scriptType", design.getPropertyValue(TextTemplateRenderer.TEMPLATE_TYPE, ""));
model.addAttribute("scriptTypes", TemplateEngineManager.getAvailableTemplateEngineNames());
model.addAttribute("successUrl", successUrl);
model.addAttribute("cancelUrl", successUrl);
}
/**
* Saves report design
* @throws UnsupportedEncodingException
*/
@RequestMapping("/module/reporting/reports/renderers/saveTextTemplateReportRendererDesign")
public String saveTextTemplateReportRendererDesign(ModelMap model, HttpServletRequest request,
@RequestParam(required=false, value="uuid") String uuid,
@RequestParam(required=true, value="name") String name,
@RequestParam(required=false, value="description") String description,
@RequestParam(required=true, value="reportDefinition") String reportDefinitionUuid,
@RequestParam(required=true, value="rendererType") Class<? extends TextTemplateRenderer> rendererType,
@RequestParam(required=true, value="script") String script,
@RequestParam(required=true, value="scriptType") String scriptType,
@RequestParam(required=true, value="successUrl") String successUrl
) throws UnsupportedEncodingException {
ReportService rs = Context.getService(ReportService.class);
ReportDesign design = null;
ReportDesignResource designResource = new ReportDesignResource();
if (StringUtils.isNotEmpty(uuid)) {
design = rs.getReportDesignByUuid(uuid);
}
if (design == null) {
design = new ReportDesign();
design.setRendererType(rendererType);
}
design.setName(name);
design.setDescription(description);
design.setReportDefinition(Context.getService(ReportDefinitionService.class).getDefinitionByUuid(reportDefinitionUuid));
design.getProperties().clear();
design.getResources().clear();
designResource.setReportDesign(design);
designResource.setName("template");
designResource.setContentType("text/html");
designResource.setContents(script.getBytes("UTF-8"));
design.addResource(designResource);
design.addPropertyValue(TextTemplateRenderer.TEMPLATE_TYPE, scriptType);
String pathToRemove = "/" + WebConstants.WEBAPP_NAME;
if (StringUtils.isEmpty(successUrl)) {
successUrl = "/module/reporting/reports/manageReportDesigns.form";
}
else if (successUrl.startsWith(pathToRemove)) {
successUrl = successUrl.substring(pathToRemove.length());
}
design = rs.saveReportDesign(design);
return "redirect:" + successUrl;
}
@ModelAttribute( "expSupportedTypes" )
public Set<String> expSupportedTypes () {
EvaluationContext ec = new EvaluationContext();
Set<String> expSupportedTypes = new HashSet<String>();
for (Object value : ec.getContextValues().values()) {
expSupportedTypes.add(value.getClass().getName());
}
return expSupportedTypes;
}
@ModelAttribute( "userParams" )
public UserParams userParams ( @RequestParam(required=false, value="userEnteredParams") Map<String, Object> userEnteredParams,
@RequestParam(required=false, value="expressions") Map<String, String> expressions,
@RequestParam(required=false, value="baseCohort") Mapped<CohortDefinition> baseCohort ) {
UserParams userParams = new UserParams();
if ( userEnteredParams != null ) {
userParams.setUserEnteredParams(userEnteredParams);
}
if ( expressions != null ) {
userParams.setExpressions(expressions);
}
if ( baseCohort != null ) {
userParams.setBaseCohort(baseCohort);
}
return userParams;
}
@RequestMapping( value="/module/reporting/reports/renderers/previewTextTemplateReportRenderer", method=RequestMethod.GET)
public void initPreviewForm(ModelMap model, HttpServletRequest request,
@RequestParam(required=true, value="reportDefinition") String reportDefinitionUuid,
@RequestParam(required=false, value="uuid") String uuid,
@RequestParam(required=false, value="iframe" ) String iframe,
@RequestParam(required=true, value="script") String script,
@RequestParam(required=true, value="scriptType") String scriptType,
@RequestParam(required=true, value="rendererType") Class<? extends TextTemplateRenderer> rendererType,
@ModelAttribute("userParams") UserParams userParams
) throws UnsupportedEncodingException {
ReportService rs = Context.getService(ReportService.class);
ReportDefinitionService rds = Context.getService(ReportDefinitionService.class);
ReportDefinition reportDefinition = rds.getDefinitionByUuid(reportDefinitionUuid);
ReportDesignResource designResource = new ReportDesignResource();
ReportDesign design = null;
if (StringUtils.isNotEmpty(uuid)) {
design = rs.getReportDesignByUuid(uuid);
}
// if it is a new Report Design then create an incomplete Report Design Object which will be used by the Preview section
if (design == null) {
design = new ReportDesign();
design.setRendererType(rendererType);
design.setName(Context.getMessageSourceService().getMessage("reporting.TextTemplateRenderer.incompleteDesign"));
design.setDescription(new Date().toString());
model.addAttribute("tempDesignUuid", design.getUuid());
}
design.setReportDefinition(Context.getService(ReportDefinitionService.class).getDefinitionByUuid(reportDefinitionUuid));
design.getProperties().clear();
design.getResources().clear();
designResource.setReportDesign(design);
designResource.setName("template");
designResource.setContentType("text/html");
designResource.setContents(script.getBytes("UTF-8"));
design.addResource(designResource);
design.addPropertyValue(TextTemplateRenderer.TEMPLATE_TYPE, scriptType);
design = rs.saveReportDesign(design);
model.addAttribute("iframe", iframe);
model.addAttribute("script", script);
model.addAttribute("scriptType", scriptType);
model.addAttribute("reportDefinition", reportDefinition);
model.addAttribute("rendererType", rendererType);
model.addAttribute("design", design );
}
@SuppressWarnings("unchecked")
@RequestMapping(value="/module/reporting/reports/renderers/previewTextTemplateReportRenderer", method=RequestMethod.POST)
public void previewScript(ModelMap model, HttpServletRequest request,
@RequestParam(required=true, value="reportDefinition") String reportDefinitionUuid,
@RequestParam(required=true, value="uuid") String uuid,
@RequestParam(required=false, value="iframe" ) String iframe,
@RequestParam(required=true, value="script") String script,
@RequestParam(required=true, value="scriptType") String scriptType,
@RequestParam(required=true, value="rendererType") Class<? extends TextTemplateRenderer> rendererType,
@ModelAttribute("userParams") UserParams userParams,
BindingResult bindingResult
) throws EvaluationException, UnsupportedEncodingException, ClassNotFoundException, InstantiationException, IllegalAccessException {
ReportDefinitionService rds = Context.getService(ReportDefinitionService.class);
ReportDefinition reportDefinition = rds.getDefinitionByUuid(reportDefinitionUuid);
// validate parameters
if ( !reportDefinition.getParameters().isEmpty() ) {
Set<String> requiredParams = new HashSet<String>();
for (Parameter parameter : reportDefinition.getParameters()) {
requiredParams.add(parameter.getName());
}
for (Map.Entry<String, Object> e : userParams.getUserEnteredParams().entrySet()) {
if (e.getValue() instanceof Iterable || e.getValue() instanceof Object[]) {
Object iterable = e.getValue();
if (e.getValue() instanceof Object[]) {
iterable = Arrays.asList((Object[]) e.getValue());
}
boolean hasNull = true;
for (Object value : (Iterable<Object>) iterable) {
hasNull = !ObjectUtil.notNull(value);
}
if (!hasNull) {
requiredParams.remove(e.getKey());
}
} else if (ObjectUtil.notNull(e.getValue())) {
requiredParams.remove(e.getKey());
}
}
if (requiredParams.size() > 0 && !userParams.getExpressions().isEmpty() && !userParams.getUserEnteredParams().isEmpty() ) {
for (Iterator<String> iterator = requiredParams.iterator(); iterator.hasNext();) {
String parameterName = (String) iterator.next();
if (StringUtils.isNotEmpty(userParams.getExpressions().get(parameterName))) {
String expression = userParams.getExpressions().get(parameterName);
if (!EvaluationUtil.isExpression(expression)){
bindingResult.rejectValue("expressions[" + parameterName + "]",
"reporting.Report.run.error.invalidParamExpression");
}
} else {
bindingResult.rejectValue("userEnteredParams[" + parameterName + "]", "error.required",
new Object[] { "This parameter" }, "{0} is required");
}
}
}
}
// Try to parse the required parameters into appropriate objects if they are available
if (!bindingResult.hasErrors()) {
Map<String, Object> params = new LinkedHashMap<String, Object>();
if (reportDefinition.getParameters() != null && (userParams.getUserEnteredParams() != null || userParams.getExpressions() != null)) {
for (Parameter parameter : reportDefinition.getParameters()) {
Object value = null;
String expression = null;
if(userParams.getExpressions() != null && ObjectUtil.notNull(userParams.getExpressions().get(parameter.getName())))
expression = userParams.getExpressions().get(parameter.getName());
else
value = userParams.getUserEnteredParams().get(parameter.getName());
if (ObjectUtil.notNull(value) || ObjectUtil.notNull(expression)) {
try {
if (StringUtils.isNotEmpty(expression))
value = expression;
else
value = WidgetUtil.parseInput(value, parameter.getType(), parameter.getCollectionType());
params.put(parameter.getName(), value);
}
catch (Exception ex) {
bindingResult.rejectValue("userEnteredParams[" + parameter.getName() + "]", ex.getMessage());
}
}
}
}
// if no errors were found while parsing, retrieve a report design object and renderer the data
if (!bindingResult.hasErrors()) {
String previewResult = "";
ReportDesign design = null;
ReportDesignResource designResource = new ReportDesignResource();
ReportData result = null;
EvaluationContext ec = new EvaluationContext();
ReportService rs = Context.getService(ReportService.class);
ByteArrayOutputStream out = new ByteArrayOutputStream();
if (StringUtils.isNotEmpty(uuid)) {
design = rs.getReportDesignByUuid(uuid);
design.setRendererType(rendererType);
design.setReportDefinition(reportDefinition);
design.getProperties().clear();
design.getResources().clear();
designResource.setReportDesign(design);
designResource.setName("template");
designResource.setContentType("text/html");
designResource.setContents(script.getBytes("UTF-8"));
design.addResource(designResource);
design.addPropertyValue(TextTemplateRenderer.TEMPLATE_TYPE, scriptType);
if ( userParams.getBaseCohort() != null ) {
try {
Cohort baseCohort = Context.getService(CohortDefinitionService.class).evaluate(userParams.getBaseCohort(), ec);
ec.setBaseCohort(baseCohort);
}
catch (Exception ex) {
throw new EvaluationException("baseCohort", ex);
}
}
if ( params != null ) {
ec.setParameterValues(params);
}
Class<?> rt = Context.loadClass(design.getRendererType().getName());
ReportTemplateRenderer reportRenderer = (ReportTemplateRenderer) rt.newInstance();
Throwable errorDetails = null;
result = rds.evaluate(reportDefinition, ec);
try {
reportRenderer.render( result, design.getUuid(), out);
} catch (Throwable e) {
errorDetails = e;
}
previewResult = (out.toByteArray() != null ? new String(out.toByteArray(), "UTF-8") : "");
StringUtils.deleteWhitespace(previewResult);
model.addAttribute("previewResult", previewResult);
model.addAttribute("design", design);
model.addAttribute("errorDetails", errorDetails);
}
}
}
model.addAttribute("iframe", iframe);
model.addAttribute("script", script);
model.addAttribute("scriptType", scriptType);
model.addAttribute("reportDefinition", reportDefinition);
model.addAttribute("rendererType", rendererType);
model.addAttribute("errors", bindingResult);
}
public class UserParams {
private Map<String, Object> userEnteredParams;
private Map<String, String> expressions;
private Mapped<CohortDefinition> baseCohort;
public UserParams() {
userEnteredParams = new LinkedHashMap<String, Object>();
expressions = new HashMap<String ,String>();
}
public Map<String, Object> getUserEnteredParams() {
return userEnteredParams;
}
public void setUserEnteredParams(Map<String, Object> userEnteredParams) {
this.userEnteredParams = userEnteredParams;
}
public Map<String, String> getExpressions() {
return expressions;
}
public void setExpressions(Map<String, String> expressions) {
this.expressions = expressions;
}
public Mapped<CohortDefinition> getBaseCohort() {
return baseCohort;
}
public void setBaseCohort(Mapped<CohortDefinition> baseCohort) {
this.baseCohort = baseCohort;
}
}
}