/**
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
* the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
*
* Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
* graphic logo is a trademark of OpenMRS Inc.
*/
package org.openmrs.util.databasechange;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import org.openmrs.util.DatabaseUpdater;
import org.openmrs.util.DatabaseUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.CustomChangeException;
import liquibase.exception.SetupException;
import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;
/**
* This change set is executed in conjunction with a change made to Patient Programs which
* automatically will complete a Patient Program if a Workflow within that Program transitions to a
* state marked as final. It is intended to warn administrators when they upgrade that they should
* carefully review any States marked as final, particularly those also marked as initial
*/
public class ProgramValidatorChangeSet implements CustomTaskChange {
protected final static Logger log = LoggerFactory.getLogger(ProgramValidatorChangeSet.class);
/**
* @see CustomTaskChange#execute(Database)
*/
@Override
public void execute(Database database) throws CustomChangeException {
Connection conn = ((JdbcConnection) database.getConnection()).getUnderlyingConnection();
List<String> messages = new ArrayList<String>();
// Warn if any states are configured as both initial and terminal
StringBuilder message = new StringBuilder();
message.append("Starting now, when you transition a patient into a state that is configured as terminal, ");
message.append("then that whole program enrollment will be marked as completed.<br/>");
message.append("Please check that programs, workflows, and states are configured.<br/>");
message.append("This check will highlight two things: ");
message.append("<ul><li>states that are marked as both initial and terminal ");
message.append("(if you start someone in that state their program enrollment will be instantly closed)</li>");
message.append("<li>workflows that have no initial states (because you don't have a state to start people in)</li>");
message.append("</ul><br/>");
message.append("The following states are configured as both initial and terminal:<br/>");
StringBuilder query = new StringBuilder();
query.append(" select s.concept_id, min(n.name) as name ");
query.append(" from program_workflow_state s, concept_name n ");
query.append(" where s.concept_id = n.concept_id and initial = '1' and terminal = '1' ");
query.append(" group by s.concept_id ");
List<List<Object>> results = DatabaseUtil.executeSQL(conn, query.toString(), true);
if (results.isEmpty()) {
message.append("None found.");
} else {
for (List<Object> row : results) {
message.append(row.get(1).toString()).append("<br/>");
}
}
// Warn if any workflows have no initial states
message.append("<br/>The following workflows have no initial states...<br/>");
query = new StringBuilder();
query.append(" select w.concept_id, s.initial, count(*) as num ");
query.append(" from program_workflow w, program_workflow_state s ");
query.append(" where w.program_workflow_id = s.program_workflow_id ");
query.append(" group by w.concept_id, s.initial ");
results = DatabaseUtil.executeSQL(conn, query.toString(), true);
List<Integer> missingInitial = new ArrayList<Integer>();
for (List<Object> row : results) {
missingInitial.add(Integer.valueOf(row.get(0).toString()));
}
for (List<Object> row : results) {
Integer conceptId = Integer.valueOf(row.get(0).toString());
boolean isInitial = "1".equals(row.get(1).toString());
int num = Integer.parseInt(row.get(2).toString());
if (isInitial && num > 0) {
missingInitial.remove(conceptId);
}
}
if (missingInitial.isEmpty()) {
message.append("None found.");
} else {
for (Integer conceptId : missingInitial) {
String sql = "select min(name) from concept_name where concept_id = " + conceptId;
String name = DatabaseUtil.executeSQL(conn, sql, true).get(0).get(0).toString();
message.append(name).append("<br/>");
}
}
messages.add(message.toString());
DatabaseUpdater.reportUpdateWarnings(messages);
}
/**
* @see liquibase.change.custom.CustomChange#getConfirmationMessage()
*/
@Override
public String getConfirmationMessage() {
return "Finished validating programs";
}
/**
* @see liquibase.change.custom.CustomChange#setFileOpener(ResourceAccessor)
*/
@Override
public void setFileOpener(ResourceAccessor fo) {
}
/**
* @see liquibase.change.custom.CustomChange#setUp()
*/
@Override
public void setUp() throws SetupException {
}
/**
* @see liquibase.change.custom.CustomChange#validate(Database)
*/
@Override
public ValidationErrors validate(Database db) {
return new ValidationErrors();
}
}