/**
* 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.util.databasechange;
import java.sql.BatchUpdateException;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import liquibase.FileOpener;
import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.exception.CustomChangeException;
import liquibase.exception.InvalidChangeDefinitionException;
import liquibase.exception.SetupException;
import liquibase.exception.UnsupportedChangeException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections.set.ListOrderedSet;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.ConceptName;
import org.openmrs.api.ConceptNameType;
import org.openmrs.api.db.hibernate.HibernateUtil;
import org.openmrs.util.DatabaseUpdater;
import org.openmrs.util.LocaleUtility;
import org.openmrs.util.OpenmrsConstants;
/**
* This change set is run just after the conversion of core concept name tags to concept name types'
* it runs through all the rows in the concept table and checks if all its conceptNames conform to
* the constraints added with the conversion of the tags.
*/
public class ConceptValidatorChangeSet implements CustomTaskChange {
private final static Log log = LogFactory.getLog(ConceptValidatorChangeSet.class);
//List to store warnings
private List<String> updateWarnings = new LinkedList<String>();
//List to store info messages
private List<String> logMessages = new LinkedList<String>();
//A set to store unique concept names that have been updated and changes have to be persisted to the database
private Set<ConceptName> updatedConceptNames = new HashSet<ConceptName>();
private Locale defaultLocale = new Locale("en");
private List<Locale> allowedLocales = null;
/**
* @see CustomTaskChange#execute(Database)
*/
public void execute(Database database) throws CustomChangeException, UnsupportedChangeException {
DatabaseConnection connection = database.getConnection();
//In the liquibase changelog file, there is a precondition that checks if this is a fresh installation
//with no rows in the concept table or if it has some active concepts, we don't need to check again.
//validate all concepts and try to fix those that can be fixed, note that updates are not committed inside this methods
validateAndCleanUpConcepts(connection);
//commit as a batch update
if (!updatedConceptNames.isEmpty())
runBatchUpdate(connection);
else
log.debug("No concept names to update");
if (!logMessages.isEmpty() || !updateWarnings.isEmpty())
writeWarningsToFile();
if (!updateWarnings.isEmpty()) {
DatabaseUpdater.reportUpdateWarnings(updateWarnings);
}
//we need this memory in case the lists are large
updateWarnings = null;
updatedConceptNames = null;
logMessages = null;
}
/**
* This method is called by the execute {@link #execute(Database)} method to run through all
* concept and their conceptNames and validates them, It also tries to fix any constraints that
* are being violated.
*
* @param connection The database connection
*/
private void validateAndCleanUpConcepts(DatabaseConnection connection) {
List<Integer> conceptIds = getAllUnretiredConceptIds(connection);
allowedLocales = getAllowedLocalesList(connection);
//default locale(if none, then 'en') is always the last in the list.
defaultLocale = allowedLocales.get(allowedLocales.size() - 1);
//a map to store all duplicates names found for each locale
Map<Locale, Set<String>> localeDuplicateNamesMap = null;
for (Integer conceptId : conceptIds) {
Map<Locale, List<ConceptName>> localeConceptNamesMap = getLocaleConceptNamesMap(connection, conceptId);
if (localeConceptNamesMap == null) {
updateWarnings.add("No names added for concept with id: " + conceptId);
continue;
}
boolean hasFullySpecifiedName = false;
Set<Locale> locales = localeConceptNamesMap.keySet();
List<ConceptName> namesWithNoLocale = null;
//for each locale
for (Locale conceptNameLocale : locales) {
boolean fullySpecifiedNameForLocaleFound = false;
boolean preferredNameForLocaleFound = false;
boolean shortNameForLocaleFound = false;
//map to hold a name and a list of conceptNames that are found as duplicates
Map<String, List<ConceptName>> nameDuplicateConceptNamesMap = new HashMap<String, List<ConceptName>>();
//for each name in the locale
for (ConceptName nameInLocale : localeConceptNamesMap.get(conceptNameLocale)) {
if (StringUtils.isBlank(nameInLocale.getName())) {
updateWarnings.add("ConceptName with id " + nameInLocale.getConceptNameId() + " ("
+ nameInLocale.getName() + ") is null, white space character or empty string");
}
//if the concept name has no locale, wonder why this would be the case but there was no not-null constraint originally
if (conceptNameLocale == null) {
if (namesWithNoLocale == null)
namesWithNoLocale = new LinkedList<ConceptName>();
namesWithNoLocale.add(nameInLocale);
continue;
}
//The concept's locale should be among the allowed locales listed in global properties
if (!allowedLocales.contains(conceptNameLocale)) {
updateWarnings.add("ConceptName with id: " + nameInLocale.getConceptNameId() + " ("
+ nameInLocale.getName() + ") has a locale (" + conceptNameLocale
+ ") that isn't listed among the allowed ones by the system admin");
}
if (nameInLocale.isLocalePreferred() != null) {
if (nameInLocale.isLocalePreferred() && !preferredNameForLocaleFound) {
if (nameInLocale.isIndexTerm()) {
nameInLocale.setLocalePreferred(false);
reportUpdatedName(nameInLocale, "Preferred name '" + nameInLocale.getName()
+ "' in locale '" + conceptNameLocale.getDisplayName()
+ "' has been dropped as the preferred name because it is a search term");
} else if (nameInLocale.isShort()) {
nameInLocale.setLocalePreferred(false);
reportUpdatedName(nameInLocale, "Preferred name '" + nameInLocale.getName()
+ "' in locale '" + conceptNameLocale.getDisplayName()
+ "' has been dropped as the preferred name because it is a short name");
} else
preferredNameForLocaleFound = true;
}
//should have one preferred name per locale
else if (nameInLocale.isLocalePreferred() && preferredNameForLocaleFound) {
//drop this name as locale preferred so that we have only one
nameInLocale.setLocalePreferred(false);
reportUpdatedName(
nameInLocale,
"Preferred name '"
+ nameInLocale.getName()
+ "' in locale '"
+ conceptNameLocale.getDisplayName()
+ "' has been dropped as the preferred name because there is already another preferred name in the same locale");
}
} else {
//Enforce not-null on locale preferred field constraint from the database table
nameInLocale.setLocalePreferred(false);
reportUpdatedName(nameInLocale, "The locale preferred property of name '" + nameInLocale.getName()
+ "' in locale '" + conceptNameLocale.getDisplayName()
+ "' has been updated to false from null");
}
if (nameInLocale.isFullySpecifiedName()) {
if (!hasFullySpecifiedName)
hasFullySpecifiedName = true;
if (!fullySpecifiedNameForLocaleFound)
fullySpecifiedNameForLocaleFound = true;
else {
nameInLocale.setConceptNameType(null);
reportUpdatedName(nameInLocale, "The name '" + nameInLocale.getName() + "' in locale '"
+ conceptNameLocale.getDisplayName()
+ "' has been converted from fully specified to a synonym");
}
}
if (nameInLocale.isShort()) {
if (!shortNameForLocaleFound)
shortNameForLocaleFound = true;
//should have one short name per locale
else {
nameInLocale.setConceptNameType(null);
reportUpdatedName(nameInLocale, "The name '" + nameInLocale.getName() + "' in locale '"
+ conceptNameLocale.getDisplayName()
+ "' has been converted from a short name to a synonym");
}
}
if (nameInLocale.isFullySpecifiedName() || nameInLocale.isPreferred()) {
if (!isNameUniqueInLocale(connection, nameInLocale, conceptId)) {
if (localeDuplicateNamesMap == null)
localeDuplicateNamesMap = new HashMap<Locale, Set<String>>();
if (!localeDuplicateNamesMap.containsKey(conceptNameLocale))
localeDuplicateNamesMap.put(conceptNameLocale, new HashSet<String>());
localeDuplicateNamesMap.get(conceptNameLocale).add(nameInLocale.getName());
}
}
String name = nameInLocale.getName().toLowerCase();
if (!nameDuplicateConceptNamesMap.containsKey(name))
nameDuplicateConceptNamesMap.put(name, new ArrayList<ConceptName>());
nameDuplicateConceptNamesMap.get(name).add(nameInLocale);
}//close for each name
//No duplicate names allowed for the same locale and concept
for (Map.Entry<String, List<ConceptName>> entry : nameDuplicateConceptNamesMap.entrySet()) {
//no duplicates found for the current name
if (entry.getValue().size() < 2)
continue;
logMessages.add("The name '" + entry.getKey() + "' was found multiple times for the concept with id '"
+ conceptId + "' in locale '" + conceptNameLocale.getDisplayName() + "'");
ConceptName chosenName = null;
List<ConceptName> voidedNames = new ArrayList<ConceptName>();
for (ConceptName duplicate : entry.getValue()) {
//The first name found should be retained and void the rest of the duplicates
if (chosenName == null)
chosenName = duplicate;
else {
duplicate.setVoided(true);
duplicate.setVoidReason("Duplicate name in locale");
voidedNames.add(duplicate);
reportUpdatedName(duplicate, "ConceptName with id: " + duplicate.getConceptNameId()
+ " has been voided because it is a duplicate name for concept with id " + conceptId
+ " in locale '" + conceptNameLocale.getDisplayName() + "'");
}
}
}
//if this locale has no preferred name found, set one
if (!preferredNameForLocaleFound) {
//find the fully specified name and set it as the locale preferred
for (ConceptName cn : localeConceptNamesMap.get(conceptNameLocale)) {
if (cn.isFullySpecifiedName()) {
cn.setLocalePreferred(true);
preferredNameForLocaleFound = true;
break;
}
}
//if there was no fully specified name found, mark one of the synonyms as locale preferred
if (!preferredNameForLocaleFound)
for (ConceptName cn : localeConceptNamesMap.get(conceptNameLocale)) {
if (cn.isSynonym()) {
cn.setLocalePreferred(true);
break;
}
}
}
}//close for each locale
//Make the first name found the fully specified name if none exists
if (!hasFullySpecifiedName)
hasFullySpecifiedName = setFullySpecifiedName(conceptId, localeConceptNamesMap);
//set default locale for names with no locale, if there was no fully specified name for the current concept,
//set the first name found as the fully specified and drop locale preferred mark and short name concept name type
if (!CollectionUtils.isEmpty(namesWithNoLocale)) {
for (ConceptName conceptName : namesWithNoLocale) {
conceptName.setLocale(defaultLocale);
reportUpdatedName(conceptName, "The locale for ConceptName with id " + conceptName.getConceptNameId()
+ " (" + conceptName.getName() + ") has been set to '" + defaultLocale.getDisplayName() + "'");
if (!hasFullySpecifiedName) {
conceptName.setConceptNameType(ConceptNameType.FULLY_SPECIFIED);
hasFullySpecifiedName = true;
reportUpdatedName(conceptName, "ConceptName with id " + conceptName.getConceptNameId() + " ("
+ conceptName.getName() + ") in locale '" + defaultLocale.getDisplayName()
+ "' has been set as the fully specified name for concept with id : " + conceptId);
}
//convert to a synonym and should not be preferred, this will avoid inconsistencies, incase
//already short, fully specified and preferred names exist
else {
conceptName.setLocalePreferred(false);
reportUpdatedName(conceptName, "ConceptName with id " + conceptName.getConceptNameId() + " ("
+ conceptName.getName() + ") is no longer marked as preferred because it had no locale");
if (conceptName.isFullySpecifiedName() || conceptName.isShort()) {
conceptName.setConceptNameType(null);
reportUpdatedName(conceptName, "The name '" + conceptName.getName() + "' in locale '"
+ conceptName.toString() + "' has been converted to a synonym because it had no locale");
}
}
}
}
if (!hasFullySpecifiedName)
updateWarnings.add("Concept with id: " + conceptId + " has no fully specified name");
}
if (!MapUtils.isEmpty(localeDuplicateNamesMap)) {
for (Map.Entry<Locale, Set<String>> entry : localeDuplicateNamesMap.entrySet()) {
//no duplicates found in the locale
if (CollectionUtils.isEmpty(entry.getValue()))
continue;
for (String duplicateName : entry.getValue())
updateWarnings.add("Concept Name '" + duplicateName + "' was found multiple times in locale '"
+ entry.getKey() + "'");
}
}
logMessages.add("Number of Updated ConceptNames: " + updatedConceptNames.size());
}
/**
* Method writes the log messages and error warnings to the application data directory
*/
private void writeWarningsToFile() {
String lineSeparator = System.getProperty("line.separator");
StringBuilder sb = new StringBuilder("WARNINGS:").append(lineSeparator);
for (String warning : updateWarnings)
sb.append(lineSeparator).append(warning);
sb.append(lineSeparator).append(lineSeparator).append("NOTIFICATIONS:").append(lineSeparator);
for (String message : logMessages)
sb.append(lineSeparator).append(message);
DatabaseUpdater.writeUpdateMessagesToFile(sb.toString());
}
/**
* Sets the fully specified name from available names
*
* @param localeConceptNamesMap, list of all concept names for the concept
* @return
*/
private boolean setFullySpecifiedName(int conceptId, Map<Locale, List<ConceptName>> localeConceptNamesMap) {
//Pick the first name in any locale by searching in order from the allowed locales
for (Locale allowedLoc : allowedLocales) {
List<ConceptName> possibleFullySpecNames = localeConceptNamesMap.get(allowedLoc);
if (CollectionUtils.isEmpty(possibleFullySpecNames))
continue;
//try the synonyms
for (ConceptName cn : possibleFullySpecNames) {
if (cn.isSynonym()) {
cn.setConceptNameType(ConceptNameType.FULLY_SPECIFIED);
reportUpdatedName(cn, "ConceptName with id " + cn.getConceptNameId() + " (" + cn.getName()
+ ") in locale '" + allowedLoc.getDisplayName()
+ "' has been set as the fully specified name for concept with id : " + conceptId);
return true;
}
}
//try the short names
for (ConceptName cn : possibleFullySpecNames) {
if (cn.isShort()) {
cn.setConceptNameType(ConceptNameType.FULLY_SPECIFIED);
reportUpdatedName(cn, "ConceptName with id " + cn.getConceptNameId() + " (" + cn.getName()
+ ") in locale '" + allowedLoc.getDisplayName()
+ "' has been changed from short to fully specified name for concept with id : " + conceptId);
return true;
}
}
}
//pick a name randomly from the conceptName map
Set<Locale> localeSet = localeConceptNamesMap.keySet();
for (Iterator<Locale> localeIterator = localeSet.iterator(); localeIterator.hasNext();) {
Locale locale = localeIterator.next();
if (locale != null) {
ConceptName fullySpecName = localeConceptNamesMap.get(locale).get(0);
fullySpecName.setConceptNameType(ConceptNameType.FULLY_SPECIFIED);
reportUpdatedName(fullySpecName, "ConceptName with id " + fullySpecName.getConceptNameId() + " ("
+ fullySpecName.getName() + ") in locale '" + locale.getDisplayName()
+ "' has been set as the fully specified name for concept with id : " + conceptId);
return true;
}
}
//most probably this concept has no names added to it yet
return false;
}
/**
* Adds the specified concept name to the list of concept names to be updated and also adds the
* specified message to the list of messages/warnings to be reported after the database updater
* has terminated
*
* @param updatedName the name that has been updated
* @param updateMessage the message to report
*/
private void reportUpdatedName(ConceptName updatedName, String updateMessage) {
updatedConceptNames.add(updatedName);
logMessages.add(updateMessage);
}
/**
* Fetches all conceptIds for un retired concepts from the database.
*
* @param connection The database connection
* @return A list of all fetched conceptIds
*/
private List<Integer> getAllUnretiredConceptIds(DatabaseConnection connection) {
LinkedList<Integer> conceptIds = null;
Statement stmt = null;
try {
stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT concept_id FROM concept WHERE retired = 0");
while (rs.next()) {
if (conceptIds == null)
conceptIds = new LinkedList<Integer>();
conceptIds.add(rs.getInt("concept_id"));
}
}
catch (SQLException e) {
log.warn("Error generated", e);
}
finally {
if (stmt != null) {
try {
stmt.close();
}
catch (SQLException e) {
log.warn("Failed to close the statement object");
}
}
}
return conceptIds;
}
/**
* Checks if the conceptName is unique among all unvoided preferred and fully specified names
* across all other un-retired concepts except the concept it is associated to.
*
* @param connection The datbase Connection
* @param conceptName The conceptName to be validated
* @return true if the conceptName is unique, otherwise false
*/
private boolean isNameUniqueInLocale(DatabaseConnection connection, ConceptName conceptName, int conceptId) {
int duplicates = getInt(connection,
"SELECT count(*) FROM concept_name cn, concept c WHERE cn.concept_id = c.concept_id AND (cn.concept_name_type = '"
+ ConceptNameType.FULLY_SPECIFIED
+ "' OR cn.locale_preferred = 1) AND cn.voided = 0 AND cn.name = '"
+ HibernateUtil.escapeSqlWildcards(conceptName.getName(), connection.getUnderlyingConnection())
+ "' AND cn.locale = '"
+ HibernateUtil.escapeSqlWildcards(conceptName.getLocale().toString(), connection
.getUnderlyingConnection()) + "' AND c.retired = 0 AND c.concept_id != " + conceptId);
return duplicates == 0;
}
/**
* Retrieves the list of allowed locales from the database, sets the default locale, english and
* the default locale will be added to the list allowed locales if not yet included
*
* @param connection The database connection
* @return A list of allowed locales
*/
@SuppressWarnings("unchecked")
private List<Locale> getAllowedLocalesList(DatabaseConnection connection) {
Statement stmt = null;
ListOrderedSet allowedLocales = new ListOrderedSet();
try {
//get the default locale
stmt = connection.createStatement();
ResultSet rs_defaultLocale = stmt.executeQuery("SELECT property_value FROM global_property WHERE property = '"
+ OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_LOCALE + "'");
if (rs_defaultLocale.next()) {
String defaultLocaleStr = rs_defaultLocale.getString("property_value");
if (!StringUtils.isBlank(defaultLocaleStr) && defaultLocaleStr.length() > 1) {
Locale defaultLocale_GP = LocaleUtility.fromSpecification(defaultLocaleStr);
if (defaultLocale_GP != null)
defaultLocale = defaultLocale_GP;
} else
updateWarnings.add("'" + defaultLocaleStr
+ "' is an invalid value for the global property default locale");
}
allowedLocales.add(defaultLocale);
//get the locale.allowed.list
ResultSet rs_allowedLocales = stmt.executeQuery("SELECT property_value FROM global_property WHERE property = '"
+ OpenmrsConstants.GLOBAL_PROPERTY_LOCALE_ALLOWED_LIST + "'");
if (rs_allowedLocales.next()) {
String allowedLocaleStr = rs_allowedLocales.getString("property_value");
if (!StringUtils.isBlank(allowedLocaleStr)) {
String[] localesArray = allowedLocaleStr.split(",");
for (String localeStr : localesArray) {
if (localeStr.trim().length() > 1)
allowedLocales.add(LocaleUtility.fromSpecification(localeStr.trim()));
else
updateWarnings.add("'" + localeStr
+ "' is an invalid value for the global property locale.allowed.list");
}
}
} else
log.warn("The global property '" + OpenmrsConstants.GLOBAL_PROPERTY_LOCALE_ALLOWED_LIST + "' isn't set");
}
catch (SQLException e) {
log.warn("Error generated", e);
}
finally {
if (stmt != null) {
try {
stmt.close();
}
catch (SQLException e) {
log.warn("Failed to close the statement object");
}
}
}
//if it isn't among
allowedLocales.add(new Locale("en"));
return allowedLocales.asList();
}
/**
* Convenience Method that fetches all non-voided concept names from the database associated to
* a concept with a matching concept id, stores the names in a map with locales as the keys and
* the lists of conceptNames in each locale as the values i.e <Locale List<ConceptNames>>.
*
* @param connection a DatabaseConnection
* @param conceptId the conceptId for the conceptNames to fetch
* @return a map of Locale with ConceptNames in them associated to the concept identified by the
* given conceptId
*/
private Map<Locale, List<ConceptName>> getLocaleConceptNamesMap(DatabaseConnection connection, int conceptId) {
PreparedStatement pStmt = null;
Map<Locale, List<ConceptName>> localeConceptNamesMap = null;
try {
pStmt = connection
.prepareStatement("SELECT concept_name_id, name, concept_name_type, locale, locale_preferred FROM concept_name WHERE voided = 0 AND concept_id = ?");
pStmt.setInt(1, conceptId);
ResultSet rs = pStmt.executeQuery();
while (rs.next()) {
if (localeConceptNamesMap == null)
localeConceptNamesMap = new HashMap<Locale, List<ConceptName>>();
ConceptName conceptName = new ConceptName();
conceptName.setConceptNameId(rs.getInt("concept_name_id"));
conceptName.setName(rs.getString("name"));
String cnType = rs.getString("concept_name_type");
if (!StringUtils.isBlank(cnType)) {
ConceptNameType conceptNameType = null;
if (cnType.equals(ConceptNameType.FULLY_SPECIFIED.toString()))
conceptNameType = ConceptNameType.FULLY_SPECIFIED;
else if (cnType.equals(ConceptNameType.SHORT.toString()))
conceptNameType = ConceptNameType.SHORT;
else if (cnType.equals(ConceptNameType.INDEX_TERM.toString()))
conceptNameType = ConceptNameType.INDEX_TERM;
conceptName.setConceptNameType(conceptNameType);
}
String localeString = rs.getString("locale");
conceptName.setLocale(!StringUtils.isBlank(localeString) ? LocaleUtility.fromSpecification(localeString)
: null);
conceptName.setLocalePreferred((rs.getInt("locale_preferred") == 1) ? true : false);
conceptName.setVoided(false);
if (!localeConceptNamesMap.containsKey(conceptName.getLocale()))
localeConceptNamesMap.put(conceptName.getLocale(), new LinkedList<ConceptName>());
localeConceptNamesMap.get(conceptName.getLocale()).add(conceptName);
}
}
catch (SQLException e) {
log.warn("Error generated", e);
}
finally {
if (pStmt != null) {
try {
pStmt.close();
}
catch (SQLException e) {
log.warn("Failed to close the prepared statement object");
}
}
}
return localeConceptNamesMap;
}
/**
* Executes all the changes to the concept names as a batch update.
*
* @param connection The database connection
*/
private void runBatchUpdate(DatabaseConnection connection) {
PreparedStatement pStmt = null;
try {
connection.setAutoCommit(false);
pStmt = connection
.prepareStatement("UPDATE concept_name SET locale = ?, concept_name_type = ?, locale_preferred = ?, voided = ?, date_voided = ?, void_reason = ?, voided_by = ? WHERE concept_name_id = ?");
Integer userId = DatabaseUpdater.getAuthenticatedUserId();
//is we have no authenticated user(for API users), set as Daemon
if (userId == null || userId < 1) {
userId = getInt(connection, "SELECT min(user_id) FROM users");
//leave it as null rather than setting it to 0
if (userId < 1)
userId = null;
}
for (ConceptName conceptName : updatedConceptNames) {
pStmt.setString(1, conceptName.getLocale().toString());
pStmt.setString(2, (conceptName.getConceptNameType() != null) ? conceptName.getConceptNameType().toString()
: null);
pStmt.setBoolean(3, conceptName.isLocalePreferred());
pStmt.setBoolean(4, conceptName.isVoided());
pStmt.setDate(5, conceptName.isVoided() ? new Date(System.currentTimeMillis()) : null);
pStmt.setString(6, conceptName.getVoidReason());
pStmt.setString(7, (conceptName.isVoided() && userId != null) ? userId.toString() : null);
pStmt.setInt(8, conceptName.getConceptNameId());
pStmt.addBatch();
}
try {
int[] updateCounts = pStmt.executeBatch();
for (int i = 0; i < updateCounts.length; i++) {
if (updateCounts[i] > -1) {
log.debug("Successfully executed: updateCount=" + updateCounts[i]);
} else if (updateCounts[i] == Statement.SUCCESS_NO_INFO) {
log.debug("Successfully executed; No Success info");
} else if (updateCounts[i] == Statement.EXECUTE_FAILED) {
log.warn("Failed to execute update");
}
}
log.debug("Committing updates...");
connection.commit();
}
catch (BatchUpdateException be) {
log.warn("Error generated while processsing batch update", be);
int[] updateCounts = be.getUpdateCounts();
for (int i = 0; i < updateCounts.length; i++) {
if (updateCounts[i] > -1) {
log.warn("Executed with exception: updateCount=" + updateCounts[i]);
} else if (updateCounts[i] == Statement.SUCCESS_NO_INFO) {
log.warn("Executed with exception; No Success info");
} else if (updateCounts[i] == Statement.EXECUTE_FAILED) {
log.warn("Failed to execute update with exception");
}
}
try {
log.warn("Rolling back batch", be);
connection.rollback();
}
catch (Exception rbe) {
log.warn("Error generated while rolling back batch update", be);
}
}
}
catch (SQLException e) {
log.warn("Error generated", e);
}
finally {
//reset to auto commit mode
try {
connection.setAutoCommit(true);
}
catch (SQLException e) {
log.warn("Failed to reset auto commit back to true", e);
}
if (pStmt != null) {
try {
pStmt.close();
}
catch (SQLException e) {
log.warn("Failed to close the prepared statement object");
}
}
}
}
/**
* returns an integer resulting from the execution of an sql statement
*
* @param connection a DatabaseConnection
* @param sql the sql statement to execute
* @return integer resulting from the execution of the sql statement
*/
private int getInt(DatabaseConnection connection, String sql) {
Statement stmt = null;
int result = 0;
try {
stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
if (rs.next())
result = rs.getInt(1);
else
log.warn("No row returned by getInt() method");
if (rs.next()) {
log.warn("Multiple rows returned by getInt() method");
}
return result;
}
catch (SQLException e) {
log.warn("Error generated", e);
}
finally {
if (stmt != null) {
try {
stmt.close();
}
catch (SQLException e) {
log.warn("Failed to close the statement object");
}
}
}
return result;
}
/**
* @see liquibase.change.custom.CustomChange#getConfirmationMessage()
*/
public String getConfirmationMessage() {
return "Finished validating concepts";
}
/**
* @see liquibase.change.custom.CustomChange#setFileOpener(liquibase.FileOpener)
*/
public void setFileOpener(FileOpener fileOpener) {
}
/**
* @see liquibase.change.custom.CustomChange#setUp()
*/
public void setUp() throws SetupException {
}
/**
* @see liquibase.change.custom.CustomChange#validate(liquibase.database.Database)
*/
public void validate(Database database) throws InvalidChangeDefinitionException {
}
}