/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 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.salesforce;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.encryption.Encr;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.KettleLogStore;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.trans.steps.salesforceinput.SalesforceInputMeta;
import com.sforce.soap.partner.DeleteResult;
import com.sforce.soap.partner.DeletedRecord;
import com.sforce.soap.partner.DescribeGlobalResult;
import com.sforce.soap.partner.DescribeGlobalSObjectResult;
import com.sforce.soap.partner.DescribeSObjectResult;
import com.sforce.soap.partner.Field;
import com.sforce.soap.partner.FieldType;
import com.sforce.soap.partner.GetDeletedResult;
import com.sforce.soap.partner.GetUpdatedResult;
import com.sforce.soap.partner.GetUserInfoResult;
import com.sforce.soap.partner.LoginResult;
import com.sforce.soap.partner.PartnerConnection;
import com.sforce.soap.partner.QueryResult;
import com.sforce.soap.partner.SaveResult;
import com.sforce.soap.partner.UpsertResult;
import com.sforce.soap.partner.fault.ExceptionCode;
import com.sforce.soap.partner.fault.LoginFault;
import com.sforce.soap.partner.sobject.SObject;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.ConnectorConfig;
import com.sforce.ws.bind.XmlObject;
import com.sforce.ws.wsdl.Constants;
public class SalesforceConnection {
private static final FieldType ID_FIELD_TYPE = FieldType.id;
private static final FieldType REFERENCE_FIELD_TYPE = FieldType.reference;
private static Class<?> PKG = SalesforceInputMeta.class; // for i18n purposes, needed by Translator2!!
private String url;
private String username;
private String password;
private String module;
private int timeout;
private PartnerConnection binding;
private LoginResult loginResult;
private GetUserInfoResult userInfo;
private String sql;
private Date serverTimestamp;
private QueryResult qr;
private GregorianCalendar startDate;
private GregorianCalendar endDate;
private SObject[] sObjects;
private int recordsFilter;
private String fieldsList;
private int queryResultSize;
private int recordsCount;
private boolean useCompression;
private boolean rollbackAllChangesOnError;
private boolean queryAll;
private HashMap<String, Date> getDeletedList;
private LogChannelInterface log;
/**
* Construct a new Salesforce Connection
*/
public SalesforceConnection( LogChannelInterface logInterface, String url, String username, String password ) throws KettleException {
if ( logInterface == null ) {
this.log = KettleLogStore.getLogChannelInterfaceFactory().create( this );
} else {
this.log = logInterface;
}
this.url = url;
setUsername( username );
setPassword( password );
setTimeOut( 0 );
this.binding = null;
this.loginResult = null;
this.userInfo = null;
this.sql = null;
this.serverTimestamp = null;
this.qr = null;
this.startDate = null;
this.endDate = null;
this.sObjects = null;
this.recordsFilter = SalesforceConnectionUtils.RECORDS_FILTER_ALL;
this.fieldsList = null;
this.queryResultSize = 0;
this.recordsCount = 0;
setUsingCompression( false );
setRollbackAllChangesOnError( false );
// check target URL
if ( Utils.isEmpty( getURL() ) ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.TargetURLMissing.Error" ) );
}
// check username
if ( Utils.isEmpty( getUsername() ) ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.UsernameMissing.Error" ) );
}
if ( log.isDetailed() ) {
logInterface.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.NewConnection" ) );
}
}
public boolean isRollbackAllChangesOnError() {
return this.rollbackAllChangesOnError;
}
/**
*
* @see #isRollbackAllChangesOnError(boolean)
*/
@Deprecated
public void rollbackAllChangesOnError( boolean value ) {
setRollbackAllChangesOnError( value );
}
public void setRollbackAllChangesOnError( boolean value ) {
this.rollbackAllChangesOnError = value;
}
public boolean isQueryAll() {
return this.queryAll;
}
/**
*
* @see #setQueryAll(boolean)
*/
@Deprecated
public void queryAll( boolean value ) {
setQueryAll( value );
}
public void setQueryAll( boolean value ) {
this.queryAll = value;
}
public void setCalendar( int recordsFilter, GregorianCalendar startDate, GregorianCalendar endDate ) throws KettleException {
this.startDate = startDate;
this.endDate = endDate;
this.recordsFilter = recordsFilter;
if ( this.startDate == null || this.endDate == null ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.EmptyStartDateOrEndDate" ) );
}
if ( this.startDate.getTime().compareTo( this.endDate.getTime() ) >= 0 ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.WrongDates" ) );
}
// Calculate difference in days
long diffDays =
( this.endDate.getTime().getTime() - this.startDate.getTime().getTime() ) / ( 24 * 60 * 60 * 1000 );
if ( diffDays > 30 ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.StartDateTooOlder" ) );
}
}
public void setSQL( String sql ) {
this.sql = sql;
}
public void setFieldsList( String fieldsList ) {
this.fieldsList = fieldsList;
}
public void setModule( String module ) {
this.module = module;
}
public String getURL() {
return this.url;
}
public String getSQL() {
return this.sql;
}
public Date getServerTimestamp() {
return this.serverTimestamp;
}
public String getModule() {
return this.module;
}
public QueryResult getQueryResult() {
return this.qr;
}
public PartnerConnection createBinding( ConnectorConfig config ) throws ConnectionException {
if ( this.binding == null ) {
this.binding = new PartnerConnection( config );
}
return this.binding;
}
public PartnerConnection getBinding() {
return this.binding;
}
public void setTimeOut( int timeout ) {
this.timeout = timeout;
}
public int getTimeOut() {
return this.timeout;
}
public boolean isUsingCompression() {
return this.useCompression;
}
public void setUsingCompression( boolean useCompression ) {
this.useCompression = useCompression;
}
public String getUsername() {
return this.username;
}
public void setUsername( String value ) {
this.username = value;
}
public String getPassword() {
return this.password;
}
public void setPassword( String value ) {
this.password = value;
}
public void connect() throws KettleException {
ConnectorConfig config = new ConnectorConfig();
config.setAuthEndpoint( getURL() );
config.setServiceEndpoint( getURL() );
config.setUsername( getUsername() );
config.setPassword( getPassword() );
config.setCompression( isUsingCompression() );
config.setManualLogin( true );
// Set timeout
if ( getTimeOut() > 0 ) {
if ( log.isDebug() ) {
log.logDebug( BaseMessages.getString( PKG, "SalesforceInput.Log.SettingTimeout", "" + this.timeout ) );
}
config.setConnectionTimeout( getTimeOut() );
config.setReadTimeout( getTimeOut() );
}
try {
PartnerConnection pConnection = createBinding( config );
if ( log.isDetailed() ) {
log.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.LoginURL", config.getAuthEndpoint() ) );
}
if ( isRollbackAllChangesOnError() ) {
// Set the SOAP header to rollback all changes
// unless all records are processed successfully.
pConnection.setAllOrNoneHeader( true );
}
// Attempt the login giving the user feedback
if ( log.isDetailed() ) {
log.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.LoginNow" ) );
log.logDetailed( "----------------------------------------->" );
log.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.LoginURL", getURL() ) );
log.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.LoginUsername", getUsername() ) );
if ( getModule() != null ) {
log.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.LoginModule", getModule() ) );
}
log.logDetailed( "<-----------------------------------------" );
}
// Login
this.loginResult =
pConnection.login( config.getUsername(), Encr.decryptPasswordOptionallyEncrypted( config.getPassword() ) );
if ( log.isDebug() ) {
log.logDebug( BaseMessages.getString( PKG, "SalesforceInput.Log.SessionId" )
+ " : " + this.loginResult.getSessionId() );
log.logDebug( BaseMessages.getString( PKG, "SalesforceInput.Log.NewServerURL" )
+ " : " + this.loginResult.getServerUrl() );
}
// Create a new session header object and set the session id to that
// returned by the login
pConnection.setSessionHeader( loginResult.getSessionId() );
config.setServiceEndpoint( loginResult.getServerUrl() );
// Return the user Infos
this.userInfo = pConnection.getUserInfo();
if ( log.isDebug() ) {
log.logDebug( BaseMessages.getString( PKG, "SalesforceInput.Log.UserInfos" )
+ " : " + this.userInfo.getUserFullName() );
log.logDebug( "----------------------------------------->" );
log.logDebug( BaseMessages.getString( PKG, "SalesforceInput.Log.UserName" )
+ " : " + this.userInfo.getUserFullName() );
log.logDebug( BaseMessages.getString( PKG, "SalesforceInput.Log.UserEmail" )
+ " : " + this.userInfo.getUserEmail() );
log.logDebug( BaseMessages.getString( PKG, "SalesforceInput.Log.UserLanguage" )
+ " : " + this.userInfo.getUserLanguage() );
log.logDebug( BaseMessages.getString( PKG, "SalesforceInput.Log.UserOrganization" )
+ " : " + this.userInfo.getOrganizationName() );
log.logDebug( "<-----------------------------------------" );
}
this.serverTimestamp = pConnection.getServerTimestamp().getTimestamp().getTime();
if ( log.isDebug() ) {
BaseMessages.getString( PKG, "SalesforceInput.Log.ServerTimestamp", getServerTimestamp() );
}
if ( log.isDetailed() ) {
log.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.Connected" ) );
}
} catch ( LoginFault ex ) {
// The LoginFault derives from AxisFault
ExceptionCode exCode = ex.getExceptionCode();
if ( exCode == ExceptionCode.FUNCTIONALITY_NOT_ENABLED
|| exCode == ExceptionCode.INVALID_CLIENT || exCode == ExceptionCode.INVALID_LOGIN
|| exCode == ExceptionCode.LOGIN_DURING_RESTRICTED_DOMAIN
|| exCode == ExceptionCode.LOGIN_DURING_RESTRICTED_TIME || exCode == ExceptionCode.ORG_LOCKED
|| exCode == ExceptionCode.PASSWORD_LOCKOUT || exCode == ExceptionCode.SERVER_UNAVAILABLE
|| exCode == ExceptionCode.TRIAL_EXPIRED || exCode == ExceptionCode.UNSUPPORTED_CLIENT ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.InvalidUsernameOrPassword" ) );
}
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.Connection" ), ex );
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.Connection" ), e );
}
}
public void query( boolean specifyQuery ) throws KettleException {
if ( getBinding() == null ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Exception.CanNotGetBiding" ) );
}
try {
if ( !specifyQuery ) {
// check if we can query this Object
DescribeSObjectResult describeSObjectResult = getBinding().describeSObject( getModule() );
if ( describeSObjectResult == null ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.ErrorGettingObject" ) );
}
if ( !describeSObjectResult.isQueryable() ) {
throw new KettleException( BaseMessages.getString(
PKG, "SalesforceInputDialog.ObjectNotQueryable", module ) );
}
if ( this.recordsFilter == SalesforceConnectionUtils.RECORDS_FILTER_UPDATED
|| this.recordsFilter == SalesforceConnectionUtils.RECORDS_FILTER_DELETED ) {
// The object must be replicateable
if ( !describeSObjectResult.isReplicateable() ) {
throw new KettleException( BaseMessages.getString(
PKG, "SalesforceInput.Error.ObjectNotReplicateable", getModule() ) );
}
}
}
if ( getSQL() != null && log.isDetailed() ) {
log.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.SQLString" ) + " : " + getSQL() );
}
switch ( this.recordsFilter ) {
case SalesforceConnectionUtils.RECORDS_FILTER_UPDATED:
// Updated records ...
GetUpdatedResult updatedRecords = getBinding().getUpdated( getModule(), this.startDate, this.endDate );
if ( updatedRecords.getIds() != null ) {
int nr = updatedRecords.getIds().length;
if ( nr > 0 ) {
String[] ids = updatedRecords.getIds();
// We can pass a maximum of 2000 object IDs
if ( nr > SalesforceConnectionUtils.MAX_UPDATED_OBJECTS_IDS ) {
this.sObjects = new SObject[nr];
List<String> list = new ArrayList<String>();
int desPos = 0;
for ( int i = 0; i < nr; i++ ) {
list.add( updatedRecords.getIds()[i] );
if ( i % SalesforceConnectionUtils.MAX_UPDATED_OBJECTS_IDS == 0 || i == nr - 1 ) {
SObject[] s =
getBinding().retrieve(
this.fieldsList, getModule(), list.toArray( new String[list.size()] ) );
System.arraycopy( s, 0, this.sObjects, desPos, s.length );
desPos += s.length;
s = null;
list = new ArrayList<String>();
}
}
} else {
this.sObjects = getBinding().retrieve( this.fieldsList, getModule(), ids );
}
if ( this.sObjects != null ) {
this.queryResultSize = this.sObjects.length;
}
}
}
break;
case SalesforceConnectionUtils.RECORDS_FILTER_DELETED:
// Deleted records ...
GetDeletedResult deletedRecordsResult =
getBinding().getDeleted( getModule(), this.startDate, this.endDate );
DeletedRecord[] deletedRecords = deletedRecordsResult.getDeletedRecords();
if ( log.isDebug() ) {
log.logDebug( toString(), BaseMessages.getString(
PKG, "SalesforceConnection.DeletedRecordsFound", String.valueOf( deletedRecords == null
? 0 : deletedRecords.length ) ) );
}
if ( deletedRecords != null && deletedRecords.length > 0 ) {
getDeletedList = new HashMap<String, Date>();
for ( DeletedRecord dr : deletedRecords ) {
getDeletedList.put( dr.getId(), dr.getDeletedDate().getTime() );
}
this.qr = getBinding().queryAll( getSQL() );
this.sObjects = getQueryResult().getRecords();
if ( this.sObjects != null ) {
this.queryResultSize = this.sObjects.length;
}
}
break;
default:
// return query result
this.qr = isQueryAll() ? getBinding().queryAll( getSQL() ) : getBinding().query( getSQL() );
this.sObjects = getQueryResult().getRecords();
this.queryResultSize = getQueryResult().getSize();
break;
}
if ( this.sObjects != null ) {
this.recordsCount = this.sObjects.length;
}
} catch ( Exception e ) {
log.logError( Const.getStackTracker( e ) );
throw new KettleException( BaseMessages.getString( PKG, "SalesforceConnection.Exception.Query" ), e );
}
}
public void close() throws KettleException {
try {
if ( !getQueryResult().isDone() ) {
this.qr.setDone( true );
this.qr = null;
}
if ( this.sObjects != null ) {
this.sObjects = null;
}
if ( this.binding != null ) {
this.binding = null;
}
if ( this.loginResult != null ) {
this.loginResult = null;
}
if ( this.userInfo != null ) {
this.userInfo = null;
}
if ( this.getDeletedList != null ) {
getDeletedList.clear();
getDeletedList = null;
}
if ( log.isDetailed() ) {
log.logDetailed( BaseMessages.getString( PKG, "SalesforceInput.Log.ConnectionClosed" ) );
}
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.ClosingConnection" ), e );
}
}
public int getQueryResultSize() {
return this.queryResultSize;
}
public int getRecordsCount() {
return this.recordsCount;
}
public SalesforceRecordValue getRecord( int recordIndex ) {
int index = recordIndex;
SObject con = this.sObjects[index];
SalesforceRecordValue retval = new SalesforceRecordValue( index );
if ( con == null ) {
return null;
}
if ( this.recordsFilter == SalesforceConnectionUtils.RECORDS_FILTER_DELETED ) {
// Special case from deleted records
// We need to compare each record with the deleted ids
// in getDeletedList
if ( getDeletedList.containsKey( con.getId() ) ) {
// this record was deleted in the specified range datetime
// We will return it
retval.setRecordValue( con );
retval.setDeletionDate( getDeletedList.get( con.getId() ) );
} else if ( index < getRecordsCount() - 1 ) {
// this record was not deleted in the range datetime
// let's move forward and see if we find records that might interest us
while ( con != null && index < getRecordsCount() - 1 && !getDeletedList.containsKey( con.getId() ) ) {
// still not a record for us !!!
// let's continue ...
index++;
con = this.sObjects[index];
}
// if we are here, it means that
// we found a record to take
// or we have fetched all available records
retval.setRecordIndexChanges( true );
retval.setRecordIndex( index );
if ( con != null && getChildren( con )[index] != null && getDeletedList.containsKey( con.getId() ) ) {
retval.setRecordValue( con );
retval.setDeletionDate( getDeletedList.get( con.getId() ) );
}
}
retval.setAllRecordsProcessed( index >= getRecordsCount() - 1 );
} else {
// Case for retrieving record also for updated records
retval.setRecordValue( con );
}
return retval;
}
public String getRecordValue( SObject con, String fieldname ) throws KettleException {
String[] fieldHierarchy = fieldname.split( "\\." );
if ( con == null ) {
return null;
} else {
XmlObject element = getMessageElementForHierarchy( con, fieldHierarchy );
if ( element != null ) {
Object object = element.getValue();
if ( object != null ) {
if ( object instanceof QueryResult ) {
return buildJsonQueryResult( (QueryResult) object );
}
return String.valueOf( object );
} else {
return (String) element.getValue();
}
}
}
return null;
}
/**
* Drill down the SObject hierarchy based on the given field hierarchy until either null or the correct MessageElement
* is found
*/
private XmlObject getMessageElementForHierarchy( SObject con, String[] fieldHierarchy ) {
final int lastIndex = fieldHierarchy.length - 1;
SObject currentSObject = con;
for ( int index = 0; index <= lastIndex; index++ ) {
for ( XmlObject element : getChildren( currentSObject ) ) {
if ( element.getName().getLocalPart().equals( fieldHierarchy[index] ) ) {
if ( index == lastIndex ) {
return element;
} else {
if ( element instanceof SObject ) {
// Found the next level, keep going
currentSObject = (SObject) element;
}
break;
}
}
}
}
return null;
}
@SuppressWarnings( "unchecked" )
private String buildJsonQueryResult( QueryResult queryResult ) throws KettleException {
JSONArray list = new JSONArray();
for ( SObject sobject : queryResult.getRecords() ) {
list.add( buildJSONSObject( sobject ) );
}
StringWriter sw = new StringWriter();
try {
list.writeJSONString( sw );
} catch ( IOException e ) {
throw new KettleException( e );
}
return sw.toString();
}
@SuppressWarnings( "unchecked" )
private JSONObject buildJSONSObject( SObject sobject ) {
JSONObject jsonObject = new JSONObject();
for ( XmlObject element : getChildren( sobject ) ) {
Object object = element.getValue();
if ( object != null && object instanceof SObject ) {
jsonObject.put( element.getName(), buildJSONSObject( (SObject) object ) );
} else {
jsonObject.put( element.getName(), element.getValue() );
}
}
return jsonObject;
}
// Get SOQL meta data (not a Good way but i don't see any other way !)
// TODO : Go back to this one
// I am sure there is an easy way to return meta for a SOQL result
public XmlObject[] getElements() throws Exception {
// Query first
this.qr = getBinding().query( getSQL() );
// and then return records
SObject con = getQueryResult().getRecords()[0];
if ( con == null ) {
return null;
}
return getChildren( con );
}
public boolean queryMore() throws KettleException {
try {
// check the done attribute on the QueryResult and call QueryMore
// with the QueryLocator if there are more records to be retrieved
if ( !getQueryResult().isDone() ) {
this.qr = getBinding().queryMore( getQueryResult().getQueryLocator() );
this.sObjects = getQueryResult().getRecords();
if ( this.sObjects != null ) {
this.recordsCount = this.sObjects.length;
}
this.queryResultSize = getQueryResult().getSize();
return true;
} else {
// Query is done .. we finished !
return false;
}
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.QueringMore" ), e );
}
}
public String[] getAllAvailableObjects( boolean OnlyQueryableObjects ) throws KettleException {
DescribeGlobalResult dgr = null;
List<String> objects = null;
DescribeGlobalSObjectResult[] sobjectResults = null;
try {
// Get object
dgr = getBinding().describeGlobal();
// let's get all objects
sobjectResults = dgr.getSobjects();
int nrObjects = dgr.getSobjects().length;
objects = new ArrayList<String>();
for ( int i = 0; i < nrObjects; i++ ) {
DescribeGlobalSObjectResult o = dgr.getSobjects()[i];
if ( ( OnlyQueryableObjects && o.isQueryable() ) || !OnlyQueryableObjects ) {
objects.add( o.getName() );
}
}
return objects.toArray( new String[objects.size()] );
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.Error.GettingModules" ), e );
} finally {
if ( dgr != null ) {
dgr = null;
}
if ( objects != null ) {
objects.clear();
objects = null;
}
if ( sobjectResults != null ) {
sobjectResults = null;
}
}
}
public Field[] getObjectFields( String objectName ) throws KettleException {
DescribeSObjectResult describeSObjectResult = null;
try {
// Get object
describeSObjectResult = getBinding().describeSObject( objectName );
if ( describeSObjectResult == null ) {
return null;
}
if ( !describeSObjectResult.isQueryable() ) {
throw new KettleException( BaseMessages.getString(
PKG, "SalesforceInputDialog.ObjectNotQueryable", this.module ) );
} else {
// we can query this object
return describeSObjectResult.getFields();
}
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString(
PKG, "SalesforceInput.Error.GettingModuleFields", this.module ), e );
} finally {
if ( describeSObjectResult != null ) {
describeSObjectResult = null;
}
}
}
/**Returns only updatable object fields and ID field if <b>excludeNonUpdatableFields</b> is true,
* otherwise all object field
* @param objectName the name of Saleforce object
* @param excludeNonUpdatableFields the flag that indicates if non-updatable fields should be excluded or not
* @return the list of object fields depending on filter or not non-updatable fields.
* @throws KettleException if any exception occurs
*/
public Field[] getObjectFields( String objectName, boolean excludeNonUpdatableFields ) throws KettleException {
Field[] fieldList = getObjectFields( objectName );
if ( excludeNonUpdatableFields ) {
ArrayList<Field> finalFieldList = new ArrayList<Field>();
for ( Field f : fieldList ) {
// Leave out fields that can't be updated but
if ( isIdField( f ) || !f.isCalculated() && f.isUpdateable() ) {
finalFieldList.add( f );
}
}
fieldList = finalFieldList.toArray( new Field[finalFieldList.size()] );
}
return fieldList;
}
private boolean isIdField( Field field ) {
return field.getType() == ID_FIELD_TYPE ? true : false;
}
private boolean isReferenceField( Field field ) {
return field.getType() == REFERENCE_FIELD_TYPE ? true : false;
}
/**
* Method returns specified object's fields' names, use #getObjectFields to get fields itself
* @param objectName object name
* @return fields' names
* @throws KettleException in case of error
* @see #getObjectFields(String)
*/
public String[] getFields( String objectName ) throws KettleException {
return getFields( getObjectFields( objectName ) );
}
/**
* Method returns specified object's fields' names, use #getObjectFields to get fields itself
*
* @param objectName
* object name
* @param excludeNonUpdatableFields
* the flag that indicates if non-updatable fields should be excluded or not
* @return fields' names
* @throws KettleException
* in case of error
*/
public String[] getFields( String objectName, boolean excludeNonUpdatableFields ) throws KettleException {
return getFields( getObjectFields( objectName, excludeNonUpdatableFields ), excludeNonUpdatableFields );
}
/**
* Method returns names of the fields specified.
* @param fields fields
* @return fields' names
* @throws KettleException in case of error
* @see #getObjectFields(String)
*/
public String[] getFields( Field[] fields ) throws KettleException {
if ( fields != null ) {
int nrFields = fields.length;
String[] fieldsMapp = new String[nrFields];
for ( int i = 0; i < nrFields; i++ ) {
Field field = fields[i];
if ( field.getRelationshipName() != null ) {
fieldsMapp[i] = field.getRelationshipName();
} else {
fieldsMapp[i] = field.getName();
}
}
return fieldsMapp;
}
return null;
}
/**
* Method returns names of the fields specified.<br>
* For the type='reference' it also returns name in the
* <code>format: objectReferenceTo:externalIdField/lookupField</code>
*
* @param fields
* fields
* @param excludeNonUpdatableFields
* the flag that indicates if non-updatable fields should be excluded or not
* @return fields' names
* @throws KettleException
*/
public String[] getFields( Field[] fields, boolean excludeNonUpdatableFields ) throws KettleException {
if ( fields != null ) {
ArrayList<String> fieldsList = new ArrayList<String>( fields.length );
for ( Field field : fields ) {
//Add the name of the field - always
fieldsList.add( field.getName() );
//Get the referenced to the field object and for this object get all its field to find possible idLookup fields
if ( isReferenceField( field ) ) {
String referenceTo = field.getReferenceTo()[0];
Field[] referenceObjectFields = this.getObjectFields( referenceTo, excludeNonUpdatableFields );
for ( Field f : referenceObjectFields ) {
if ( f.isIdLookup() && !isIdField( f ) ) {
fieldsList.add( String.format( "%s:%s/%s", referenceTo, f.getName(), field.getRelationshipName() ) );
}
}
}
}
return fieldsList.toArray( new String[fieldsList.size()] );
}
return null;
}
public UpsertResult[] upsert( String upsertField, SObject[] sfBuffer ) throws KettleException {
try {
return getBinding().upsert( upsertField, sfBuffer );
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.ErrorUpsert", e ) );
}
}
public SaveResult[] insert( SObject[] sfBuffer ) throws KettleException {
try {
List<SObject> normalizedSfBuffer = new ArrayList<>();
for ( SObject part : sfBuffer ) {
if ( part != null ) {
normalizedSfBuffer.add( part );
}
}
return getBinding().create( normalizedSfBuffer.toArray( new SObject[normalizedSfBuffer.size()] ) );
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.ErrorInsert", e ) );
}
}
public SaveResult[] update( SObject[] sfBuffer ) throws KettleException {
try {
return getBinding().update( sfBuffer );
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.ErrorUpdate", e ) );
}
}
public DeleteResult[] delete( String[] id ) throws KettleException {
try {
return getBinding().delete( id );
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceInput.ErrorDelete", e ) );
}
}
public static XmlObject createMessageElement( String name, Object value, boolean useExternalKey ) throws Exception {
XmlObject me = null;
if ( useExternalKey ) {
// We use an external key
// the structure should be like this :
// object:externalId/lookupField
// where
// object is the type of the object
// externalId is the name of the field in the object to resolve the value
// lookupField is the name of the field in the current object to update (is the "__r" version)
int indexOfType = name.indexOf( ":" );
if ( indexOfType > 0 ) {
String type = name.substring( 0, indexOfType );
String extIdName = null;
String lookupField = null;
String rest = name.substring( indexOfType + 1, name.length() );
int indexOfExtId = rest.indexOf( "/" );
if ( indexOfExtId > 0 ) {
extIdName = rest.substring( 0, indexOfExtId );
lookupField = rest.substring( indexOfExtId + 1, rest.length() );
} else {
extIdName = rest;
lookupField = extIdName;
}
me = createForeignKeyElement( type, lookupField, extIdName, value );
} else {
throw new KettleException( BaseMessages.getString( PKG, "SalesforceConnection.UnableToFindObjectType" ) );
}
} else {
me = fromTemplateElement( name, value, true );
}
return me;
}
private static XmlObject createForeignKeyElement( String type, String lookupField, String extIdName,
Object extIdValue ) throws Exception {
// Foreign key relationship to the object
XmlObject me = fromTemplateElement( lookupField, null, false );
me.addField( "type", type );
me.addField( extIdName, extIdValue );
return me;
}
public static XmlObject fromTemplateElement( String name, Object value, boolean setValue ) throws SOAPException {
// Use the TEMPLATE org.w3c.dom.Element to create new Message Elements
XmlObject me = new XmlObject();
if ( setValue ) {
me.setValue( value );
}
me.setName( new QName( name ) );
return me;
}
public static XmlObject[] getChildren( SObject object ) {
List<String> reservedFieldNames = Arrays.asList( "type", "fieldsToNull" );
if ( object == null ) {
return null;
}
List<XmlObject> children = new ArrayList<>();
Iterator<XmlObject> iterator = object.getChildren();
while ( iterator.hasNext() ) {
XmlObject child = iterator.next();
if ( child.getName().getNamespaceURI().equals( Constants.PARTNER_SOBJECT_NS )
&& reservedFieldNames.contains( child.getName().getLocalPart() ) ) {
continue;
}
children.add( child );
}
if ( children.size() == 0 ) {
return null;
}
return children.toArray( new XmlObject[children.size()] );
}
}