/** * 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; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.common.ObjectUtil; import org.openmrs.module.reporting.evaluation.EvaluationContext; 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.ReportRequest; import org.openmrs.module.reporting.report.ReportRequest.Priority; import org.openmrs.module.reporting.report.definition.ReportDefinition; import org.openmrs.module.reporting.report.definition.service.ReportDefinitionService; import org.openmrs.module.reporting.report.renderer.RenderingMode; import org.openmrs.module.reporting.report.renderer.ReportRenderer; import org.openmrs.module.reporting.report.service.ReportService; import org.openmrs.util.OpenmrsUtil; import org.quartz.CronExpression; import org.springframework.util.StringUtils; import org.springframework.validation.BindException; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.BaseCommandController; import org.springframework.web.servlet.mvc.SimpleFormController; import org.springframework.web.servlet.view.RedirectView; /** * This controller runs a report (which must be passed in with the reportId parameter) after * allowing the user to enter parameters (if any) and to choose a ReportRenderer. If the chosen * ReportRenderer is a WebReportRenderer, then the report data is placed in the session and this * page redirects to the WebReportRenderer's specified URL. Otherwise the renderer writes to this * form's response. */ public class RunReportFormController extends SimpleFormController implements Validator { private transient Log log = LogFactory.getLog(this.getClass()); /** * @see BaseCommandController#initBinder(HttpServletRequest, ServletRequestDataBinder) */ protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception { super.initBinder(request, binder); binder.registerCustomEditor(Mapped.class, new MappedEditor()); } @SuppressWarnings("rawtypes") public boolean supports(Class c) { return c == CommandObject.class; } @Override public void validate(Object commandObject, Errors errors) { CommandObject command = (CommandObject) commandObject; ValidationUtils.rejectIfEmpty(errors, "reportDefinition", "reporting.Report.run.error.missingReportID"); if (command.getReportDefinition() != null) { ReportDefinition reportDefinition = command.getReportDefinition(); Set<String> requiredParams = new HashSet<String>(); if (reportDefinition.getParameters() != null) { for (Parameter parameter : reportDefinition.getParameters()) { if (parameter.isRequired()) { requiredParams.add(parameter.getName()); } } } for (Map.Entry<String, Object> e : command.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) { for (Iterator<String> iterator = requiredParams.iterator(); iterator.hasNext();) { String parameterName = (String) iterator.next(); if (StringUtils.hasText(command.getExpressions().get(parameterName))) { String expression = command.getExpressions().get(parameterName); if (!EvaluationUtil.isExpression(expression)){ errors.rejectValue("expressions[" + parameterName + "]", "reporting.Report.run.error.invalidParamExpression"); } } else { errors.rejectValue("userEnteredParams[" + parameterName + "]", "error.required", new Object[] { "This parameter" }, "{0} is required"); } } } if (reportDefinition.getDataSetDefinitions() == null || reportDefinition.getDataSetDefinitions().size() == 0) { errors.reject("reporting.Report.run.error.definitionNotDeclared"); } if (ObjectUtil.notNull(command.getSchedule())) { if (!CronExpression.isValidExpression(command.getSchedule())) { errors.rejectValue("schedule", "reporting.Report.run.error.invalidCronExpression"); } } } ValidationUtils.rejectIfEmpty(errors, "selectedRenderer", "reporting.Report.run.error.noRendererSelected"); } @Override protected Object formBackingObject(HttpServletRequest request) throws Exception { CommandObject command = new CommandObject(); if (Context.isAuthenticated()) { ReportDefinitionService rds = Context.getService(ReportDefinitionService.class); ReportService reportService = Context.getService(ReportService.class); if (StringUtils.hasText(request.getParameter("copyRequest"))) { ReportRequest req = reportService.getReportRequestByUuid(request.getParameter("copyRequest")); // avoid lazy init exceptions command.setReportDefinition(rds.getDefinitionByUuid(req.getReportDefinition().getParameterizable().getUuid())); for (Map.Entry<String, Object> param : req.getReportDefinition().getParameterMappings().entrySet()) { Object value = param.getValue(); if ( value != null && EvaluationUtil.isExpression( value.toString() ) ) { command.getExpressions().put( param.getKey(), ( String ) value ); value = ""; } command.getUserEnteredParams().put(param.getKey(), value ); } command.setSelectedRenderer(req.getRenderingMode().getDescriptor()); } else if (StringUtils.hasText(request.getParameter("requestUuid"))) { String reqUuid = request.getParameter("requestUuid"); ReportRequest rr = reportService.getReportRequestByUuid(reqUuid); command.setExistingRequestUuid(reqUuid); command.setReportDefinition(rr.getReportDefinition().getParameterizable()); command.setUserEnteredParams(rr.getReportDefinition().getParameterMappings()); command.setBaseCohort(rr.getBaseCohort()); command.setSelectedRenderer(rr.getRenderingMode().getDescriptor()); command.setSchedule(rr.getSchedule()); } else { String uuid = request.getParameter("reportId"); ReportDefinition reportDefinition = rds.getDefinitionByUuid(uuid); command.setReportDefinition(reportDefinition); for (Parameter p : reportDefinition.getParameters()) { if (p.getDefaultValue() != null) { command.getUserEnteredParams().put(p.getName(), p.getDefaultValue()); } } } command.setRenderingModes(reportService.getRenderingModes(command.getReportDefinition())); } return command; } @Override protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object commandObject, BindException errors) throws Exception { CommandObject command = (CommandObject) commandObject; ReportDefinition reportDefinition = command.getReportDefinition(); ReportService rs = Context.getService(ReportService.class); // Parse the input parameters into appropriate objects and fail validation if any are invalid Map<String, Object> params = new LinkedHashMap<String, Object>(); if (reportDefinition.getParameters() != null && (command.getUserEnteredParams() != null || command.getExpressions() != null)) { for (Parameter parameter : reportDefinition.getParameters()) { Object value = null; String expression = null; if (command.getExpressions() != null && ObjectUtil.notNull(command.getExpressions().get(parameter.getName()))) { expression = command.getExpressions().get(parameter.getName()); } else { value = command.getUserEnteredParams().get(parameter.getName()); } if (ObjectUtil.notNull(value) || ObjectUtil.notNull(expression)) { try { if (StringUtils.hasText(expression)) value = expression; else value = WidgetUtil.parseInput(value, parameter.getType(), parameter.getCollectionType()); params.put(parameter.getName(), value); } catch (Exception ex) { errors.rejectValue("userEnteredParams[" + parameter.getName() + "]", ex.getMessage()); } } } } // Ensure that the chosen renderer is valid for this report RenderingMode renderingMode = command.getSelectedMode(); if (!renderingMode.getRenderer().canRender(reportDefinition)) { errors.rejectValue("selectedRenderer", "reporting.Report.run.error.invalidRenderer"); } if (errors.hasErrors()) { return showForm(request, response, errors); } ReportRequest rr = null; if (command.getExistingRequestUuid() != null) { rr = rs.getReportRequestByUuid(command.getExistingRequestUuid()); } else { rr = new ReportRequest(); } rr.setReportDefinition(new Mapped<ReportDefinition>(reportDefinition, params)); rr.setBaseCohort(command.getBaseCohort()); rr.setRenderingMode(command.getSelectedMode()); rr.setPriority(Priority.NORMAL); rr.setSchedule(command.getSchedule()); // TODO: We might want to check here if this exact same report request is already queued and just re-direct if so rr = rs.queueReport(rr); rs.processNextQueuedReports(); return new ModelAndView(new RedirectView("../reports/reportHistoryOpen.form?uuid="+rr.getUuid())); } /** * @see org.springframework.web.servlet.mvc.SimpleFormController#referenceData(javax.servlet.http.HttpServletRequest) */ @Override protected Map<String, Object> referenceData(HttpServletRequest request, Object commandObject, Errors errors) throws Exception { CommandObject command = (CommandObject) commandObject; Map<String, Object> map = new HashMap<String, Object>(); EvaluationContext ec = new EvaluationContext(); Set<String> expSupportedTypes = new HashSet<String>(); Set<String> inputsToToggle = new HashSet<String>(); for (Object value : ec.getContextValues().values()) { expSupportedTypes.add(value.getClass().getName()); } map.put("expSupportedTypes", expSupportedTypes); for (Map.Entry<String, Object> e : command.getUserEnteredParams().entrySet()) { if (StringUtils.hasText(command.getExpressions().get(e.getKey()))) { inputsToToggle.add( e.getKey() ); } } map.put( "inputsToToggle", inputsToToggle ); return map; } public class CommandObject { private String existingRequestUuid; private ReportDefinition reportDefinition; private Mapped<CohortDefinition> baseCohort; private Map<String, Object> userEnteredParams; private String selectedRenderer; // as RendererClass!Arg private String schedule; private Map<String, String> expressions; private List<RenderingMode> renderingModes; public CommandObject() { userEnteredParams = new LinkedHashMap<String, Object>(); expressions = new HashMap<String ,String>(); } @SuppressWarnings("unchecked") public RenderingMode getSelectedMode() { if (selectedRenderer != null) { try { String[] temp = selectedRenderer.split("!"); Class<? extends ReportRenderer> rc = (Class<? extends ReportRenderer>) Context.loadClass(temp[0]); String arg = (temp.length > 1 && StringUtils.hasText(temp[1])) ? temp[1] : null; for (RenderingMode mode : renderingModes) { if (mode.getRenderer().getClass().equals(rc) && OpenmrsUtil.nullSafeEquals(mode.getArgument(), arg)) { return mode; } } log.warn("Could not find requested rendering mode: " + selectedRenderer); } catch (Exception e) { log.warn("Could not load requested renderer", e); } } return null; } public String getExistingRequestUuid() { return existingRequestUuid; } public void setExistingRequestUuid(String existingRequestUuid) { this.existingRequestUuid = existingRequestUuid; } public List<RenderingMode> getRenderingModes() { return renderingModes; } public void setRenderingModes(List<RenderingMode> rendereringModes) { this.renderingModes = rendereringModes; } public ReportDefinition getReportDefinition() { return reportDefinition; } public void setReportDefinition(ReportDefinition reportDefinition) { this.reportDefinition = reportDefinition; } public Mapped<CohortDefinition> getBaseCohort() { return baseCohort; } public void setBaseCohort(Mapped<CohortDefinition> baseCohort) { this.baseCohort = baseCohort; } public String getSelectedRenderer() { return selectedRenderer; } public void setSelectedRenderer(String selectedRenderer) { this.selectedRenderer = selectedRenderer; } public Map<String, Object> getUserEnteredParams() { return userEnteredParams; } public void setUserEnteredParams(Map<String, Object> userEnteredParams) { this.userEnteredParams = userEnteredParams; } public String getSchedule() { return schedule; } public void setSchedule(String schedule) { this.schedule = schedule; } /** * @return the expressions */ public Map<String, String> getExpressions() { return expressions; } /** * @param expressions the expressions to set */ public void setExpressions(Map<String, String> expressions) { this.expressions = expressions; } } }