/*! ****************************************************************************** * * 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.salesforceinput; import java.text.SimpleDateFormat; import java.util.GregorianCalendar; import org.pentaho.di.core.Const; import org.pentaho.di.core.util.Utils; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.row.RowDataUtil; import org.pentaho.di.core.row.RowMeta; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.row.ValueMetaInterface; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.StepDataInterface; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.step.StepMetaInterface; import org.pentaho.di.trans.steps.salesforce.SalesforceConnectionUtils; import org.pentaho.di.trans.steps.salesforce.SalesforceRecordValue; import org.pentaho.di.trans.steps.salesforce.SalesforceStep; /** * Read data from Salesforce module, convert them to rows and writes these to one or more output streams. * * @author Samatar * @since 10-06-2007 */ public class SalesforceInput extends SalesforceStep { private static Class<?> PKG = SalesforceInputMeta.class; // for i18n purposes, needed by Translator2!! private SalesforceInputMeta meta; private SalesforceInputData data; public SalesforceInput( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans ) { super( stepMeta, stepDataInterface, copyNr, transMeta, trans ); } @Override public boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleException { if ( first ) { first = false; // Create the output row meta-data data.outputRowMeta = new RowMeta(); meta.getFields( data.outputRowMeta, getStepname(), null, null, this, repository, metaStore ); // For String to <type> conversions, we allocate a conversion meta data row as well... // data.convertRowMeta = data.outputRowMeta.cloneToType( ValueMetaInterface.TYPE_STRING ); // Let's query Salesforce data.connection.query( meta.isSpecifyQuery() ); data.limitReached = true; data.recordcount = data.connection.getQueryResultSize(); if ( data.recordcount > 0 ) { data.limitReached = false; data.nrRecords = data.connection.getRecordsCount(); } if ( log.isDetailed() ) { logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.RecordCount" ) + " : " + data.recordcount ); } } Object[] outputRowData = null; try { // get one row ... outputRowData = getOneRow(); if ( outputRowData == null ) { setOutputDone(); return false; } putRow( data.outputRowMeta, outputRowData ); // copy row to output rowset(s); if ( checkFeedback( getLinesInput() ) ) { if ( log.isDetailed() ) { logDetailed( BaseMessages.getString( PKG, "SalesforceInput.log.LineRow", "" + getLinesInput() ) ); } } data.rownr++; data.recordIndex++; return true; } catch ( KettleException e ) { boolean sendToErrorRow = false; String errorMessage = null; if ( getStepMeta().isDoingErrorHandling() ) { sendToErrorRow = true; errorMessage = e.toString(); } else { logError( BaseMessages.getString( PKG, "SalesforceInput.log.Exception", e.getMessage() ) ); logError( Const.getStackTracker( e ) ); setErrors( 1 ); stopAll(); setOutputDone(); // signal end to receiver(s) return false; } if ( sendToErrorRow ) { // Simply add this row to the error row putError( getInputRowMeta(), outputRowData, 1, errorMessage, null, "SalesforceInput001" ); } } return true; } private Object[] getOneRow() throws KettleException { if ( data.limitReached || data.rownr >= data.recordcount ) { return null; } // Build an empty row based on the meta-data Object[] outputRowData = buildEmptyRow(); try { // check for limit rows if ( data.limit > 0 && data.rownr >= data.limit ) { // User specified limit and we reached it // We end here data.limitReached = true; return null; } else { if ( data.rownr >= data.nrRecords || data.finishedRecord ) { if ( meta.getRecordsFilter() != SalesforceConnectionUtils.RECORDS_FILTER_UPDATED ) { // We retrieved all records available here // maybe we need to query more again ... if ( log.isDetailed() ) { logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.NeedQueryMore", "" + data.rownr ) ); } if ( data.connection.queryMore() ) { // We returned more result (query is not done yet) int nr = data.connection.getRecordsCount(); data.nrRecords += nr; if ( log.isDetailed() ) { logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.QueryMoreRetrieved", "" + nr ) ); } // We need here to initialize recordIndex data.recordIndex = 0; data.finishedRecord = false; } else { // Query is done .. we finished ! return null; } } } } // Return a record SalesforceRecordValue srvalue = data.connection.getRecord( data.recordIndex ); data.finishedRecord = srvalue.isAllRecordsProcessed(); if ( meta.getRecordsFilter() == SalesforceConnectionUtils.RECORDS_FILTER_DELETED ) { if ( srvalue.isRecordIndexChanges() ) { // We have moved forward... data.recordIndex = srvalue.getRecordIndex(); } if ( data.finishedRecord && srvalue.getRecordValue() == null ) { // We processed all records return null; } } for ( int i = 0; i < data.nrfields; i++ ) { String value = data.connection.getRecordValue( srvalue.getRecordValue(), meta.getInputFields()[i].getField() ); // DO Trimming! switch ( meta.getInputFields()[i].getTrimType() ) { case SalesforceInputField.TYPE_TRIM_LEFT: value = Const.ltrim( value ); break; case SalesforceInputField.TYPE_TRIM_RIGHT: value = Const.rtrim( value ); break; case SalesforceInputField.TYPE_TRIM_BOTH: value = Const.trim( value ); break; default: break; } // DO CONVERSIONS... // ValueMetaInterface targetValueMeta = data.outputRowMeta.getValueMeta( i ); ValueMetaInterface sourceValueMeta = data.convertRowMeta.getValueMeta( i ); outputRowData[i] = targetValueMeta.convertData( sourceValueMeta, value ); // Do we need to repeat this field if it is null? if ( meta.getInputFields()[i].isRepeated() ) { if ( data.previousRow != null && Utils.isEmpty( value ) ) { outputRowData[i] = data.previousRow[i]; } } } // End of loop over fields... int rowIndex = data.nrfields; // See if we need to add the url to the row... if ( meta.includeTargetURL() && !Utils.isEmpty( meta.getTargetURLField() ) ) { outputRowData[rowIndex++] = data.connection.getURL(); } // See if we need to add the module to the row... if ( meta.includeModule() && !Utils.isEmpty( meta.getModuleField() ) ) { outputRowData[rowIndex++] = data.connection.getModule(); } // See if we need to add the generated SQL to the row... if ( meta.includeSQL() && !Utils.isEmpty( meta.getSQLField() ) ) { outputRowData[rowIndex++] = data.connection.getSQL(); } // See if we need to add the server timestamp to the row... if ( meta.includeTimestamp() && !Utils.isEmpty( meta.getTimestampField() ) ) { outputRowData[rowIndex++] = data.connection.getServerTimestamp(); } // See if we need to add the row number to the row... if ( meta.includeRowNumber() && !Utils.isEmpty( meta.getRowNumberField() ) ) { outputRowData[rowIndex++] = new Long( data.rownr ); } if ( meta.includeDeletionDate() && !Utils.isEmpty( meta.getDeletionDateField() ) ) { outputRowData[rowIndex++] = srvalue.getDeletionDate(); } RowMetaInterface irow = getInputRowMeta(); data.previousRow = irow == null ? outputRowData : irow.cloneRow( outputRowData ); // copy it to make } catch ( Exception e ) { throw new KettleException( BaseMessages .getString( PKG, "SalesforceInput.Exception.CanNotReadFromSalesforce" ), e ); } return outputRowData; } /* * build the SQL statement to send to Salesforce */ private String BuiltSOQl() { String sql = ""; SalesforceInputField[] fields = meta.getInputFields(); switch ( meta.getRecordsFilter() ) { case SalesforceConnectionUtils.RECORDS_FILTER_UPDATED: for ( int i = 0; i < data.nrfields; i++ ) { SalesforceInputField field = fields[i]; sql += environmentSubstitute( field.getField() ); if ( i < data.nrfields - 1 ) { sql += ","; } } break; case SalesforceConnectionUtils.RECORDS_FILTER_DELETED: sql += "SELECT "; for ( int i = 0; i < data.nrfields; i++ ) { SalesforceInputField field = fields[i]; sql += environmentSubstitute( field.getField() ); if ( i < data.nrfields - 1 ) { sql += ","; } } sql += " FROM " + environmentSubstitute( meta.getModule() ) + " WHERE isDeleted = true"; break; default: sql += "SELECT "; for ( int i = 0; i < data.nrfields; i++ ) { SalesforceInputField field = fields[i]; sql += environmentSubstitute( field.getField() ); if ( i < data.nrfields - 1 ) { sql += ","; } } sql = sql + " FROM " + environmentSubstitute( meta.getModule() ); if ( !Utils.isEmpty( environmentSubstitute( meta.getCondition() ) ) ) { sql += " WHERE " + environmentSubstitute( meta.getCondition().replace( "\n\r", "" ).replace( "\n", "" ) ); } break; } return sql; } /** * Build an empty row based on the meta-data. * * @return */ private Object[] buildEmptyRow() { Object[] rowData = RowDataUtil.allocateRowData( data.outputRowMeta.size() ); return rowData; } @Override public boolean init( StepMetaInterface smi, StepDataInterface sdi ) { meta = (SalesforceInputMeta) smi; data = (SalesforceInputData) sdi; if ( super.init( smi, sdi ) ) { // get total fields in the grid data.nrfields = meta.getInputFields().length; // Check if field list is filled if ( data.nrfields == 0 ) { log.logError( BaseMessages.getString( PKG, "SalesforceInputDialog.FieldsMissing.DialogMessage" ) ); return false; } String soSQL = environmentSubstitute( meta.getQuery() ); try { if ( meta.isSpecifyQuery() ) { // Check if user specified a query if ( Utils.isEmpty( soSQL ) ) { log.logError( BaseMessages.getString( PKG, "SalesforceInputDialog.QueryMissing.DialogMessage" ) ); return false; } } else { // check records filter if ( meta.getRecordsFilter() != SalesforceConnectionUtils.RECORDS_FILTER_ALL ) { String realFromDateString = environmentSubstitute( meta.getReadFrom() ); if ( Utils.isEmpty( realFromDateString ) ) { log.logError( BaseMessages.getString( PKG, "SalesforceInputDialog.FromDateMissing.DialogMessage" ) ); return false; } String realToDateString = environmentSubstitute( meta.getReadTo() ); if ( Utils.isEmpty( realToDateString ) ) { log.logError( BaseMessages.getString( PKG, "SalesforceInputDialog.ToDateMissing.DialogMessage" ) ); return false; } try { SimpleDateFormat dateFormat = new SimpleDateFormat( SalesforceInputMeta.DATE_TIME_FORMAT ); data.startCal = new GregorianCalendar(); data.startCal.setTime( dateFormat.parse( realFromDateString ) ); data.endCal = new GregorianCalendar(); data.endCal.setTime( dateFormat.parse( realToDateString ) ); dateFormat = null; } catch ( Exception e ) { log.logError( BaseMessages.getString( PKG, "SalesforceInput.ErrorParsingDate" ), e ); return false; } } } data.limit = Const.toLong( environmentSubstitute( meta.getRowLimit() ), 0 ); // Do we have to query for all records included deleted records data.connection.setQueryAll( meta.isQueryAll() ); // Build query if needed if ( meta.isSpecifyQuery() ) { // Free hand SOQL Query data.connection.setSQL( soSQL.replace( "\n\r", " " ).replace( "\n", " " ) ); } else { // Set calendars for update or deleted records if ( meta.getRecordsFilter() != SalesforceConnectionUtils.RECORDS_FILTER_ALL ) { data.connection.setCalendar( meta.getRecordsFilter(), data.startCal, data.endCal ); } if ( meta.getRecordsFilter() == SalesforceConnectionUtils.RECORDS_FILTER_UPDATED ) { // Return fields list data.connection.setFieldsList( BuiltSOQl() ); } else { // Build now SOQL data.connection.setSQL( BuiltSOQl() ); } } // Now connect ... data.connection.connect(); return true; } catch ( KettleException ke ) { logError( BaseMessages.getString( PKG, "SalesforceInput.Log.ErrorOccurredDuringStepInitialize" ) + ke.getMessage() ); return false; } } return false; } @Override public void dispose( StepMetaInterface smi, StepDataInterface sdi ) { if ( data.outputRowMeta != null ) { data.outputRowMeta = null; } if ( data.convertRowMeta != null ) { data.convertRowMeta = null; } if ( data.previousRow != null ) { data.previousRow = null; } if ( data.startCal != null ) { data.startCal = null; } if ( data.endCal != null ) { data.endCal = null; } super.dispose( smi, sdi ); } }