org.openmrs.web.controller.report.CohortReportFormController.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.web.controller.report.CohortReportFormController.java

Source

/**
 * 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.web.controller.report;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Location;
import org.openmrs.api.ReportService;
import org.openmrs.api.context.Context;
import org.openmrs.cohort.CohortDefinition;
import org.openmrs.cohort.CohortUtil;
import org.openmrs.report.CohortDataSetDefinition;
import org.openmrs.report.DataSetDefinition;
import org.openmrs.report.Parameter;
import org.openmrs.report.ReportSchema;
import org.openmrs.report.ReportSchemaXml;
import org.openmrs.reporting.AbstractReportObject;
import org.openmrs.reporting.PatientSearchReportObject;
import org.openmrs.reporting.ReportObjectService;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import org.simpleframework.xml.Serializer;
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.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.view.RedirectView;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;

/**
 * This form lets you create or edit a report with a single CohortDataSetDefinition. You should
 * <strong>not</strong> use this form to edit other types of reports.
 */
public class CohortReportFormController extends SimpleFormController implements Validator {

    Log log = LogFactory.getLog(getClass());

    /**
     * Creates a command object and tries to fill it with data from the saved report schema with the
     * id given by the 'reportId' parameter.
     * 
     * @see org.springframework.web.servlet.mvc.AbstractFormController#formBackingObject(javax.servlet.http.HttpServletRequest)
     */
    protected Object formBackingObject(HttpServletRequest request) throws Exception {
        CommandObject command = new CommandObject();

        if (Context.isAuthenticated() && !isFormSubmission(request)) {
            // if this is an existing report, get its data
            String idString = request.getParameter("reportId");
            if (idString != null) {
                Integer id = Integer.valueOf(idString);
                ReportService rs = (ReportService) Context.getService(ReportService.class);
                ReportSchemaXml schemaXml = rs.getReportSchemaXml(id);
                ReportSchema schema = rs.getReportSchema(schemaXml);
                CohortDataSetDefinition cohorts = null;
                if (schema.getDataSetDefinitions() == null)
                    schema.setDataSetDefinitions(new ArrayList<DataSetDefinition>());
                if (schema.getDataSetDefinitions().size() == 0)
                    schema.getDataSetDefinitions().add(new CohortDataSetDefinition());
                for (DataSetDefinition d : schema.getDataSetDefinitions()) {
                    if (d instanceof CohortDataSetDefinition) {
                        if (cohorts != null)
                            throw new Exception(
                                    "You may not edit a report that contains more than one Cohort Dataset Definition");
                        cohorts = (CohortDataSetDefinition) d;
                    } else {
                        throw new Exception(
                                "You may not edit a report that contains datasets besides Cohort Dataset Definition");
                    }
                }
                if (cohorts == null)
                    throw new Exception(
                            "You may only edit a report that has exactly one Cohort Dataset Definition");

                command.setReportId(id);
                command.setName(schema.getName());
                command.setDescription(schema.getDescription());
                command.getParameters().addAll(schema.getReportParameters());
                command.setUuid(schemaXml.getUuid());

                // populate command.rows, directly from XML
                Document xml = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                        .parse(new InputSource(new StringReader(schemaXml.getXml())));
                // xml looks like <reportSchema>...<dataSets>...<dataSetDefinition class="org.openmrs.report.CohortDataSetDefinition">
                // TODO: do this with xpath
                Node temp = findChild(xml, "reportSchema");
                temp = findChild(temp, "dataSets");
                temp = findChildWithAttribute(temp, "dataSetDefinition", "class",
                        "org.openmrs.report.CohortDataSetDefinition");

                Map<String, String> nameToStrategy = new LinkedHashMap<String, String>();
                Node strategies = findChild(temp, "strategies");
                if (strategies != null) {
                    NodeList nl = strategies.getChildNodes();
                    // each is a <entry><string>name</string><cohort ...><specification>strategy</specification></cohort></entry>
                    for (int i = 0; i < nl.getLength(); ++i) {
                        Node node = nl.item(i);
                        if ("entry".equals(node.getNodeName())) {
                            String name = findChild(node, "string").getFirstChild().getNodeValue();
                            String strategy = findChild(findChild(node, "cohort"), "specification").getFirstChild()
                                    .getNodeValue();
                            nameToStrategy.put(name, strategy);
                        }
                    }
                }

                Map<String, String> nameToDescription = new LinkedHashMap<String, String>();
                Node descriptions = findChild(temp, "descriptions");
                if (descriptions != null) {
                    NodeList nl = descriptions.getChildNodes();
                    // each is a <entry><string>name</string><string>descr</string></entry>
                    for (int i = 0; i < nl.getLength(); ++i) {
                        Node node = nl.item(i);
                        if ("entry".equals(node.getNodeName())) {
                            String name = findChild(node, "string").getFirstChild().getNodeValue();
                            String descr = findChild(node, "string", 2).getFirstChild().getNodeValue();
                            nameToDescription.put(name, descr);
                        }
                    }
                }

                LinkedHashSet<String> names = new LinkedHashSet<String>();
                names.addAll(nameToStrategy.keySet());
                names.addAll(nameToDescription.keySet());

                List<CohortReportRow> rows = new ArrayList<CohortReportRow>();
                for (String name : names) {
                    String descr = nameToDescription.get(name);
                    String strat = nameToStrategy.get(name);
                    CohortReportRow row = new CohortReportRow();
                    row.setName(name);
                    row.setDescription(descr);
                    row.setQuery(strat);
                    rows.add(row);
                }
                command.setRows(rows);
            }
        }
        return command;
    }

