From dd77827e8c2d7fe0eb71e8847057c5927f9fbb2b Mon Sep 17 00:00:00 2001 From: tahsinz Date: Tue, 21 May 2019 13:55:42 -0700 Subject: [PATCH 1/6] Supporting inheritance for query factory In certain projects, the FFLIB library can be used as a seperated package (locked, unlocked or managed). Other package or library might have a need to inherit query factory to add or modify certain functionality and need the core library intact. Currently, the class is not abstract or virtual so the class can be inherited and also variables are private so can't be accessed by the inherited class. So this class makes the class virtual and change the access modifiers (private to projected). --- fflib/src/classes/fflib_QueryFactory.cls | 48 ++++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/fflib/src/classes/fflib_QueryFactory.cls b/fflib/src/classes/fflib_QueryFactory.cls index 0ee244f0a49..8db665bff26 100644 --- a/fflib/src/classes/fflib_QueryFactory.cls +++ b/fflib/src/classes/fflib_QueryFactory.cls @@ -52,28 +52,28 @@ * There is a google doc providing additional guideance on the use of this class with field sets at * https://docs.google.com/a/financialforce.com/document/d/1I4cxN4xHT4UJj_3Oi0YBL_MJ5chm-KG8kMN1D1un8-g/edit?usp=sharing **/ -public class fflib_QueryFactory { //No explicit sharing declaration - inherit from caller +public virtual class fflib_QueryFactory { //No explicit sharing declaration - inherit from caller public enum SortOrder {ASCENDING, DESCENDING} /** * This property is read-only and may not be set after instantiation. * The {@link Schema.SObjectType} token of the SObject that will be used in the FROM clause of the resultant query. **/ - public Schema.SObjectType table {get; private set;} + public Schema.SObjectType table {get; protected set;} @testVisible - private Set fields; - private String conditionExpression; - private Integer limitCount; - private Integer offsetCount; - private List order; + protected Set fields; + protected String conditionExpression; + protected Integer limitCount; + protected Integer offsetCount; + protected List order; /** * Integrate checking for READ Field Level Security within the selectField(s) methods * This can optionally be enforced (or not) by calling the setEnforceFLS method prior to calling * one of the selectField or selectFieldset methods. **/ - private Boolean enforceFLS; + protected Boolean enforceFLS; - private Boolean sortSelectFields = true; + protected Boolean sortSelectFields = true; /** * The relationship and subselectQueryMap variables are used to support subselect queries. Subselects can be added to @@ -81,10 +81,10 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * a query, but they may only be 1 level deep (no subselect inside a subselect) * to add a subselect, call the subselectQuery method, passing in the ChildRelationship. **/ - private Schema.ChildRelationship relationship; - private Map subselectQueryMap; + protected Schema.ChildRelationship relationship; + protected Map subselectQueryMap; - private String getFieldPath(String fieldName){ + protected String getFieldPath(String fieldName){ if(!fieldName.contains('.')){ //single field Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(table).getField(fieldName.toLowerCase()); if(token == null) @@ -124,7 +124,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr } @TestVisible - private static String getFieldTokenPath(Schema.SObjectField field){ + protected static String getFieldTokenPath(Schema.SObjectField field){ if(field == null){ throw new InvalidFieldException('Invalid field: null'); } @@ -162,7 +162,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * You *must* call selectField(s) before {@link #toSOQL} will return a valid, runnable query. * @param relationship the ChildRelationship to be used in the FROM Clause of the resultant Query (when set overrides value of table). This sets the value of {@link #relationship} and {@link #table}. **/ - private fflib_QueryFactory(Schema.ChildRelationship relationship){ + protected fflib_QueryFactory(Schema.ChildRelationship relationship){ this(relationship.getChildSObject()); this.relationship = relationship; } @@ -437,7 +437,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationship The ChildRelationship to be added as a subquery **/ - private fflib_QueryFactory setSubselectQuery(ChildRelationship relationship, Boolean assertIsAccessible){ + protected fflib_QueryFactory setSubselectQuery(ChildRelationship relationship, Boolean assertIsAccessible){ if (this.relationship != null){ throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery. You may not add a subselect query to a subselect query.'); } @@ -474,7 +474,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * Get the ChildRelationship from the Table for the object type passed in. * @param objType The object type of the child relationship to get **/ - private Schema.ChildRelationship getChildRelationship(sObjectType objType){ + protected Schema.ChildRelationship getChildRelationship(sObjectType objType){ for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ //occasionally on some standard objects (Like Contact child of Contact) do not have a relationship name. //if there is no relationship name, we cannot query on it, so throw an exception. @@ -489,7 +489,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * Get the ChildRelationship from the Table for the relationship name passed in. * @param relationshipName The name of the object's ChildRelationship on get **/ - private Schema.ChildRelationship getChildRelationship(String relationshipName){ + protected Schema.ChildRelationship getChildRelationship(String relationshipName){ for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ if (childRow.getRelationshipName() == relationshipName){ return childRow; @@ -708,9 +708,9 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr } public class Ordering{ - private SortOrder direction; - private boolean nullsLast; - private String field; + protected SortOrder direction; + protected boolean nullsLast; + protected String field; public Ordering(String sobjType, String fieldName, SortOrder direction){ this( @@ -729,11 +729,11 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr this(fflib_QueryFactory.getFieldTokenPath(field), direction, nullsLast); } @testVisible - private Ordering(String field, SortOrder direction){ + protected Ordering(String field, SortOrder direction){ this(field, direction, false); } @testVisible - private Ordering(String field, SortOrder direction, Boolean nullsLast){ + protected Ordering(String field, SortOrder direction, Boolean nullsLast){ this.direction = direction; this.field = field; this.nullsLast = nullsLast; @@ -752,8 +752,8 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr public class InvalidFieldException extends Exception{ - private String fieldName; - private Schema.SObjectType objectType; + protected String fieldName; + protected Schema.SObjectType objectType; public InvalidFieldException(String fieldname, Schema.SObjectType objectType){ this.objectType = objectType; this.fieldName = fieldName; From 6e5e9ce55e5ef688857a6b9529cd2cb3163aa0b4 Mon Sep 17 00:00:00 2001 From: Tahsin Zulkarnine Date: Tue, 21 May 2019 14:58:33 -0700 Subject: [PATCH 2/6] Default Sorting considering invalid field and shield Financial Force library (FFLIB) by default sorts record based on CreatedDate. Some object (ie. account share) might not have the field and hence will through error. This change checks the field encryption (taken from @wimvelzeboer ) and if the object does not have created date field, assigns Id as default sorting field. --- fflib/src/classes/fflib_SObjectSelector.cls | 50 ++++++++++++++------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectSelector.cls b/fflib/src/classes/fflib_SObjectSelector.cls index 8ec7ace33fb..859aff4d6c5 100644 --- a/fflib/src/classes/fflib_SObjectSelector.cls +++ b/fflib/src/classes/fflib_SObjectSelector.cls @@ -47,17 +47,17 @@ public abstract with sharing class fflib_SObjectSelector /** * Should this selector automatically include the FieldSet fields when building queries? **/ - private Boolean m_includeFieldSetFields; + protected Boolean m_includeFieldSetFields; /** * Enforce FLS Security **/ - private Boolean m_enforceFLS; + protected Boolean m_enforceFLS; /** * Enforce CRUD Security **/ - private Boolean m_enforceCRUD; + protected Boolean m_enforceCRUD; /** * Order by field @@ -143,20 +143,38 @@ public abstract with sharing class fflib_SObjectSelector { return null; } - + /** - * Override this method to control the default ordering of records returned by the base queries, - * defaults to the name field of the object or CreatedDate if there is none - **/ + * Override this method to control the default ordering of records returned by the base queries, + * defaults to the name field of the object if it is not encrypted or CreatedDate if there the object has createdDated or Id + **/ public virtual String getOrderBy() { - if(m_orderBy == null) { - m_orderBy = 'CreatedDate'; - if(describeWrapper.getNameField() != null) { - m_orderBy = describeWrapper.getNameField().getDescribe().getName(); - } - } - return m_orderBy; + if (m_orderBy == null) + { + Schema.SObjectField nameField = describeWrapper.getNameField(); + if (nameField != null && !nameField.getDescribe().isEncrypted()) + { + m_orderBy = nameField.getDescribe().getName(); + } + else + { + m_orderBy = 'CreatedDate'; + //Tahsin Zulkarnine: Check whether createdDate field exists in the object. Certain object might not have it, ie AccountShare, etc + Boolean invalidField = false; + try + { + if(describeWrapper.getField(m_orderBy) == null) invalidField = true; + } + catch(fflib_QueryFactory.InvalidFieldException ex){ + // Tahsin Zulkarnine: if not found, making the default field to Id as all saleforce object has id + invalidField = true; + } + if(invalidField) m_orderBy = 'Id'; + + } + } + return m_orderBy; } /** @@ -385,7 +403,7 @@ public abstract with sharing class fflib_SObjectSelector /** * Configures a QueryFactory instance according to the configuration of this selector **/ - private fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory, Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) + protected fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory, Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) { // CRUD and FLS security required? if (assertCRUD) @@ -437,4 +455,4 @@ public abstract with sharing class fflib_SObjectSelector return queryFactory; } -} \ No newline at end of file +} From 1e3e94f8e482b971390bf57fc12ab61f72ba274b Mon Sep 17 00:00:00 2001 From: Tahsin Zulkarnine Date: Tue, 21 May 2019 22:47:15 -0700 Subject: [PATCH 3/6] Cleaned up the code based on pull request feedback. --- fflib/src/classes/fflib_SObjectSelector.cls | 29 ++++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectSelector.cls b/fflib/src/classes/fflib_SObjectSelector.cls index 859aff4d6c5..e50f097a383 100644 --- a/fflib/src/classes/fflib_SObjectSelector.cls +++ b/fflib/src/classes/fflib_SObjectSelector.cls @@ -69,7 +69,7 @@ public abstract with sharing class fflib_SObjectSelector * Switch this off if you need more performant queries. **/ private Boolean m_sortSelectFields; - + /** * Describe helper **/ @@ -81,7 +81,13 @@ public abstract with sharing class fflib_SObjectSelector } set; } - + /** + * static variables + **/ + + protected static String DEFAULT_SORT_FIELD = 'CreatedDate'; + protected static String SF_ID_FIELD = 'Id'; + /** * Implement this method to inform the base class of the SObject (custom or standard) to be queried **/ @@ -148,7 +154,7 @@ public abstract with sharing class fflib_SObjectSelector * Override this method to control the default ordering of records returned by the base queries, * defaults to the name field of the object if it is not encrypted or CreatedDate if there the object has createdDated or Id **/ - public virtual String getOrderBy() + public virtual String getOrderBy() { if (m_orderBy == null) { @@ -159,19 +165,16 @@ public abstract with sharing class fflib_SObjectSelector } else { - m_orderBy = 'CreatedDate'; - //Tahsin Zulkarnine: Check whether createdDate field exists in the object. Certain object might not have it, ie AccountShare, etc - Boolean invalidField = false; - try + m_orderBy = DEFAULT_SORT_FIELD; + try { + if (describeWrapper.getField(m_orderBy) == null) { - if(describeWrapper.getField(m_orderBy) == null) invalidField = true; + m_orderBy = SF_ID_FIELD; + } } - catch(fflib_QueryFactory.InvalidFieldException ex){ - // Tahsin Zulkarnine: if not found, making the default field to Id as all saleforce object has id - invalidField = true; + catch(fflib_QueryFactory.InvalidFieldException ex) { + m_orderBy = SF_ID_FIELD; } - if(invalidField) m_orderBy = 'Id'; - } } return m_orderBy; From 4cd860698235c5b098b0facc309a4aa62aca45e5 Mon Sep 17 00:00:00 2001 From: Tahsin Zulkarnine Date: Wed, 22 May 2019 23:36:26 -0700 Subject: [PATCH 4/6] Update fflib_SObjectSelector.cls Fixing the comment-blocking indenting issues (caused by IDE) --- fflib/src/classes/fflib_SObjectSelector.cls | 113 ++++++++++---------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectSelector.cls b/fflib/src/classes/fflib_SObjectSelector.cls index e50f097a383..5d222242d9d 100644 --- a/fflib/src/classes/fflib_SObjectSelector.cls +++ b/fflib/src/classes/fflib_SObjectSelector.cls @@ -52,17 +52,17 @@ public abstract with sharing class fflib_SObjectSelector /** * Enforce FLS Security **/ - protected Boolean m_enforceFLS; + protected Boolean m_enforceFLS; /** * Enforce CRUD Security **/ - protected Boolean m_enforceCRUD; + protected Boolean m_enforceCRUD; - /** - * Order by field - **/ - private String m_orderBy; + /** + * Order by field + **/ + private String m_orderBy; /** * Sort the query fields in the select statement (defaults to true, at the expense of performance). @@ -70,23 +70,22 @@ public abstract with sharing class fflib_SObjectSelector **/ private Boolean m_sortSelectFields; - /** - * Describe helper - **/ - private fflib_SObjectDescribe describeWrapper { - get { - if(describeWrapper == null) - describeWrapper = fflib_SObjectDescribe.getDescribe(getSObjectType()); - return describeWrapper; - } - set; + /** + * Describe helper + **/ + private fflib_SObjectDescribe describeWrapper { + get { + if(describeWrapper == null) + describeWrapper = fflib_SObjectDescribe.getDescribe(getSObjectType()); + return describeWrapper; + } + set; } - /** - * static variables - **/ - - protected static String DEFAULT_SORT_FIELD = 'CreatedDate'; - protected static String SF_ID_FIELD = 'Id'; + /** + * static variables + **/ + protected static String DEFAULT_SORT_FIELD = 'CreatedDate'; + protected static String SF_ID_FIELD = 'Id'; /** * Implement this method to inform the base class of the SObject (custom or standard) to be queried @@ -150,12 +149,12 @@ public abstract with sharing class fflib_SObjectSelector return null; } - /** - * Override this method to control the default ordering of records returned by the base queries, - * defaults to the name field of the object if it is not encrypted or CreatedDate if there the object has createdDated or Id - **/ + /** + * Override this method to control the default ordering of records returned by the base queries, + * defaults to the name field of the object if it is not encrypted or CreatedDate if there the object has createdDated or Id + **/ public virtual String getOrderBy() - { + { if (m_orderBy == null) { Schema.SObjectField nameField = describeWrapper.getNameField(); @@ -341,20 +340,20 @@ public abstract with sharing class fflib_SObjectSelector assertCRUD, enforceFLS, includeSelectorFields); } - /** - * Adds the selectors fields to the given QueryFactory using the given relationship path as a prefix - * - * // TODO: This should be consistant (ideally) with configureQueryFactory below - **/ - public void configureQueryFactoryFields(fflib_QueryFactory queryFactory, String relationshipFieldPath) - { - // Add fields from selector prefixing the relationship path - for(SObjectField field : getSObjectFieldList()) - queryFactory.selectField(relationshipFieldPath + '.' + field.getDescribe().getName()); - // Automatically select the CurrencyIsoCode for MC orgs (unless the object is a known exception to the rule) + /** + * Adds the selectors fields to the given QueryFactory using the given relationship path as a prefix + * + * // TODO: This should be consistant (ideally) with configureQueryFactory below + **/ + public void configureQueryFactoryFields(fflib_QueryFactory queryFactory, String relationshipFieldPath) + { + // Add fields from selector prefixing the relationship path + for(SObjectField field : getSObjectFieldList()) + queryFactory.selectField(relationshipFieldPath + '.' + field.getDescribe().getName()); + // Automatically select the CurrencyIsoCode for MC orgs (unless the object is a known exception to the rule) if(Userinfo.isMultiCurrencyOrganization() && CURRENCY_ISO_CODE_ENABLED) queryFactory.selectField(relationshipFieldPath+'.CurrencyIsoCode'); - } + } /** * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the parentQueryFactory @@ -378,22 +377,22 @@ public abstract with sharing class fflib_SObjectSelector includeSelectorFields); } - /** - * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the parentQueryFactory - **/ - public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName) - { - return addQueryFactorySubselect(parentQueryFactory, relationshipName, TRUE); - } + /** + * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the parentQueryFactory + **/ + public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName) + { + return addQueryFactorySubselect(parentQueryFactory, relationshipName, TRUE); + } - /** - * Adds a subselect QueryFactory based on this selector to the given QueryFactor - **/ - public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName, Boolean includeSelectorFields) - { - fflib_QueryFactory subSelectQueryFactory = parentQueryFactory.subselectQuery(relationshipName); - return configureQueryFactory(subSelectQueryFactory, m_enforceCRUD, m_enforceFLS, includeSelectorFields); - } + /** + * Adds a subselect QueryFactory based on this selector to the given QueryFactor + **/ + public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName, Boolean includeSelectorFields) + { + fflib_QueryFactory subSelectQueryFactory = parentQueryFactory.subselectQuery(relationshipName); + return configureQueryFactory(subSelectQueryFactory, m_enforceCRUD, m_enforceFLS, includeSelectorFields); + } /** * Constructs the default SOQL query for this selector, see selectSObjectsById and queryLocatorById @@ -403,9 +402,9 @@ public abstract with sharing class fflib_SObjectSelector return newQueryFactory().setCondition('id in :idSet').toSOQL(); } - /** - * Configures a QueryFactory instance according to the configuration of this selector - **/ + /** + * Configures a QueryFactory instance according to the configuration of this selector + **/ protected fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory, Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) { // CRUD and FLS security required? From 5f56c3ec0f3cb5e68c9e87c6312c01b04ecadb49 Mon Sep 17 00:00:00 2001 From: Tahsin Zulkarnine Date: Thu, 23 May 2019 09:44:17 -0700 Subject: [PATCH 5/6] Update fflib_SObjectSelector.cls Updated the access modifiers from protected to private. --- fflib/src/classes/fflib_SObjectSelector.cls | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fflib/src/classes/fflib_SObjectSelector.cls b/fflib/src/classes/fflib_SObjectSelector.cls index 5d222242d9d..df04919e058 100644 --- a/fflib/src/classes/fflib_SObjectSelector.cls +++ b/fflib/src/classes/fflib_SObjectSelector.cls @@ -47,17 +47,17 @@ public abstract with sharing class fflib_SObjectSelector /** * Should this selector automatically include the FieldSet fields when building queries? **/ - protected Boolean m_includeFieldSetFields; + private Boolean m_includeFieldSetFields; /** * Enforce FLS Security **/ - protected Boolean m_enforceFLS; + private Boolean m_enforceFLS; /** * Enforce CRUD Security **/ - protected Boolean m_enforceCRUD; + private Boolean m_enforceCRUD; /** * Order by field @@ -84,8 +84,8 @@ public abstract with sharing class fflib_SObjectSelector /** * static variables **/ - protected static String DEFAULT_SORT_FIELD = 'CreatedDate'; - protected static String SF_ID_FIELD = 'Id'; + private static String DEFAULT_SORT_FIELD = 'CreatedDate'; + private static String SF_ID_FIELD = 'Id'; /** * Implement this method to inform the base class of the SObject (custom or standard) to be queried @@ -405,7 +405,7 @@ public abstract with sharing class fflib_SObjectSelector /** * Configures a QueryFactory instance according to the configuration of this selector **/ - protected fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory, Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) + private fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory, Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) { // CRUD and FLS security required? if (assertCRUD) From f8e7d905baede28e4d5ad4ff08aac47aa92cf0d2 Mon Sep 17 00:00:00 2001 From: Tahsin Zulkarnine Date: Tue, 4 Jun 2019 14:48:53 -0700 Subject: [PATCH 6/6] Incorporated onBeforeSOQL method so the extension can override to include more advanced WHERE clause. --- fflib/src/classes/fflib_QueryFactory.cls | 283 ++++++++++---------- fflib/src/classes/fflib_SObjectSelector.cls | 252 ++++++++--------- 2 files changed, 274 insertions(+), 261 deletions(-) diff --git a/fflib/src/classes/fflib_QueryFactory.cls b/fflib/src/classes/fflib_QueryFactory.cls index 8db665bff26..87e2230a653 100644 --- a/fflib/src/classes/fflib_QueryFactory.cls +++ b/fflib/src/classes/fflib_QueryFactory.cls @@ -61,7 +61,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i **/ public Schema.SObjectType table {get; protected set;} @testVisible - protected Set fields; + protected Set fields; protected String conditionExpression; protected Integer limitCount; protected Integer offsetCount; @@ -72,9 +72,9 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i * one of the selectField or selectFieldset methods. **/ protected Boolean enforceFLS; - + protected Boolean sortSelectFields = true; - + /** * The relationship and subselectQueryMap variables are used to support subselect queries. Subselects can be added to * a query, as long as it isn't a subselect query itself. You may have many subselects inside @@ -89,8 +89,8 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(table).getField(fieldName.toLowerCase()); if(token == null) throw new InvalidFieldException(fieldName,this.table); - if (enforceFLS) - fflib_SecurityUtils.checkFieldIsReadable(this.table, token); + if (enforceFLS) + fflib_SecurityUtils.checkFieldIsReadable(this.table, token); return token.getDescribe().getName(); } @@ -102,7 +102,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i String field = i.next(); Schema.SObjectField token = fflib_SObjectDescribe.getDescribe(lastSObjectType).getField(field.toLowerCase()); DescribeFieldResult tokenDescribe = token != null ? token.getDescribe() : null; - + if (token != null && enforceFLS) { fflib_SecurityUtils.checkFieldIsReadable(lastSObjectType, token); } @@ -116,7 +116,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i if(token == null) throw new InvalidFieldException(field,lastSObjectType); else - throw new NonReferenceFieldException(lastSObjectType+'.'+field+' is not a lookup or master-detail field but is used in a cross-object query field.'); + throw new NonReferenceFieldException(lastSObjectType+'.'+field+' is not a lookup or master-detail field but is used in a cross-object query field.'); } } @@ -124,9 +124,9 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i } @TestVisible - protected static String getFieldTokenPath(Schema.SObjectField field){ + private static String getFieldTokenPath(Schema.SObjectField field){ if(field == null){ - throw new InvalidFieldException('Invalid field: null'); + throw new InvalidFieldException('Invalid field: null'); } return field.getDescribe().getName(); } @@ -134,7 +134,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i /** * fflib_QueryFactory instances will be considered equal if they produce the same SOQL query. * A faster comparison will first be attempted to check if they apply to the same table, and contain the same number of fields selected. - * This method will never return true if the provided object is not an instance of fflib_QueryFactory. + * This method will never return true if the provided object is not an instance of fflib_QueryFactory. * @param obj the object to check equality of. **/ public boolean equals(Object obj){ @@ -144,7 +144,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i } /** - * Construct a new fflib_QueryFactory instance with no options other than the FROM caluse. + * Construct a new fflib_QueryFactory instance with no options other than the FROM caluse. * You *must* call selectField(s) before {@link #toSOQL} will return a valid, runnable query. * @param table the SObject to be used in the FROM clause of the resultant query. This sets the value of {@link #table}. **/ @@ -157,18 +157,18 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i /** * Construct a new fflib_QueryFactory instance with no options other than the FROM clause and the relationship. - * This should be used when constructing a subquery query for addition to a parent query. + * This should be used when constructing a subquery query for addition to a parent query. * Objects created with this constructor cannot be added to another object using the subselectQuery method. * You *must* call selectField(s) before {@link #toSOQL} will return a valid, runnable query. * @param relationship the ChildRelationship to be used in the FROM Clause of the resultant Query (when set overrides value of table). This sets the value of {@link #relationship} and {@link #table}. **/ protected fflib_QueryFactory(Schema.ChildRelationship relationship){ - this(relationship.getChildSObject()); + this(relationship.getChildSObject()); this.relationship = relationship; } /** - * This method checks to see if the User has Read Access on {@link #table}. + * This method checks to see if the User has Read Access on {@link #table}. * Asserts true if User has access. **/ public fflib_QueryFactory assertIsAccessible(){ @@ -197,16 +197,16 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i this.sortSelectFields = doSort; return this; } - + /** * Selects a single field from the SObject specified in {@link #table}. * Selecting fields is idempotent, if this field is already selected calling this method will have no additional impact. * @param fieldName the API name of the field to add to the query's SELECT clause. **/ - public fflib_QueryFactory selectField(String fieldName){ + public fflib_QueryFactory selectField(String fieldName){ fields.add( getFieldPath(fieldName) ); return this; - } + } /** * Selects a field, avoiding the possible ambiguitiy of String API names. * @see #selectField(String) @@ -216,7 +216,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i public fflib_QueryFactory selectField(Schema.SObjectField field){ if(field == null) throw new InvalidFieldException(null,this.table); - if (enforceFLS) + if (enforceFLS) fflib_SecurityUtils.checkFieldIsReadable(table, field); fields.add( getFieldTokenPath(field) ); return this; @@ -228,7 +228,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i public fflib_QueryFactory selectFields(Set fieldNames){ for(String fieldName:fieldNames){ fields.add( getFieldPath(fieldName) ); - } + } return this; } /** @@ -248,9 +248,9 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i public fflib_QueryFactory selectFields(Set fields){ for(Schema.SObjectField token:fields){ if(token == null) - throw new InvalidFieldException(); - if (enforceFLS) - fflib_SecurityUtils.checkFieldIsReadable(table, token); + throw new InvalidFieldException(); + if (enforceFLS) + fflib_SecurityUtils.checkFieldIsReadable(table, token); this.fields.add( getFieldTokenPath(token) ); } return this; @@ -258,14 +258,14 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i /** * Selects multiple fields. This acts the same as calling {@link #selectField(Schema.SObjectField)} multiple times. * @param fieldNames the set of {@link Schema.SObjectField}s to select. - * @exception InvalidFieldException if the fields are null {@code fields}. + * @exception InvalidFieldException if the fields are null {@code fields}. **/ public fflib_QueryFactory selectFields(List fields){ for(Schema.SObjectField token:fields){ if(token == null) throw new InvalidFieldException(); - if (enforceFLS) - fflib_SecurityUtils.checkFieldIsReadable(table, token); + if (enforceFLS) + fflib_SecurityUtils.checkFieldIsReadable(table, token); this.fields.add( getFieldTokenPath(token) ); } return this; @@ -278,11 +278,11 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i } /** * This is equivielent to iterating the fields in the field set and calling {@link #selectField(String)} on each. - * @param fieldSet Select all fields included in the field set. + * @param fieldSet Select all fields included in the field set. * @param allowCrossObject if false this method will throw an exception if any fields in the field set reference fields on a related record. - * @exception InvalidFieldSetException if the fieldset is invalid for table {@code fields}. + * @exception InvalidFieldSetException if the fieldset is invalid for table {@code fields}. **/ - public fflib_QueryFactory selectFieldSet(Schema.FieldSet fieldSet, Boolean allowCrossObject){ + public fflib_QueryFactory selectFieldSet(Schema.FieldSet fieldSet, Boolean allowCrossObject){ if(fieldSet.getSObjectType() != table) throw new InvalidFieldSetException('Field set "'+fieldSet.getName()+'" is not for SObject type "'+table+'"'); for(Schema.FieldSetMember field: fieldSet.getFields()){ @@ -356,7 +356,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i /** * @returns the selected fields **/ - public Set getSelectedFields() { + public Set getSelectedFields() { return this.fields; } @@ -364,10 +364,10 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. * @deprecated Replaced by {@link #subselectQuery(String relationshipName)} and {@link #subselectQuery(ChildRelationship relationship)} - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param related The related object type **/ - public fflib_QueryFactory subselectQuery(SObjectType related){ + public fflib_QueryFactory subselectQuery(SObjectType related){ System.debug(LoggingLevel.WARN, 'fflib_QueryFactory.subselectQuery(Schema.SObjectType) is deprecated and will be removed in a future release. Use fflib_QueryFactory.subselectQuery(String) or fflib_QueryFactory.subselectQuery(ChildRelationship) instead.'); return setSubselectQuery(getChildRelationship(related), false); } @@ -376,29 +376,29 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. * @deprecated Replaced by {@link #subselectQuery(String relationshipName, Boolean assertIsAccessible)} and {@link #subselectQuery(ChildRelationship relationship, Boolean assertIsAccessible)} - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param related The related object type * @param assertIsAccessible indicates whether to check if the user has access to the subquery object **/ public fflib_QueryFactory subselectQuery(SObjectType related, Boolean assertIsAccessible){ - System.debug(LoggingLevel.WARN, 'fflib_QueryFactory.subselectQuery(Schema.SObjectType, Boolean) is deprecated and will be removed in a future release. Use fflib_QueryFactory.subselectQuery(String, Boolean) or fflib_QueryFactory.subselectQuery(ChildRelationship, Boolean) instead.'); + System.debug(LoggingLevel.WARN, 'fflib_QueryFactory.subselectQuery(Schema.SObjectType, Boolean) is deprecated and will be removed in a future release. Use fflib_QueryFactory.subselectQuery(String, Boolean) or fflib_QueryFactory.subselectQuery(ChildRelationship, Boolean) instead.'); return setSubselectQuery(getChildRelationship(related), assertIsAccessible); } /** * Add a subquery query to this query. If a subquery for this relationshipName already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationshipName The relationshipName to be added as a subquery **/ - public fflib_QueryFactory subselectQuery(String relationshipName){ + public fflib_QueryFactory subselectQuery(String relationshipName){ return subselectQuery(relationshipName, false); } /** * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationshipName The relationshipName to be added as a subquery * @param assertIsAccessible indicates whether to check if the user has access to the subquery object **/ @@ -407,23 +407,23 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i if (relationship != null) { return setSubselectQuery(relationship, assertIsAccessible); } - throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery with relationshipName = '+relationshipName +'. Relationship does not exist for ' + table.getDescribe().getName()); + throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery with relationshipName = '+relationshipName +'. Relationship does not exist for ' + table.getDescribe().getName()); } /** * Add a subquery query to this query. If a subquery for this relationshipName already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationship The ChildRelationship to be added as a subquery **/ - public fflib_QueryFactory subselectQuery(ChildRelationship relationship){ + public fflib_QueryFactory subselectQuery(ChildRelationship relationship){ return subselectQuery(relationship, false); } /** * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationship The ChildRelationship to be added as a subquery * @param assertIsAccessible indicates whether to check if the user has access to the subquery object **/ @@ -434,22 +434,22 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i /** * Add a subquery query to this query. If a subquery for this relationship already exists, it will be returned. * If not, a new one will be created and returned. - * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship + * @exception InvalidSubqueryRelationshipException If this method is called on a subselectQuery or with an invalid relationship * @param relationship The ChildRelationship to be added as a subquery **/ protected fflib_QueryFactory setSubselectQuery(ChildRelationship relationship, Boolean assertIsAccessible){ if (this.relationship != null){ throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery. You may not add a subselect query to a subselect query.'); - } + } if (this.subselectQueryMap == null){ this.subselectQueryMap = new Map(); } if (this.subselectQueryMap.containsKey(relationship)){ return subselectQueryMap.get(relationship); } - + fflib_QueryFactory subselectQuery = new fflib_QueryFactory(relationship); - + //The child queryFactory should be configured in the same way as the parent by default - can override after if required subSelectQuery.setSortSelectFields(sortSelectFields); @@ -466,7 +466,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i public List getSubselectQueries(){ if (subselectQueryMap != null) { return subselectQueryMap.values(); - } + } return null; } @@ -474,103 +474,103 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i * Get the ChildRelationship from the Table for the object type passed in. * @param objType The object type of the child relationship to get **/ - protected Schema.ChildRelationship getChildRelationship(sObjectType objType){ - for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ - //occasionally on some standard objects (Like Contact child of Contact) do not have a relationship name. - //if there is no relationship name, we cannot query on it, so throw an exception. - if (childRow.getChildSObject() == objType && childRow.getRelationshipName() != null){ - return childRow; - } - } - throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery. Invalid relationship for table '+table + ' and objtype='+objType); - } + protected Schema.ChildRelationship getChildRelationship(sObjectType objType){ + for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ + //occasionally on some standard objects (Like Contact child of Contact) do not have a relationship name. + //if there is no relationship name, we cannot query on it, so throw an exception. + if (childRow.getChildSObject() == objType && childRow.getRelationshipName() != null){ + return childRow; + } + } + throw new InvalidSubqueryRelationshipException('Invalid call to subselectQuery. Invalid relationship for table '+table + ' and objtype='+objType); + } /** * Get the ChildRelationship from the Table for the relationship name passed in. * @param relationshipName The name of the object's ChildRelationship on get **/ - protected Schema.ChildRelationship getChildRelationship(String relationshipName){ - for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ - if (childRow.getRelationshipName() == relationshipName){ - return childRow; - } - } - return null; - } + protected Schema.ChildRelationship getChildRelationship(String relationshipName){ + for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ + if (childRow.getRelationshipName() == relationshipName){ + return childRow; + } + } + return null; + } /** - * Add a field to be sorted on. This may be a direct field or a field + * Add a field to be sorted on. This may be a direct field or a field * related through an object lookup or master-detail relationship. * Use the set to store unique field names, since we only want to sort * by the same field one time. The sort expressions are stored in a list * so that they are applied to the SOQL in the same order that they - * were added in. + * were added in. * @param fieldName The string value of the field to be sorted on * @param SortOrder the direction to be sorted on (ASCENDING or DESCENDING) * @param nullsLast whether to sort null values last (NULLS LAST keyword included). - **/ - public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction, Boolean nullsLast){ + **/ + public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction, Boolean nullsLast){ order.add( - new Ordering(getFieldPath(fieldName), direction, nullsLast) - ); + new Ordering(getFieldPath(fieldName), direction, nullsLast) + ); return this; - } + } - /** - * Add a field to be sorted on. This may be a direct field or a field - * related through an object lookup or master-detail relationship. - * Use the set to store unique field names, since we only want to sort - * by the same field one time. The sort expressions are stored in a list - * so that they are applied to the SOQL in the same order that they - * were added in. - * @param field The SObjectfield to sort. This can only be a direct reference. - * @param SortOrder the direction to be sorted on (ASCENDING or DESCENDING) - * @param nullsLast whether to sort null values last (NULLS LAST keyword included). - **/ - public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction, Boolean nullsLast){ + /** + * Add a field to be sorted on. This may be a direct field or a field + * related through an object lookup or master-detail relationship. + * Use the set to store unique field names, since we only want to sort + * by the same field one time. The sort expressions are stored in a list + * so that they are applied to the SOQL in the same order that they + * were added in. + * @param field The SObjectfield to sort. This can only be a direct reference. + * @param SortOrder the direction to be sorted on (ASCENDING or DESCENDING) + * @param nullsLast whether to sort null values last (NULLS LAST keyword included). + **/ + public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction, Boolean nullsLast){ order.add( - new Ordering(getFieldTokenPath(field), direction, nullsLast) - ); + new Ordering(getFieldTokenPath(field), direction, nullsLast) + ); return this; - } + } - /** - * Add a field to be sorted on. This may be a direct field or a field - * related through an object lookup or master-detail relationship. - * Use the set to store unique field names, since we only want to sort - * by the same field one time. The sort expressions are stored in a list - * so that they are applied to the SOQL in the same order that they - * were added in. - * The "NULLS FIRST" keywords will be included by default. If "NULLS LAST" - * is required, use one of the overloaded addOrdering methods which include this parameter. - * @param fieldName The string value of the field to be sorted on - * @param SortOrder the direction to be sorted on (ASCENDING or DESCENDING) - **/ - public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction){ + /** + * Add a field to be sorted on. This may be a direct field or a field + * related through an object lookup or master-detail relationship. + * Use the set to store unique field names, since we only want to sort + * by the same field one time. The sort expressions are stored in a list + * so that they are applied to the SOQL in the same order that they + * were added in. + * The "NULLS FIRST" keywords will be included by default. If "NULLS LAST" + * is required, use one of the overloaded addOrdering methods which include this parameter. + * @param fieldName The string value of the field to be sorted on + * @param SortOrder the direction to be sorted on (ASCENDING or DESCENDING) + **/ + public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction){ order.add( - new Ordering(getFieldPath(fieldName), direction) - ); + new Ordering(getFieldPath(fieldName), direction) + ); return this; - } + } - /** - * Add a field to be sorted on. This may be a direct field or a field - * related through an object lookup or master-detail relationship. - * Use the set to store unique field names, since we only want to sort - * by the same field one time. The sort expressions are stored in a list - * so that they are applied to the SOQL in the same order that they - * were added in. - * The "NULLS FIRST" keywords will be included by default. If "NULLS LAST" - * is required, use one of the overloaded addOrdering methods which include this parameter. - * @param field The SObjectfield to sort. This can only be a direct reference. - * @param SortOrder the direction to be sorted on (ASCENDING or DESCENDING) - **/ - public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction){ + /** + * Add a field to be sorted on. This may be a direct field or a field + * related through an object lookup or master-detail relationship. + * Use the set to store unique field names, since we only want to sort + * by the same field one time. The sort expressions are stored in a list + * so that they are applied to the SOQL in the same order that they + * were added in. + * The "NULLS FIRST" keywords will be included by default. If "NULLS LAST" + * is required, use one of the overloaded addOrdering methods which include this parameter. + * @param field The SObjectfield to sort. This can only be a direct reference. + * @param SortOrder the direction to be sorted on (ASCENDING or DESCENDING) + **/ + public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction){ order.add( - new Ordering(getFieldTokenPath(field), direction) - ); + new Ordering(getFieldTokenPath(field), direction) + ); return this; - } + } /** * Remove existing ordering and set a field to be sorted on. This may be a direct field or a field @@ -633,12 +633,25 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i Ordering ordr = new Ordering(getFieldTokenPath(field), direction); return setOrdering(ordr); } + /** + * Virtual method that is called at the start of the toSOQL method to allow final state of the factory + * to be set by an extending class prior to the SOQL query string being constructed. With the above hooks in place, + * the developers would then extend the fflib_QueryFactory with their own class and add more advanced WHERE clause + * building methods + **/ + public virtual void onBeforeToSOQL(){ + + } /** * Convert the values provided to this instance into a full SOQL string for use with Database.query * Check to see if subqueries queries need to be added after the field list. **/ public String toSOQL(){ + + // ensuring that extension class had a chance to provide more advanced where clause + onBeforeToSOQL(); + String result = 'SELECT '; //if no fields have been added, just add the Id field so that the query or subquery will not just fail if (fields.size() == 0){ @@ -646,18 +659,18 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i result += 'Id'; }else { List fieldsToQuery = new List(fields); - + if(sortSelectFields){ - fieldsToQuery.sort(); - } - + fieldsToQuery.sort(); + } + result += String.join(fieldsToQuery,', '); } - + if(subselectQueryMap != null && !subselectQueryMap.isEmpty()){ for (fflib_QueryFactory childRow : subselectQueryMap.values()){ result += ', (' + childRow.toSOQL() + ') '; - } + } } result += ' FROM ' + (relationship != null ? relationship.getRelationshipName() : table.getDescribe().getName()); if(conditionExpression != null) @@ -669,7 +682,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i result += o.toSOQL() +', '; result = result.substring(0,result.length()-2); } - + if(limitCount != null) result += ' LIMIT '+limitCount; @@ -683,13 +696,13 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i * Create a "deep" clone of this object that can be safely mutated without affecting the cloned instance * @return a deep clone of this fflib_QueryFactory **/ - public fflib_QueryFactory deepClone(){ + public fflib_QueryFactory deepClone(){ fflib_QueryFactory clone = new fflib_QueryFactory(this.table) - .setLimit(this.limitCount) - .setOffset(this.offsetCount) - .setCondition(this.conditionExpression) - .setEnforceFLS(this.enforceFLS); + .setLimit(this.limitCount) + .setOffset(this.offsetCount) + .setCondition(this.conditionExpression) + .setEnforceFLS(this.enforceFLS); Map subqueries = this.subselectQueryMap; if(subqueries != null) { @@ -706,16 +719,16 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i return clone; } - - public class Ordering{ + + public virtual class Ordering{ protected SortOrder direction; protected boolean nullsLast; protected String field; public Ordering(String sobjType, String fieldName, SortOrder direction){ this( - fflib_SObjectDescribe.getDescribe(sobjType).getField(fieldName), - direction + fflib_SObjectDescribe.getDescribe(sobjType).getField(fieldName), + direction ); } /** @@ -750,7 +763,7 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i } - + public class InvalidFieldException extends Exception{ protected String fieldName; protected Schema.SObjectType objectType; @@ -762,5 +775,5 @@ public virtual class fflib_QueryFactory { //No explicit sharing declaration - i } public class InvalidFieldSetException extends Exception{} public class NonReferenceFieldException extends Exception{} - public class InvalidSubqueryRelationshipException extends Exception{} + public class InvalidSubqueryRelationshipException extends Exception{} } diff --git a/fflib/src/classes/fflib_SObjectSelector.cls b/fflib/src/classes/fflib_SObjectSelector.cls index df04919e058..2e652b6c400 100644 --- a/fflib/src/classes/fflib_SObjectSelector.cls +++ b/fflib/src/classes/fflib_SObjectSelector.cls @@ -28,11 +28,11 @@ * Class providing common database query support for abstracting and encapsulating query logic **/ public abstract with sharing class fflib_SObjectSelector - implements fflib_ISObjectSelector + implements fflib_ISObjectSelector { /** * Indicates whether the sObject has the currency ISO code field for organisations which have multi-currency - * enabled. + * enabled. **/ private Boolean CURRENCY_ISO_CODE_ENABLED { get { @@ -41,24 +41,24 @@ public abstract with sharing class fflib_SObjectSelector } return CURRENCY_ISO_CODE_ENABLED; } - set; + set; } - + /** * Should this selector automatically include the FieldSet fields when building queries? **/ - private Boolean m_includeFieldSetFields; - + protected Boolean m_includeFieldSetFields; + /** * Enforce FLS Security **/ - private Boolean m_enforceFLS; + protected Boolean m_enforceFLS; /** * Enforce CRUD Security **/ - private Boolean m_enforceCRUD; - + protected Boolean m_enforceCRUD; + /** * Order by field **/ @@ -74,13 +74,13 @@ public abstract with sharing class fflib_SObjectSelector * Describe helper **/ private fflib_SObjectDescribe describeWrapper { - get { - if(describeWrapper == null) - describeWrapper = fflib_SObjectDescribe.getDescribe(getSObjectType()); - return describeWrapper; - } - set; - } + get { + if(describeWrapper == null) + describeWrapper = fflib_SObjectDescribe.getDescribe(getSObjectType()); + return describeWrapper; + } + set; + } /** * static variables **/ @@ -91,7 +91,7 @@ public abstract with sharing class fflib_SObjectSelector * Implement this method to inform the base class of the SObject (custom or standard) to be queried **/ abstract Schema.SObjectType getSObjectType(); - + /** * Implement this method to inform the base class of the common fields to be queried or listed by the base class methods **/ @@ -104,21 +104,21 @@ public abstract with sharing class fflib_SObjectSelector { this(false); } - + /** * Constructs the Selector * - * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well + * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well **/ public fflib_SObjectSelector(Boolean includeFieldSetFields) { this(includeFieldSetFields, true, false); } - + /** * Constructs the Selector * - * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well + * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well **/ public fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS) { @@ -128,7 +128,7 @@ public abstract with sharing class fflib_SObjectSelector /** * Constructs the Selector * - * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well + * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well * @param enforceCRUD Enforce CRUD security * @param enforeFLS Enforce Field Level Security * @param sortSelectFields Set to false if selecting many columns to skip sorting select fields and improve performance @@ -149,12 +149,12 @@ public abstract with sharing class fflib_SObjectSelector return null; } - /** - * Override this method to control the default ordering of records returned by the base queries, - * defaults to the name field of the object if it is not encrypted or CreatedDate if there the object has createdDated or Id - **/ - public virtual String getOrderBy() - { + /** + * Override this method to control the default ordering of records returned by the base queries, + * defaults to the name field of the object if it is not encrypted or CreatedDate if there the object has createdDated or Id + **/ + public virtual String getOrderBy() + { if (m_orderBy == null) { Schema.SObjectField nameField = describeWrapper.getNameField(); @@ -179,28 +179,28 @@ public abstract with sharing class fflib_SObjectSelector return m_orderBy; } - /** + /** * Returns True if this Selector instance has been instructed by the caller to include Field Set fields **/ - public Boolean isIncludeFieldSetFields() + public Boolean isIncludeFieldSetFields() { return m_includeFieldSetFields; } - + /** * Returns True if this Selector is enforcing FLS **/ public Boolean isEnforcingFLS() { - return m_enforceFLS; + return m_enforceFLS; } - + /** * Returns True if this Selector is enforcing CRUD Security **/ public Boolean isEnforcingCRUD() { - return m_enforceCRUD; + return m_enforceCRUD; } /** @@ -211,38 +211,38 @@ public abstract with sharing class fflib_SObjectSelector **/ public fflib_StringBuilder.CommaDelimitedListBuilder getFieldListBuilder() { - return - new fflib_StringBuilder.CommaDelimitedListBuilder( - new List(newQueryFactory().getSelectedFields())); + return + new fflib_StringBuilder.CommaDelimitedListBuilder( + new List(newQueryFactory().getSelectedFields())); } /** - * Use this method to override the default FieldListBuilder (created on demand via getFieldListBuilder) with a custom one, + * Use this method to override the default FieldListBuilder (created on demand via getFieldListBuilder) with a custom one, * warning, this will bypass anything getSObjectFieldList or getSObjectFieldSetList returns * * @depricated See newQueryFactory - **/ + **/ public void setFieldListBuilder(fflib_StringBuilder.FieldListBuilder fieldListBuilder) { - // TODO: Consider if given the known use cases for this (dynamic selector optomisation) if it's OK to leave this as a null operation + // TODO: Consider if given the known use cases for this (dynamic selector optomisation) if it's OK to leave this as a null operation } /** * Returns in string form a comma delimted list of fields as defined via getSObjectFieldList and optionally getSObjectFieldSetList * * @depricated See newQueryFactory - **/ + **/ public String getFieldListString() { return getFieldListBuilder().getStringValue(); } - + /** * Returns in string form a comma delimted list of fields as defined via getSObjectFieldList and optionally getSObjectFieldSetList * @param relation Will prefix fields with the given relation, e.g. MyLookupField__r * * @depricated See newQueryFactory - **/ + **/ public String getRelatedFieldListString(String relation) { return getFieldListBuilder().getStringValue(relation + '.'); @@ -255,10 +255,10 @@ public abstract with sharing class fflib_SObjectSelector { return describeWrapper.getDescribe().getName(); } - + /** - * Performs a SOQL query, - * - Selecting the fields described via getSObjectFieldsList and getSObjectFieldSetList (if included) + * Performs a SOQL query, + * - Selecting the fields described via getSObjectFieldsList and getSObjectFieldSetList (if included) * - From the SObject described by getSObjectType * - Where the Id's match those provided in the set * - Ordered by the fields returned via getOrderBy @@ -268,10 +268,10 @@ public abstract with sharing class fflib_SObjectSelector { return Database.query(buildQuerySObjectById()); } - + /** - * Performs a SOQL query, - * - Selecting the fields described via getSObjectFieldsList and getSObjectFieldSetList (if included) + * Performs a SOQL query, + * - Selecting the fields described via getSObjectFieldsList and getSObjectFieldSetList (if included) * - From the SObject described by getSObjectType * - Where the Id's match those provided in the set * - Ordered by the fields returned via getOrderBy @@ -281,21 +281,21 @@ public abstract with sharing class fflib_SObjectSelector { return Database.getQueryLocator(buildQuerySObjectById()); } - + /** * Throws an exception if the SObject indicated by getSObjectType is not accessible to the current user (read access) * - * @depricated If you utilise the newQueryFactory method this is automatically done for you (unless disabled by the selector) + * @depricated If you utilise the newQueryFactory method this is automatically done for you (unless disabled by the selector) **/ public void assertIsAccessible() { if(!getSObjectType().getDescribe().isAccessible()) - throw new fflib_SObjectDomain.DomainException( - 'Permission to access an ' + getSObjectType().getDescribe().getName() + ' denied.'); + throw new fflib_SObjectDomain.DomainException( + 'Permission to access an ' + getSObjectType().getDescribe().getName() + ' denied.'); } /** - * Public acccess for the getSObjectType during Mock registration + * Public acccess for the getSObjectType during Mock registration * (adding public to the existing method broken base class API backwards compatability) **/ public SObjectType getSObjectType2() @@ -304,27 +304,27 @@ public abstract with sharing class fflib_SObjectSelector } /** - * Public acccess for the getSObjectType during Mock registration + * Public acccess for the getSObjectType during Mock registration * (adding public to the existing method broken base class API backwards compatability) **/ public SObjectType sObjectType() { return getSObjectType(); } - + /** * Returns a QueryFactory configured with the Selectors object, fields, fieldsets and default order by **/ public fflib_QueryFactory newQueryFactory() - { + { return newQueryFactory(m_enforceCRUD, m_enforceFLS, true); } - + /** * Returns a QueryFactory configured with the Selectors object, fields, fieldsets and default order by **/ public fflib_QueryFactory newQueryFactory(Boolean includeSelectorFields) - { + { return newQueryFactory(m_enforceCRUD, m_enforceFLS, includeSelectorFields); } @@ -334,109 +334,109 @@ public abstract with sharing class fflib_SObjectSelector **/ public fflib_QueryFactory newQueryFactory(Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) { - // Construct QueryFactory around the given SObject + // Construct QueryFactory around the given SObject return configureQueryFactory( - new fflib_QueryFactory(getSObjectType2()), - assertCRUD, enforceFLS, includeSelectorFields); + new fflib_QueryFactory(getSObjectType2()), + assertCRUD, enforceFLS, includeSelectorFields); } - /** - * Adds the selectors fields to the given QueryFactory using the given relationship path as a prefix - * - * // TODO: This should be consistant (ideally) with configureQueryFactory below - **/ - public void configureQueryFactoryFields(fflib_QueryFactory queryFactory, String relationshipFieldPath) - { - // Add fields from selector prefixing the relationship path - for(SObjectField field : getSObjectFieldList()) - queryFactory.selectField(relationshipFieldPath + '.' + field.getDescribe().getName()); - // Automatically select the CurrencyIsoCode for MC orgs (unless the object is a known exception to the rule) + /** + * Adds the selectors fields to the given QueryFactory using the given relationship path as a prefix + * + * // TODO: This should be consistant (ideally) with configureQueryFactory below + **/ + public void configureQueryFactoryFields(fflib_QueryFactory queryFactory, String relationshipFieldPath) + { + // Add fields from selector prefixing the relationship path + for(SObjectField field : getSObjectFieldList()) + queryFactory.selectField(relationshipFieldPath + '.' + field.getDescribe().getName()); + // Automatically select the CurrencyIsoCode for MC orgs (unless the object is a known exception to the rule) if(Userinfo.isMultiCurrencyOrganization() && CURRENCY_ISO_CODE_ENABLED) - queryFactory.selectField(relationshipFieldPath+'.CurrencyIsoCode'); - } - + queryFactory.selectField(relationshipFieldPath+'.CurrencyIsoCode'); + } + /** * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the parentQueryFactory **/ public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory) - { - return addQueryFactorySubselect(parentQueryFactory, true); + { + return addQueryFactorySubselect(parentQueryFactory, true); } - + /** * Adds a subselect QueryFactory based on this selector to the given QueryFactor **/ public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, Boolean includeSelectorFields) - { - fflib_QueryFactory subSelectQueryFactory = - parentQueryFactory.subselectQuery(getSObjectType2()); - return configureQueryFactory( - subSelectQueryFactory, - m_enforceCRUD, - m_enforceFLS, - includeSelectorFields); + { + fflib_QueryFactory subSelectQueryFactory = + parentQueryFactory.subselectQuery(getSObjectType2()); + return configureQueryFactory( + subSelectQueryFactory, + m_enforceCRUD, + m_enforceFLS, + includeSelectorFields); + } + + /** + * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the parentQueryFactory + **/ + public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName) + { + return addQueryFactorySubselect(parentQueryFactory, relationshipName, TRUE); } - - /** - * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the parentQueryFactory - **/ - public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName) - { - return addQueryFactorySubselect(parentQueryFactory, relationshipName, TRUE); - } - /** - * Adds a subselect QueryFactory based on this selector to the given QueryFactor - **/ - public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName, Boolean includeSelectorFields) - { - fflib_QueryFactory subSelectQueryFactory = parentQueryFactory.subselectQuery(relationshipName); - return configureQueryFactory(subSelectQueryFactory, m_enforceCRUD, m_enforceFLS, includeSelectorFields); - } + /** + * Adds a subselect QueryFactory based on this selector to the given QueryFactor + **/ + public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName, Boolean includeSelectorFields) + { + fflib_QueryFactory subSelectQueryFactory = parentQueryFactory.subselectQuery(relationshipName); + return configureQueryFactory(subSelectQueryFactory, m_enforceCRUD, m_enforceFLS, includeSelectorFields); + } /** * Constructs the default SOQL query for this selector, see selectSObjectsById and queryLocatorById - **/ + **/ private String buildQuerySObjectById() - { + { return newQueryFactory().setCondition('id in :idSet').toSOQL(); } - + /** * Configures a QueryFactory instance according to the configuration of this selector - **/ - private fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory, Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) + **/ + protected fflib_QueryFactory configureQueryFactory(fflib_QueryFactory queryFactory, Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) { // CRUD and FLS security required? if (assertCRUD) { - try { - // Leverage QueryFactory for CRUD checking - queryFactory.assertIsAccessible(); - } catch (fflib_SecurityUtils.CrudException e) { - // Marshal exception into DomainException for backwards compatability - throw new fflib_SObjectDomain.DomainException( - 'Permission to access an ' + getSObjectType().getDescribe().getName() + ' denied.'); - } + try { + // Leverage QueryFactory for CRUD checking + queryFactory.assertIsAccessible(); + } catch (fflib_SecurityUtils.CrudException e) { + // Marshal exception into DomainException for backwards compatability + throw new fflib_SObjectDomain.DomainException( + 'Permission to access an ' + getSObjectType().getDescribe().getName() + ' denied.'); + } } queryFactory.setEnforceFLS(enforceFLS); - - // Configure the QueryFactory with the Selector fields? + + // Configure the QueryFactory with the Selector fields? if(includeSelectorFields) { - // select the Selector fields and Fieldsets and set order + // select the Selector fields and Fieldsets and set order queryFactory.selectFields(getSObjectFieldList()); List fieldSetList = getSObjectFieldSetList(); if(m_includeFieldSetFields && fieldSetList != null) for(Schema.FieldSet fieldSet : fieldSetList) queryFactory.selectFieldSet(fieldSet); - + // Automatically select the CurrencyIsoCode for MC orgs (unless the object is a known exception to the rule) if(Userinfo.isMultiCurrencyOrganization() && CURRENCY_ISO_CODE_ENABLED) queryFactory.selectField('CurrencyIsoCode'); } - + // Parse the getOrderBy() for(String orderBy : getOrderBy().split(',')) { @@ -451,10 +451,10 @@ public abstract with sharing class fflib_SObjectSelector else if(fieldSortOrderPart.equalsIgnoreCase('ASC')) fieldSortOrder = fflib_QueryFactory.SortOrder.ASCENDING; queryFactory.addOrdering(fieldNamePart, fieldSortOrder, orderBy.containsIgnoreCase('NULLS LAST')); - } - - queryFactory.setSortSelectFields(m_sortSelectFields); + } + + queryFactory.setSortSelectFields(m_sortSelectFields); - return queryFactory; - } + return queryFactory; + } }