/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* 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 org.jumpmind.symmetric.io.data;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.jumpmind.db.model.Column;
import org.jumpmind.db.model.Table;
import org.jumpmind.util.LinkedCaseInsensitiveMap;
/**
* Holder for references to both parsed and unparsed CSV data.
*/
public class CsvData {
public static final int MAX_DATA_SIZE_TO_PRINT_TO_LOG = 1024 * 1000;
public static final String OLD_DATA = "oldData";
public static final String ROW_DATA = "rowData";
public static final String PK_DATA = "pkData";
public static final String RESOLVE_DATA = "resolveData";
public static final String ATTRIBUTE_TABLE_NAME = "tableName";
public static final String ATTRIBUTE_CHANNEL_ID = "channelId";
public static final String ATTRIBUTE_TABLE_ID = "tableId";
public static final String ATTRIBUTE_TX_ID = "transactionId";
public static final String ATTRIBUTE_SOURCE_NODE_ID = "sourceNodeId";
public static final String ATTRIBUTE_EXTERNAL_DATA = "externalData";
public static final String ATTRIBUTE_NODE_LIST = "nodeList";
public static final String ATTRIBUTE_ROUTER_ID = "routerId";
public static final String ATTRIBUTE_DATA_ID = "dataId";
public static final String ATTRIBUTE_CREATE_TIME = "createTime";
private Map<String, String[]> parsedCsvData = null;
private Map<String, String> csvData = null;
private Map<String, Object> attributes;
private boolean noBinaryOldData = false;
protected DataEventType dataEventType;
protected boolean[] changedDataIndicators;
public CsvData(DataEventType dataEventType) {
this.dataEventType = dataEventType;
}
public CsvData(DataEventType dataEventType, String[] pkData, String[] rowData) {
this(dataEventType);
this.putParsedData(PK_DATA, pkData);
this.putParsedData(ROW_DATA, rowData);
}
public CsvData(DataEventType dataEventType, String[] rowData) {
this(dataEventType);
this.putParsedData(ROW_DATA, rowData);
}
public CsvData(DataEventType dataEventType, String[] rowData, String[] oldData,
String[] resolveData) {
this(dataEventType);
this.putParsedData(ROW_DATA, rowData);
this.putParsedData(OLD_DATA, oldData);
this.putParsedData(RESOLVE_DATA, resolveData);
}
public CsvData() {
}
public boolean contains(String key) {
return (parsedCsvData != null && parsedCsvData.get(key) != null)
|| (csvData != null && csvData.get(key) != null);
}
public void setDataEventType(DataEventType dataEventType) {
this.dataEventType = dataEventType;
}
public DataEventType getDataEventType() {
return dataEventType;
}
public void putAttribute(String attributeName, Object attributeValue) {
if (attributes == null) {
attributes = new HashMap<String, Object>();
}
attributes.put(attributeName, attributeValue);
}
@SuppressWarnings("unchecked")
public <T> T getAttribute(String attributeName) {
return attributes == null ? null : (T) attributes.get(attributeName);
}
public void removeCsvData(String key) {
if (csvData != null) {
csvData.remove(key);
}
}
public void removeParsedData(String key) {
if (parsedCsvData != null) {
parsedCsvData.remove(key);
}
}
public void removeAllData(String key) {
removeParsedData(key);
removeCsvData(key);
}
public void putCsvData(String key, String data) {
removeAllData(key);
if (csvData == null) {
csvData = new HashMap<String, String>(2);
}
changedDataIndicators = null;
csvData.put(key, data);
}
public String getCsvData(String key) {
String data = null;
if (csvData != null) {
data = csvData.get(key);
}
if (data == null && parsedCsvData != null) {
String[] parsedData = parsedCsvData.get(key);
if (parsedData != null) {
data = CsvUtils.escapeCsvData(parsedData);
// swap out data for parsed data so we don't
// don't double the amount of memory being used
putCsvData(key, data);
}
}
return data;
}
public boolean[] getChangedDataIndicators() {
if (changedDataIndicators == null) {
String[] newData = getParsedData(ROW_DATA);
boolean[] changes = new boolean[newData.length];
String[] oldData = getParsedData(OLD_DATA);
for (int i = 0; i < newData.length; i++) {
if (oldData != null && oldData.length > i) {
if (newData[i] == null) {
changes[i] = oldData[i] != null;
} else if (oldData[i] == null) {
changes[i] = newData[i] != null;
} else {
changes[i] = !newData[i].equals(oldData[i]);
}
} else {
changes[i] = true;
}
}
changedDataIndicators = changes;
}
return changedDataIndicators;
}
public void putParsedData(String key, String[] data) {
removeAllData(key);
if (parsedCsvData == null) {
parsedCsvData = new HashMap<String, String[]>(2);
}
changedDataIndicators = null;
parsedCsvData.put(key, data);
}
public String[] getParsedData(String key) {
String[] values = null;
if (parsedCsvData != null && parsedCsvData.containsKey(key)) {
values = parsedCsvData.get(key);
} else if (csvData != null && csvData.containsKey(key)) {
String data = csvData.get(key);
if (data != null) {
values = CsvUtils.tokenizeCsvData(data);
putParsedData(key, values);
}
}
return values;
}
public Map<String, String> toKeyColumnValuePairs(Table table) {
Map<String, String> data = toColumnNameValuePairs(table.getPrimaryKeyColumnNames(), CsvData.PK_DATA);
if (data.size() == 0) {
data = toColumnNameValuePairs(table.getColumnNames(), CsvData.OLD_DATA);
if (data.size() == 0) {
data = toColumnNameValuePairs(table.getColumnNames(), CsvData.ROW_DATA);
}
Column[] columns = table.getColumns();
for (Column column : columns) {
if (!column.isPrimaryKey()) {
data.remove(column.getName());
}
}
}
return data;
}
public String[] getPkData(Table table) {
Map<String, String> data = toKeyColumnValuePairs(table);
return data.values().toArray(new String[data.size()]);
}
public Map<String, String> toColumnNameValuePairs(String[] keyNames, String key) {
String[] values = getParsedData(key);
if (values != null && keyNames != null && values.length >= keyNames.length) {
Map<String, String> map = new LinkedCaseInsensitiveMap<String>(keyNames.length);
for (int i = 0; i < keyNames.length; i++) {
map.put(keyNames[i], values[i]);
}
return map;
} else {
return new HashMap<String, String>(0);
}
}
public boolean requiresTable() {
return dataEventType != null && dataEventType != DataEventType.CREATE
&& dataEventType != DataEventType.BSH;
}
public boolean isNoBinaryOldData() {
return noBinaryOldData;
}
public void setNoBinaryOldData(boolean noBinaryOldData) {
this.noBinaryOldData = noBinaryOldData;
}
public CsvData copyWithoutOldData() {
CsvData data = new CsvData(getDataEventType(), getParsedData(CsvData.ROW_DATA));
data.attributes = attributes;
return data;
}
public void writeCsvDataDetails (StringBuilder message) {
String rowData = getCsvData(CsvData.PK_DATA);
if (StringUtils.isNotBlank(rowData)) {
message.append("Failed pk data was: ");
message.append(rowData);
message.append("\n");
}
rowData = getCsvData(CsvData.ROW_DATA);
if (StringUtils.isNotBlank(rowData)) {
if (rowData.length() < MAX_DATA_SIZE_TO_PRINT_TO_LOG) {
message.append("Failed row data was: ");
message.append(rowData);
message.append("\n");
} else {
message.append("Row data was bigger than ");
message.append(MAX_DATA_SIZE_TO_PRINT_TO_LOG);
message.append(" bytes (it was ");
message.append(rowData.length());
message.append(" bytes). It will not be printed to the log file");
}
}
rowData = getCsvData(CsvData.OLD_DATA);
if (StringUtils.isNotBlank(rowData)) {
if (rowData.length() < MAX_DATA_SIZE_TO_PRINT_TO_LOG) {
message.append("Failed old data was: ");
message.append(rowData);
message.append("\n");
} else {
message.append("Old data was bigger than ");
message.append(MAX_DATA_SIZE_TO_PRINT_TO_LOG);
message.append(" bytes (it was ");
message.append(rowData.length());
message.append(" bytes). It will not be printed to the log file");
}
}
}
public long getSizeInBytes() {
long size = 0;
if (csvData != null) {
Collection<String> values = csvData.values();
for (String string : values) {
if (string != null) {
size += string.getBytes().length;
}
}
}
return size;
}
}