/** * 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.route; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.jumpmind.db.platform.DatabaseNamesConstants; import org.jumpmind.symmetric.SymmetricException; import org.jumpmind.symmetric.db.ISymmetricDialect; import org.jumpmind.symmetric.io.data.DataEventType; import org.jumpmind.symmetric.model.DataMetaData; import org.jumpmind.symmetric.model.Node; import org.jumpmind.symmetric.model.OutgoingBatch; import org.jumpmind.util.LinkedCaseInsensitiveMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A common superclass for data routers */ public abstract class AbstractDataRouter implements IDataRouter { private static final String OLD_ = "OLD_"; protected Logger log = LoggerFactory.getLogger(getClass()); public void contextCommitted(SimpleRouterContext context) { } protected Map<String, String> getDataMap(DataMetaData dataMetaData, ISymmetricDialect symmetricDialect) { Map<String, String> data = null; DataEventType dml = dataMetaData.getData().getDataEventType(); switch (dml) { case UPDATE: data = new LinkedCaseInsensitiveMap<String>(dataMetaData.getTable().getColumnCount() * 4); data.putAll(getNewDataAsString(null, dataMetaData, symmetricDialect)); data.putAll(getOldDataAsString(OLD_, dataMetaData, symmetricDialect)); break; case INSERT: data = new LinkedCaseInsensitiveMap<String>(dataMetaData.getTable().getColumnCount() * 4); data.putAll(getNewDataAsString(null, dataMetaData, symmetricDialect)); Map<String, String> map = getNullData(OLD_, dataMetaData); data.putAll(map); break; case DELETE: data = new LinkedCaseInsensitiveMap<String>(dataMetaData.getTable().getColumnCount() * 4); data.putAll(getOldDataAsString(null, dataMetaData, symmetricDialect)); data.putAll(getOldDataAsString(OLD_, dataMetaData, symmetricDialect)); break; default: data = new LinkedCaseInsensitiveMap<String>(1); break; } if (data != null) { if (data.size() == 0) { data.putAll(getPkDataAsString(dataMetaData, symmetricDialect)); } data.put("EXTERNAL_DATA", dataMetaData.getData().getExternalData()); } return data; } protected Map<String, String> getNewDataAsString(String prefix, DataMetaData dataMetaData, ISymmetricDialect symmetricDialect) { String[] rowData = dataMetaData.getData().toParsedRowData(); return getDataAsString(prefix, dataMetaData, symmetricDialect, rowData); } protected Map<String, String> getOldDataAsString(String prefix, DataMetaData dataMetaData, ISymmetricDialect symmetricDialect) { String[] rowData = dataMetaData.getData().toParsedOldData(); return getDataAsString(prefix, dataMetaData, symmetricDialect, rowData); } protected Map<String, String> getDataAsString(String prefix, DataMetaData dataMetaData, ISymmetricDialect symmetricDialect, String[] rowData) { String[] columns = dataMetaData.getTriggerHistory().getParsedColumnNames(); Map<String, String> map = new LinkedCaseInsensitiveMap<String>(columns.length * 2); if (rowData != null) { testColumnNamesMatchValues(dataMetaData, symmetricDialect, columns, rowData); for (int i = 0; i < columns.length; i++) { String columnName = columns[i]; columnName = prefix != null ? prefix + columnName : columnName; map.put(columnName, rowData[i]); map.put(columnName.toUpperCase(), rowData[i]); } } return map; } protected Map<String, String> getPkDataAsString(DataMetaData dataMetaData, ISymmetricDialect symmetricDialect) { String[] columns = dataMetaData.getTriggerHistory().getParsedPkColumnNames(); String[] rowData = dataMetaData.getData().toParsedPkData(); Map<String, String> map = new LinkedCaseInsensitiveMap<String>(columns.length); if (rowData != null) { testColumnNamesMatchValues(dataMetaData, symmetricDialect, columns, rowData); for (int i = 0; i < columns.length; i++) { String columnName = columns[i].toUpperCase(); map.put(columnName, rowData[i]); } } return map; } protected Map<String, Object> getDataObjectMap(DataMetaData dataMetaData, ISymmetricDialect symmetricDialect, boolean upperCase) { Map<String, Object> data = null; DataEventType dml = dataMetaData.getData().getDataEventType(); switch (dml) { case UPDATE: data = new LinkedCaseInsensitiveMap<Object>(dataMetaData.getTable().getColumnCount() * 2); data.putAll(getNewDataAsObject(null, dataMetaData, symmetricDialect, upperCase)); data.putAll(getOldDataAsObject(OLD_, dataMetaData, symmetricDialect, upperCase)); break; case INSERT: data = new LinkedCaseInsensitiveMap<Object>(dataMetaData.getTable().getColumnCount() * 2); data.putAll(getNewDataAsObject(null, dataMetaData, symmetricDialect, upperCase)); Map<String, Object> map = getNullData(OLD_, dataMetaData); data.putAll(map); break; case DELETE: data = new LinkedCaseInsensitiveMap<Object>(dataMetaData.getTable().getColumnCount() * 2); data.putAll(getOldDataAsObject(null, dataMetaData, symmetricDialect, upperCase)); data.putAll(getOldDataAsObject(OLD_, dataMetaData, symmetricDialect, upperCase)); if (data.size() == 0) { data.putAll(getPkDataAsObject(dataMetaData, symmetricDialect)); } break; default: break; } if (data != null && data.size() == 0) { data.putAll(getPkDataAsString(dataMetaData, symmetricDialect)); } if (StringUtils.isNotBlank(dataMetaData.getData().getExternalData())) { if (data == null) { data = new LinkedCaseInsensitiveMap<Object>(1); } data.put("EXTERNAL_DATA", dataMetaData.getData().getExternalData()); } else if (data != null) { data.put("EXTERNAL_DATA", null); } return data; } protected Map<String, Object> getNewDataAsObject(String prefix, DataMetaData dataMetaData, ISymmetricDialect symmetricDialect, boolean upperCase) { return getDataAsObject(prefix, dataMetaData, symmetricDialect, dataMetaData.getData() .toParsedRowData(), upperCase); } protected Map<String, Object> getOldDataAsObject(String prefix, DataMetaData dataMetaData, ISymmetricDialect symmetricDialect, boolean upperCase) { return getDataAsObject(prefix, dataMetaData, symmetricDialect, dataMetaData.getData() .toParsedOldData(), upperCase); } protected <T> Map<String, T> getNullData(String prefix, DataMetaData dataMetaData) { String[] columnNames = dataMetaData.getTriggerHistory().getParsedColumnNames(); Map<String, T> data = new LinkedCaseInsensitiveMap<T>(columnNames.length * 2); for (String columnName : columnNames) { columnName = prefix != null ? prefix + columnName : columnName; data.put(columnName, null); data.put(columnName.toUpperCase(), null); } return data; } protected Map<String, Object> getDataAsObject(String prefix, DataMetaData dataMetaData, ISymmetricDialect symmetricDialect, String[] rowData, boolean upperCase) { if (rowData != null) { Map<String, Object> data = new LinkedCaseInsensitiveMap<Object>(rowData.length); String[] columnNames = dataMetaData.getTriggerHistory().getParsedColumnNames(); Object[] objects = symmetricDialect.getPlatform().getObjectValues( symmetricDialect.getBinaryEncoding(), dataMetaData.getTable(), columnNames, rowData); testColumnNamesMatchValues(dataMetaData, symmetricDialect, columnNames, objects); for (int i = 0; i < columnNames.length; i++) { String colName = upperCase ? columnNames[i].toUpperCase() : columnNames[i]; data.put(prefix != null ? (prefix + colName) : colName, objects[i]); } return data; } else { return Collections.emptyMap(); } } protected void testColumnNamesMatchValues(DataMetaData dataMetaData, ISymmetricDialect symmetricDialect, String[] columnNames, Object[] values) { if (columnNames.length != values.length) { String additionalErrorMessage = ""; if (symmetricDialect != null && symmetricDialect.getPlatform().getName().equals(DatabaseNamesConstants.ORACLE)) { boolean isContainsBigLobs = dataMetaData.getNodeChannel().isContainsBigLob(); additionalErrorMessage += String.format("\nOne possible cause of this issue is when channel.contains_big_lobs=0 and the captured row_data size exceeds 4k, captured data will be truncated at 4k. channel.contains_big_lobs is currently set to %s.", isContainsBigLobs ? "1" : "0"); } String message = String.format( "The number of recorded column names (%d) did not match the number of captured data values (%d). The data_id %d failed for an %s on %s. %s\ncolumn_names:\n%s\nvalues:\n%s", columnNames.length, values.length, dataMetaData.getData().getDataId(), dataMetaData.getData().getDataEventType().name(), dataMetaData.getData().getTableName(), additionalErrorMessage, ArrayUtils.toString(columnNames), ArrayUtils.toString(values)); throw new SymmetricException(message); } } protected Map<String, Object> getPkDataAsObject(DataMetaData dataMetaData, ISymmetricDialect symmetricDialect) { String[] rowData = dataMetaData.getData().toParsedPkData(); if (rowData != null) { Map<String, Object> data = new LinkedCaseInsensitiveMap<Object>(rowData.length); String[] columnNames = dataMetaData.getTriggerHistory().getParsedPkColumnNames(); Object[] objects = symmetricDialect.getPlatform().getObjectValues( symmetricDialect.getBinaryEncoding(), dataMetaData.getTable(), columnNames, rowData); testColumnNamesMatchValues(dataMetaData, symmetricDialect, columnNames, objects); for (int i = 0; i < columnNames.length; i++) { data.put(columnNames[i].toUpperCase(), objects[i]); } return data; } else { return Collections.emptyMap(); } } protected Set<String> addNodeId(String nodeId, Set<String> nodeIds, Set<Node> nodes) { nodeIds = nodeIds == null ? new HashSet<String>(1) : nodeIds; for (Node node : nodes) { if (node.getNodeId().equals(nodeId)) { nodeIds.add(nodeId); break; } } return nodeIds; } protected Set<String> toNodeIds(Set<Node> nodes, Set<String> nodeIds) { nodeIds = nodeIds == null ? new HashSet<String>(nodes.size()) : nodeIds; for (Node node : nodes) { nodeIds.add(node.getNodeId()); } return nodeIds; } protected Set<String> toExternalIds(Set<Node> nodes) { Set<String> externalIds = new HashSet<String>(); for (Node node : nodes) { externalIds.add(node.getExternalId()); } return externalIds; } /** * Override if needed. */ public void completeBatch(SimpleRouterContext context, OutgoingBatch batch) { log.debug("Completing batch {}", batch.getBatchId()); } /** * Override if a router is not configurable. */ public boolean isConfigurable() { return true; } }