/** * Copyright 2008-2016 Qualogy Solutions B.V. * * Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.qualogy.qafe.business.integration.rdb; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; import com.qualogy.qafe.business.integration.adapter.AdaptedToService; import com.qualogy.qafe.core.conflictdetection.ConflicDetectionConstants; import com.qualogy.qafe.core.conflictdetection.UpdateConflictException; public class ConflictDetectionUtil { public static Long removeChecksum(Map<String, AdaptedToService> paramsIn) { AdaptedToService checksumContainer = paramsIn.remove(ConflicDetectionConstants.QAFE_CHECKSUM); if(checksumContainer == null || checksumContainer.getValue().toString().isEmpty()) { return null; } return Long.valueOf(checksumContainer.getValue().toString()); } public static void addChecksums(List<Map<String, Object>> recordList, String sql) { if (recordList == null) { return; } if (hasColumnAliases(sql)) { return; } for (Map<String, Object> record : recordList) { record.put(ConflicDetectionConstants.QAFE_CHECKSUM, calculateChecksum(record)); } } public static void validateChecksum(SimpleJdbcTemplate template, String updateQuery, Map<String, Object> localRecord, Long oldChecksum) { if (oldChecksum != null) { Map<String, Object> localRecordContent = getContent(localRecord); String sqlSelectByPk = createSelectByPkQuery(localRecordContent.keySet(), updateQuery); Map<String, Object> remoteRecord = template.queryForMap(sqlSelectByPk, localRecord); if (hasChanged(remoteRecord, oldChecksum)) { throw new UpdateConflictException(localRecordContent); } } } private static boolean hasColumnAliases(String sql) { String normalizedSql = sql.trim().toUpperCase().replaceAll("\\s+AS\\s+", " "); return normalizedSql.matches("^SELECT\\s.*?\\w \\w.*?\\sFROM\\s.+"); } private static Map<String, Object> getContent(Map<String, Object> localRecord) { Map<String, Object> shallowClone = new HashMap<String, Object>(localRecord); removeMetaData(shallowClone); return shallowClone; } private static boolean hasChanged(Map<String, Object> newContent, long oldChecksum) { return calculateChecksum(newContent) != oldChecksum; } private static long calculateChecksum(Map<String, Object> record) { long checksum = 0; for (Map.Entry<String, Object> field : record.entrySet()) { if (!isMetaData(field.getKey())) { checksum += calculateChecksum(field.getKey(), field.getValue()); } } return checksum; } /** * IMPORTANT: this method must be an exact copy of the one used in the qafe-web-gwt project */ private static long calculateChecksum(String key, Object value) { long checksum = key.hashCode(); if (value instanceof Number) { checksum += value.toString().hashCode(); } else if (value != null) { checksum += value.hashCode(); } return checksum; } private static String createSelectByPkQuery(Set<String> columnNames, String updateQuery) { String updateSql = updateQuery.trim().toUpperCase(); updateSql = updateSql.replaceAll("\\r|\\n", " "); String find = "^UPDATE\\s+(\\w+).*?(\\sWHERE\\s.*)"; String fromAndWhereClauseTemplate = " FROM $1$2"; String fromAndWhereClause = updateSql.replaceFirst(find, fromAndWhereClauseTemplate); StringBuilder sql = new StringBuilder("SELECT "); for (String columnName : columnNames) { sql.append(columnName).append(","); } sql.deleteCharAt(sql.length() - 1); sql.append(fromAndWhereClause); return sql.toString(); } private static boolean isMetaData(String key) { if (key == null) { return false; } return ConflicDetectionConstants.CONSTANTS.contains(key.toUpperCase()); } private static void removeMetaData(Map<String,Object> record) { if (record == null) { return; } List<String> keys = new ArrayList<String>(); Iterator<String> itrKey = record.keySet().iterator(); while (itrKey.hasNext()) { String key = itrKey.next(); if (isMetaData(key)) { keys.add(key); } } for (String key : keys) { record.remove(key); } } }