    /**
     * @see org.springframework.web.servlet.mvc.SimpleFormController#referenceData(javax.servlet.http.HttpServletRequest)
     */
    @Override
    protected Map<String, Object> referenceData(HttpServletRequest request) throws Exception {
        Map<String, Object> ret = new HashMap<String, Object>();

        List<Class<?>> classes = new ArrayList<Class<?>>();
        classes.add(Date.class);
        classes.add(Integer.class);
        classes.add(Double.class);
        classes.add(Location.class);
        ret.put("parameterClasses", classes);

        ReportObjectService rs = (ReportObjectService) Context.getService(ReportObjectService.class);
        List<AbstractReportObject> searches = rs
                .getReportObjectsByType(OpenmrsConstants.REPORT_OBJECT_TYPE_PATIENTSEARCH);
        Map<String, String> map = new LinkedHashMap<String, String>();
        for (AbstractReportObject o : searches) {
            if (o instanceof PatientSearchReportObject) {
                StringBuilder searchName = new StringBuilder(o.getName());
                List<Parameter> parameters = ((PatientSearchReportObject) o).getPatientSearch().getParameters();
                if (parameters != null && !parameters.isEmpty()) {
                    searchName.append("|");
                    for (Iterator<Parameter> i = parameters.iterator(); i.hasNext();) {
                        Parameter p = i.next();
                        searchName.append(p.getName()).append("=${?}");
                        if (i.hasNext()) {
                            searchName.append(",");
                        }
                    }
                }
                map.put(searchName.toString(), o.getDescription());
            } else {
                map.put(o.getName(), o.getDescription());
            }
        }
        ret.put("patientSearches", map);

        ReportService rptSvc = (ReportService) Context.getService(ReportService.class);
        Properties macros = rptSvc.getReportXmlMacros();
        map = new LinkedHashMap<String, String>();
        for (Map.Entry<Object, Object> e : macros.entrySet()) {
            if (!e.getKey().toString().equals("macroPrefix") && !e.getKey().toString().equals("macroSuffix"))
                map.put(e.getKey().toString(), e.getValue().toString());
        }
        ret.put("macros", map);
        ret.put("macroPrefix", macros.get("macroPrefix"));
        ret.put("macroSuffix", macros.get("macroSuffix"));

        return ret;
    }

    /**
     * Handles parameters and rows, since Spring isn't good with lists
     * 
     * @see org.springframework.web.servlet.mvc.BaseCommandController#onBind(javax.servlet.http.HttpServletRequest,
     *      java.lang.Object, org.springframework.validation.BindException)
     */
    @Override
    protected void onBind(HttpServletRequest request, Object commandObj, BindException errors) throws Exception {
        CommandObject command = (CommandObject) commandObj;

        // parameters
        String[] paramNames = request.getParameterValues("parameterName");
        String[] paramLabels = request.getParameterValues("parameterLabel");
        String[] paramClasses = request.getParameterValues("parameterClass");
        List<Parameter> params = new ArrayList<Parameter>();
        if (paramNames != null) {
            for (int i = 0; i < paramNames.length; ++i) {
                if (StringUtils.hasText(paramNames[i]) || StringUtils.hasText(paramLabels[i])
                        || StringUtils.hasText(paramClasses[i])) {
                    try {
                        Class<?> clz = null;
                        if (StringUtils.hasText(paramClasses[i]))
                            clz = Class.forName(paramClasses[i]);
                        Parameter p = new Parameter(paramNames[i], paramLabels[i], clz, null);
                        params.add(p);
                    } catch (Exception ex) {
                        errors.rejectValue("parameters", null, "Parameter error: " + ex.toString());
                    }
                }
            }
        }
        command.setParameters(params);

        // rows
        String[] rowNames = request.getParameterValues("rowName");
        String[] rowDescriptions = request.getParameterValues("rowDescription");
        String[] rowQueries = request.getParameterValues("rowQuery");
        List<CohortReportRow> rows = new ArrayList<CohortReportRow>();
        if (rowNames != null) {
            for (int i = 0; i < rowNames.length; ++i) {
                try {
                    CohortReportRow row = new CohortReportRow();
                    row.setName(rowNames[i]);
                    row.setDescription(rowDescriptions[i]);
                    row.setQuery(rowQueries[i]);
                    rows.add(row);
                } catch (Exception ex) {
                    errors.rejectValue("rows", null, "Row error: " + ex.toString());
                }
            }
        }
        command.setRows(rows);
    }

