/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * 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 org.pentaho.di.trans.steps.checksum; import java.security.MessageDigest; import java.util.zip.Adler32; import java.util.zip.CRC32; import org.apache.commons.codec.binary.Hex; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.row.RowDataUtil; import org.pentaho.di.core.util.Utils; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.BaseStep; import org.pentaho.di.trans.step.StepDataInterface; import org.pentaho.di.trans.step.StepInterface; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.step.StepMetaInterface; /** * Caculate a checksum for each row. * * @author Samatar Hassan * @since 30-06-2008 */ public class CheckSum extends BaseStep implements StepInterface { private static Class<?> PKG = CheckSumMeta.class; // for i18n purposes, needed by Translator2!! private CheckSumMeta meta; private CheckSumData data; public CheckSum( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans ) { super( stepMeta, stepDataInterface, copyNr, transMeta, trans ); } @SuppressWarnings( "deprecation" ) @Override public boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleException { meta = (CheckSumMeta) smi; data = (CheckSumData) sdi; Object[] r = getRow(); // get row, set busy! if ( r == null ) { // no more input to be expected... setOutputDone(); return false; } if ( first ) { first = false; data.outputRowMeta = getInputRowMeta().clone(); data.nrInfields = data.outputRowMeta.size(); meta.getFields( data.outputRowMeta, getStepname(), null, null, this, repository, metaStore ); if ( meta.getFieldName() == null || meta.getFieldName().length > 0 ) { data.fieldnrs = new int[meta.getFieldName().length]; for ( int i = 0; i < meta.getFieldName().length; i++ ) { data.fieldnrs[i] = getInputRowMeta().indexOfValue( meta.getFieldName()[i] ); if ( data.fieldnrs[i] < 0 ) { logError( BaseMessages.getString( PKG, "CheckSum.Log.CanNotFindField", meta.getFieldName()[i] ) ); throw new KettleException( BaseMessages.getString( PKG, "CheckSum.Log.CanNotFindField", meta .getFieldName()[i] ) ); } } } else { data.fieldnrs = new int[r.length]; for ( int i = 0; i < r.length; i++ ) { data.fieldnrs[i] = i; } } data.fieldnr = data.fieldnrs.length; try { if ( meta.getCheckSumType().equals( CheckSumMeta.TYPE_MD5 ) || meta.getCheckSumType().equals( CheckSumMeta.TYPE_SHA1 ) || meta.getCheckSumType().equals( CheckSumMeta.TYPE_SHA256 ) ) { data.digest = MessageDigest.getInstance( meta.getCheckSumType() ); } } catch ( Exception e ) { throw new KettleException( BaseMessages.getString( PKG, "CheckSum.Error.Digest" ), e ); } } // end if first Object[] outputRowData = null; try { if ( meta.getCheckSumType().equals( CheckSumMeta.TYPE_ADLER32 ) || meta.getCheckSumType().equals( CheckSumMeta.TYPE_CRC32 ) ) { // get checksum Long checksum = calculCheckSum( r ); outputRowData = RowDataUtil.addValueData( r, data.nrInfields, checksum ); } else { // get checksum byte[] o = createCheckSum( r ); switch ( meta.getResultType() ) { case CheckSumMeta.result_TYPE_BINARY: outputRowData = RowDataUtil.addValueData( r, data.nrInfields, o ); break; case CheckSumMeta.result_TYPE_HEXADECIMAL: String hex = meta.isCompatibilityMode() ? byteToHexEncode_compatible( o ) : new String( Hex.encodeHex( o ) ); outputRowData = RowDataUtil.addValueData( r, data.nrInfields, hex ); break; default: outputRowData = RowDataUtil.addValueData( r, data.nrInfields, getStringFromBytes( o ) ); break; } } if ( checkFeedback( getLinesRead() ) ) { if ( log.isDetailed() ) { logDetailed( BaseMessages.getString( PKG, "CheckSum.Log.LineNumber", Long.toString( getLinesRead() ) ) ); } } // add new values to the row. putRow( data.outputRowMeta, outputRowData ); // copy row to output // rowset(s); } catch ( Exception e ) { boolean sendToErrorRow = false; String errorMessage = null; if ( getStepMeta().isDoingErrorHandling() ) { sendToErrorRow = true; errorMessage = e.toString(); } else { logError( BaseMessages.getString( PKG, "CheckSum.ErrorInStepRunning" ) + e.getMessage() ); setErrors( 1 ); stopAll(); setOutputDone(); // signal end to receiver(s) return false; } if ( sendToErrorRow ) { // Simply add this row to the error row putError( getInputRowMeta(), r, 1, errorMessage, meta.getResultFieldName(), "CheckSum001" ); } } return true; } private byte[] createCheckSum( Object[] r ) throws Exception { StringBuilder Buff = new StringBuilder(); // Loop through fields for ( int i = 0; i < data.fieldnr; i++ ) { String fieldvalue = getInputRowMeta().getString( r, data.fieldnrs[i] ); Buff.append( fieldvalue ); } // Updates the digest using the specified array of bytes data.digest.update( Buff.toString().getBytes() ); // Completes the hash computation by performing final operations such as padding byte[] hash = data.digest.digest(); // After digest has been called, the MessageDigest object is reset to its initialized state return hash; } private static String getStringFromBytes( byte[] bytes ) { StringBuilder sb = new StringBuilder(); for ( int i = 0; i < bytes.length; i++ ) { byte b = bytes[i]; sb.append( 0x00FF & b ); if ( i + 1 < bytes.length ) { sb.append( "-" ); } } return sb.toString(); } public String byteToHexEncode_compatible( byte[] in ) { if ( in == null ) { return null; } final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; String hex = new String( in ); char[] s = hex.toCharArray(); StringBuilder hexString = new StringBuilder( 2 * s.length ); for ( int i = 0; i < s.length; i++ ) { hexString.append( hexDigits[( s[i] & 0x00F0 ) >> 4] ); // hi nibble hexString.append( hexDigits[s[i] & 0x000F] ); // lo nibble } return hexString.toString(); } private Long calculCheckSum( Object[] r ) throws Exception { Long retval; StringBuilder Buff = new StringBuilder(); // Loop through fields for ( int i = 0; i < data.fieldnr; i++ ) { String fieldvalue = getInputRowMeta().getString( r, data.fieldnrs[i] ); Buff.append( fieldvalue ); } if ( meta.getCheckSumType().equals( CheckSumMeta.TYPE_CRC32 ) ) { CRC32 crc32 = new CRC32(); crc32.update( Buff.toString().getBytes() ); retval = new Long( crc32.getValue() ); } else { Adler32 adler32 = new Adler32(); adler32.update( Buff.toString().getBytes() ); retval = new Long( adler32.getValue() ); } return retval; } @SuppressWarnings( "deprecation" ) @Override public boolean init( StepMetaInterface smi, StepDataInterface sdi ) { meta = (CheckSumMeta) smi; data = (CheckSumData) sdi; if ( super.init( smi, sdi ) ) { if ( Utils.isEmpty( meta.getResultFieldName() ) ) { logError( BaseMessages.getString( PKG, "CheckSum.Error.ResultFieldMissing" ) ); return false; } if ( meta.isCompatibilityMode() && meta.getCheckSumType() == CheckSumMeta.TYPE_SHA256 ) { logError( BaseMessages.getString( PKG, "CheckSumMeta.CheckResult.CompatibilityModeSHA256Error" ) ); return false; } return true; } return false; } }