Java tutorial
/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2017 by Hitachi Vantara : 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.apache.commons.lang.StringUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.pentaho.di.core.Const; 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.core.util.Utils; 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); String proxyUrl = System.getProperty("http.proxyHost", null); if (StringUtils.isNotEmpty(proxyUrl)) { int proxyPort = Integer.parseInt(System.getProperty("http.proxyPort", "80")); String proxyUser = System.getProperty("http.proxyUser", null); String proxyPassword = Encr .decryptPasswordOptionallyEncrypted(System.getProperty("http.proxyPassword", null)); config.setProxy(proxyUrl, proxyPort); config.setProxyUsername(proxyUser); config.setProxyPassword(proxyPassword); } // 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]; 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()]); } }