Monday, April 26, 2010

Well, nothing

We've got no reply yet. Should have marked it as needs review, however we didn't have that ability, like permissions or some fun stuff like that. Anyway, it's submitted for review now. Fun.

Friday, April 23, 2010

Done... or are we

We submitted our patch. We'll see if it gets accepted.
Goooooooooooooo openMRS!
Ticket 1150

Irrelevant Post 2


This bears absolutely no relevance to OpenMRS but watch it anyway.

Win.

Monday, April 19, 2010

Completely Phased Out

Soooooooo... we got an email from Darius. He was actually quite helpful, however the first part of the email was troubling. Apparently CohortReportsFromControler has been completely phased out due to a better reporting module. Well... woulda been nice to know. Anyway,
The helpful part was that if we still should complete this task, we found out some good stuff about the refactroing that we're doing. It seems that the onSubmit method we pulled out needs to be pulled out, however they just want the meat of the method pulled out. They would like it if the method onSubmit stayed in place even though it isn't called by anything outside cohortReportsFormController. That's obviously doable, so I'm not really all that worried if Darius says he wants it done that way for 100% real. Also, they want the method itself broken down more... or refacored. Bad a. I can finally do what I've been trying to do all semester.
Freakin WOOT.
The End.
For Now.
Or IS ITTTTTTTTT????!!!?!?!???!?!?!!!!!
Ya, it is.
Bye.

Monday, April 12, 2010

Success

So we got CohortReportFormController refactored successfully. See code below:

CohortReportFormController:




/**
* 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 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.module.reportingcompatibility.ReportingCompatibilityConstants;
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
* not 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());
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());

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

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

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

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

List rows = new ArrayList();
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 referenceData(HttpServletRequest request) throws Exception {
Map ret = new HashMap();

List> classes = new ArrayList>();
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 searches = rs.getReportObjectsByType(
OpenmrsConstants.REPORT_OBJECT_TYPE_PATIENTSEARCH);
Map map = new LinkedHashMap();
for (AbstractReportObject o : searches) {
if (o instanceof PatientSearchReportObject) {
StringBuilder searchName = new StringBuilder(o.getName());
List parameters = ((PatientSearchReportObject) o).getPatientSearch().getParameters();
if (parameters != null && !parameters.isEmpty()) {
searchName.append("|");
for (Iterator 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();
for (Map.Entry 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 params = new ArrayList();
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 rows = new ArrayList();
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);
}

// ***** 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 parameters;

private List rows;

public CommandObject() {
parameters = new ArrayList();
rows = new ArrayList();
}

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 getParameters() {
return parameters;
}

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

public List getRows() {
return rows;
}

public void setRows(List 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;
}
}

}


CohortReportXMLBuilder:




package org.openmrs.web.controller.report;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;

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.openmrs.api.ReportService;
import org.openmrs.api.context.Context;
import org.openmrs.report.DataSetDefinition;
import org.openmrs.report.ReportSchema;
import org.openmrs.report.ReportSchemaXml;
import org.openmrs.util.OpenmrsUtil;
import org.openmrs.web.controller.report.CohortReportFormController.CohortReportRow;
import org.openmrs.web.controller.report.CohortReportFormController.CommandObject;
import org.simpleframework.xml.Serializer;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
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;

public class CohortReportXMLBuilder extends SimpleFormController {

@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());
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(
"" + 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();

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()));
}

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
*/
protected 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;
}

protected 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;
}

}



Frustration

I'm getting more and more frustrated with this, but hopefully we're almost at an end with the problems. Today we'll be refactoring the code in yet another build. I'll post the results.