    /**
     * @see org.springframework.web.servlet.mvc.SimpleFormController#onSubmit(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse, java.lang.Object,
     *      org.springframework.validation.BindException)
     */
    @Override
    protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object commandObj,
            BindException errors) throws Exception {
        CommandObject command = (CommandObject) commandObj;

        // do simpleframework serialization of everything but 'rows', and add those via handcoded xml, since
        // serializing them is not reversible

        ReportSchema rs = new ReportSchema();
        rs.setReportSchemaId(command.getReportId());
        rs.setName(command.getName());
        rs.setDescription(command.getDescription());
        rs.setReportParameters(command.getParameters());
        rs.setDataSetDefinitions(new ArrayList<DataSetDefinition>());
        Serializer serializer = OpenmrsUtil.getSerializer();
        StringWriter sw = new StringWriter();
        serializer.write(rs, sw);

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setValidating(false);
        DocumentBuilder db = dbf.newDocumentBuilder();

        Document xml = db.parse(new InputSource(
                new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" + sw.toString())));
        Node node = findChild(xml, "reportSchema");
        node = findChild(node, "dataSets");
        Element dsd = xml.createElement("dataSetDefinition");
        dsd.setAttribute("name", "cohorts");
        dsd.setAttribute("class", "org.openmrs.report.CohortDataSetDefinition");
        node.appendChild(dsd);
        Element strategies = xml.createElement("strategies");
        strategies.setAttribute("class", "java.util.LinkedHashMap");
        dsd.appendChild(strategies);
        Element descriptions = xml.createElement("descriptions");
        descriptions.setAttribute("class", "java.util.LinkedHashMap");
        dsd.appendChild(descriptions);
        for (CohortReportRow row : command.getRows()) {
            if (StringUtils.hasText(row.getQuery())) {
                Element entry = xml.createElement("entry");
                strategies.appendChild(entry);
                Element nameEl = xml.createElement("string");
                Text val = xml.createTextNode(row.getName());
                val.setNodeValue(row.getName());
                nameEl.appendChild(val);
                entry.appendChild(nameEl);
                Element cohort = xml.createElement("cohort");
                entry.appendChild(cohort);
                cohort.setAttribute("class", "org.openmrs.reporting.PatientSearch");
                Element strategyEl = xml.createElement("specification");
                val = xml.createTextNode(row.getQuery());
                val.setNodeValue(row.getQuery());
                strategyEl.appendChild(val);
                cohort.appendChild(strategyEl);
            }
            if (StringUtils.hasText(row.getDescription())) {
                Element entry = xml.createElement("entry");
                descriptions.appendChild(entry);
                Element el = xml.createElement("string");
                Text val = xml.createTextNode(row.getName());
                val.setNodeValue(row.getName());
                el.appendChild(val);
                entry.appendChild(el);
                el = xml.createElement("string");
                val = xml.createTextNode(row.getDescription());
                val.setNodeValue(row.getDescription());
                el.appendChild(val);
                entry.appendChild(el);
            }
        }

        // now turn this into an xml string
        System.setProperty("javax.xml.transform.TransformerFactory",
                "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
        TransformerFactory transfac = TransformerFactory.newInstance();
        Transformer trans = transfac.newTransformer();
        trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        trans.setOutputProperty(OutputKeys.INDENT, "yes");
        trans.setOutputProperty(OutputKeys.METHOD, "xml");
        StringWriter out = new StringWriter();
        StreamResult result = new StreamResult(out);
        DOMSource source = new DOMSource(xml);
        trans.transform(source, result);
        String schemaXml = out.toString();

        ReportSchemaXml rsx = new ReportSchemaXml();
        rsx.populateFromReportSchema(rs);
        rsx.setXml(schemaXml);
        rsx.updateXmlFromAttributes();
        rsx.setUuid(request.getParameter("parentUUID"));

        ReportService rptSvc = (ReportService) Context.getService(ReportService.class);
        if (rsx.getReportSchemaId() != null) {
            rptSvc.saveReportSchemaXml(rsx);
        } else {
            rptSvc.saveReportSchemaXml(rsx);
        }

        return new ModelAndView(new RedirectView(getSuccessView() + "?reportId=" + rsx.getReportSchemaId()));
    }

    private Node findChild(Node parent, String name) {
        NodeList list = parent.getChildNodes();
        for (int i = 0; i < list.getLength(); ++i) {
            Node node = list.item(i);
            if (node.getNodeName().equals(name))
                return node;
        }
        return null;
    }

    /**
     * finds the ith occurrence of a child with the given name
     */
    private Node findChild(Node parent, String name, int index) {
        int soFar = 0;
        NodeList list = parent.getChildNodes();
        for (int i = 0; i < list.getLength(); ++i) {
            Node node = list.item(i);
            if (node.getNodeName().equals(name)) {
                ++soFar;
                if (soFar == index)
                    return node;
            }
        }
        return null;
    }

    private Node findChildWithAttribute(Node parent, String name, String attrName, String attrValue) {
        NodeList list = parent.getChildNodes();
        for (int i = 0; i < list.getLength(); ++i) {
            Node node = list.item(i);
            if (node.getNodeName().equals(name)) {
                Node attr = node.getAttributes().getNamedItem(attrName);
                if (attr != null && attr.getNodeValue().equals(attrValue))
                    return node;
            }
        }
        return null;
    }

    // ***** Validator methods *****

    /**
     * @see org.springframework.validation.Validator#supports(java.lang.Class)
     */
    @SuppressWarnings("unchecked")
    public boolean supports(Class clazz) {
        return clazz.equals(CommandObject.class);
    }

    /**
     * @see org.springframework.validation.Validator#validate(java.lang.Object,
     *      org.springframework.validation.Errors)
     */
    public void validate(Object commandObj, Errors errors) {
        CommandObject command = (CommandObject) commandObj;

        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "error.null");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "description", "error.null");

        for (Parameter p : command.getParameters()) {
            if (!StringUtils.hasText(p.getName()) || !StringUtils.hasText(p.getLabel()) || p.getClazz() == null)
                errors.rejectValue("parameters", null, "All parameters must have a name, a label, and a datatype");
        }

        for (CohortReportRow row : command.getRows()) {
            if (!StringUtils.hasText(row.getName()))
                errors.rejectValue("rows", null, "Each row must have a name");

            try {
                String query = row.getQuery();
                ReportService rptSvc = (ReportService) Context.getService(ReportService.class);
                query = rptSvc.applyReportXmlMacros(query);
                CohortDefinition def = CohortUtil.parse(query);
                if (def == null)
                    throw new Exception();
            } catch (Exception ex) {
                errors.rejectValue("rows", null,
                        "Failed to parse: " + row.getQuery() + " (" + ex.getMessage() + ")");
            }
        }
    }

    // ***** Command object *****

    public class CommandObject {

        private Integer reportId;

        private String name;

        private String description;

        private List<Parameter> parameters;

        private List<CohortReportRow> rows;

        private String uuid;

        public CommandObject() {
            parameters = new ArrayList<Parameter>();
            rows = new ArrayList<CohortReportRow>();
            uuid = UUID.randomUUID().toString();
        }

        public String getUuid() {
            return uuid;
        }

        public void setUuid(String uuid) {
            this.uuid = uuid;
        }

        public Integer getReportId() {
            return reportId;
        }

        public void setReportId(Integer reportId) {
            this.reportId = reportId;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public List<Parameter> getParameters() {
            return parameters;
        }

        public void setParameters(List<Parameter> parameters) {
            this.parameters = parameters;
        }

        public List<CohortReportRow> getRows() {
            return rows;
        }

        public void setRows(List<CohortReportRow> rows) {
            this.rows = rows;
        }
    }

    public class CohortReportRow {

        private String name;

        private String description;

        private String query;

        public CohortReportRow() {
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public String getQuery() {
            return query;
        }

        public void setQuery(String query) {
            this.query = query;
        }
    }

}