From 1dd023ab0cbdef38b30957d30ce2850a97d3c381 Mon Sep 17 00:00:00 2001 From: ashitsalesforce Date: Wed, 24 Jan 2024 10:30:24 -0800 Subject: [PATCH 1/3] code cleanup to support parent's idlookup fields in polymorphic relationship code cleanup to support parent's idlookup fields in polymorphic relationship --- .../dataloader/dyna/ObjectField.java | 69 ++++++++++++------- .../dataloader/dyna/SObjectReference.java | 10 +-- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java b/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java index 7f37e44b..d6b0b8f6 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java +++ b/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java @@ -34,44 +34,49 @@ * @since 8.0 */ public class ObjectField { - private String objectName; - private String fieldName; + private String relationshipName; + private String parentFieldName; + private String parentObjectName = null; public static final String VALUE_SEPARATOR_CHAR = ":"; //$NON-NLS-1$ - - /** - * @param objectName - * @param fieldName - */ - public ObjectField(String objectName, String fieldName) { - this.objectName = objectName; - this.fieldName = fieldName; - } + // old format - : + // Example - "Owner:username" where Account.Owner field is a lookup + // field to User object, username is an idLookup field in User + // + // new format to support polymorphic lookup relationship - + // :. + // Example - "Account:Owner.username" + public static final String NEW_FORMAT_VALUE_SEPARATOR_CHAR = "."; + public static final String NEW_FORMAT_OBJECT_NAME_SEPARATOR_CHAR = ":"; /** * @param objectField */ public ObjectField(String objectField) { - String[] refFieldNameInfo = objectField.split(ObjectField.VALUE_SEPARATOR_CHAR); - objectName = refFieldNameInfo[0]; - fieldName = refFieldNameInfo[1]; + String[] refFieldNameInfo = objectField.split(ObjectField.NEW_FORMAT_VALUE_SEPARATOR_CHAR); + if (refFieldNameInfo.length < 2) { + refFieldNameInfo = objectField.split(ObjectField.VALUE_SEPARATOR_CHAR); + relationshipName = refFieldNameInfo[0]; + parentFieldName = refFieldNameInfo[1]; + } else { // new format + parentFieldName = refFieldNameInfo[1]; + refFieldNameInfo = objectField.split(ObjectField.NEW_FORMAT_OBJECT_NAME_SEPARATOR_CHAR); + relationshipName = refFieldNameInfo[1]; + parentObjectName = refFieldNameInfo[0]; + } } - /** - * @param objectFieldArray - */ - public ObjectField(String[] objectFieldArray) { - objectName = objectFieldArray[0]; - fieldName = objectFieldArray[1]; + public String getParentFieldName() { + return parentFieldName; } - public String getFieldName() { - return fieldName; + public String getRelationshipName() { + return relationshipName; } - public String getObjectName() { - return objectName; + public String getParentObjectName() { + return parentObjectName; } - + /** * @param objectName * @param fieldName @@ -81,11 +86,23 @@ static public String formatAsString(String objectName, String fieldName) { return objectName + ObjectField.VALUE_SEPARATOR_CHAR + fieldName; } + static public String formatAsString(String parentObjectName, String objectName, String fieldName) { + return parentObjectName + + ObjectField.NEW_FORMAT_OBJECT_NAME_SEPARATOR_CHAR + + objectName + + ObjectField.NEW_FORMAT_VALUE_SEPARATOR_CHAR + + fieldName; + } + /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { - return formatAsString(objectName, fieldName); + if (parentObjectName == null) { + return formatAsString(relationshipName, parentFieldName); + } else { + return formatAsString(parentObjectName, relationshipName, parentFieldName); + } } } diff --git a/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java b/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java index 03894bdc..adbb0a14 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java +++ b/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java @@ -60,8 +60,8 @@ public SObjectReference(Object refValue) { public void addReferenceToSObject(Controller controller, SObject sObj, String refFieldName) throws ParameterLoadException { // break the name into relationship and field name components ObjectField refField = new ObjectField(refFieldName); - String relationshipName = refField.getObjectName(); - String fieldName = refField.getFieldName(); + String relationshipName = refField.getRelationshipName(); + String parentFieldName = refField.getParentFieldName(); // get object info for the given reference (foreign key) relationship DescribeRefObject entityRefInfo = controller.getReferenceDescribes().get(relationshipName); @@ -71,9 +71,9 @@ public void addReferenceToSObject(Controller controller, SObject sObj, String re // set entity type, has to be set before all others sObjRef.setType(entityRefInfo.getParentObjectName()); // set external id, do type conversion as well - Class typeClass = SforceDynaBean.getConverterClass(entityRefInfo.getParentObjectFieldMap().get(fieldName)); + Class typeClass = SforceDynaBean.getConverterClass(entityRefInfo.getParentObjectFieldMap().get(parentFieldName)); Object extIdValue = ConvertUtils.convert(this.referenceExtIdValue.toString(), typeClass); - sObjRef.setField(fieldName, extIdValue); + sObjRef.setField(parentFieldName, extIdValue); // Add the sObject reference as a child elemetn, name set to relationshipName sObj.addField(relationshipName, sObjRef); } @@ -100,7 +100,7 @@ public Object getReferenceExtIdValue() { } public static String getRelationshipField(Controller controller, String refFieldName) { - final String relName = new ObjectField(refFieldName).getObjectName(); + final String relName = new ObjectField(refFieldName).getRelationshipName(); controller.getReferenceDescribes().get(relName).getParentObjectFieldMap(); for (Field f : controller.getFieldTypes().getFields()) { if (f != null) { From 3f65240b0d3fa3ac2ea93005cc39596aa7c9882f Mon Sep 17 00:00:00 2001 From: ashitsalesforce Date: Mon, 29 Jan 2024 19:39:16 -0800 Subject: [PATCH 2/3] changes for polymorphic lookup relationship Changes to support reference to the related object in a polymorphic lookup relationship using an idLookup field other than id. --- .../dataloader/client/PartnerClient.java | 39 +--- .../client/ReferenceEntitiesDescribeMap.java | 113 +++++++++++ .../dataloader/controller/Controller.java | 3 +- .../dataloader/dyna/ObjectField.java | 108 ----------- .../dataloader/dyna/RelationshipField.java | 178 ++++++++++++++++++ .../dataloader/dyna/SObjectReference.java | 11 +- .../dataloader/dyna/SforceDynaBean.java | 17 +- .../ui/ForeignKeyExternalIdPage.java | 19 +- src/main/resources/labels.properties | 4 +- .../dataloader/client/PartnerClientTest.java | 8 +- .../dyna/SObjectReferenceConverterTest.java | 14 +- 11 files changed, 349 insertions(+), 165 deletions(-) create mode 100644 src/main/java/com/salesforce/dataloader/client/ReferenceEntitiesDescribeMap.java delete mode 100644 src/main/java/com/salesforce/dataloader/dyna/ObjectField.java create mode 100644 src/main/java/com/salesforce/dataloader/dyna/RelationshipField.java diff --git a/src/main/java/com/salesforce/dataloader/client/PartnerClient.java b/src/main/java/com/salesforce/dataloader/client/PartnerClient.java index 2aa43a37..9028b54f 100644 --- a/src/main/java/com/salesforce/dataloader/client/PartnerClient.java +++ b/src/main/java/com/salesforce/dataloader/client/PartnerClient.java @@ -224,7 +224,7 @@ public DescribeSObjectResult run(String entity) throws ConnectionException { }; private DescribeGlobalResult describeGlobalResults; - private final Map referenceEntitiesDescribesMap = new HashMap(); + private final ReferenceEntitiesDescribeMap referenceEntitiesDescribesMap = new ReferenceEntitiesDescribeMap(); private final Map describeGlobalResultsMap = new HashMap(); private final Map entityFieldDescribesMap = new HashMap(); @@ -562,7 +562,7 @@ public DescribeSObjectResult getFieldTypes() { } } - public Map getReferenceDescribes() { + public ReferenceEntitiesDescribeMap getReferenceDescribes() { return referenceEntitiesDescribesMap; } @@ -938,42 +938,23 @@ private boolean checkConnectionException(ConnectionException ex, String operatio private final Map fieldsByName = new HashMap(); - public Field getField(String apiName) { - apiName = apiName.toLowerCase(); - Field field = this.fieldsByName.get(apiName); + public Field getField(String sObjectFieldName) { + sObjectFieldName = sObjectFieldName.toLowerCase(); + Field field = this.fieldsByName.get(sObjectFieldName); if (field == null) { - field = lookupField(apiName); - this.fieldsByName.put(apiName, field); + field = lookupField(sObjectFieldName); + this.fieldsByName.put(sObjectFieldName, field); } return field; } - private Field lookupField(String apiName) { + private Field lookupField(String sObjectFieldName) { // look for field on target object for (Field f : getFieldTypes().getFields()) { - if (apiName.equals(f.getName().toLowerCase()) || apiName.equals(f.getLabel().toLowerCase())) + if (sObjectFieldName.equals(f.getName().toLowerCase()) || sObjectFieldName.equals(f.getLabel().toLowerCase())) return f; } - // look for reference field on target object - if (apiName.contains(":")) { - Map refs = getReferenceDescribes(); - for (Map.Entry ent : refs.entrySet()) { - String relName = ent.getKey().toLowerCase(); - if (apiName.startsWith(relName)) { - for (Map.Entry refEntry : ent.getValue().getParentObjectFieldMap().entrySet()) { - String thisRefName = relName + ":" + refEntry.getKey().toLowerCase(); - if (apiName.contains(".")) { - thisRefName = relName + ":" - + ent.getValue().getParentObjectName() - + "." + refEntry.getKey().toLowerCase(); - } - - if (apiName.equalsIgnoreCase(thisRefName)) return refEntry.getValue(); - } - } - } - } - return null; + return this.referenceEntitiesDescribesMap.getParentField(sObjectFieldName); } } diff --git a/src/main/java/com/salesforce/dataloader/client/ReferenceEntitiesDescribeMap.java b/src/main/java/com/salesforce/dataloader/client/ReferenceEntitiesDescribeMap.java new file mode 100644 index 00000000..949d9b9b --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/ReferenceEntitiesDescribeMap.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.client; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.salesforce.dataloader.dyna.RelationshipField; +import com.sforce.soap.partner.Field; + +/** + * + */ +public class ReferenceEntitiesDescribeMap { + + private Map referenceEntitiesDescribeMap = new HashMap(); + /** + * + */ + public ReferenceEntitiesDescribeMap() { + + } + + public void put(String relationshipFieldName, DescribeRefObject parent) { + RelationshipField objField = new RelationshipField(parent.getParentObjectName(), relationshipFieldName); + referenceEntitiesDescribeMap.put(objField.toFormattedRelationshipString(), parent); + } + + // fieldName could be in the old format that assumes single parent: + // : + // + // fieldName could also be in the new format + // :. + + public DescribeRefObject getParentSObject(String lookupFieldName) { + return getParentSObject(new RelationshipField(lookupFieldName, false)); + } + + public void clear() { + this.referenceEntitiesDescribeMap.clear(); + } + + public int size() { + return this.referenceEntitiesDescribeMap.size(); + } + + public Set keySet() { + return this.referenceEntitiesDescribeMap.keySet(); + } + + // fieldName could be in the old format that assumes single parent: + // : + // + // fieldName could also be in the new format + // :. + public Field getParentField(String fieldName) { + RelationshipField fieldName4LR = new RelationshipField(fieldName, true); + if (fieldName4LR == null + || fieldName4LR.getParentFieldName() == null + || fieldName4LR.getRelationshipName() == null) { + return null; + } else { + DescribeRefObject parent = getParentSObject(fieldName4LR); + if (parent == null) { + return null; + } + for (Map.Entry refEntry : parent.getParentObjectFieldMap().entrySet()) { + if (fieldName4LR.getParentFieldName().equalsIgnoreCase(refEntry.getKey())) { + return refEntry.getValue(); + } + } + return null; + } + } + + private DescribeRefObject getParentSObject(RelationshipField fieldName4LR) { + if (fieldName4LR == null || fieldName4LR.getRelationshipName() == null) { + return null; + } + for (Map.Entry ent : referenceEntitiesDescribeMap.entrySet()) { + String relNameInEntry = ent.getKey().toLowerCase(); + if (fieldName4LR.isRelationshipName(relNameInEntry)) { + return ent.getValue(); + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/controller/Controller.java b/src/main/java/com/salesforce/dataloader/controller/Controller.java index 4e59873f..9772d067 100644 --- a/src/main/java/com/salesforce/dataloader/controller/Controller.java +++ b/src/main/java/com/salesforce/dataloader/controller/Controller.java @@ -34,6 +34,7 @@ import com.salesforce.dataloader.client.DescribeRefObject; import com.salesforce.dataloader.client.HttpClientTransport; import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.client.ReferenceEntitiesDescribeMap; import com.salesforce.dataloader.config.Config; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.dao.DataAccessObject; @@ -205,7 +206,7 @@ public DescribeSObjectResult getFieldTypes() { return getPartnerClient().getFieldTypes(); } - public Map getReferenceDescribes() { + public ReferenceEntitiesDescribeMap getReferenceDescribes() { validateSession(); return getPartnerClient().getReferenceDescribes(); } diff --git a/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java b/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java deleted file mode 100644 index d6b0b8f6..00000000 --- a/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.dyna; - - -/** - * Container for an object field of format - * objectName:fieldName - * - * @author Alex Warshavsky - * @since 8.0 - */ -public class ObjectField { - private String relationshipName; - private String parentFieldName; - private String parentObjectName = null; - public static final String VALUE_SEPARATOR_CHAR = ":"; //$NON-NLS-1$ - // old format - : - // Example - "Owner:username" where Account.Owner field is a lookup - // field to User object, username is an idLookup field in User - // - // new format to support polymorphic lookup relationship - - // :. - // Example - "Account:Owner.username" - public static final String NEW_FORMAT_VALUE_SEPARATOR_CHAR = "."; - public static final String NEW_FORMAT_OBJECT_NAME_SEPARATOR_CHAR = ":"; - - /** - * @param objectField - */ - public ObjectField(String objectField) { - String[] refFieldNameInfo = objectField.split(ObjectField.NEW_FORMAT_VALUE_SEPARATOR_CHAR); - if (refFieldNameInfo.length < 2) { - refFieldNameInfo = objectField.split(ObjectField.VALUE_SEPARATOR_CHAR); - relationshipName = refFieldNameInfo[0]; - parentFieldName = refFieldNameInfo[1]; - } else { // new format - parentFieldName = refFieldNameInfo[1]; - refFieldNameInfo = objectField.split(ObjectField.NEW_FORMAT_OBJECT_NAME_SEPARATOR_CHAR); - relationshipName = refFieldNameInfo[1]; - parentObjectName = refFieldNameInfo[0]; - } - } - - public String getParentFieldName() { - return parentFieldName; - } - - public String getRelationshipName() { - return relationshipName; - } - - public String getParentObjectName() { - return parentObjectName; - } - - /** - * @param objectName - * @param fieldName - * @return String formatted as objectName:fieldName - */ - static public String formatAsString(String objectName, String fieldName) { - return objectName + ObjectField.VALUE_SEPARATOR_CHAR + fieldName; - } - - static public String formatAsString(String parentObjectName, String objectName, String fieldName) { - return parentObjectName - + ObjectField.NEW_FORMAT_OBJECT_NAME_SEPARATOR_CHAR - + objectName - + ObjectField.NEW_FORMAT_VALUE_SEPARATOR_CHAR - + fieldName; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - if (parentObjectName == null) { - return formatAsString(relationshipName, parentFieldName); - } else { - return formatAsString(parentObjectName, relationshipName, parentFieldName); - } - } -} diff --git a/src/main/java/com/salesforce/dataloader/dyna/RelationshipField.java b/src/main/java/com/salesforce/dataloader/dyna/RelationshipField.java new file mode 100644 index 00000000..b31e2fac --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/dyna/RelationshipField.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.dyna; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Container for an object field of format + * objectName:fieldName + * + * @author Alex Warshavsky + * @since 8.0 + */ +public class RelationshipField { + private String relationshipName; + private String parentFieldName; + private String parentObjectName = null; + private static final Logger logger = LogManager.getLogger(RelationshipField.class); + + private static final String OLD_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR = ":"; //$NON-NLS-1$ + // old format - : + // Example - "Owner:username" where Account.Owner field is a lookup + // field to User object, username is an idLookup field in User + // + // new format to support polymorphic lookup relationship - + // :. + // Example - "Account:Owner.username" + private static final String NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR = " - "; + private static final String NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR = ":"; + + public RelationshipField(String parentObjectName, String relationshipName) { + this.parentObjectName = parentObjectName; + this.relationshipName = relationshipName; + } + + // fieldName param can be in one of the following formats: + // format 1: alphanumeric string without any ':' or '#' in it. Represents name of child's non-polymorphic relationship field + // format 1 => it is name of a non-polymorphic relationship field in child object. + // + // format 2: alphanumeric string with a ':' in it + // format 2 has 2 interpretations: + // interpretation 1: : + // - this is the new format for keys of the hashmap referenceEntitiesDescribeMap + // interpretation 2 (legacy format): : + // + // format 3: alphanumeric string with a single ':' and a single '#' in it + // format 3 => it is name of a field in child object with reference to an idlookup field in parent object + // + // Given 2 interpretations of format 2, an additional parameter, 'isFieldName', is required. + // If 'hasParentIdLookupFieldName' == true, the code processes fieldName parameter according + // to the 2nd interpretation for format 2. It processes fieldName parameter according to 1st interpretation otherwise. + + public RelationshipField(String fieldName, boolean hasParentIdLookupFieldName) { + String[] fieldNameParts = fieldName.split(RelationshipField.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR); + if (fieldNameParts.length == 2) { + // parent name not specified + parentFieldName = fieldNameParts[1]; + hasParentIdLookupFieldName = true; // '.' char shows up only in format 3 + fieldName = fieldNameParts[0]; + } + fieldNameParts = fieldName.split(RelationshipField.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR); + if (hasParentIdLookupFieldName) { // format 2, interpretation 2 or format 3 + if (fieldNameParts.length == 2) { + if (parentFieldName == null) {// format 2, interpretation 2 + relationshipName = fieldNameParts[0]; + parentFieldName = fieldNameParts[1]; + } else { // format 3 + relationshipName = fieldNameParts[0]; + parentObjectName = fieldNameParts[1]; + } + } else { // Should not happen - no ':' char in name, may have '#' char + if (parentFieldName == null) { // no ':' and no '.' in name + logger.error("field name " + fieldName + " does not have ':' or '.' char" ); + } else { + // '#' char in name but no ':' + logger.error("field name " + fieldName + " has '.' but does not have ':' char" ); + } + } + } else { // format 1 or format 2, interpretation 1 + if (fieldNameParts.length == 2) { // format 2, interpretation 1 + relationshipName = fieldNameParts[0]; + parentObjectName = fieldNameParts[1]; + } else { // format 1 + relationshipName = fieldName; + } + } + } + + public String getParentFieldName() { + return parentFieldName; + } + + public void setParentFieldName(String parentIdLookupFieldName) { + this.parentFieldName = parentIdLookupFieldName; + } + + public String getRelationshipName() { + return relationshipName; + } + + public String getParentObjectName() { + return parentObjectName; + } + + public String toFormattedRelationshipString() { + if (parentObjectName == null) { + return relationshipName; + } + return relationshipName + + RelationshipField.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR + + parentObjectName; + } + + public boolean isRelationshipName(String nameStr) { + if (this.relationshipName == null) { + return false; + } + if (parentObjectName == null) { + return nameStr.toLowerCase().startsWith(this.relationshipName.toLowerCase()); + } else { + return nameStr.toLowerCase().equalsIgnoreCase(this.relationshipName + NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR + this.parentObjectName); + } + } + + /** + * @param objectName + * @param fieldName + * @return String formatted as objectName:fieldName + */ + static public String formatAsString(String relationshipName, String parentIDLookupFieldName) { + return relationshipName + RelationshipField.OLD_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR + parentIDLookupFieldName; + } + + static public String formatAsString(String parentObjectName, String relationshipName, String parentIDLookupFieldName) { + return relationshipName + + RelationshipField.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR + + parentObjectName + + RelationshipField.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR + + parentIDLookupFieldName; + } + + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + if (parentObjectName == null) { + return formatAsString(relationshipName, parentFieldName); + } else { + return formatAsString(parentObjectName, relationshipName, parentFieldName); + } + } +} diff --git a/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java b/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java index adbb0a14..3b9868ad 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java +++ b/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java @@ -59,18 +59,17 @@ public SObjectReference(Object refValue) { */ public void addReferenceToSObject(Controller controller, SObject sObj, String refFieldName) throws ParameterLoadException { // break the name into relationship and field name components - ObjectField refField = new ObjectField(refFieldName); + RelationshipField refField = new RelationshipField(refFieldName, true); String relationshipName = refField.getRelationshipName(); String parentFieldName = refField.getParentFieldName(); - // get object info for the given reference (foreign key) relationship - DescribeRefObject entityRefInfo = controller.getReferenceDescribes().get(relationshipName); + DescribeRefObject entityRefInfo = controller.getReferenceDescribes().getParentSObject(refField.toFormattedRelationshipString()); // build the reference SObject SObject sObjRef = new SObject(); // set entity type, has to be set before all others sObjRef.setType(entityRefInfo.getParentObjectName()); - // set external id, do type conversion as well + // set idLookup, do type conversion as well Class typeClass = SforceDynaBean.getConverterClass(entityRefInfo.getParentObjectFieldMap().get(parentFieldName)); Object extIdValue = ConvertUtils.convert(this.referenceExtIdValue.toString(), typeClass); sObjRef.setField(parentFieldName, extIdValue); @@ -100,8 +99,8 @@ public Object getReferenceExtIdValue() { } public static String getRelationshipField(Controller controller, String refFieldName) { - final String relName = new ObjectField(refFieldName).getRelationshipName(); - controller.getReferenceDescribes().get(relName).getParentObjectFieldMap(); + final String relName = new RelationshipField(refFieldName, true).getRelationshipName(); + controller.getReferenceDescribes().getParentSObject(relName).getParentObjectFieldMap(); for (Field f : controller.getFieldTypes().getFields()) { if (f != null) { if (relName.equals(f.getRelationshipName())) { return f.getName(); } diff --git a/src/main/java/com/salesforce/dataloader/dyna/SforceDynaBean.java b/src/main/java/com/salesforce/dataloader/dyna/SforceDynaBean.java index 3ea515da..e2e47798 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/SforceDynaBean.java +++ b/src/main/java/com/salesforce/dataloader/dyna/SforceDynaBean.java @@ -88,12 +88,18 @@ static public DynaProperty[] createDynaProps(DescribeSObjectResult describer, Co if (fieldType == FieldType.reference && field.getReferenceTo().length == 1 && relationshipName != null && relationshipName.length() > 0) { - DescribeRefObject refInfo = controller.getReferenceDescribes().get(relationshipName); - if(refInfo != null) { - for(String refFieldName : refInfo.getParentObjectFieldMap().keySet()) { + DescribeRefObject parent = controller.getReferenceDescribes().getParentSObject(relationshipName); + if(parent != null) { + for(String refFieldName : parent.getParentObjectFieldMap().keySet()) { // property name contains information for mapping - dynaProps.add(new DynaProperty(ObjectField.formatAsString(relationshipName, refFieldName), - SObjectReference.class)); + // add old format to dyna props + dynaProps.add(new DynaProperty( + RelationshipField.formatAsString(relationshipName, refFieldName), + SObjectReference.class)); + // add new format to dyna props + dynaProps.add(new DynaProperty( + RelationshipField.formatAsString(parent.getParentObjectName(), relationshipName, refFieldName), + SObjectReference.class)); } } } @@ -209,7 +215,6 @@ static public DynaBean convertToDynaBean(BasicDynaClass dynaClass, Row sforceDat DynaBean sforceObj = null; try { sforceObj = dynaClass.newInstance(); - //This does an automatic conversion of types. BeanUtils.copyProperties(sforceObj, sforceDataRow); return sforceObj; diff --git a/src/main/java/com/salesforce/dataloader/ui/ForeignKeyExternalIdPage.java b/src/main/java/com/salesforce/dataloader/ui/ForeignKeyExternalIdPage.java index 3e1c120c..39bc29dd 100644 --- a/src/main/java/com/salesforce/dataloader/ui/ForeignKeyExternalIdPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/ForeignKeyExternalIdPage.java @@ -36,9 +36,10 @@ import com.salesforce.dataloader.action.OperationInfo; import com.salesforce.dataloader.client.DescribeRefObject; +import com.salesforce.dataloader.client.ReferenceEntitiesDescribeMap; import com.salesforce.dataloader.config.Config; import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dyna.ObjectField; +import com.salesforce.dataloader.dyna.RelationshipField; import com.sforce.soap.partner.Field; import com.sforce.soap.partner.FieldType; @@ -53,7 +54,7 @@ public class ForeignKeyExternalIdPage extends LoadPage { private final Map extIdSelections = new HashMap(); private Composite containerComp; private ScrolledComposite scrollComp; - private Map referenceObjects; + private ReferenceEntitiesDescribeMap referenceObjects; private int numChildFieldsWithNonIdLookupFieldSelections = 0; @@ -102,7 +103,7 @@ private void createFkExtIdUi() { if(referenceObjects != null) { for(String relationshipName : referenceObjects.keySet()) { OperationInfo operation = controller.getConfig().getOperationInfo(); - Field childField = referenceObjects.get(relationshipName).getChildField(); + Field childField = referenceObjects.getParentSObject(relationshipName).getChildField(); boolean isCreateableOrUpdateable = true; if (childField != null) { switch (operation) { @@ -137,8 +138,8 @@ private void createFkExtIdUi() { */ private void createObjectExtIdUi(Composite comp, String relationshipName) { Label labelExtId = new Label(comp, SWT.RIGHT); - DescribeRefObject extIdInfo = referenceObjects.get(relationshipName); - labelExtId.setText(relationshipName + " (" + extIdInfo.getParentObjectName() + ")"); + DescribeRefObject extIdInfo = referenceObjects.getParentSObject(relationshipName); + labelExtId.setText(relationshipName); labelExtId.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); // Add the ext id dropdown @@ -190,12 +191,14 @@ private Map saveExtIdData() { // make sure that the item selection has occurred and that the default text is not displayed anymore if(extIdFieldName != null && extIdFieldName.length() > 0 && ! extIdFieldName.equals(Labels.getString("ForeignKeyExternalIdPage.defaultComboText"))) { - DescribeRefObject refObjectInfo = referenceObjects.get(relationshipName); - extIdReferences.put(relationshipName, ObjectField.formatAsString(refObjectInfo.getParentObjectName(), extIdFieldName)); + DescribeRefObject refObjectInfo = referenceObjects.getParentSObject(relationshipName); + extIdReferences.put(relationshipName, RelationshipField.formatAsString(refObjectInfo.getParentObjectName(), extIdFieldName)); Field relatedField = new Field(); Field parentField = refObjectInfo.getParentObjectFieldMap().get(extIdFieldName); Field childField = refObjectInfo.getChildField(); - relatedField.setName(relationshipName + ":" + parentField.getName()); + RelationshipField relField = new RelationshipField(relationshipName, false); + relField.setParentFieldName(parentField.getName()); + relatedField.setName(relField.toString()); String childFieldLabel = childField.getLabel(); String[] childFieldLabelParts = childFieldLabel.split(" \\(.+\\)$"); relatedField.setLabel(childFieldLabelParts[0] + " (" + parentField.getLabel() + ")"); diff --git a/src/main/resources/labels.properties b/src/main/resources/labels.properties index bda18845..de4556bd 100644 --- a/src/main/resources/labels.properties +++ b/src/main/resources/labels.properties @@ -260,9 +260,9 @@ FinishPage.output=Output FinishPage.chooseDir=Folder: ForeignKeyExternalIdPage.title=Step 2b: (Optional) Choose lookup fields on related objects -ForeignKeyExternalIdPage.description=For each related object, select the relationship lookup field to use for matching. Otherwise, leave the selection blank. +ForeignKeyExternalIdPage.description=For each related object, select lookup fields of related objects for relationship fields. Otherwise, leave the selection blank. ForeignKeyExternalIdPage.defaultComboText= -ForeignKeyExternalIdPage.pageMessage=Relationship fields of {0} object are listed below with related object names in paranthesis.\n\nChoose the lookup field of the related object from the dropdown if the upload data contains the lookup field values. +ForeignKeyExternalIdPage.pageMessage=Relationship fields of {0} are listed below, suffixed by related object name. Choose a lookup field of the related object from the dropdown if the CSV file contains values for that lookup field to reference the related object. HardDeleteFinishPage.title=Step 4: Finish HardDeleteFinishPage.description=Select the folder where your success and error files will be saved. diff --git a/src/test/java/com/salesforce/dataloader/client/PartnerClientTest.java b/src/test/java/com/salesforce/dataloader/client/PartnerClientTest.java index e6cdd371..f9c3d486 100644 --- a/src/test/java/com/salesforce/dataloader/client/PartnerClientTest.java +++ b/src/test/java/com/salesforce/dataloader/client/PartnerClientTest.java @@ -26,7 +26,7 @@ package com.salesforce.dataloader.client; import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.dyna.ObjectField; +import com.salesforce.dataloader.dyna.RelationshipField; import com.salesforce.dataloader.dyna.SforceDynaBean; import com.salesforce.dataloader.process.ProcessTestBase; import com.sforce.soap.partner.DeleteResult; @@ -430,7 +430,7 @@ private void doUpsertAccount(boolean upsertFk) throws Exception { doUpsertAccount(false); parentExtIdValue = getRandomExtId("Account", ACCOUNT_WHERE_CLAUSE, extIdValue); } - sforceMapping.put(ObjectField.formatAsString("Parent", extIdField), parentExtIdValue); + sforceMapping.put(RelationshipField.formatAsString("Parent", extIdField), parentExtIdValue); } doUpsert("Account", sforceMapping); @@ -467,7 +467,7 @@ private void doUpsertContact(boolean upsertFk) throws Exception { doUpsertAccount(false); accountExtIdValue = getRandomExtId("Account", ACCOUNT_WHERE_CLAUSE, accountExtIdValue); } - sforceMapping.put(ObjectField.formatAsString("Account", acctExtIdField), accountExtIdValue); + sforceMapping.put(RelationshipField.formatAsString("Account", acctExtIdField), accountExtIdValue); // restore ext id field setExtIdField(oldExtIdField); @@ -523,7 +523,7 @@ private void doUpsertFailBasic(boolean upsertFk) throws Exception { if (upsertFk) { sforceMapping.put(extIdField, extIdValue); // forget to set the foreign key external id value - sforceMapping.put(ObjectField.formatAsString("Parent", extIdField), "bogus"); + sforceMapping.put(RelationshipField.formatAsString("Parent", extIdField), "bogus"); } // now convert to a dynabean array for the client diff --git a/src/test/java/com/salesforce/dataloader/dyna/SObjectReferenceConverterTest.java b/src/test/java/com/salesforce/dataloader/dyna/SObjectReferenceConverterTest.java index 48571f0d..2e3e72b0 100644 --- a/src/test/java/com/salesforce/dataloader/dyna/SObjectReferenceConverterTest.java +++ b/src/test/java/com/salesforce/dataloader/dyna/SObjectReferenceConverterTest.java @@ -80,7 +80,8 @@ private void testValidSObjectReference(String refValue, String relationshipName, String fkFieldName = ConfigTestBase.DEFAULT_ACCOUNT_EXT_ID_FIELD; try { - ref.addReferenceToSObject(getController(), sObj, ObjectField.formatAsString("Parent", + // legacy formatting + ref.addReferenceToSObject(getController(), sObj, RelationshipField.formatAsString("Parent", ConfigTestBase.DEFAULT_ACCOUNT_EXT_ID_FIELD)); SObject child = (SObject)sObj.getChild(relationshipName); @@ -89,6 +90,17 @@ private void testValidSObjectReference(String refValue, String relationshipName, if (expectSuccess && !succeeded || !expectSuccess && succeeded) { Assert.fail(); } + + // new formatting + ref.addReferenceToSObject(getController(), sObj, RelationshipField.formatAsString("Account", "Parent", + ConfigTestBase.DEFAULT_ACCOUNT_EXT_ID_FIELD)); + + child = (SObject)sObj.getChild(relationshipName); + succeeded = child != null && child.getField(fkFieldName) != null && child.getField(fkFieldName) + .equals(refValue); + if (expectSuccess && !succeeded || !expectSuccess && succeeded) { + Assert.fail(); + } } catch (Exception e) { if (expectSuccess) { Assert.fail(); From f5a21531d28765a3b9db6dcf215c2852fefaa917 Mon Sep 17 00:00:00 2001 From: ashitsalesforce Date: Mon, 29 Jan 2024 19:44:37 -0800 Subject: [PATCH 3/3] minor formatting change to show lookup relationship minor formatting change to show lookup relationship --- .../java/com/salesforce/dataloader/dyna/RelationshipField.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/salesforce/dataloader/dyna/RelationshipField.java b/src/main/java/com/salesforce/dataloader/dyna/RelationshipField.java index b31e2fac..8392261c 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/RelationshipField.java +++ b/src/main/java/com/salesforce/dataloader/dyna/RelationshipField.java @@ -49,7 +49,7 @@ public class RelationshipField { // new format to support polymorphic lookup relationship - // :. // Example - "Account:Owner.username" - private static final String NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR = " - "; + private static final String NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR = "-"; private static final String NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR = ":"; public RelationshipField(String parentObjectName, String relationshipName) {