diff --git a/sfdx-source/apex-common/main/classes/fflib_Application.cls b/sfdx-source/apex-common/main/classes/fflib_Application.cls index d957ca1d51..3901540067 100644 --- a/sfdx-source/apex-common/main/classes/fflib_Application.cls +++ b/sfdx-source/apex-common/main/classes/fflib_Application.cls @@ -29,19 +29,24 @@ * of the Apex Enterprise Patterns, Service, Unit Of Work, Selector and Domain. * See the sample applications Application.cls file for an example **/ +@NamespaceAccessible public virtual class fflib_Application { /** * Class implements a Unit of Work factory **/ + @NamespaceAccessible public virtual class UnitOfWorkFactory implements fflib_IUnitOfWorkFactory { + @NamespaceAccessible protected List m_objectTypes; + @NamespaceAccessible protected fflib_ISObjectUnitOfWork m_mockUow; /** * Constructs a Unit Of Work factory **/ + @NamespaceAccessible public UnitOfWorkFactory() { } /** @@ -49,6 +54,7 @@ public virtual class fflib_Application * * @param objectTypes List of SObjectTypes in dependency order **/ + @NamespaceAccessible public UnitOfWorkFactory(List objectTypes) { m_objectTypes = objectTypes.clone(); @@ -59,6 +65,7 @@ public virtual class fflib_Application * SObjectType list provided in the constructor, returns a Mock implementation * if set via the setMock method **/ + @NamespaceAccessible public virtual fflib_ISObjectUnitOfWork newInstance() { // Mock? @@ -72,6 +79,7 @@ public virtual class fflib_Application * SObjectType list provided in the constructor, returns a Mock implementation * if set via the setMock method **/ + @NamespaceAccessible public virtual fflib_ISObjectUnitOfWork newInstance(fflib_SObjectUnitOfWork.IDML dml) { // Mock? @@ -88,6 +96,7 @@ public virtual class fflib_Application * @remark If mock is set, the list of SObjectType in the mock could be different * than the list of SObjectType specified in this method call **/ + @NamespaceAccessible public virtual fflib_ISObjectUnitOfWork newInstance(List objectTypes) { // Mock? @@ -104,6 +113,7 @@ public virtual class fflib_Application * @remark If mock is set, the list of SObjectType in the mock could be different * than the list of SObjectType specified in this method call **/ + @NamespaceAccessible public virtual fflib_ISObjectUnitOfWork newInstance(List objectTypes, fflib_SObjectUnitOfWork.IDML dml) { // Mock? @@ -113,6 +123,7 @@ public virtual class fflib_Application } @TestVisible + @NamespaceAccessible protected virtual void setMock(fflib_ISObjectUnitOfWork mockUow) { m_mockUow = mockUow; @@ -122,15 +133,19 @@ public virtual class fflib_Application /** * Simple Service Factory implementation **/ + @NamespaceAccessible public virtual class ServiceFactory implements fflib_IServiceFactory { + @NamespaceAccessible protected Map m_serviceInterfaceTypeByServiceImplType; + @NamespaceAccessible protected Map m_serviceInterfaceTypeByMockService; /** * Constructs a simple Service Factory **/ + @NamespaceAccessible public ServiceFactory() { } /** @@ -141,6 +156,7 @@ public virtual class fflib_Application * * @param serviceInterfaceTypeByServiceImplType Map of interfaces to classes **/ + @NamespaceAccessible public ServiceFactory(Map serviceInterfaceTypeByServiceImplType) { m_serviceInterfaceTypeByServiceImplType = serviceInterfaceTypeByServiceImplType; @@ -155,6 +171,7 @@ public virtual class fflib_Application * @param serviceInterfaceType Apex interface type * @exception Is thrown if there is no registered Apex class for the interface type **/ + @NamespaceAccessible public virtual Object newInstance(Type serviceInterfaceType) { // Mock implementation? @@ -169,6 +186,7 @@ public virtual class fflib_Application } @TestVisible + @NamespaceAccessible protected virtual void setMock(Type serviceInterfaceType, Object serviceImpl) { m_serviceInterfaceTypeByMockService.put(serviceInterfaceType, serviceImpl); @@ -178,14 +196,18 @@ public virtual class fflib_Application /** * Class implements a Selector class factory **/ + @NamespaceAccessible public virtual class SelectorFactory implements fflib_ISelectorFactory { + @NamespaceAccessible protected Map m_sObjectBySelectorType; + @NamespaceAccessible protected Map m_sObjectByMockSelector; /** * Constructs a simple Selector Factory **/ + @NamespaceAccessible public SelectorFactory() { } /** @@ -195,6 +217,7 @@ public virtual class fflib_Application * * @param sObjectBySelectorType Map of SObjectType's to Selector Apex Classes **/ + @NamespaceAccessible public SelectorFactory(Map sObjectBySelectorType) { m_sObjectBySelectorType = sObjectBySelectorType; @@ -207,6 +230,7 @@ public virtual class fflib_Application * * @param sObjectType An SObjectType token, e.g. Account.SObjectType **/ + @NamespaceAccessible public virtual fflib_ISObjectSelector newInstance(SObjectType sObjectType) { // Mock implementation? @@ -230,6 +254,7 @@ public virtual class fflib_Application * @param recordIds The SObject record Ids, must be all the same SObjectType * @exception Is thrown if the record Ids are not all the same or the SObjectType is not registered **/ + @NamespaceAccessible public virtual List selectById(Set recordIds) { // No point creating an empty Domain class, nor can we determine the SObjectType anyway @@ -258,6 +283,7 @@ public virtual class fflib_Application * @param relatedRecords used to extract the related record Ids, e.g. Opportunity records * @param relationshipField field in the passed records that contains the relationship records to query, e.g. Opportunity.AccountId **/ + @NamespaceAccessible public virtual List selectByRelationship(List relatedRecords, SObjectField relationshipField) { Set relatedIds = new Set(); @@ -271,6 +297,7 @@ public virtual class fflib_Application } @TestVisible + @NamespaceAccessible protected virtual void setMock(fflib_ISObjectSelector selectorInstance) { m_sObjectByMockSelector.put(selectorInstance.sObjectType(), selectorInstance); @@ -280,17 +307,22 @@ public virtual class fflib_Application /** * Class implements a Domain class factory **/ + @NamespaceAccessible public virtual class DomainFactory implements fflib_IDomainFactory { + @NamespaceAccessible protected fflib_Application.SelectorFactory m_selectorFactory; + @NamespaceAccessible protected Map constructorTypeByObject; + @NamespaceAccessible protected Map mockDomainByObject; /** * Constructs a Domain factory **/ + @NamespaceAccessible public DomainFactory() { } /** @@ -302,6 +334,7 @@ public virtual class fflib_Application * @param selectorFactory , e.g. Application.Selector * @param constructorTypeByObject Map of Domain classes by ObjectType **/ + @NamespaceAccessible public DomainFactory(fflib_Application.SelectorFactory selectorFactory, Map constructorTypeByObject) { @@ -319,6 +352,7 @@ public virtual class fflib_Application * @param selectorFactory, e.g. Application.Selector * @param sObjectByDomainConstructorType Map of Apex classes by SObjectType **/ + @NamespaceAccessible public DomainFactory(fflib_Application.SelectorFactory selectorFactory, Map sObjectByDomainConstructorType) { @@ -335,6 +369,7 @@ public virtual class fflib_Application * @param recordIds A list of Id's of the same type * @exception Throws an exception via the Selector Factory if the Ids are not all of the same SObjectType **/ + @NamespaceAccessible public virtual fflib_IDomain newInstance(Set recordIds) { return newInstance(m_selectorFactory.selectById(recordIds)); @@ -349,6 +384,7 @@ public virtual class fflib_Application * @exception Throws an exception if the SObjectType cannot be determined from the list * or the constructor for Domain class was not registered for the SObjectType **/ + @NamespaceAccessible public virtual fflib_IDomain newInstance(List records) { SObjectType domainSObjectType = records.getSObjectType(); @@ -358,6 +394,7 @@ public virtual class fflib_Application return newInstance((List) records, (Object) domainSObjectType); } + @NamespaceAccessible public virtual fflib_IDomain newInstance(List objects, Object objectType) { // Mock implementation? @@ -401,6 +438,7 @@ public virtual class fflib_Application * @remark Will support List but all records in the list will be assumed to be of * the type specified in sObjectType **/ + @NamespaceAccessible public virtual fflib_IDomain newInstance(List records, SObjectType domainSObjectType) { if(domainSObjectType==null) @@ -413,17 +451,20 @@ public virtual class fflib_Application } @TestVisible + @NamespaceAccessible protected virtual void setMock(fflib_ISObjectDomain mockDomain) { mockDomainByObject.put((Object) mockDomain.sObjectType(), (fflib_IDomain) mockDomain); } @TestVisible + @NamespaceAccessible protected virtual void setMock(fflib_IDomain mockDomain) { mockDomainByObject.put(mockDomain.getType(), mockDomain); } + @NamespaceAccessible protected virtual Map getConstructorTypeByObject(Map constructorTypeBySObjectType) { Map result = new Map(); @@ -438,10 +479,12 @@ public virtual class fflib_Application } } + @NamespaceAccessible public class ApplicationException extends Exception { } /** * Exception representing a developer coding error, not intended for end user eyes **/ + @NamespaceAccessible public class DeveloperException extends Exception { } } \ No newline at end of file diff --git a/sfdx-source/apex-common/main/classes/fflib_IDomain.cls b/sfdx-source/apex-common/main/classes/fflib_IDomain.cls index 298f5b8ed4..1bc95dd166 100644 --- a/sfdx-source/apex-common/main/classes/fflib_IDomain.cls +++ b/sfdx-source/apex-common/main/classes/fflib_IDomain.cls @@ -23,6 +23,7 @@ * 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. **/ +@NamespaceAccessible public interface fflib_IDomain { Object getType(); diff --git a/sfdx-source/apex-common/main/classes/fflib_IDomainConstructor.cls b/sfdx-source/apex-common/main/classes/fflib_IDomainConstructor.cls index c4096d1d00..ff2de4e17a 100644 --- a/sfdx-source/apex-common/main/classes/fflib_IDomainConstructor.cls +++ b/sfdx-source/apex-common/main/classes/fflib_IDomainConstructor.cls @@ -23,6 +23,7 @@ * 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. **/ +@NamespaceAccessible public interface fflib_IDomainConstructor { fflib_IDomain construct(List objects); diff --git a/sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls b/sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls index 1e0f982d5a..1f425a567b 100644 --- a/sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls +++ b/sfdx-source/apex-common/main/classes/fflib_IDomainFactory.cls @@ -23,6 +23,7 @@ * 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. **/ +@NamespaceAccessible public interface fflib_IDomainFactory { fflib_IDomain newInstance(Set recordIds); diff --git a/sfdx-source/apex-common/main/classes/fflib_IObjects.cls b/sfdx-source/apex-common/main/classes/fflib_IObjects.cls index 80358b22dd..55f23ac7d8 100644 --- a/sfdx-source/apex-common/main/classes/fflib_IObjects.cls +++ b/sfdx-source/apex-common/main/classes/fflib_IObjects.cls @@ -23,6 +23,7 @@ * 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. **/ +@NamespaceAccessible public interface fflib_IObjects extends fflib_IDomain { /** diff --git a/sfdx-source/apex-common/main/classes/fflib_ISObjectDomain.cls b/sfdx-source/apex-common/main/classes/fflib_ISObjectDomain.cls index 3dfadce34d..ab59bb6877 100644 --- a/sfdx-source/apex-common/main/classes/fflib_ISObjectDomain.cls +++ b/sfdx-source/apex-common/main/classes/fflib_ISObjectDomain.cls @@ -23,7 +23,7 @@ * 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. **/ - +@NamespaceAccessible public interface fflib_ISObjectDomain extends fflib_IDomain { /** diff --git a/sfdx-source/apex-common/main/classes/fflib_ISObjectSelector.cls b/sfdx-source/apex-common/main/classes/fflib_ISObjectSelector.cls index 6cc8a3badd..4aa4791846 100644 --- a/sfdx-source/apex-common/main/classes/fflib_ISObjectSelector.cls +++ b/sfdx-source/apex-common/main/classes/fflib_ISObjectSelector.cls @@ -23,7 +23,7 @@ * 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. **/ - +@NamespaceAccessible public interface fflib_ISObjectSelector { /** diff --git a/sfdx-source/apex-common/main/classes/fflib_ISObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_ISObjectUnitOfWork.cls index 164920c9c4..a8cae0e4a2 100644 --- a/sfdx-source/apex-common/main/classes/fflib_ISObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_ISObjectUnitOfWork.cls @@ -27,6 +27,7 @@ /** * @see fflib_SObjectUnitOfWork **/ +@NamespaceAccessible public interface fflib_ISObjectUnitOfWork { /** diff --git a/sfdx-source/apex-common/main/classes/fflib_ISObjects.cls b/sfdx-source/apex-common/main/classes/fflib_ISObjects.cls index bf30d9a276..b34e5056fc 100644 --- a/sfdx-source/apex-common/main/classes/fflib_ISObjects.cls +++ b/sfdx-source/apex-common/main/classes/fflib_ISObjects.cls @@ -23,6 +23,7 @@ * 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. **/ +@NamespaceAccessible public interface fflib_ISObjects extends fflib_IObjects { /** diff --git a/sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls b/sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls index a39095b289..1d30475da6 100644 --- a/sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls +++ b/sfdx-source/apex-common/main/classes/fflib_ISelectorFactory.cls @@ -23,6 +23,7 @@ * 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. **/ +@NamespaceAccessible public interface fflib_ISelectorFactory { fflib_ISObjectSelector newInstance(SObjectType sObjectType); diff --git a/sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls b/sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls index 93fa4124cc..500b5cf048 100644 --- a/sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls +++ b/sfdx-source/apex-common/main/classes/fflib_IServiceFactory.cls @@ -23,6 +23,7 @@ * 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. **/ +@NamespaceAccessible public interface fflib_IServiceFactory { Object newInstance(Type serviceInterfaceType); diff --git a/sfdx-source/apex-common/main/classes/fflib_IUnitOfWorkFactory.cls b/sfdx-source/apex-common/main/classes/fflib_IUnitOfWorkFactory.cls index 371ab1ccde..0cbb38e2d5 100644 --- a/sfdx-source/apex-common/main/classes/fflib_IUnitOfWorkFactory.cls +++ b/sfdx-source/apex-common/main/classes/fflib_IUnitOfWorkFactory.cls @@ -23,6 +23,7 @@ * 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. **/ +@NamespaceAccessible public interface fflib_IUnitOfWorkFactory { fflib_ISObjectUnitOfWork newInstance(); diff --git a/sfdx-source/apex-common/main/classes/fflib_Objects.cls b/sfdx-source/apex-common/main/classes/fflib_Objects.cls index 44c4f069ea..d065f8e8e9 100644 --- a/sfdx-source/apex-common/main/classes/fflib_Objects.cls +++ b/sfdx-source/apex-common/main/classes/fflib_Objects.cls @@ -23,33 +23,40 @@ * 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. **/ -public virtual class fflib_Objects implements fflib_IObjects +@NamespaceAccessible + public virtual class fflib_Objects implements fflib_IObjects { + @NamespaceAccessible protected List objects { get; private set;} { objects = new List(); } /** * Class constructor */ + @NamespaceAccessible public fflib_Objects(List objects) { this.objects = objects.clone(); } + @NamespaceAccessible public virtual Object getType() { return Object.class; } + @NamespaceAccessible public List getObjects() { return this.objects; } + @NamespaceAccessible public Boolean contains(Object value) { return getObjects()?.contains(value); } + @NamespaceAccessible public Boolean containsAll(List values) { if (values == null) return false; @@ -57,6 +64,7 @@ public virtual class fflib_Objects implements fflib_IObjects return containsAll(new Set(values)); } + @NamespaceAccessible public Boolean containsAll(Set values) { if (values == null) return false; @@ -68,11 +76,13 @@ public virtual class fflib_Objects implements fflib_IObjects return true; } + @NamespaceAccessible public Boolean containsNot(Object value) { return !contains(value); } + @NamespaceAccessible public Boolean containsNot(List values) { if (values == null) return true; @@ -80,6 +90,7 @@ public virtual class fflib_Objects implements fflib_IObjects return containsNot(new Set(values)); } + @NamespaceAccessible public Boolean containsNot(Set values) { if (values == null) return true; @@ -91,21 +102,25 @@ public virtual class fflib_Objects implements fflib_IObjects return true; } + @NamespaceAccessible public Boolean isEmpty() { return (getObjects() == null || getObjects().isEmpty()); } + @NamespaceAccessible public Boolean isNotEmpty() { return !isEmpty(); } + @NamespaceAccessible public Integer size() { return getObjects().size(); } + @NamespaceAccessible protected void setObjects(List objects) { this.objects = objects; diff --git a/sfdx-source/apex-common/main/classes/fflib_QueryFactory.cls b/sfdx-source/apex-common/main/classes/fflib_QueryFactory.cls index 9ecc46f31e..b7493bf39f 100644 --- a/sfdx-source/apex-common/main/classes/fflib_QueryFactory.cls +++ b/sfdx-source/apex-common/main/classes/fflib_QueryFactory.cls @@ -51,14 +51,18 @@ * There is a google doc providing additional guidance 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 **/ +@NamespaceAccessible public class fflib_QueryFactory { //No explicit sharing declaration - inherit from caller + @NamespaceAccessible public enum SortOrder {ASCENDING, DESCENDING} + @NamespaceAccessible public enum FLSEnforcement{NONE, LEGACY, USER_MODE, SYSTEM_MODE} /** * 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. **/ + @NamespaceAccessible public Schema.SObjectType table {get; private set;} @TestVisible private Set fields; @@ -170,6 +174,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * 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. **/ + @NamespaceAccessible public Boolean equals(Object obj){ if( !(obj instanceof fflib_QueryFactory) || ((fflib_QueryFactory)obj).table != this.table || ((fflib_QueryFactory)obj).fields.size() != this.fields.size() ) return false; @@ -181,6 +186,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 table the SObject to be used in the FROM clause of the resultant query. This sets the value of {@link #table}. **/ + @NamespaceAccessible public fflib_QueryFactory(Schema.SObjectType table){ this.table = table; fields = new Set(); @@ -204,6 +210,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * This method checks to see if the User has Read Access on {@link #table}. * Asserts true if User has access. **/ + @NamespaceAccessible public fflib_QueryFactory assertIsAccessible(){ fflib_SecurityUtils.checkObjectIsReadable(table); return this; @@ -216,10 +223,12 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param enforce whether to enforce field level security (read) * @deprecated - use the setEnforceFLS overload that specifies Legacy or Native FLS enforcement **/ + @NamespaceAccessible public fflib_QueryFactory setEnforceFLS(Boolean enforce){ return setEnforceFLS(enforce ? FLSEnforcement.LEGACY : FLSEnforcement.NONE); } + @NamespaceAccessible public fflib_QueryFactory setEnforceFLS(FLSEnforcement enforcement){ this.mFlsEnforcement = enforcement; return this; @@ -232,6 +241,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * If you are processing large query sets, you should switch this off. * @param doSort whether or not select fields should be sorted in the soql statement. **/ + @NamespaceAccessible public fflib_QueryFactory setSortSelectFields(Boolean doSort){ this.sortSelectFields = doSort; return this; @@ -241,6 +251,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * 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. **/ + @NamespaceAccessible public fflib_QueryFactory selectField(String fieldName){ return selectFields(new Set{fieldName}); } @@ -251,6 +262,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param fieldName the API name of the field to add to the query's SELECT clause. * @param relatedSObjectType the related sObjectType to resolve polymorphic object fields. **/ + @NamespaceAccessible public fflib_QueryFactory selectField(String fieldName, Schema.sObjectType relatedObjectType) { addField(getFieldPath(fieldName, relatedObjectType)); return this; @@ -262,6 +274,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param field the {@link Schema.SObjectField} to select with this query. * @exception InvalidFieldException If the field is null {@code field}. **/ + @NamespaceAccessible public fflib_QueryFactory selectField(Schema.SObjectField field){ return selectFields(new Set{field}); } @@ -269,6 +282,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * Selects multiple fields. This acts the same as calling {@link #selectField(String)} multiple times. * @param fieldNames the Set of field API names to select. **/ + @NamespaceAccessible public fflib_QueryFactory selectFields(Set fieldNames){ return selectStringField(fieldNames.iterator()); } @@ -276,6 +290,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * Selects multiple fields. This acts the same as calling {@link #selectField(String)} multiple times. * @param fieldNames the List of field API names to select. **/ + @NamespaceAccessible public fflib_QueryFactory selectFields(List fieldNames){ return selectStringField(fieldNames.iterator()); } @@ -292,6 +307,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param fields the Set of {@link Schema.SObjectField}s to select. * @exception InvalidFieldException if the fields are null {@code fields}. **/ + @NamespaceAccessible public fflib_QueryFactory selectFields(Set fields){ return selectSObjectFields(fields.iterator()); } @@ -301,6 +317,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param fields the List of {@link Schema.SObjectField}s to select. * @exception InvalidFieldException if the fields are null {@code fields}. **/ + @NamespaceAccessible public fflib_QueryFactory selectFields(List fields) { return selectSObjectFields(fields.iterator()); } @@ -322,6 +339,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @see #selectFieldSet(Schema.FieldSet,Boolean) **/ + @NamespaceAccessible public fflib_QueryFactory selectFieldSet(Schema.FieldSet fieldSet){ return selectFieldSet(fieldSet,true); } @@ -331,6 +349,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @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}. **/ + @NamespaceAccessible 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+'"'); @@ -360,6 +379,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @param conditionExpression Sets the WHERE clause to the string provided. Do not include the "WHERE". **/ + @NamespaceAccessible public fflib_QueryFactory setCondition(String conditionExpression){ this.conditionExpression = conditionExpression; return this; @@ -367,12 +387,14 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @returns the current value of the WHERE clause, if any, as set by {@link #setCondition} **/ + @NamespaceAccessible public String getCondition(){ return this.conditionExpression; } /** * @param limitCount if not null causes a LIMIT clause to be added to the resulting query. **/ + @NamespaceAccessible public fflib_QueryFactory setLimit(Integer limitCount){ this.limitCount = limitCount; return this; @@ -380,12 +402,14 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @returns the current value of the LIMIT clause, if any. **/ + @NamespaceAccessible public Integer getLimit(){ return this.limitCount; } /** * @param offsetCount if not null causes a OFFSET clause to be added to the resulting query. **/ + @NamespaceAccessible public fflib_QueryFactory setOffset(Integer offsetCount){ this.offsetCount = offsetCount; return this; @@ -393,12 +417,14 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @returns the current value of the OFFSET clause, if any. **/ + @NamespaceAccessible public Integer getOffset(){ return this.offsetCount; } /** * @param o an instance of {@link fflib_QueryFactory.Ordering} to be added to the query's ORDER BY clause. **/ + @NamespaceAccessible public fflib_QueryFactory addOrdering(Ordering o){ this.order.add(o); return this; @@ -407,6 +433,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @param o an instance of {@link fflib_QueryFactory.Ordering} to remove all existing (for instance defaults) and be added to the query's ORDER BY clause. **/ + @NamespaceAccessible public fflib_QueryFactory setOrdering(Ordering o){ this.order = new List{ o }; return this; @@ -414,6 +441,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @returns the list of orderings that will be used as the query's ORDER BY clause. You may remove elements from the returned list, or otherwise mutate it, to remove previously added orderings. **/ + @NamespaceAccessible public List getOrderings(){ return this.order; } @@ -421,6 +449,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @returns the selected fields **/ + @NamespaceAccessible public Set getSelectedFields() { return this.fields; } @@ -432,6 +461,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 related The related object type **/ + @NamespaceAccessible 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); @@ -445,6 +475,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param related The related object type * @param assertIsAccessible indicates whether to check if the user has access to the subquery object **/ + @NamespaceAccessible 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.'); return setSubselectQuery(getChildRelationship(related), assertIsAccessible); @@ -456,6 +487,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 relationshipName The relationshipName to be added as a subquery **/ + @NamespaceAccessible public fflib_QueryFactory subselectQuery(String relationshipName){ return subselectQuery(relationshipName, false); } @@ -467,6 +499,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @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 **/ + @NamespaceAccessible public fflib_QueryFactory subselectQuery(String relationshipName, Boolean assertIsAccessible){ Schema.ChildRelationship relationship = getChildRelationship(relationshipName); if (relationship != null) { @@ -481,6 +514,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 **/ + @NamespaceAccessible public fflib_QueryFactory subselectQuery(Schema.ChildRelationship relationship){ return subselectQuery(relationship, false); } @@ -492,6 +526,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @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 **/ + @NamespaceAccessible public fflib_QueryFactory subselectQuery(Schema.ChildRelationship relationship, Boolean assertIsAccessible){ return setSubselectQuery(relationship, assertIsAccessible); } @@ -525,6 +560,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * @returns the list of subquery instances of fflib_QueryFactory which will be added to the SOQL as relationship/child/sub-queries. **/ + @NamespaceAccessible public List getSubselectQueries(){ if (subselectQueryMap != null) { return subselectQueryMap.values(); @@ -537,28 +573,28 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param objType The object type of the child relationship to get **/ private 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); - } + 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 **/ private Schema.ChildRelationship getChildRelationship(String relationshipName){ - for (Schema.ChildRelationship childRow : table.getDescribe().getChildRelationships()){ - if (childRow.getRelationshipName() == relationshipName){ - return childRow; - } - } - return null; - } + 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 @@ -571,14 +607,15 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param direction 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){ + @NamespaceAccessible + public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction, Boolean nullsLast){ order.add( 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 @@ -589,14 +626,15 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param direction 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){ + @NamespaceAccessible + public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction, Boolean nullsLast){ order.add( 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 @@ -608,14 +646,15 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param fieldName The string value of the field to be sorted on * @param direction the direction to be sorted on (ASCENDING or DESCENDING) **/ - public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction){ + @NamespaceAccessible + public fflib_QueryFactory addOrdering(String fieldName, SortOrder direction){ order.add( 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 @@ -627,12 +666,13 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param field The SObjectField to sort. This can only be a direct reference. * @param direction the direction to be sorted on (ASCENDING or DESCENDING) **/ - public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction){ + @NamespaceAccessible + public fflib_QueryFactory addOrdering(SObjectField field, SortOrder direction){ order.add( 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 @@ -645,6 +685,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param direction the direction to be sorted on (ASCENDING or DESCENDING) * @param nullsLast whether to sort null values last (NULLS LAST keyword included). **/ + @NamespaceAccessible public fflib_QueryFactory setOrdering(String fieldName, SortOrder direction, Boolean nullsLast){ Ordering ordr = new Ordering(getFieldPath(fieldName), direction, nullsLast); return setOrdering(ordr); @@ -661,6 +702,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param direction the direction to be sorted on (ASCENDING or DESCENDING) * @param nullsLast whether to sort null values last (NULLS LAST keyword included). **/ + @NamespaceAccessible public fflib_QueryFactory setOrdering(SObjectField field, SortOrder direction, Boolean nullsLast){ Ordering ordr = new Ordering(getFieldTokenPath(field), direction, nullsLast); return setOrdering(ordr); @@ -676,6 +718,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param fieldName The string value of the field to be sorted on * @param direction the direction to be sorted on (ASCENDING or DESCENDING) **/ + @NamespaceAccessible public fflib_QueryFactory setOrdering(String fieldName, SortOrder direction){ Ordering ordr = new Ordering(getFieldPath(fieldName), direction); return setOrdering(ordr); @@ -691,6 +734,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * @param field The SObjectField to sort. This can only be a direct reference. * @param direction the direction to be sorted on (ASCENDING or DESCENDING) **/ + @NamespaceAccessible public fflib_QueryFactory setOrdering(SObjectField field, SortOrder direction){ Ordering ordr = new Ordering(getFieldTokenPath(field), direction); return setOrdering(ordr); @@ -699,6 +743,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr /** * whether an ALL ROWS clause will be added to the resulting query **/ + @NamespaceAccessible public fflib_QueryFactory setAllRows(){ this.allRows = true; return this; @@ -708,6 +753,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * 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. **/ + @NamespaceAccessible public String toSOQL(){ String result = 'SELECT '; //if no fields have been added, just add the Id field so that the query or subquery will not just fail @@ -769,6 +815,7 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * 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 **/ + @NamespaceAccessible public fflib_QueryFactory deepClone(){ fflib_QueryFactory clone = new fflib_QueryFactory(this.table) @@ -793,12 +840,14 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr return clone; } + @NamespaceAccessible public class Ordering{ private SortOrder direction; private boolean nullsLast; private String field; - public Ordering(String sobjType, String fieldName, SortOrder direction){ + @NamespaceAccessible + public Ordering(String sobjType, String fieldName, SortOrder direction){ this( fflib_SObjectDescribe.getDescribe(sobjType).getField(fieldName), direction @@ -808,10 +857,12 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr * Construct a new ordering instance for use with {@link fflib_QueryFactory#addOrdering} * Once constructed it's properties may not be modified. **/ - public Ordering(Schema.SObjectField field, SortOrder direction){ + @NamespaceAccessible + public Ordering(Schema.SObjectField field, SortOrder direction){ this(fflib_QueryFactory.getFieldTokenPath(field), direction, false); //SOQL docs state NULLS FIRST is default behavior } - public Ordering(Schema.SObjectField field, SortOrder direction, Boolean nullsLast){ + @NamespaceAccessible + public Ordering(Schema.SObjectField field, SortOrder direction, Boolean nullsLast){ this(fflib_QueryFactory.getFieldTokenPath(field), direction, nullsLast); } @TestVisible @@ -824,30 +875,38 @@ public class fflib_QueryFactory { //No explicit sharing declaration - inherit fr this.field = field; this.nullsLast = nullsLast; } - public String getField(){ + @NamespaceAccessible + public String getField(){ return this.field; } - public SortOrder getDirection(){ + @NamespaceAccessible + public SortOrder getDirection(){ return direction; } - public String toSOQL(){ + @NamespaceAccessible + public String toSOQL(){ return field + ' ' + (direction == SortOrder.ASCENDING ? 'ASC' : 'DESC') + (nullsLast ? ' NULLS LAST ' : ' NULLS FIRST '); } } + @NamespaceAccessible public class InvalidFieldException extends Exception{ private String fieldName; private Schema.SObjectType objectType; - public InvalidFieldException(String fieldName, Schema.SObjectType objectType){ + @NamespaceAccessible + public InvalidFieldException(String fieldName, Schema.SObjectType objectType){ this.objectType = objectType; this.fieldName = fieldName; this.setMessage( 'Invalid field \''+fieldName+'\' for object \''+objectType+'\'' ); } } + @NamespaceAccessible public class InvalidFieldSetException extends Exception{} + @NamespaceAccessible public class NonReferenceFieldException extends Exception{} + @NamespaceAccessible public class InvalidSubqueryRelationshipException extends Exception{} } diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectDescribe.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectDescribe.cls index 0eac50185f..81579aa6c1 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectDescribe.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectDescribe.cls @@ -36,6 +36,7 @@ * This allows both upgrading old code to APIv29 by making use of these as a nearly drop in replacement, as well as keeping * namespace detection logic encapsulated. **/ +@NamespaceAccessible public class fflib_SObjectDescribe { //internal implementation details private Schema.SObjectType token; @@ -85,12 +86,14 @@ public class fflib_SObjectDescribe { /** * Returns the Schema.SObjectType this fflib_SObjectDescribe instance is based on. **/ + @NamespaceAccessible public Schema.SObjectType getSObjectType(){ return token; } /** * This method is a convenient shorthand for calling getField(name, true) **/ + @NamespaceAccessible public Schema.SObjectField getField(String name){ return this.getField(name, true); } @@ -99,6 +102,7 @@ public class fflib_SObjectDescribe { * Additionally it handles finding the correct SObjectField for relationship notation, * e.g. getting the Account field on Contact would fail without being referenced as AccountId - both work here. **/ + @NamespaceAccessible public Schema.SObjectField getField(String fieldName, Boolean implyNamespace){ String fieldNameAdjusted = fieldName; @@ -121,6 +125,7 @@ public class fflib_SObjectDescribe { /** * Returns the field where isNameField() is true (if any); otherwise returns null **/ + @NamespaceAccessible public Schema.SObjectField getNameField() { if(nameField == null) { @@ -137,6 +142,7 @@ public class fflib_SObjectDescribe { /** * Returns the raw Schema.DescribeSObjectResult an fflib_SObjectDescribe instance wraps. **/ + @NamespaceAccessible public Schema.DescribeSObjectResult getDescribe(){ return describe; } @@ -144,12 +150,15 @@ public class fflib_SObjectDescribe { * This method returns the raw data and provides no namespace handling. * Due to this, __use of this method is discouraged__ in favor of getFields(). **/ + @NamespaceAccessible public Map getFieldsMap(){ return fields; } + @NamespaceAccessible public FieldsMap getFields(){ return wrappedFields; } + @NamespaceAccessible public Map getFieldSetsMap(){ return fieldSets; } @@ -185,6 +194,7 @@ public class fflib_SObjectDescribe { } set; } + @NamespaceAccessible public static fflib_SObjectDescribe getDescribe(String sObjectName){ if(String.isBlank(sObjectName)) return null; @@ -198,6 +208,7 @@ public class fflib_SObjectDescribe { } return result; } + @NamespaceAccessible public static fflib_SObjectDescribe getDescribe(Schema.SObjectType token){ if(token == null) return null; @@ -206,6 +217,7 @@ public class fflib_SObjectDescribe { result = new fflib_SObjectDescribe(token); return result; } + @NamespaceAccessible public static fflib_SObjectDescribe getDescribe(Schema.DescribeSObjectResult nativeDescribe){ if(nativeDescribe == null) return null; @@ -214,6 +226,7 @@ public class fflib_SObjectDescribe { result = new fflib_SObjectDescribe(nativeDescribe.getSObjectType()); return result; } + @NamespaceAccessible public static fflib_SObjectDescribe getDescribe(SObject instance){ if(instance == null) return null; @@ -221,14 +234,17 @@ public class fflib_SObjectDescribe { } //returns the same results as the native method, just with caching built in to avoid limits + @NamespaceAccessible public static Map getRawGlobalDescribe(){ return rawGlobalDescribe; } + @NamespaceAccessible public static GlobalDescribeMap getGlobalDescribe(){ return wrappedGlobalDescribe; } //Useful when working in heap space constrained environments. //Existing references to SObjectDescribe instances will continue to work. + @NamespaceAccessible public static void flushCache(){ rawGlobalDescribe = null; instanceCache = null; @@ -278,9 +294,11 @@ public class fflib_SObjectDescribe { return null; } } + public virtual Boolean containsKey(String name){ return this.containsKey(name, true); } + public virtual Boolean containsKey(String name, Boolean implyNamespace){ if(name == null) //short-circuit lookup logic since null can't possibly be a valid field name, and it saves us null checking return null; @@ -318,6 +336,7 @@ public class fflib_SObjectDescribe { /** * A subclass of NamespacedAttributeMap for handling the data returned by #Schema.DescribeSObjectResult.fields.getMap **/ + @NamespaceAccessible public class FieldsMap extends NamespacedAttributeMap{ @TestVisible @@ -325,12 +344,15 @@ public class fflib_SObjectDescribe { super(values); } + @NamespaceAccessible public Schema.SObjectField get(String name){ return this.get(name, true); } + @NamespaceAccessible public Schema.SObjectField get(String name, Boolean implyNamespace){ return (Schema.SObjectField) this.getObject(name, implyNamespace); } + @NamespaceAccessible public List values(){ return (List) values.values(); } @@ -339,25 +361,32 @@ public class fflib_SObjectDescribe { /** * A subclass of NamespacedAttributeMap for handling the data returned by #Schema.getGlobalDescribe **/ + @NamespaceAccessible public class GlobalDescribeMap extends NamespacedAttributeMap{ @TestVisible private GlobalDescribeMap(Map values){ super(values); } - public Schema.SObjectType get(String name){ + @NamespaceAccessible + public Schema.SObjectType get(String name){ return this.get(name, true); } + @NamespaceAccessible public Schema.SObjectType get(String name, Boolean implyNamespace){ return (Schema.SObjectType) this.getObject(name, implyNamespace); } + @NamespaceAccessible public List values(){ return (List) values.values(); } } + @NamespaceAccessible public abstract class DescribeException extends Exception{} + @NamespaceAccessible public class DuplicateDescribeException extends DescribeException{} //Test coverage for this requires APIv28's @testVisible annotation to force exception cases. + @NamespaceAccessible public class InvalidDescribeException extends DescribeException{} } \ No newline at end of file diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectDomain.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectDomain.cls index bd982249d4..746ab102f6 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectDomain.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectDomain.cls @@ -38,6 +38,7 @@ * http://martinfowler.com/eaaCatalog/domainModel.html * **/ +@NamespaceAccessible public virtual with sharing class fflib_SObjectDomain extends fflib_SObjects implements fflib_ISObjectDomain @@ -45,6 +46,7 @@ public virtual with sharing class fflib_SObjectDomain /** * Provides access to the data represented by this domain class **/ + @NamespaceAccessible public List Records { get { @@ -54,9 +56,10 @@ public virtual with sharing class fflib_SObjectDomain /** - * Provides access to Trigger.oldMap and allowing it to be mocked in unit-tests - **/ + * Provides access to Trigger.oldMap and allowing it to be mocked in unit-tests + **/ @TestVisible + @NamespaceAccessible protected Map ExistingRecords { get @@ -82,16 +85,19 @@ public virtual with sharing class fflib_SObjectDomain /** * Exposes the configuration for this domain class instance **/ + @NamespaceAccessible public Configuration Configuration {get; private set;} /** * DEPRECATED, This property has been moved to fflib_SObjects **/ + @NamespaceAccessible public static fflib_SObjectDomain.ErrorFactory Errors {get; private set;} /** * Useful during unit testing to access mock support for database inserts and updates (testing without DML) **/ + @NamespaceAccessible public static TestFactory Test {get; private set;} /** @@ -121,6 +127,7 @@ public virtual with sharing class fflib_SObjectDomain * @param sObjectList A concrete list (e.g. List vs List) of records **/ + @NamespaceAccessible public fflib_SObjectDomain(List sObjectList) { this(sObjectList, sObjectList.getSObjectType()); @@ -135,6 +142,7 @@ public virtual with sharing class fflib_SObjectDomain * @remark Will support List but all records in the list will be assumed to be of * the type specified in sObjectType **/ + @NamespaceAccessible public fflib_SObjectDomain(List sObjectList, SObjectType sObjectType) { // Ensure the domain class has its own copy of the data @@ -147,146 +155,165 @@ public virtual with sharing class fflib_SObjectDomain /** * Override this to apply defaults to the records, this is called by the handleBeforeInsert method **/ + @NamespaceAccessible public virtual void onApplyDefaults() { } /** * Override this to apply general validation to be performed during insert or update, called by the handleAfterInsert and handleAfterUpdate methods **/ + @NamespaceAccessible public virtual void onValidate() { } /** * Override this to apply validation to be performed during insert, called by the handleAfterUpdate method **/ + @NamespaceAccessible public virtual void onValidate(Map existingRecords) { } /** * Override this to perform processing during the before insert phase, this is called by the handleBeforeInsert method **/ - public virtual void onBeforeInsert() { } - + @NamespaceAccessible + public virtual void onBeforeInsert() { } + /** * Override this to perform processing during the before update phase, this is called by the handleBeforeUpdate method **/ - public virtual void onBeforeUpdate(Map existingRecords) { } - + @NamespaceAccessible + public virtual void onBeforeUpdate(Map existingRecords) { } + /** * Override this to perform processing during the before delete phase, this is called by the handleBeforeDelete method **/ - public virtual void onBeforeDelete() { } - + @NamespaceAccessible + public virtual void onBeforeDelete() { } + /** * Override this to perform processing during the after insert phase, this is called by the handleAfterInsert method **/ - public virtual void onAfterInsert() { } - + @NamespaceAccessible + public virtual void onAfterInsert() { } + /** * Override this to perform processing during the after update phase, this is called by the handleAfterUpdate method **/ - public virtual void onAfterUpdate(Map existingRecords) { } - + @NamespaceAccessible + public virtual void onAfterUpdate(Map existingRecords) { } + /** * Override this to perform processing during the after delete phase, this is called by the handleAfterDelete method **/ - public virtual void onAfterDelete() { } + @NamespaceAccessible + public virtual void onAfterDelete() { } /** * Override this to perform processing during the after undelete phase, this is called by the handleAfterDelete method **/ - public virtual void onAfterUndelete() { } + @NamespaceAccessible + public virtual void onAfterUndelete() { } /** * Base handler for the Apex Trigger event Before Insert, calls the onApplyDefaults method, followed by onBeforeInsert **/ - public virtual void handleBeforeInsert() - { - onApplyDefaults(); - onBeforeInsert(); - } - - /** - * Base handler for the Apex Trigger event Before Update, calls the onBeforeUpdate method - **/ - public virtual void handleBeforeUpdate(Map existingRecords) - { - onBeforeUpdate(existingRecords); - } - - /** - * Base handler for the Apex Trigger event Before Delete, calls the onBeforeDelete method - **/ - public virtual void handleBeforeDelete() - { - onBeforeDelete(); - } - - /** - * Base handler for the Apex Trigger event After Insert, checks object security and calls the onValidate and onAfterInsert methods - * - * @throws DomainException if the current user context is not able to create records - **/ - public virtual void handleAfterInsert() - { - if(Configuration.EnforcingTriggerCRUDSecurity && !SObjectDescribe.isCreateable()) - throw new DomainException('Permission to create an ' + SObjectDescribe.getName() + ' denied.'); - - onValidate(); - onAfterInsert(); - } - - /** - * Base handler for the Apex Trigger event After Update, checks object security and calls the onValidate, onValidate(Map) and onAfterUpdate methods - * - * @throws DomainException if the current user context is not able to update records - **/ - public virtual void handleAfterUpdate(Map existingRecords) - { - if(Configuration.EnforcingTriggerCRUDSecurity && !SObjectDescribe.isUpdateable()) - throw new DomainException('Permission to update an ' + SObjectDescribe.getName() + ' denied.'); - - if(Configuration.OldOnUpdateValidateBehaviour) - onValidate(); - onValidate(existingRecords); - onAfterUpdate(existingRecords); - } - - /** - * Base handler for the Apex Trigger event After Delete, checks object security and calls the onAfterDelete method - * - * @throws DomainException if the current user context is not able to delete records - **/ - public virtual void handleAfterDelete() - { - if(Configuration.EnforcingTriggerCRUDSecurity && !SObjectDescribe.isDeletable()) - throw new DomainException('Permission to delete an ' + SObjectDescribe.getName() + ' denied.'); - - onAfterDelete(); - } - - /** - * Base handler for the Apex Trigger event After Undelete, checks object security and calls the onAfterUndelete method - * - * @throws DomainException if the current user context is not able to delete records - **/ - public virtual void handleAfterUndelete() - { - if(Configuration.EnforcingTriggerCRUDSecurity && !SObjectDescribe.isUndeletable()) - throw new DomainException('Permission to undelete an ' + SObjectDescribe.getName() + ' denied.'); - - onAfterUndelete(); - } - - /** - * Returns the SObjectType this Domain class represents - **/ - public SObjectType sObjectType() - { - return getSObjectType(); - } + @NamespaceAccessible + public virtual void handleBeforeInsert() + { + onApplyDefaults(); + onBeforeInsert(); + } + + /** + * Base handler for the Apex Trigger event Before Update, calls the onBeforeUpdate method + **/ + @NamespaceAccessible + public virtual void handleBeforeUpdate(Map existingRecords) + { + onBeforeUpdate(existingRecords); + } + + /** + * Base handler for the Apex Trigger event Before Delete, calls the onBeforeDelete method + **/ + @NamespaceAccessible + public virtual void handleBeforeDelete() + { + onBeforeDelete(); + } + + /** + * Base handler for the Apex Trigger event After Insert, checks object security and calls the onValidate and onAfterInsert methods + * + * @throws DomainException if the current user context is not able to create records + **/ + @NamespaceAccessible + public virtual void handleAfterInsert() + { + if(Configuration.EnforcingTriggerCRUDSecurity && !SObjectDescribe.isCreateable()) + throw new DomainException('Permission to create an ' + SObjectDescribe.getName() + ' denied.'); + + onValidate(); + onAfterInsert(); + } + + /** + * Base handler for the Apex Trigger event After Update, checks object security and calls the onValidate, onValidate(Map) and onAfterUpdate methods + * + * @throws DomainException if the current user context is not able to update records + **/ + @NamespaceAccessible + public virtual void handleAfterUpdate(Map existingRecords) + { + if(Configuration.EnforcingTriggerCRUDSecurity && !SObjectDescribe.isUpdateable()) + throw new DomainException('Permission to update an ' + SObjectDescribe.getName() + ' denied.'); + + if(Configuration.OldOnUpdateValidateBehaviour) + onValidate(); + onValidate(existingRecords); + onAfterUpdate(existingRecords); + } + + /** + * Base handler for the Apex Trigger event After Delete, checks object security and calls the onAfterDelete method + * + * @throws DomainException if the current user context is not able to delete records + **/ + @NamespaceAccessible + public virtual void handleAfterDelete() + { + if(Configuration.EnforcingTriggerCRUDSecurity && !SObjectDescribe.isDeletable()) + throw new DomainException('Permission to delete an ' + SObjectDescribe.getName() + ' denied.'); + + onAfterDelete(); + } + + /** + * Base handler for the Apex Trigger event After Undelete, checks object security and calls the onAfterUndelete method + * + * @throws DomainException if the current user context is not able to delete records + **/ + @NamespaceAccessible + public virtual void handleAfterUndelete() + { + if(Configuration.EnforcingTriggerCRUDSecurity && !SObjectDescribe.isUndeletable()) + throw new DomainException('Permission to undelete an ' + SObjectDescribe.getName() + ' denied.'); + + onAfterUndelete(); + } + + /** + * Returns the SObjectType this Domain class represents + **/ + @NamespaceAccessible + public SObjectType sObjectType() + { + return getSObjectType(); + } /** * Detects whether any values in context records have changed for given fields as strings * Returns list of SObject records that have changes in the specified fields **/ + @NamespaceAccessible public List getChangedRecords(Set fieldNames) { List changedRecords = new List(); @@ -315,6 +342,7 @@ public virtual with sharing class fflib_SObjectDomain * Detects whether any values in context records have changed for given fields as tokens * Returns list of SObject records that have changes in the specified fields **/ + @NamespaceAccessible public List getChangedRecords(Set fieldTokens) { List changedRecords = new List(); @@ -341,6 +369,7 @@ public virtual with sharing class fflib_SObjectDomain /** * Interface used to aid the triggerHandler in constructing instances of Domain classes **/ + @NamespaceAccessible public interface IConstructable { fflib_SObjectDomain construct(List sObjectList); @@ -349,6 +378,7 @@ public virtual with sharing class fflib_SObjectDomain /** * Interface used to aid the triggerHandler in constructing instances of Domain classes **/ + @NamespaceAccessible public interface IConstructable2 extends IConstructable { fflib_SObjectDomain construct(List sObjectList, SObjectType sObjectType); @@ -361,6 +391,7 @@ public virtual with sharing class fflib_SObjectDomain * the ITriggerStateful interface. Note this method is sensitive to recursion, meaning * it will return the applicable domain instance for the level of recursion **/ + @NamespaceAccessible public static fflib_SObjectDomain getTriggerInstance(Type domainClass) { List domains = TriggerStateByClass.get(domainClass); @@ -373,6 +404,7 @@ public virtual with sharing class fflib_SObjectDomain * Method constructs the given Domain class with the current Trigger context * before calling the applicable override methods such as beforeInsert, beforeUpdate etc. **/ + @NamespaceAccessible public static void triggerHandler(Type domainClass) { // Process the trigger context @@ -411,15 +443,15 @@ public virtual with sharing class fflib_SObjectDomain IConstructable domainConstructor = (IConstructable) constructableClass.newInstance(); // Construct the domain class with the approprite record set - if(isInsert) domainObject = domainConstructor.construct(newRecords); - else if(isUpdate) domainObject = domainConstructor.construct(newRecords); - else if(isDelete) domainObject = domainConstructor.construct(oldRecordsMap.values()); - else if(isUndelete) domainObject = domainConstructor.construct(newRecords); - - // Should this instance be reused on the next trigger invocation? - if(domainObject.Configuration.TriggerStateEnabled) - // Push this instance onto the stack to be popped during the after phase - pushTriggerInstance(domainClass, domainObject); + if(isInsert) domainObject = domainConstructor.construct(newRecords); + else if(isUpdate) domainObject = domainConstructor.construct(newRecords); + else if(isDelete) domainObject = domainConstructor.construct(oldRecordsMap.values()); + else if(isUndelete) domainObject = domainConstructor.construct(newRecords); + + // Should this instance be reused on the next trigger invocation? + if(domainObject.Configuration.TriggerStateEnabled) + // Push this instance onto the stack to be popped during the after phase + pushTriggerInstance(domainClass, domainObject); } // has this event been disabled? @@ -430,18 +462,18 @@ public virtual with sharing class fflib_SObjectDomain // Invoke the applicable handler if(isBefore) - { - if(isInsert) domainObject.handleBeforeInsert(); - else if(isUpdate) domainObject.handleBeforeUpdate(oldRecordsMap); - else if(isDelete) domainObject.handleBeforeDelete(); - } - else - { - if(isInsert) domainObject.handleAfterInsert(); - else if(isUpdate) domainObject.handleAfterUpdate(oldRecordsMap); - else if(isDelete) domainObject.handleAfterDelete(); - else if(isUndelete) domainObject.handleAfterUndelete(); - } + { + if(isInsert) domainObject.handleBeforeInsert(); + else if(isUpdate) domainObject.handleBeforeUpdate(oldRecordsMap); + else if(isDelete) domainObject.handleBeforeDelete(); + } + else + { + if(isInsert) domainObject.handleAfterInsert(); + else if(isUpdate) domainObject.handleAfterUpdate(oldRecordsMap); + else if(isDelete) domainObject.handleAfterDelete(); + else if(isUndelete) domainObject.handleAfterUndelete(); + } } /** @@ -469,6 +501,7 @@ public virtual with sharing class fflib_SObjectDomain return domain; } + @NamespaceAccessible public static TriggerEvent getTriggerEvent(Type domainClass) { if(!TriggerEventByClass.containsKey(domainClass)) @@ -479,74 +512,104 @@ public virtual with sharing class fflib_SObjectDomain return TriggerEventByClass.get(domainClass); } + @NamespaceAccessible public class TriggerEvent { - public boolean BeforeInsertEnabled {get; private set;} - public boolean BeforeUpdateEnabled {get; private set;} - public boolean BeforeDeleteEnabled {get; private set;} - - public boolean AfterInsertEnabled {get; private set;} - public boolean AfterUpdateEnabled {get; private set;} - public boolean AfterDeleteEnabled {get; private set;} - public boolean AfterUndeleteEnabled {get; private set;} - - public TriggerEvent() + @NamespaceAccessible + public boolean BeforeInsertEnabled {get; private set;} + @NamespaceAccessible + public boolean BeforeUpdateEnabled {get; private set;} + @NamespaceAccessible + public boolean BeforeDeleteEnabled {get; private set;} + + @NamespaceAccessible + public boolean AfterInsertEnabled {get; private set;} + @NamespaceAccessible + public boolean AfterUpdateEnabled {get; private set;} + @NamespaceAccessible + public boolean AfterDeleteEnabled {get; private set;} + @NamespaceAccessible + public boolean AfterUndeleteEnabled {get; private set;} + + @NamespaceAccessible + public TriggerEvent() { this.enableAll(); } // befores - public TriggerEvent enableBeforeInsert() {BeforeInsertEnabled = true; return this;} - public TriggerEvent enableBeforeUpdate() {BeforeUpdateEnabled = true; return this;} - public TriggerEvent enableBeforeDelete() {BeforeDeleteEnabled = true; return this;} - - public TriggerEvent disableBeforeInsert() {BeforeInsertEnabled = false; return this;} - public TriggerEvent disableBeforeUpdate() {BeforeUpdateEnabled = false; return this;} - public TriggerEvent disableBeforeDelete() {BeforeDeleteEnabled = false; return this;} + @NamespaceAccessible + public TriggerEvent enableBeforeInsert() {BeforeInsertEnabled = true; return this;} + @NamespaceAccessible + public TriggerEvent enableBeforeUpdate() {BeforeUpdateEnabled = true; return this;} + @NamespaceAccessible + public TriggerEvent enableBeforeDelete() {BeforeDeleteEnabled = true; return this;} + + @NamespaceAccessible + public TriggerEvent disableBeforeInsert() {BeforeInsertEnabled = false; return this;} + @NamespaceAccessible + public TriggerEvent disableBeforeUpdate() {BeforeUpdateEnabled = false; return this;} + @NamespaceAccessible + public TriggerEvent disableBeforeDelete() {BeforeDeleteEnabled = false; return this;} // afters - public TriggerEvent enableAfterInsert() {AfterInsertEnabled = true; return this;} - public TriggerEvent enableAfterUpdate() {AfterUpdateEnabled = true; return this;} - public TriggerEvent enableAfterDelete() {AfterDeleteEnabled = true; return this;} - public TriggerEvent enableAfterUndelete() {AfterUndeleteEnabled = true; return this;} + @NamespaceAccessible + public TriggerEvent enableAfterInsert() {AfterInsertEnabled = true; return this;} + @NamespaceAccessible + public TriggerEvent enableAfterUpdate() {AfterUpdateEnabled = true; return this;} + @NamespaceAccessible + public TriggerEvent enableAfterDelete() {AfterDeleteEnabled = true; return this;} + @NamespaceAccessible + public TriggerEvent enableAfterUndelete() {AfterUndeleteEnabled = true; return this;} - public TriggerEvent disableAfterInsert() {AfterInsertEnabled = false; return this;} - public TriggerEvent disableAfterUpdate() {AfterUpdateEnabled = false; return this;} - public TriggerEvent disableAfterDelete() {AfterDeleteEnabled = false; return this;} - public TriggerEvent disableAfterUndelete(){AfterUndeleteEnabled = false; return this;} - - public TriggerEvent enableAll() + @NamespaceAccessible + public TriggerEvent disableAfterInsert() {AfterInsertEnabled = false; return this;} + @NamespaceAccessible + public TriggerEvent disableAfterUpdate() {AfterUpdateEnabled = false; return this;} + @NamespaceAccessible + public TriggerEvent disableAfterDelete() {AfterDeleteEnabled = false; return this;} + @NamespaceAccessible + public TriggerEvent disableAfterUndelete(){AfterUndeleteEnabled = false; return this;} + + @NamespaceAccessible + public TriggerEvent enableAll() { return this.enableAllBefore().enableAllAfter(); } - public TriggerEvent disableAll() + @NamespaceAccessible + public TriggerEvent disableAll() { return this.disableAllBefore().disableAllAfter(); } - public TriggerEvent enableAllBefore() + @NamespaceAccessible + public TriggerEvent enableAllBefore() { return this.enableBeforeInsert().enableBeforeUpdate().enableBeforeDelete(); } - public TriggerEvent disableAllBefore() + @NamespaceAccessible + public TriggerEvent disableAllBefore() { return this.disableBeforeInsert().disableBeforeUpdate().disableBeforeDelete(); } - public TriggerEvent enableAllAfter() + @NamespaceAccessible + public TriggerEvent enableAllAfter() { return this.enableAfterInsert().enableAfterUpdate().enableAfterDelete().enableAfterUndelete(); } - public TriggerEvent disableAllAfter() + @NamespaceAccessible + public TriggerEvent disableAllAfter() { return this.disableAfterInsert().disableAfterUpdate().disableAfterDelete().disableAfterUndelete(); } - public boolean isEnabled(Boolean isBefore, Boolean isAfter, Boolean isInsert, Boolean isUpdate, Boolean isDelete, Boolean isUndelete) + @NamespaceAccessible + public boolean isEnabled(Boolean isBefore, Boolean isAfter, Boolean isInsert, Boolean isUpdate, Boolean isDelete, Boolean isUndelete) { if(isBefore) { @@ -568,26 +631,31 @@ public virtual with sharing class fflib_SObjectDomain /** * Fluent style Configuration system for Domain class creation **/ + @NamespaceAccessible public class Configuration { /** * Backwards compatibility mode for handleAfterUpdate routing to onValidate() **/ - public Boolean OldOnUpdateValidateBehaviour {get; private set;} + @NamespaceAccessible + public Boolean OldOnUpdateValidateBehaviour {get; private set;} /** * True if the base class is checking the users CRUD requirements before invoking trigger methods **/ - public Boolean EnforcingTriggerCRUDSecurity {get; private set;} + @NamespaceAccessible + public Boolean EnforcingTriggerCRUDSecurity {get; private set;} /** * Enables reuse of the same Domain instance between before and after trigger phases (subject to recursive scenarios) **/ - public Boolean TriggerStateEnabled {get; private set;} + @NamespaceAccessible + public Boolean TriggerStateEnabled {get; private set;} /** * Default configuration **/ - public Configuration() + @NamespaceAccessible + public Configuration() { EnforcingTriggerCRUDSecurity = true; // Default is true for backwards compatability TriggerStateEnabled = false; @@ -597,7 +665,8 @@ public virtual with sharing class fflib_SObjectDomain /** * See associated property **/ - public Configuration enableTriggerState() + @NamespaceAccessible + public Configuration enableTriggerState() { TriggerStateEnabled = true; return this; @@ -606,7 +675,8 @@ public virtual with sharing class fflib_SObjectDomain /** * See associated property **/ - public Configuration disableTriggerState() + @NamespaceAccessible + public Configuration disableTriggerState() { TriggerStateEnabled = false; return this; @@ -615,7 +685,8 @@ public virtual with sharing class fflib_SObjectDomain /** * See associated property **/ - public Configuration enforceTriggerCRUDSecurity() + @NamespaceAccessible + public Configuration enforceTriggerCRUDSecurity() { EnforcingTriggerCRUDSecurity = true; return this; @@ -624,7 +695,8 @@ public virtual with sharing class fflib_SObjectDomain /** * See associated property **/ - public Configuration disableTriggerCRUDSecurity() + @NamespaceAccessible + public Configuration disableTriggerCRUDSecurity() { EnforcingTriggerCRUDSecurity = false; return this; @@ -633,7 +705,8 @@ public virtual with sharing class fflib_SObjectDomain /** * See associated property **/ - public Configuration enableOldOnUpdateValidateBehaviour() + @NamespaceAccessible + public Configuration enableOldOnUpdateValidateBehaviour() { OldOnUpdateValidateBehaviour = true; return this; @@ -642,7 +715,8 @@ public virtual with sharing class fflib_SObjectDomain /** * See associated property **/ - public Configuration disableOldOnUpdateValidateBehaviour() + @NamespaceAccessible + public Configuration disableOldOnUpdateValidateBehaviour() { OldOnUpdateValidateBehaviour = false; return this; @@ -652,6 +726,7 @@ public virtual with sharing class fflib_SObjectDomain /** * General exception class for the domain layer **/ + @NamespaceAccessible public class DomainException extends Exception { } @@ -659,6 +734,7 @@ public virtual with sharing class fflib_SObjectDomain /** * Ensures logging of errors in the Domain context for later assertions in tests **/ + @NamespaceAccessible public override String error(String message, SObject record) { return fflib_SObjectDomain.Errors.error(this, message, record); @@ -667,6 +743,7 @@ public virtual with sharing class fflib_SObjectDomain /** * Ensures logging of errors in the Domain context for later assertions in tests **/ + @NamespaceAccessible public override String error(String message, SObject record, SObjectField field) { return fflib_SObjectDomain.Errors.error(this, message, record, field); @@ -675,6 +752,7 @@ public virtual with sharing class fflib_SObjectDomain /** * DEPRECATED, This class has been moved to fflib_SObjects **/ + @NamespaceAccessible public class ErrorFactory { private List errorList = new List(); @@ -684,7 +762,8 @@ public virtual with sharing class fflib_SObjectDomain } - public String error(String message, SObject record) + @NamespaceAccessible + public String error(String message, SObject record) { return error(null, message, record); } @@ -699,7 +778,8 @@ public virtual with sharing class fflib_SObjectDomain return message; } - public String error(String message, SObject record, SObjectField field) + @NamespaceAccessible + public String error(String message, SObject record, SObjectField field) { return error(null, message, record, field); } @@ -715,12 +795,14 @@ public virtual with sharing class fflib_SObjectDomain return message; } - public List getAll() + @NamespaceAccessible + public List getAll() { return errorList.clone(); } - public void clearAll() + @NamespaceAccessible + public void clearAll() { errorList.clear(); } @@ -729,11 +811,14 @@ public virtual with sharing class fflib_SObjectDomain /** * DEPRECATED, This class has been moved to fflib_SObjects **/ + @NamespaceAccessible public virtual class FieldError extends ObjectError { - public SObjectField field; + @NamespaceAccessible + public SObjectField field; - public FieldError() + @NamespaceAccessible + public FieldError() { } @@ -742,11 +827,14 @@ public virtual with sharing class fflib_SObjectDomain /** * DEPRECATED, This class has been moved to fflib_SObjects **/ + @NamespaceAccessible public virtual class ObjectError extends Error { - public SObject record; + @NamespaceAccessible + public SObject record; - public ObjectError() + @NamespaceAccessible + public ObjectError() { } @@ -755,18 +843,23 @@ public virtual with sharing class fflib_SObjectDomain /** * DEPRECATED, This class has been moved to fflib_SObjects **/ + @NamespaceAccessible public abstract class Error { - public String message; - public fflib_SObjectDomain domain; + @NamespaceAccessible + public String message; + @NamespaceAccessible + public fflib_SObjectDomain domain; } /** * Provides test context mocking facilities to unit tests testing domain classes **/ + @NamespaceAccessible public class TestFactory { - public MockDatabase Database = new MockDatabase(); + @NamespaceAccessible + public MockDatabase Database = new MockDatabase(); private TestFactory() { @@ -777,6 +870,7 @@ public virtual with sharing class fflib_SObjectDomain /** * Class used during Unit testing of Domain classes, can be used (not exclusively) to speed up test execution and focus testing **/ + @NamespaceAccessible public class MockDatabase { private Boolean isInsert = false; @@ -800,7 +894,8 @@ public virtual with sharing class fflib_SObjectDomain triggerHandler(domainClass, false, true, isInsert, isUpdate, isDelete, isUndelete, records, oldRecords); } - public void onInsert(List records) + @NamespaceAccessible + public void onInsert(List records) { this.isInsert = true; this.isUpdate = false; @@ -809,7 +904,8 @@ public virtual with sharing class fflib_SObjectDomain this.records = records; } - public void onUpdate(List records, Map oldRecords) + @NamespaceAccessible + public void onUpdate(List records, Map oldRecords) { this.isInsert = false; this.isUpdate = true; @@ -819,7 +915,8 @@ public virtual with sharing class fflib_SObjectDomain this.oldRecords = oldRecords; } - public void onDelete(Map records) + @NamespaceAccessible + public void onDelete(Map records) { this.isInsert = false; this.isUpdate = false; @@ -828,7 +925,8 @@ public virtual with sharing class fflib_SObjectDomain this.oldRecords = records; } - public void onUndelete(List records) + @NamespaceAccessible + public void onUndelete(List records) { this.isInsert = false; this.isUpdate = false; @@ -837,7 +935,8 @@ public virtual with sharing class fflib_SObjectDomain this.records = records; } - public Boolean hasRecords() + @NamespaceAccessible + public Boolean hasRecords() { return records!=null && records.size()>0 || oldRecords!=null && oldRecords.size()>0; } @@ -846,23 +945,27 @@ public virtual with sharing class fflib_SObjectDomain /** * Test domain class (ideally this would be in the test class, however Type.newInstance does not see such classes) **/ + @NamespaceAccessible public with sharing class TestSObjectDomain extends fflib_SObjectDomain { private String someState; - public TestSObjectDomain(List sObjectList) + @NamespaceAccessible + public TestSObjectDomain(List sObjectList) { // Domain classes are initialised with lists to enforce bulkification throughout super(sObjectList); } - public TestSObjectDomain(List sObjectList, SObjectType sObjectType) + @NamespaceAccessible + public TestSObjectDomain(List sObjectList, SObjectType sObjectType) { // Domain classes are initialised with lists to enforce bulkification throughout super(sObjectList, sObjectType); } - public override void onApplyDefaults() + @NamespaceAccessible + public override void onApplyDefaults() { // Not required in production code super.onApplyDefaults(); @@ -874,7 +977,8 @@ public virtual with sharing class fflib_SObjectDomain } } - public override void onValidate() + @NamespaceAccessible + public override void onValidate() { // Not required in production code super.onValidate(); @@ -889,7 +993,8 @@ public virtual with sharing class fflib_SObjectDomain } } - public override void onValidate(Map existingRecords) + @NamespaceAccessible + public override void onValidate(Map existingRecords) { // Not required in production code super.onValidate(existingRecords); @@ -905,7 +1010,8 @@ public virtual with sharing class fflib_SObjectDomain } } - public override void onBeforeDelete() + @NamespaceAccessible + public override void onBeforeDelete() { // Not required in production code super.onBeforeDelete(); @@ -917,19 +1023,22 @@ public virtual with sharing class fflib_SObjectDomain } } - public override void onAfterUndelete() + @NamespaceAccessible + public override void onAfterUndelete() { // Not required in production code super.onAfterUndelete(); } - public override void onBeforeInsert() + @NamespaceAccessible + public override void onBeforeInsert() { // Assert this variable is null in the after insert (since this domain class is stateless) someState = 'This should not survice the trigger after phase'; } - public override void onAfterInsert() + @NamespaceAccessible + public override void onAfterInsert() { // This is a stateless domain class, so should not retain anything betweet before and after System.assertEquals(null, someState); @@ -939,9 +1048,11 @@ public virtual with sharing class fflib_SObjectDomain /** * Typically an inner class to the domain class, supported here for test purposes **/ + @NamespaceAccessible public class TestSObjectDomainConstructor implements fflib_SObjectDomain.IConstructable { - public fflib_SObjectDomain construct(List sObjectList) + @NamespaceAccessible + public fflib_SObjectDomain construct(List sObjectList) { return new TestSObjectDomain(sObjectList); } @@ -950,12 +1061,15 @@ public virtual with sharing class fflib_SObjectDomain /** * Test domain class (ideally this would be in the test class, however Type.newInstance does not see such classes) **/ + @NamespaceAccessible public with sharing class TestSObjectStatefulDomain extends fflib_SObjectDomain { - public String someState; + @NamespaceAccessible + public String someState; - public TestSObjectStatefulDomain(List sObjectList) + @NamespaceAccessible + public TestSObjectStatefulDomain(List sObjectList) { super(sObjectList); @@ -963,7 +1077,8 @@ public virtual with sharing class fflib_SObjectDomain Configuration.enableTriggerState(); } - public override void onBeforeInsert() + @NamespaceAccessible + public override void onBeforeInsert() { // This must always be null, as we do not reuse domain instances within recursive scenarios (different record sets) System.assertEquals(null, someState); @@ -989,7 +1104,8 @@ public virtual with sharing class fflib_SObjectDomain } } - public override void onAfterInsert() + @NamespaceAccessible + public override void onAfterInsert() { // Use the state set in the before insert (since this is a stateful domain class) if(someState!=null) @@ -1001,9 +1117,11 @@ public virtual with sharing class fflib_SObjectDomain /** * Typically an inner class to the domain class, supported here for test purposes **/ + @NamespaceAccessible public class TestSObjectStatefulDomainConstructor implements fflib_SObjectDomain.IConstructable { - public fflib_SObjectDomain construct(List sObjectList) + @NamespaceAccessible + public fflib_SObjectDomain construct(List sObjectList) { return new TestSObjectStatefulDomain(sObjectList); } @@ -1012,10 +1130,12 @@ public virtual with sharing class fflib_SObjectDomain /** * Test domain class (ideally this would be in the test class, however Type.newInstance does not see such classes) **/ + @NamespaceAccessible public with sharing class TestSObjectOnValidateBehaviour extends fflib_SObjectDomain { - public TestSObjectOnValidateBehaviour(List sObjectList) + @NamespaceAccessible + public TestSObjectOnValidateBehaviour(List sObjectList) { super(sObjectList); @@ -1024,7 +1144,8 @@ public virtual with sharing class fflib_SObjectDomain Configuration.enableOldOnUpdateValidateBehaviour(); } - public override void onValidate() + @NamespaceAccessible + public override void onValidate() { // Throw exception to give the test somethign to assert on throw new DomainException('onValidate called'); @@ -1034,9 +1155,11 @@ public virtual with sharing class fflib_SObjectDomain /** * Typically an inner class to the domain class, supported here for test purposes **/ + @NamespaceAccessible public class TestSObjectOnValidateBehaviourConstructor implements fflib_SObjectDomain.IConstructable { - public fflib_SObjectDomain construct(List sObjectList) + @NamespaceAccessible + public fflib_SObjectDomain construct(List sObjectList) { return new TestSObjectOnValidateBehaviour(sObjectList); } @@ -1045,10 +1168,12 @@ public virtual with sharing class fflib_SObjectDomain /** * Test domain class (ideally this would be in the test class, however Type.newInstance does not see such classes) **/ + @NamespaceAccessible public with sharing class TestSObjectChangedRecords extends fflib_SObjectDomain { - public TestSObjectChangedRecords(List sObjectList) + @NamespaceAccessible + public TestSObjectChangedRecords(List sObjectList) { super(sObjectList); } @@ -1057,9 +1182,11 @@ public virtual with sharing class fflib_SObjectDomain /** * Typically an inner class to the domain class, supported here for test purposes **/ + @NamespaceAccessible public class TestSObjectChangedRecordsConstructor implements fflib_SObjectDomain.IConstructable { - public fflib_SObjectDomain construct(List sObjectList) + @NamespaceAccessible + public fflib_SObjectDomain construct(List sObjectList) { return new TestSObjectChangedRecords(sObjectList); } @@ -1068,45 +1195,54 @@ public virtual with sharing class fflib_SObjectDomain /** * Test domain class (ideally this would be in the test class, however Type.newInstance does not see such classes) **/ + @NamespaceAccessible public with sharing class TestSObjectDisableBehaviour extends fflib_SObjectDomain { - public TestSObjectDisableBehaviour(List sObjectList) + @NamespaceAccessible + public TestSObjectDisableBehaviour(List sObjectList) { super(sObjectList); } - public override void onAfterInsert() { + @NamespaceAccessible + public override void onAfterInsert() { // Throw exception to give the test somethign to assert on throw new DomainException('onAfterInsert called'); } - public override void onBeforeInsert() { + @NamespaceAccessible + public override void onBeforeInsert() { // Throw exception to give the test somethign to assert on throw new DomainException('onBeforeInsert called'); } - public override void onAfterUpdate(map existing) { + @NamespaceAccessible + public override void onAfterUpdate(map existing) { // Throw exception to give the test somethign to assert on throw new DomainException('onAfterUpdate called'); } - public override void onBeforeUpdate(map existing) { + @NamespaceAccessible + public override void onBeforeUpdate(map existing) { // Throw exception to give the test somethign to assert on throw new DomainException('onBeforeUpdate called'); } - public override void onAfterDelete() { + @NamespaceAccessible + public override void onAfterDelete() { // Throw exception to give the test somethign to assert on throw new DomainException('onAfterDelete called'); } - public override void onBeforeDelete() { + @NamespaceAccessible + public override void onBeforeDelete() { // Throw exception to give the test somethign to assert on throw new DomainException('onBeforeDelete called'); } - public override void onAfterUndelete() { + @NamespaceAccessible + public override void onAfterUndelete() { // Throw exception to give the test somethign to assert on throw new DomainException('onAfterUndelete called'); } @@ -1115,9 +1251,11 @@ public virtual with sharing class fflib_SObjectDomain /** * Typically an inner class to the domain class, supported here for test purposes **/ + @NamespaceAccessible public class TestSObjectDisableBehaviourConstructor implements fflib_SObjectDomain.IConstructable { - public fflib_SObjectDomain construct(List sObjectList) + @NamespaceAccessible + public fflib_SObjectDomain construct(List sObjectList) { return new TestSObjectDisableBehaviour(sObjectList); } diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectSelector.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectSelector.cls index 3abbdc4e38..8734a93564 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectSelector.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectSelector.cls @@ -27,57 +27,59 @@ /** * Class providing common database query support for abstracting and encapsulating query logic **/ -public abstract with sharing class fflib_SObjectSelector +@NamespaceAccessible + public abstract with sharing class fflib_SObjectSelector implements fflib_ISObjectSelector { - public enum DataAccess{LEGACY, USER_MODE, SYSTEM_MODE} - - /** - * Indicates whether the sObject has the currency ISO code field for organisations which have multi-currency - * enabled. - **/ - private Boolean CURRENCY_ISO_CODE_ENABLED { - get { - if(CURRENCY_ISO_CODE_ENABLED == null){ - CURRENCY_ISO_CODE_ENABLED = describeWrapper.getFieldsMap().keySet().contains('currencyisocode'); - } - return CURRENCY_ISO_CODE_ENABLED; - } + @NamespaceAccessible + public enum DataAccess{LEGACY, USER_MODE, SYSTEM_MODE} + + /** + * Indicates whether the sObject has the currency ISO code field for organisations which have multi-currency + * enabled. + **/ + private Boolean CURRENCY_ISO_CODE_ENABLED { + get { + if(CURRENCY_ISO_CODE_ENABLED == null){ + CURRENCY_ISO_CODE_ENABLED = describeWrapper.getFieldsMap().keySet().contains('currencyisocode'); + } + return CURRENCY_ISO_CODE_ENABLED; + } set; - } - - /** - * Should this selector automatically include the FieldSet fields when building queries? - **/ - private Boolean m_includeFieldSetFields = false; - - /** - * Enforce FLS Security - **/ - private Boolean m_enforceFLS = false; - - /** - * Enforce CRUD Security - **/ - private Boolean m_enforceCRUD = true; + } + + /** + * Should this selector automatically include the FieldSet fields when building queries? + **/ + private Boolean m_includeFieldSetFields = false; + + /** + * Enforce FLS Security + **/ + private Boolean m_enforceFLS = false; + + /** + * Enforce CRUD Security + **/ + private Boolean m_enforceCRUD = true; - /** - * Order by field - **/ - private String m_orderBy; - - private DataAccess m_dataAccess = DataAccess.LEGACY; - - /** - * Sort the query fields in the select statement (defaults to true, at the expense of performance). - * Switch this off if you need more performant queries. - **/ - private Boolean m_sortSelectFields = true; - - /** - * Describe helper - **/ - private fflib_SObjectDescribe describeWrapper { + /** + * Order by field + **/ + private String m_orderBy; + + private DataAccess m_dataAccess = DataAccess.LEGACY; + + /** + * Sort the query fields in the select statement (defaults to true, at the expense of performance). + * Switch this off if you need more performant queries. + **/ + private Boolean m_sortSelectFields = true; + + /** + * Describe helper + **/ + private fflib_SObjectDescribe describeWrapper { get { if(describeWrapper == null) describeWrapper = fflib_SObjectDescribe.getDescribe(getSObjectType()); @@ -85,348 +87,382 @@ public abstract with sharing class fflib_SObjectSelector } set; } - /** - * static variables - **/ - 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 - **/ - 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 - **/ - abstract List getSObjectFieldList(); - - /** - * Constructs the Selector with the default settings - **/ - public fflib_SObjectSelector() { } - - /** - * Constructs the Selector - * - * @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); - } - - public fflib_SObjectSelector(Boolean includeFieldSetFields, DataAccess dataAccess) - { - this(includeFieldSetFields, false, false, false, dataAccess); - } - - /** - * Constructs the Selector - * - * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well - * @deprecated - consider using dataAccess for native platform enforcement of CRUD and FLS - **/ - public fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS) - { - this(includeFieldSetFields, enforceCRUD, enforceFLS, true); - } - - /** - * Constructs the Selector - * - * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well - * @param enforceCRUD Enforce CRUD security - * @param enforceFLS Enforce Field Level Security - * @param sortSelectFields Set to false if selecting many columns to skip sorting select fields and improve performance - * @deprecated - consider using dataAccess for native platform enforcement of CRUD and FLS - **/ - public fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields) - { - this(includeFieldSetFields,enforceCRUD,enforceFLS,sortSelectFields,DataAccess.LEGACY); - } - - private fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields, DataAccess dataAccess) - { - m_includeFieldSetFields = includeFieldSetFields; - m_enforceCRUD = enforceCRUD; - m_enforceFLS = enforceFLS; - m_sortSelectFields = sortSelectFields; - m_DataAccess = dataAccess; - } - - - /** - * Override this method to provide a list of Fieldsets that can optionally drive inclusion of additional fields in the base queries - **/ - public virtual List getSObjectFieldSetList() - { - 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() - { - if (m_orderBy == null) - { - Schema.SObjectField nameField = describeWrapper.getNameField(); - if (nameField != null && !nameField.getDescribe().isEncrypted()) - { - m_orderBy = nameField.getDescribe().getName(); - } - else - { - m_orderBy = DEFAULT_SORT_FIELD; - try { - if (describeWrapper.getField(m_orderBy) == null) - { - m_orderBy = SF_ID_FIELD; - } - } - catch(fflib_QueryFactory.InvalidFieldException ex) { - m_orderBy = SF_ID_FIELD; - } - } - } - return m_orderBy; - } - - - /** - * @description Set the selector to enforce FLS Security - * @deprecated -- consider using setDataAccess to enforce native Apex User Mode Operations instead - **/ - public fflib_SObjectSelector enforceFLS() - { - m_enforceFLS = true; - return this; - } - - /** - * @description Set the selector to automatically include the FieldSet fields when building queries - **/ - public fflib_SObjectSelector includeFieldSetFields() - { - this.m_includeFieldSetFields = true; - return this; - } - - /** - * @description Set the selector to ignore CRUD security - * @return - */ - public fflib_SObjectSelector ignoreCRUD() - { - this.m_enforceCRUD = false; - return this; - } - - public fflib_SObjectSelector unsortedSelectFields() - { - this.m_sortSelectFields = false; - return this; - } - - public fflib_SObjectSelector setDataAccess(DataAccess access){ - this.m_dataAccess = access; - - //You can't mix and match the legacy enforceFls and assertCRUD with the SYSTEM_MODE or USER_MODE - if(this.m_dataAccess != DataAccess.LEGACY){ - ignoreCRUD(); - m_enforceFLS = false; - } - - return this; - } - - /** - * Returns True if this Selector instance has been instructed by the caller to include Field Set fields - **/ - public Boolean isIncludeFieldSetFields() - { - return m_includeFieldSetFields; - } - - /** - * Returns True if this Selector is enforcing FLS - **/ - public Boolean isEnforcingFLS() - { - return m_enforceFLS; - } - - /** - * Returns True if this Selector is enforcing CRUD Security - **/ - public Boolean isEnforcingCRUD() - { - return m_enforceCRUD; - } - - public DataAccess getDataAccess(){ - return m_dataAccess; - } - - /** - * Provides access to the builder containing the list of fields base queries are using, this is demand - * created if one has not already been defined via setFieldListBuilder - * - * @depricated See newQueryFactory - **/ - public fflib_StringBuilder.CommaDelimitedListBuilder getFieldListBuilder() - { - 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, - * 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 optimisation) if it's OK to leave this as a null operation - } - - /** - * Returns in string form a comma delimited list of fields as defined via getSObjectFieldList and optionally getSObjectFieldSetList - * - * @deprecated See newQueryFactory - **/ - public String getFieldListString() - { - return getFieldListBuilder().getStringValue(); - } - - /** - * Returns in string form a comma delimited 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 + '.'); - } - - /** - * Returns the string representation of the SObject this selector represents - **/ - public String getSObjectName() - { - return describeWrapper.getDescribe().getName(); - } - - /** - * 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 - * @returns A list of SObject's - **/ - public virtual List selectSObjectsById(Set idSet) - { - return Database.query(buildQuerySObjectById()); - } - - /** - * 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 - * @returns A QueryLocator (typically for use in a Batch Apex job) - **/ - public virtual Database.QueryLocator queryLocatorById(Set idSet) - { - return Database.getQueryLocator(buildQuerySObjectById()); - } - - /** - * Throws an exception if the SObject indicated by getSObjectType is not accessible to the current user (read access) - * - * @deprecated 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.'); - } - - /** - * Public access for the getSObjectType during Mock registration - * (adding public to the existing method broken base class API backwards compatibility) - **/ - public SObjectType getSObjectType2() - { - return getSObjectType(); - } - - /** - * Public access for the getSObjectType during Mock registration - * (adding public to the existing method broken base class API backwards compatibility1) - **/ - 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, m_DataAccess); - } - - /** - * 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, m_DataAccess); - } - - /** - * Returns a QueryFactory configured with the Selectors object, fields, fieldsets and default order by - * CRUD and FLS read security will be checked if the corresponding inputs are true (overrides that defined in the selector). - **/ - public fflib_QueryFactory newQueryFactory(Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) - { - // Construct QueryFactory around the given SObject - return newQueryFactory( - assertCRUD, - enforceFLS, - includeSelectorFields, - DataAccess.LEGACY); - } - - private fflib_QueryFactory newQueryFactory(Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields, DataAccess dataAccess) - { - // Construct QueryFactory around the given SObject - return configureQueryFactory( - new fflib_QueryFactory(getSObjectType2()), - assertCRUD, - enforceFLS, - includeSelectorFields, - dataAccess); - } - - /** - * Adds the selectors fields to the given QueryFactory using the given relationship path as a prefix - * - * // TODO: This should be consistent (ideally) with configureQueryFactory below - **/ - public void configureQueryFactoryFields(fflib_QueryFactory queryFactory, String relationshipFieldPath) - { + /** + * static variables + **/ + 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 + **/ + 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 + **/ + abstract List getSObjectFieldList(); + + /** + * Constructs the Selector with the default settings + **/ + @NamespaceAccessible + public fflib_SObjectSelector() { } + + /** + * Constructs the Selector + * + * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well + **/ + @NamespaceAccessible + public fflib_SObjectSelector(Boolean includeFieldSetFields) + { + this(includeFieldSetFields, true, false); + } + + @NamespaceAccessible + public fflib_SObjectSelector(Boolean includeFieldSetFields, DataAccess dataAccess) + { + this(includeFieldSetFields, false, false, false, dataAccess); + } + + /** + * Constructs the Selector + * + * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well + * @deprecated - consider using dataAccess for native platform enforcement of CRUD and FLS + **/ + @NamespaceAccessible + public fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS) + { + this(includeFieldSetFields, enforceCRUD, enforceFLS, true); + } + + /** + * Constructs the Selector + * + * @param includeFieldSetFields Set to true if the Selector queries are to include Fieldset fields as well + * @param enforceCRUD Enforce CRUD security + * @param enforceFLS Enforce Field Level Security + * @param sortSelectFields Set to false if selecting many columns to skip sorting select fields and improve performance + * @deprecated - consider using dataAccess for native platform enforcement of CRUD and FLS + **/ + @NamespaceAccessible + public fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields) + { + this(includeFieldSetFields,enforceCRUD,enforceFLS,sortSelectFields,DataAccess.LEGACY); + } + + private fflib_SObjectSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS, Boolean sortSelectFields, DataAccess dataAccess) + { + m_includeFieldSetFields = includeFieldSetFields; + m_enforceCRUD = enforceCRUD; + m_enforceFLS = enforceFLS; + m_sortSelectFields = sortSelectFields; + m_DataAccess = dataAccess; + } + + + /** + * Override this method to provide a list of Fieldsets that can optionally drive inclusion of additional fields in the base queries + **/ + @NamespaceAccessible + public virtual List getSObjectFieldSetList() + { + 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 + **/ + @NamespaceAccessible + public virtual String getOrderBy() + { + if (m_orderBy == null) + { + Schema.SObjectField nameField = describeWrapper.getNameField(); + if (nameField != null && !nameField.getDescribe().isEncrypted()) + { + m_orderBy = nameField.getDescribe().getName(); + } + else + { + m_orderBy = DEFAULT_SORT_FIELD; + try { + if (describeWrapper.getField(m_orderBy) == null) + { + m_orderBy = SF_ID_FIELD; + } + } + catch(fflib_QueryFactory.InvalidFieldException ex) { + m_orderBy = SF_ID_FIELD; + } + } + } + return m_orderBy; + } + + + /** + * @description Set the selector to enforce FLS Security + * @deprecated -- consider using setDataAccess to enforce native Apex User Mode Operations instead + **/ + @NamespaceAccessible + public fflib_SObjectSelector enforceFLS() + { + m_enforceFLS = true; + return this; + } + + /** + * @description Set the selector to automatically include the FieldSet fields when building queries + **/ + @NamespaceAccessible + public fflib_SObjectSelector includeFieldSetFields() + { + this.m_includeFieldSetFields = true; + return this; + } + + /** + * @description Set the selector to ignore CRUD security + * @return + */ + @NamespaceAccessible + public fflib_SObjectSelector ignoreCRUD() + { + this.m_enforceCRUD = false; + return this; + } + + @NamespaceAccessible + public fflib_SObjectSelector unsortedSelectFields() + { + this.m_sortSelectFields = false; + return this; + } + + @NamespaceAccessible + public fflib_SObjectSelector setDataAccess(DataAccess access){ + this.m_dataAccess = access; + + //You can't mix and match the legacy enforceFls and assertCRUD with the SYSTEM_MODE or USER_MODE + if(this.m_dataAccess != DataAccess.LEGACY){ + ignoreCRUD(); + m_enforceFLS = false; + } + + return this; + } + + /** + * Returns True if this Selector instance has been instructed by the caller to include Field Set fields + **/ + @NamespaceAccessible + public Boolean isIncludeFieldSetFields() + { + return m_includeFieldSetFields; + } + + /** + * Returns True if this Selector is enforcing FLS + **/ + @NamespaceAccessible + public Boolean isEnforcingFLS() + { + return m_enforceFLS; + } + + /** + * Returns True if this Selector is enforcing CRUD Security + **/ + @NamespaceAccessible + public Boolean isEnforcingCRUD() + { + return m_enforceCRUD; + } + + @NamespaceAccessible + public DataAccess getDataAccess(){ + return m_dataAccess; + } + + /** + * Provides access to the builder containing the list of fields base queries are using, this is demand + * created if one has not already been defined via setFieldListBuilder + * + * @depricated See newQueryFactory + **/ + @NamespaceAccessible + public fflib_StringBuilder.CommaDelimitedListBuilder getFieldListBuilder() + { + 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, + * warning, this will bypass anything getSObjectFieldList or getSObjectFieldSetList returns + * + * @depricated See newQueryFactory + **/ + @NamespaceAccessible + public void setFieldListBuilder(fflib_StringBuilder.FieldListBuilder fieldListBuilder) + { + // TODO: Consider if given the known use cases for this (dynamic selector optimisation) if it's OK to leave this as a null operation + } + + /** + * Returns in string form a comma delimited list of fields as defined via getSObjectFieldList and optionally getSObjectFieldSetList + * + * @deprecated See newQueryFactory + **/ + @NamespaceAccessible + public String getFieldListString() + { + return getFieldListBuilder().getStringValue(); + } + + /** + * Returns in string form a comma delimited 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 + **/ + @NamespaceAccessible + public String getRelatedFieldListString(String relation) + { + return getFieldListBuilder().getStringValue(relation + '.'); + } + + /** + * Returns the string representation of the SObject this selector represents + **/ + @NamespaceAccessible + public String getSObjectName() + { + return describeWrapper.getDescribe().getName(); + } + + /** + * 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 + * @returns A list of SObject's + **/ + @NamespaceAccessible + public virtual List selectSObjectsById(Set idSet) + { + return Database.query(buildQuerySObjectById()); + } + + /** + * 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 + * @returns A QueryLocator (typically for use in a Batch Apex job) + **/ + @NamespaceAccessible + public virtual Database.QueryLocator queryLocatorById(Set idSet) + { + return Database.getQueryLocator(buildQuerySObjectById()); + } + + /** + * Throws an exception if the SObject indicated by getSObjectType is not accessible to the current user (read access) + * + * @deprecated If you utilise the newQueryFactory method this is automatically done for you (unless disabled by the selector) + **/ + @NamespaceAccessible + public void assertIsAccessible() + { + if(!getSObjectType().getDescribe().isAccessible()) + throw new fflib_SObjectDomain.DomainException( + 'Permission to access an ' + getSObjectType().getDescribe().getName() + ' denied.'); + } + + /** + * @NamespaceAccessible + public access for the getSObjectType during Mock registration + * (adding @NamespaceAccessible + public to the existing method broken base class API backwards compatibility) + **/ + @NamespaceAccessible + public SObjectType getSObjectType2() + { + return getSObjectType(); + } + + /** + * @NamespaceAccessible + public access for the getSObjectType during Mock registration + * (adding @NamespaceAccessible + public to the existing method broken base class API backwards compatibility1) + **/ + @NamespaceAccessible + public SObjectType sObjectType() + { + return getSObjectType(); + } + + /** + * Returns a QueryFactory configured with the Selectors object, fields, fieldsets and default order by + **/ + @NamespaceAccessible + public fflib_QueryFactory newQueryFactory() + { + return newQueryFactory(m_enforceCRUD, m_enforceFLS, true, m_DataAccess); + } + + /** + * Returns a QueryFactory configured with the Selectors object, fields, fieldsets and default order by + **/ + @NamespaceAccessible + public fflib_QueryFactory newQueryFactory(Boolean includeSelectorFields) + { + return newQueryFactory(m_enforceCRUD, m_enforceFLS, includeSelectorFields, m_DataAccess); + } + + /** + * Returns a QueryFactory configured with the Selectors object, fields, fieldsets and default order by + * CRUD and FLS read security will be checked if the corresponding inputs are true (overrides that defined in the selector). + **/ + @NamespaceAccessible + public fflib_QueryFactory newQueryFactory(Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields) + { + // Construct QueryFactory around the given SObject + return newQueryFactory( + assertCRUD, + enforceFLS, + includeSelectorFields, + DataAccess.LEGACY); + } + + private fflib_QueryFactory newQueryFactory(Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields, DataAccess dataAccess) + { + // Construct QueryFactory around the given SObject + return configureQueryFactory( + new fflib_QueryFactory(getSObjectType2()), + assertCRUD, + enforceFLS, + includeSelectorFields, + dataAccess); + } + + /** + * Adds the selectors fields to the given QueryFactory using the given relationship path as a prefix + * + * // TODO: This should be consistent (ideally) with configureQueryFactory below + **/ + @NamespaceAccessible + 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(), this.getSObjectType()); @@ -435,125 +471,129 @@ public abstract with sharing class fflib_SObjectSelector if(UserInfo.isMultiCurrencyOrganization() && CURRENCY_ISO_CODE_ENABLED){ queryFactory.selectField(relationshipFieldPath+'.CurrencyIsoCode', getSObjectType()); } - } - - /** - * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the child QueryFactory - **/ - public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory) - { - 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, - m_dataAccess); - } - - /** - * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the child QueryFactory - **/ - public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, String relationshipName) - { + } + + /** + * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the child QueryFactory + **/ + @NamespaceAccessible + public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory) + { + return addQueryFactorySubselect(parentQueryFactory, true); + } + + /** + * Adds a subselect QueryFactory based on this selector to the given QueryFactor + **/ + @NamespaceAccessible + public fflib_QueryFactory addQueryFactorySubselect(fflib_QueryFactory parentQueryFactory, Boolean includeSelectorFields) + { + fflib_QueryFactory subSelectQueryFactory = + parentQueryFactory.subselectQuery(getSObjectType2()); + return configureQueryFactory( + subSelectQueryFactory, + m_enforceCRUD, + m_enforceFLS, + includeSelectorFields, + m_dataAccess); + } + + /** + * Adds a subselect QueryFactory based on this selector to the given QueryFactor, returns the child QueryFactory + **/ + @NamespaceAccessible + 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, m_dataAccess); - } - - /** - * Constructs the default SOQL query for this selector, see selectSObjectsById and queryLocatorById - **/ - protected 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, - DataAccess access) - { - // Legacy CRUD enforcement required? - if (assertCRUD) - { - try { - // Leverage QueryFactory for CRUD checking - queryFactory.assertIsAccessible(); - } catch (fflib_SecurityUtils.CrudException e) { - // Marshal exception into DomainException for backwards compatibility + } + + /** + * Adds a subselect QueryFactory based on this selector to the given QueryFactor + **/ + @NamespaceAccessible + 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, m_dataAccess); + } + + /** + * Constructs the default SOQL query for this selector, see selectSObjectsById and queryLocatorById + **/ + protected 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, + DataAccess access) + { + // Legacy CRUD enforcement required? + if (assertCRUD) + { + try { + // Leverage QueryFactory for CRUD checking + queryFactory.assertIsAccessible(); + } catch (fflib_SecurityUtils.CrudException e) { + // Marshal exception into DomainException for backwards compatibility throw new fflib_SObjectDomain.DomainException( - 'Permission to access an ' + getSObjectType().getDescribe().getName() + ' denied.'); - } - } - - fflib_QueryFactory.FLSEnforcement fls = fflib_QueryFactory.FLSEnforcement.NONE; - if(access == DataAccess.USER_MODE){ - fls = fflib_QueryFactory.FLSEnforcement.USER_MODE; - } - else if(access == DataAccess.SYSTEM_MODE){ - fls = fflib_QueryFactory.FLSEnforcement.SYSTEM_MODE; - } - else if(enforceFLS){ - fls = fflib_QueryFactory.FLSEnforcement.LEGACY; - } - - queryFactory.setEnforceFLS(fls); - - // Configure the QueryFactory with the Selector fields? - if(includeSelectorFields) - { - // 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); + 'Permission to access an ' + getSObjectType().getDescribe().getName() + ' denied.'); + } + } + + fflib_QueryFactory.FLSEnforcement fls = fflib_QueryFactory.FLSEnforcement.NONE; + if(access == DataAccess.USER_MODE){ + fls = fflib_QueryFactory.FLSEnforcement.USER_MODE; + } + else if(access == DataAccess.SYSTEM_MODE){ + fls = fflib_QueryFactory.FLSEnforcement.SYSTEM_MODE; + } + else if(enforceFLS){ + fls = fflib_QueryFactory.FLSEnforcement.LEGACY; + } + + queryFactory.setEnforceFLS(fls); + + // Configure the QueryFactory with the Selector fields? + if(includeSelectorFields) + { + // 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', getSObjectType()); - } - - // Parse the getOrderBy() - for(String orderBy : getOrderBy().split(',')) - { - List orderByParts = orderBy.trim().split(' '); - String fieldNamePart = orderByParts[0]; - String fieldSortOrderPart = orderByParts.size() > 1 ? orderByParts[1] : null; - fflib_QueryFactory.SortOrder fieldSortOrder = fflib_QueryFactory.SortOrder.ASCENDING; - if(fieldSortOrderPart==null) - fieldSortOrder = fflib_QueryFactory.SortOrder.ASCENDING; - else if(fieldSortOrderPart.equalsIgnoreCase('DESC')) - fieldSortOrder = fflib_QueryFactory.SortOrder.DESCENDING; - else if(fieldSortOrderPart.equalsIgnoreCase('ASC')) - fieldSortOrder = fflib_QueryFactory.SortOrder.ASCENDING; - queryFactory.addOrdering(fieldNamePart, fieldSortOrder, orderBy.containsIgnoreCase('NULLS LAST')); - } - - queryFactory.setSortSelectFields(m_sortSelectFields); - - return queryFactory; - } + // 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', getSObjectType()); + } + + // Parse the getOrderBy() + for(String orderBy : getOrderBy().split(',')) + { + List orderByParts = orderBy.trim().split(' '); + String fieldNamePart = orderByParts[0]; + String fieldSortOrderPart = orderByParts.size() > 1 ? orderByParts[1] : null; + fflib_QueryFactory.SortOrder fieldSortOrder = fflib_QueryFactory.SortOrder.ASCENDING; + if(fieldSortOrderPart==null) + fieldSortOrder = fflib_QueryFactory.SortOrder.ASCENDING; + else if(fieldSortOrderPart.equalsIgnoreCase('DESC')) + fieldSortOrder = fflib_QueryFactory.SortOrder.DESCENDING; + else if(fieldSortOrderPart.equalsIgnoreCase('ASC')) + fieldSortOrder = fflib_QueryFactory.SortOrder.ASCENDING; + queryFactory.addOrdering(fieldNamePart, fieldSortOrder, orderBy.containsIgnoreCase('NULLS LAST')); + } + + queryFactory.setSortSelectFields(m_sortSelectFields); + + return queryFactory; + } } diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls index 537bf3ed78..929a117eec 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls @@ -51,71 +51,80 @@ * TODO: Need to add some more test methods for more complex use cases and some unexpected (e.g. registerDirty and then registerDeleted) * **/ +@NamespaceAccessible public virtual class fflib_SObjectUnitOfWork - implements fflib_ISObjectUnitOfWork + implements fflib_ISObjectUnitOfWork { - protected List m_sObjectTypes = new List(); - - protected Map> m_newListByType = new Map>(); - - protected Map> m_dirtyMapByType = new Map>(); - - protected Map> m_deletedMapByType = new Map>(); - protected Map> m_emptyRecycleBinMapByType = new Map>(); - - protected Map m_relationships = new Map(); - - protected Map> m_publishBeforeListByType = new Map>(); - protected Map> m_publishAfterSuccessListByType = new Map>(); - protected Map> m_publishAfterFailureListByType = new Map>(); - - protected List m_workList = new List(); - - @TestVisible - protected IEmailWork m_emailWork = new SendEmailWork(); - - protected IDML m_dml; - - /** - * Interface describes work to be performed during the commitWork method - **/ - public interface IDoWork - { - void doWork(); - } - - public interface IDML - { - void dmlInsert(List objList); - void dmlUpdate(List objList); - void dmlDelete(List objList); - void eventPublish(List objList); - void emptyRecycleBin(List objList); - } - - public virtual class SimpleDML implements IDML - { - public virtual void dmlInsert(List objList) - { - insert objList; - } - public virtual void dmlUpdate(List objList) - { - update objList; - } - public virtual void dmlDelete(List objList) - { - delete objList; - } - public virtual void eventPublish(List objList) - { - if (objList.isEmpty()) - { - return; - } - - EventBus.publish(objList); - } + protected List m_sObjectTypes = new List(); + + protected Map> m_newListByType = new Map>(); + + protected Map> m_dirtyMapByType = new Map>(); + + protected Map> m_deletedMapByType = new Map>(); + protected Map> m_emptyRecycleBinMapByType = new Map>(); + + protected Map m_relationships = new Map(); + + protected Map> m_publishBeforeListByType = new Map>(); + protected Map> m_publishAfterSuccessListByType = new Map>(); + protected Map> m_publishAfterFailureListByType = new Map>(); + + protected List m_workList = new List(); + + @TestVisible + protected IEmailWork m_emailWork = new SendEmailWork(); + + protected IDML m_dml; + + /** + * Interface describes work to be performed during the commitWork method + **/ + @NamespaceAccessible + public interface IDoWork + { + void doWork(); + } + + @NamespaceAccessible + public interface IDML + { + void dmlInsert(List objList); + void dmlUpdate(List objList); + void dmlDelete(List objList); + void eventPublish(List objList); + void emptyRecycleBin(List objList); + } + + @NamespaceAccessible + public virtual class SimpleDML implements IDML + { + @NamespaceAccessible + public virtual void dmlInsert(List objList) + { + insert objList; + } + @NamespaceAccessible + public virtual void dmlUpdate(List objList) + { + update objList; + } + @NamespaceAccessible + public virtual void dmlDelete(List objList) + { + delete objList; + } + @NamespaceAccessible + public virtual void eventPublish(List objList) + { + if (objList.isEmpty()) + { + return; + } + + EventBus.publish(objList); + } + @NamespaceAccessible public virtual void emptyRecycleBin(List objList) { if (objList.isEmpty()) @@ -125,126 +134,151 @@ public virtual class fflib_SObjectUnitOfWork Database.emptyRecycleBin(objList); } - } + } + @NamespaceAccessible public virtual class UserModeDML extends SimpleDML{ @TestVisible private AccessLevel m_accessLevel; + @NamespaceAccessible public UserModeDML(){ this(AccessLevel.USER_MODE); } /** Supply the AccessLevel explicitly (UserModeDML uses AccessMode.USER_MODE, by default) */ + @NamespaceAccessible public UserModeDML(AccessLevel access){ m_accessLevel = access; } + @NamespaceAccessible public virtual override void dmlInsert(List objList){ Database.insert(objList,m_accessLevel); } + @NamespaceAccessible public virtual override void dmlUpdate(List objList){ Database.update(objList,m_accessLevel); } + @NamespaceAccessible public virtual override void dmlDelete(List objList){ Database.delete(objList,m_accessLevel); } } - /** - * Constructs a new UnitOfWork to support work against the given object list - * - * @param sObjectTypes A list of objects given in dependency order (least dependent first) - */ - public fflib_SObjectUnitOfWork(List sObjectTypes) - { - this(sObjectTypes,new SimpleDML()); - } + /** + * Constructs a new UnitOfWork to support work against the given object list + * + * @param sObjectTypes A list of objects given in dependency order (least dependent first) + */ + @NamespaceAccessible + public fflib_SObjectUnitOfWork(List sObjectTypes) + { + this(sObjectTypes,new SimpleDML()); + } - public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml) - { - m_sObjectTypes = sObjectTypes.clone(); + @NamespaceAccessible + public fflib_SObjectUnitOfWork(List sObjectTypes, IDML dml) + { + m_sObjectTypes = sObjectTypes.clone(); - for (Schema.SObjectType sObjectType : m_sObjectTypes) - { - // register the type - handleRegisterType(sObjectType); - } + for (Schema.SObjectType sObjectType : m_sObjectTypes) + { + // register the type + handleRegisterType(sObjectType); + } m_relationships.put(Messaging.SingleEmailMessage.class.getName(), new Relationships()); - m_dml = dml; - } - - // default implementations for commitWork events - public virtual void onRegisterType(Schema.SObjectType sObjectType) {} - public virtual void onCommitWorkStarting() {} - - public virtual void onPublishBeforeEventsStarting() {} - public virtual void onPublishBeforeEventsFinished() {} - - public virtual void onDMLStarting() {} - public virtual void onDMLFinished() {} - - public virtual void onDoWorkStarting() {} - public virtual void onDoWorkFinished() {} - - public virtual void onPublishAfterSuccessEventsStarting() {} - public virtual void onPublishAfterSuccessEventsFinished() {} - - public virtual void onPublishAfterFailureEventsStarting() {} - public virtual void onPublishAfterFailureEventsFinished() {} - - public virtual void onCommitWorkFinishing() {} - public virtual void onCommitWorkFinished(Boolean wasSuccessful) {} - - /** - * Registers the type to be used for DML operations - * - * @param sObjectType - The type to register - * - */ - private void handleRegisterType(Schema.SObjectType sObjectType) - { - String sObjectName = sObjectType.getDescribe().getName(); - - // add type to dml operation tracking - m_newListByType.put(sObjectName, new List()); - m_dirtyMapByType.put(sObjectName, new Map()); - m_deletedMapByType.put(sObjectName, new Map()); - m_emptyRecycleBinMapByType.put(sObjectName, new Map()); - m_relationships.put(sObjectName, new Relationships()); - - m_publishBeforeListByType.put(sObjectName, new List()); - m_publishAfterSuccessListByType.put(sObjectName, new List()); - m_publishAfterFailureListByType.put(sObjectName, new List()); - - // give derived class opportunity to register the type - onRegisterType(sObjectType); - } - - /** - * Register a generic piece of work to be invoked during the commitWork phase - **/ - public void registerWork(IDoWork work) - { - m_workList.add(work); - } - - /** - * Registers the given email to be sent during the commitWork - **/ - public void registerEmail(Messaging.Email email) - { - m_emailWork.registerEmail(email); - } + m_dml = dml; + } + + // default implementations for commitWork events + @NamespaceAccessible + public virtual void onRegisterType(Schema.SObjectType sObjectType) {} + @NamespaceAccessible + public virtual void onCommitWorkStarting() {} + + @NamespaceAccessible + public virtual void onPublishBeforeEventsStarting() {} + @NamespaceAccessible + public virtual void onPublishBeforeEventsFinished() {} + + @NamespaceAccessible + public virtual void onDMLStarting() {} + @NamespaceAccessible + public virtual void onDMLFinished() {} + + @NamespaceAccessible + public virtual void onDoWorkStarting() {} + @NamespaceAccessible + public virtual void onDoWorkFinished() {} + + @NamespaceAccessible + public virtual void onPublishAfterSuccessEventsStarting() {} + @NamespaceAccessible + public virtual void onPublishAfterSuccessEventsFinished() {} + + @NamespaceAccessible + public virtual void onPublishAfterFailureEventsStarting() {} + @NamespaceAccessible + public virtual void onPublishAfterFailureEventsFinished() {} + + @NamespaceAccessible + public virtual void onCommitWorkFinishing() {} + @NamespaceAccessible + public virtual void onCommitWorkFinished(Boolean wasSuccessful) {} + + /** + * Registers the type to be used for DML operations + * + * @param sObjectType - The type to register + * + */ + private void handleRegisterType(Schema.SObjectType sObjectType) + { + String sObjectName = sObjectType.getDescribe().getName(); + + // add type to dml operation tracking + m_newListByType.put(sObjectName, new List()); + m_dirtyMapByType.put(sObjectName, new Map()); + m_deletedMapByType.put(sObjectName, new Map()); + m_emptyRecycleBinMapByType.put(sObjectName, new Map()); + m_relationships.put(sObjectName, new Relationships()); + + m_publishBeforeListByType.put(sObjectName, new List()); + m_publishAfterSuccessListByType.put(sObjectName, new List()); + m_publishAfterFailureListByType.put(sObjectName, new List()); + + // give derived class opportunity to register the type + onRegisterType(sObjectType); + } + + /** + * Register a generic piece of work to be invoked during the commitWork phase + **/ + @NamespaceAccessible + public void registerWork(IDoWork work) + { + m_workList.add(work); + } + + /** + * Registers the given email to be sent during the commitWork + **/ + @NamespaceAccessible + public void registerEmail(Messaging.Email email) + { + m_emailWork.registerEmail(email); + } /** * Register an deleted record to be removed from the recycle bin during the commitWork method * * @param record An deleted record **/ + @NamespaceAccessible public void registerEmptyRecycleBin(SObject record) { String sObjectType = record.getSObjectType().getDescribe().getName(); @@ -258,6 +292,7 @@ public virtual class fflib_SObjectUnitOfWork * * @param records Deleted records **/ + @NamespaceAccessible public void registerEmptyRecycleBin(List records) { for (SObject record : records) @@ -266,127 +301,135 @@ public virtual class fflib_SObjectUnitOfWork } } - /** - * Register a newly created SObject instance to be inserted when commitWork is called - * - * @param record A newly created SObject instance to be inserted during commitWork - **/ - public void registerNew(SObject record) - { - registerNew(record, null, null); - } - - /** - * Register a list of newly created SObject instances to be inserted when commitWork is called - * - * @param records A list of newly created SObject instances to be inserted during commitWork - **/ - public void registerNew(List records) - { - for (SObject record : records) - { - registerNew(record, null, null); - } - } - - /** - * Register a newly created SObject instance to be inserted when commitWork is called, - * you may also provide a reference to the parent record instance (should also be registered as new separately) - * - * @param record A newly created SObject instance to be inserted during commitWork - * @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent - * @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separately) - **/ - public void registerNew(SObject record, Schema.SObjectField relatedToParentField, SObject relatedToParentRecord) - { - if (record.Id != null) - throw new UnitOfWorkException('Only new records can be registered as new'); - String sObjectType = record.getSObjectType().getDescribe().getName(); + /** + * Register a newly created SObject instance to be inserted when commitWork is called + * + * @param record A newly created SObject instance to be inserted during commitWork + **/ + @NamespaceAccessible + public void registerNew(SObject record) + { + registerNew(record, null, null); + } + + /** + * Register a list of newly created SObject instances to be inserted when commitWork is called + * + * @param records A list of newly created SObject instances to be inserted during commitWork + **/ + @NamespaceAccessible + public void registerNew(List records) + { + for (SObject record : records) + { + registerNew(record, null, null); + } + } + + /** + * Register a newly created SObject instance to be inserted when commitWork is called, + * you may also provide a reference to the parent record instance (should also be registered as new separately) + * + * @param record A newly created SObject instance to be inserted during commitWork + * @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent + * @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separately) + **/ + @NamespaceAccessible + public void registerNew(SObject record, Schema.SObjectField relatedToParentField, SObject relatedToParentRecord) + { + if (record.Id != null) + throw new UnitOfWorkException('Only new records can be registered as new'); + String sObjectType = record.getSObjectType().getDescribe().getName(); assertForNonEventSObjectType(sObjectType); assertForSupportedSObjectType(m_newListByType, sObjectType); - m_newListByType.get(sObjectType).add(record); - if (relatedToParentRecord!=null && relatedToParentField!=null) - registerRelationship(record, relatedToParentField, relatedToParentRecord); - } - - /** - * Register a relationship between two records that have yet to be inserted to the database. This information will be - * used during the commitWork phase to make the references only when related records have been inserted to the database. - * - * @param record An existing or newly created record - * @param relatedToField A SObjectField reference to the lookup field that relates the two records together - * @param relatedTo A SObject instance (yet to be committed to the database) - */ - public void registerRelationship(SObject record, Schema.SObjectField relatedToField, SObject relatedTo) - { - String sObjectType = record.getSObjectType().getDescribe().getName(); + m_newListByType.get(sObjectType).add(record); + if (relatedToParentRecord!=null && relatedToParentField!=null) + registerRelationship(record, relatedToParentField, relatedToParentRecord); + } + + /** + * Register a relationship between two records that have yet to be inserted to the database. This information will be + * used during the commitWork phase to make the references only when related records have been inserted to the database. + * + * @param record An existing or newly created record + * @param relatedToField A SObjectField reference to the lookup field that relates the two records together + * @param relatedTo A SObject instance (yet to be committed to the database) + */ + @NamespaceAccessible + public void registerRelationship(SObject record, Schema.SObjectField relatedToField, SObject relatedTo) + { + String sObjectType = record.getSObjectType().getDescribe().getName(); assertForNonEventSObjectType(sObjectType); assertForSupportedSObjectType(m_newListByType, sObjectType); - m_relationships.get(sObjectType).add(record, relatedToField, relatedTo); - } - - /** - * Registers a relationship between a record and a Messaging.Email where the record has yet to be inserted - * to the database. This information will be - * used during the commitWork phase to make the references only when related records have been inserted to the database. - * - * @param email a single email message instance - * @param relatedTo A SObject instance (yet to be committed to the database) - */ - public void registerRelationship( Messaging.SingleEmailMessage email, SObject relatedTo ) - { - m_relationships.get( Messaging.SingleEmailMessage.class.getName() ).add(email, relatedTo); - } - - /** - * Registers a relationship between a record and a lookup value using an external ID field and a provided value. This - * information will be used during the commitWork phase to make the lookup reference requested when inserted to the database. - * - * @param record An existing or newly created record - * @param relatedToField A SObjectField reference to the lookup field that relates the two records together - * @param externalIdField A SObjectField reference to a field on the target SObject that is marked as isExternalId - * @param externalId A Object representing the targeted value of the externalIdField in said lookup - * - * Usage Example: uow.registerRelationship(recordSObject, record_sobject__c.relationship_field__c, lookup_sobject__c.external_id__c, 'abc123'); - * - * Wraps putSObject, creating a new instance of the lookup sobject using the external id field and value. - */ - public void registerRelationship(SObject record, Schema.SObjectField relatedToField, Schema.SObjectField externalIdField, Object externalId) - { - // NOTE: Due to the lack of ExternalID references on Standard Objects, this method can not be provided a standardized Unit Test. - Rick Parker - String sObjectType = record.getSObjectType().getDescribe().getName(); - if(!m_newListByType.containsKey(sObjectType)) - throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType })); - m_relationships.get(sObjectType).add(record, relatedToField, externalIdField, externalId); - } - - /** - * Register an existing record to be updated during the commitWork method - * - * @param record An existing record - **/ - public void registerDirty(SObject record) - { - registerDirty(record, new List()); - } - - /** - * Registers the entire records as dirty or just only the dirty fields if the record was already registered - * - * @param records SObjects to register as dirty - * @param dirtyFields A list of modified fields - */ - public void registerDirty(List records, List dirtyFields) - { - for (SObject record : records) - { - registerDirty(record, dirtyFields); - } - } + m_relationships.get(sObjectType).add(record, relatedToField, relatedTo); + } + + /** + * Registers a relationship between a record and a Messaging.Email where the record has yet to be inserted + * to the database. This information will be + * used during the commitWork phase to make the references only when related records have been inserted to the database. + * + * @param email a single email message instance + * @param relatedTo A SObject instance (yet to be committed to the database) + */ + @NamespaceAccessible + public void registerRelationship( Messaging.SingleEmailMessage email, SObject relatedTo ) + { + m_relationships.get( Messaging.SingleEmailMessage.class.getName() ).add(email, relatedTo); + } + + /** + * Registers a relationship between a record and a lookup value using an external ID field and a provided value. This + * information will be used during the commitWork phase to make the lookup reference requested when inserted to the database. + * + * @param record An existing or newly created record + * @param relatedToField A SObjectField reference to the lookup field that relates the two records together + * @param externalIdField A SObjectField reference to a field on the target SObject that is marked as isExternalId + * @param externalId A Object representing the targeted value of the externalIdField in said lookup + * + * Usage Example: uow.registerRelationship(recordSObject, record_sobject__c.relationship_field__c, lookup_sobject__c.external_id__c, 'abc123'); + * + * Wraps putSObject, creating a new instance of the lookup sobject using the external id field and value. + */ + @NamespaceAccessible + public void registerRelationship(SObject record, Schema.SObjectField relatedToField, Schema.SObjectField externalIdField, Object externalId) + { + // NOTE: Due to the lack of ExternalID references on Standard Objects, this method can not be provided a standardized Unit Test. - Rick Parker + String sObjectType = record.getSObjectType().getDescribe().getName(); + if(!m_newListByType.containsKey(sObjectType)) + throw new UnitOfWorkException(String.format('SObject type {0} is not supported by this unit of work', new String[] { sObjectType })); + m_relationships.get(sObjectType).add(record, relatedToField, externalIdField, externalId); + } + + /** + * Register an existing record to be updated during the commitWork method + * + * @param record An existing record + **/ + @NamespaceAccessible + public void registerDirty(SObject record) + { + registerDirty(record, new List()); + } + + /** + * Registers the entire records as dirty or just only the dirty fields if the record was already registered + * + * @param records SObjects to register as dirty + * @param dirtyFields A list of modified fields + */ + @NamespaceAccessible + public void registerDirty(List records, List dirtyFields) + { + for (SObject record : records) + { + registerDirty(record, dirtyFields); + } + } /** * Registers the entire record as dirty or just only the dirty fields if the record was already registered @@ -394,230 +437,246 @@ public virtual class fflib_SObjectUnitOfWork * @param record SObject to register as dirty * @param dirtyFields A list of modified fields */ - public void registerDirty(SObject record, List dirtyFields) - { - if (record.Id == null) - throw new UnitOfWorkException('New records cannot be registered as dirty'); - String sObjectType = record.getSObjectType().getDescribe().getName(); + @NamespaceAccessible + public void registerDirty(SObject record, List dirtyFields) + { + if (record.Id == null) + throw new UnitOfWorkException('New records cannot be registered as dirty'); + String sObjectType = record.getSObjectType().getDescribe().getName(); assertForNonEventSObjectType(sObjectType); assertForSupportedSObjectType(m_dirtyMapByType, sObjectType); - // If record isn't registered as dirty, or no dirty fields to drive a merge - if (!m_dirtyMapByType.get(sObjectType).containsKey(record.Id) || dirtyFields.isEmpty()) - { - // Register the record as dirty - m_dirtyMapByType.get(sObjectType).put(record.Id, record); - } - else - { - // Update the registered record's fields - SObject registeredRecord = m_dirtyMapByType.get(sObjectType).get(record.Id); - - for (SObjectField dirtyField : dirtyFields) { - registeredRecord.put(dirtyField, record.get(dirtyField)); - } - - m_dirtyMapByType.get(sObjectType).put(record.Id, registeredRecord); - } - } - - /** - * Register an existing record to be updated when commitWork is called, - * you may also provide a reference to the parent record instance (should also be registered as new separately) - * - * @param record A newly created SObject instance to be inserted during commitWork - * @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent - * @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separately) - **/ - public void registerDirty(SObject record, Schema.SObjectField relatedToParentField, SObject relatedToParentRecord) - { - registerDirty(record); - if (relatedToParentRecord!=null && relatedToParentField!=null) - registerRelationship(record, relatedToParentField, relatedToParentRecord); - } - - /** - * Register a list of existing records to be updated during the commitWork method - * - * @param records A list of existing records - **/ - public void registerDirty(List records) - { - for (SObject record : records) - { - this.registerDirty(record); - } - } - - /** - * Register a new or existing record to be inserted/updated during the commitWork method - * - * @param record A new or existing record - **/ - public void registerUpsert(SObject record) - { - if (record.Id == null) - { - registerNew(record, null, null); - } - else - { - registerDirty(record, new List()); - } - } - - /** - * Register a list of mix of new and existing records to be inserted updated during the commitWork method - * - * @param records A list of mix of new and existing records - **/ - public void registerUpsert(List records) - { - for (SObject record : records) - { - this.registerUpsert(record); - } - } - - /** - * Register an existing record to be deleted during the commitWork method - * - * @param record An existing record - **/ - public void registerDeleted(SObject record) - { - if (record.Id == null) - throw new UnitOfWorkException('New records cannot be registered for deletion'); - String sObjectType = record.getSObjectType().getDescribe().getName(); + // If record isn't registered as dirty, or no dirty fields to drive a merge + if (!m_dirtyMapByType.get(sObjectType).containsKey(record.Id) || dirtyFields.isEmpty()) + { + // Register the record as dirty + m_dirtyMapByType.get(sObjectType).put(record.Id, record); + } + else + { + // Update the registered record's fields + SObject registeredRecord = m_dirtyMapByType.get(sObjectType).get(record.Id); + + for (SObjectField dirtyField : dirtyFields) { + registeredRecord.put(dirtyField, record.get(dirtyField)); + } + + m_dirtyMapByType.get(sObjectType).put(record.Id, registeredRecord); + } + } + + /** + * Register an existing record to be updated when commitWork is called, + * you may also provide a reference to the parent record instance (should also be registered as new separately) + * + * @param record A newly created SObject instance to be inserted during commitWork + * @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent + * @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separately) + **/ + @NamespaceAccessible + public void registerDirty(SObject record, Schema.SObjectField relatedToParentField, SObject relatedToParentRecord) + { + registerDirty(record); + if (relatedToParentRecord!=null && relatedToParentField!=null) + registerRelationship(record, relatedToParentField, relatedToParentRecord); + } + + /** + * Register a list of existing records to be updated during the commitWork method + * + * @param records A list of existing records + **/ + @NamespaceAccessible + public void registerDirty(List records) + { + for (SObject record : records) + { + this.registerDirty(record); + } + } + + /** + * Register a new or existing record to be inserted/updated during the commitWork method + * + * @param record A new or existing record + **/ + @NamespaceAccessible + public void registerUpsert(SObject record) + { + if (record.Id == null) + { + registerNew(record, null, null); + } + else + { + registerDirty(record, new List()); + } + } + + /** + * Register a list of mix of new and existing records to be inserted updated during the commitWork method + * + * @param records A list of mix of new and existing records + **/ + @NamespaceAccessible + public void registerUpsert(List records) + { + for (SObject record : records) + { + this.registerUpsert(record); + } + } + + /** + * Register an existing record to be deleted during the commitWork method + * + * @param record An existing record + **/ + @NamespaceAccessible + public void registerDeleted(SObject record) + { + if (record.Id == null) + throw new UnitOfWorkException('New records cannot be registered for deletion'); + String sObjectType = record.getSObjectType().getDescribe().getName(); assertForNonEventSObjectType(sObjectType); assertForSupportedSObjectType(m_deletedMapByType, sObjectType); - m_deletedMapByType.get(sObjectType).put(record.Id, record); - } - - /** - * Register a list of existing records to be deleted during the commitWork method - * - * @param records A list of existing records - **/ - public void registerDeleted(List records) - { - for (SObject record : records) - { - this.registerDeleted(record); - } - } - - /** - * Register a list of existing records to be deleted and removed from the recycle bin during the commitWork method - * - * @param records A list of existing records - **/ - public void registerPermanentlyDeleted(List records) - { - this.registerEmptyRecycleBin(records); - this.registerDeleted(records); - } - - /** - * Register a list of existing records to be deleted and removed from the recycle bin during the commitWork method - * - * @param record A list of existing records - **/ - public void registerPermanentlyDeleted(SObject record) - { - this.registerEmptyRecycleBin(record); - this.registerDeleted(record); - } - - /** - * Register a newly created SObject (Platform Event) instance to be published when commitWork is called - * - * @param record A newly created SObject (Platform Event) instance to be inserted during commitWork - **/ - public void registerPublishBeforeTransaction(SObject record) - { - String sObjectType = record.getSObjectType().getDescribe().getName(); + m_deletedMapByType.get(sObjectType).put(record.Id, record); + } + + /** + * Register a list of existing records to be deleted during the commitWork method + * + * @param records A list of existing records + **/ + @NamespaceAccessible + public void registerDeleted(List records) + { + for (SObject record : records) + { + this.registerDeleted(record); + } + } + + /** + * Register a list of existing records to be deleted and removed from the recycle bin during the commitWork method + * + * @param records A list of existing records + **/ + @NamespaceAccessible + public void registerPermanentlyDeleted(List records) + { + this.registerEmptyRecycleBin(records); + this.registerDeleted(records); + } + + /** + * Register a list of existing records to be deleted and removed from the recycle bin during the commitWork method + * + * @param record A list of existing records + **/ + @NamespaceAccessible + public void registerPermanentlyDeleted(SObject record) + { + this.registerEmptyRecycleBin(record); + this.registerDeleted(record); + } + + /** + * Register a newly created SObject (Platform Event) instance to be published when commitWork is called + * + * @param record A newly created SObject (Platform Event) instance to be inserted during commitWork + **/ + @NamespaceAccessible + public void registerPublishBeforeTransaction(SObject record) + { + String sObjectType = record.getSObjectType().getDescribe().getName(); assertForEventSObjectType(sObjectType); assertForSupportedSObjectType(m_publishBeforeListByType, sObjectType); - m_publishBeforeListByType.get(sObjectType).add(record); - } - - /** - * Register a list of newly created SObject (Platform Event) instance to be published when commitWork is called - * - * @param records A list of existing records - **/ - public void registerPublishBeforeTransaction(List records) - { - for (SObject record : records) - { - this.registerPublishBeforeTransaction(record); - } - } - - /** - * Register a newly created SObject (Platform Event) instance to be published when commitWork is called - * - * @param record A newly created SObject (Platform Event) instance to be inserted during commitWork - **/ - public void registerPublishAfterSuccessTransaction(SObject record) - { - String sObjectType = record.getSObjectType().getDescribe().getName(); + m_publishBeforeListByType.get(sObjectType).add(record); + } + + /** + * Register a list of newly created SObject (Platform Event) instance to be published when commitWork is called + * + * @param records A list of existing records + **/ + @NamespaceAccessible + public void registerPublishBeforeTransaction(List records) + { + for (SObject record : records) + { + this.registerPublishBeforeTransaction(record); + } + } + + /** + * Register a newly created SObject (Platform Event) instance to be published when commitWork is called + * + * @param record A newly created SObject (Platform Event) instance to be inserted during commitWork + **/ + @NamespaceAccessible + public void registerPublishAfterSuccessTransaction(SObject record) + { + String sObjectType = record.getSObjectType().getDescribe().getName(); assertForEventSObjectType(sObjectType); assertForSupportedSObjectType(m_publishAfterSuccessListByType, sObjectType); - m_publishAfterSuccessListByType.get(sObjectType).add(record); - } - - /** - * Register a list of newly created SObject (Platform Event) instance to be published when commitWork is called - * - * @param records A list of existing records - **/ - public void registerPublishAfterSuccessTransaction(List records) - { - for (SObject record : records) - { - this.registerPublishAfterSuccessTransaction(record); - } - } - /** - * Register a newly created SObject (Platform Event) instance to be published when commitWork is called - * - * @param record A newly created SObject (Platform Event) instance to be inserted during commitWork - **/ - public void registerPublishAfterFailureTransaction(SObject record) - { - String sObjectType = record.getSObjectType().getDescribe().getName(); + m_publishAfterSuccessListByType.get(sObjectType).add(record); + } + + /** + * Register a list of newly created SObject (Platform Event) instance to be published when commitWork is called + * + * @param records A list of existing records + **/ + @NamespaceAccessible + public void registerPublishAfterSuccessTransaction(List records) + { + for (SObject record : records) + { + this.registerPublishAfterSuccessTransaction(record); + } + } + /** + * Register a newly created SObject (Platform Event) instance to be published when commitWork is called + * + * @param record A newly created SObject (Platform Event) instance to be inserted during commitWork + **/ + @NamespaceAccessible + public void registerPublishAfterFailureTransaction(SObject record) + { + String sObjectType = record.getSObjectType().getDescribe().getName(); assertForEventSObjectType(sObjectType); assertForSupportedSObjectType(m_publishAfterFailureListByType, sObjectType); - m_publishAfterFailureListByType.get(sObjectType).add(record); - } - - /** - * Register a list of newly created SObject (Platform Event) instance to be published when commitWork is called - * - * @param records A list of existing records - **/ - public void registerPublishAfterFailureTransaction(List records) - { - for (SObject record : records) - { - this.registerPublishAfterFailureTransaction(record); - } - } - - /** - * Takes all the work that has been registered with the UnitOfWork and commits it to the database - **/ + m_publishAfterFailureListByType.get(sObjectType).add(record); + } + + /** + * Register a list of newly created SObject (Platform Event) instance to be published when commitWork is called + * + * @param records A list of existing records + **/ + @NamespaceAccessible + public void registerPublishAfterFailureTransaction(List records) + { + for (SObject record : records) + { + this.registerPublishAfterFailureTransaction(record); + } + } + + /** + * Takes all the work that has been registered with the UnitOfWork and commits it to the database + **/ + @NamespaceAccessible public void commitWork() { Savepoint sp = Database.setSavepoint(); @@ -802,151 +861,153 @@ public virtual class fflib_SObjectUnitOfWork } } - private class Relationships - { - private List m_relationships = new List(); - - public void resolve() - { - // Resolve relationships - for (IRelationship relationship : m_relationships) - { - //relationship.Record.put(relationship.RelatedToField, relationship.RelatedTo.Id); - relationship.resolve(); - } - - } - - public void add(SObject record, Schema.SObjectField relatedToField, Schema.SObjectField externalIdField, Object externalId) - { - if (relatedToField == null) { - throw new UnitOfWorkException('Invalid argument: relatedToField.'); - } - - String relationshipName = relatedToField.getDescribe().getRelationshipName(); - if (String.isBlank(relationshipName)) { - throw new UnitOfWorkException('Invalid argument: relatedToField. Field supplied is not a relationship field.'); - } - - List relatedObjects = relatedToField.getDescribe().getReferenceTo(); - Schema.SObjectType relatedObject = relatedObjects[0]; - - String externalIdFieldName = externalIdField.getDescribe().getName(); - Boolean relatedHasExternalIdField = relatedObject.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); - Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId(); - - if (!relatedHasExternalIdField) { - throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); - } - - if (!externalIdFieldIsValid) { - throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.'); - } - - RelationshipByExternalId relationship = new RelationshipByExternalId(); - relationship.Record = record; - relationship.RelatedToField = relatedToField; - relationship.RelatedTo = relatedObject; - relationship.RelationshipName = relationshipName; - relationship.ExternalIdField = externalIdField; - relationship.ExternalId = externalId; - m_relationships.add(relationship); - } - - public void add(SObject record, Schema.SObjectField relatedToField, SObject relatedTo) - { - // Relationship to resolve - Relationship relationship = new Relationship(); - relationship.Record = record; - relationship.RelatedToField = relatedToField; - relationship.RelatedTo = relatedTo; - m_relationships.add(relationship); - } - - public void add(Messaging.SingleEmailMessage email, SObject relatedTo) - { - EmailRelationship emailRelationship = new EmailRelationship(); - emailRelationship.email = email; - emailRelationship.relatedTo = relatedTo; - m_relationships.add(emailRelationship); - } - } - - private interface IRelationship - { - void resolve(); - } - - private class RelationshipByExternalId implements IRelationship - { - public SObject Record; - public Schema.SObjectField RelatedToField; - public Schema.SObjectType RelatedTo; - public String RelationshipName; - public Schema.SObjectField ExternalIdField; - public Object ExternalId; - - public void resolve() - { - SObject relationshipObject = this.RelatedTo.newSObject(); - relationshipObject.put( ExternalIdField.getDescribe().getName(), this.ExternalId ); - this.Record.putSObject( this.RelationshipName, relationshipObject ); - } - } - - private class Relationship implements IRelationship - { - public SObject Record; - public Schema.SObjectField RelatedToField; - public SObject RelatedTo; - - public void resolve() - { - this.Record.put( this.RelatedToField, this.RelatedTo.Id); - } - } - - private class EmailRelationship implements IRelationship - { - public Messaging.SingleEmailMessage email; - public SObject relatedTo; - - public void resolve() - { - this.email.setWhatId( this.relatedTo.Id ); - } - } - - /** - * UnitOfWork Exception - **/ - public class UnitOfWorkException extends Exception {} - - /** - * Internal implementation of Messaging.sendEmail, see outer class registerEmail method - **/ - public interface IEmailWork extends IDoWork - { - void registerEmail(Messaging.Email email); - } - - private class SendEmailWork implements IEmailWork - { - private List emails; - - public SendEmailWork() - { - this.emails = new List(); - } - - public void registerEmail(Messaging.Email email) - { - this.emails.add(email); - } - - public void doWork() - { - if (emails.size() > 0) Messaging.sendEmail(emails); - } - } + private class Relationships + { + private List m_relationships = new List(); + + public void resolve() + { + // Resolve relationships + for (IRelationship relationship : m_relationships) + { + //relationship.Record.put(relationship.RelatedToField, relationship.RelatedTo.Id); + relationship.resolve(); + } + + } + + public void add(SObject record, Schema.SObjectField relatedToField, Schema.SObjectField externalIdField, Object externalId) + { + if (relatedToField == null) { + throw new UnitOfWorkException('Invalid argument: relatedToField.'); + } + + String relationshipName = relatedToField.getDescribe().getRelationshipName(); + if (String.isBlank(relationshipName)) { + throw new UnitOfWorkException('Invalid argument: relatedToField. Field supplied is not a relationship field.'); + } + + List relatedObjects = relatedToField.getDescribe().getReferenceTo(); + Schema.SObjectType relatedObject = relatedObjects[0]; + + String externalIdFieldName = externalIdField.getDescribe().getName(); + Boolean relatedHasExternalIdField = relatedObject.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); + Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId(); + + if (!relatedHasExternalIdField) { + throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); + } + + if (!externalIdFieldIsValid) { + throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.'); + } + + RelationshipByExternalId relationship = new RelationshipByExternalId(); + relationship.Record = record; + relationship.RelatedToField = relatedToField; + relationship.RelatedTo = relatedObject; + relationship.RelationshipName = relationshipName; + relationship.ExternalIdField = externalIdField; + relationship.ExternalId = externalId; + m_relationships.add(relationship); + } + + public void add(SObject record, Schema.SObjectField relatedToField, SObject relatedTo) + { + // Relationship to resolve + Relationship relationship = new Relationship(); + relationship.Record = record; + relationship.RelatedToField = relatedToField; + relationship.RelatedTo = relatedTo; + m_relationships.add(relationship); + } + + public void add(Messaging.SingleEmailMessage email, SObject relatedTo) + { + EmailRelationship emailRelationship = new EmailRelationship(); + emailRelationship.email = email; + emailRelationship.relatedTo = relatedTo; + m_relationships.add(emailRelationship); + } + } + + private interface IRelationship + { + void resolve(); + } + + private class RelationshipByExternalId implements IRelationship + { + public SObject Record; + public Schema.SObjectField RelatedToField; + public Schema.SObjectType RelatedTo; + public String RelationshipName; + public Schema.SObjectField ExternalIdField; + public Object ExternalId; + + public void resolve() + { + SObject relationshipObject = this.RelatedTo.newSObject(); + relationshipObject.put( ExternalIdField.getDescribe().getName(), this.ExternalId ); + this.Record.putSObject( this.RelationshipName, relationshipObject ); + } + } + + private class Relationship implements IRelationship + { + public SObject Record; + public Schema.SObjectField RelatedToField; + public SObject RelatedTo; + + public void resolve() + { + this.Record.put( this.RelatedToField, this.RelatedTo.Id); + } + } + + private class EmailRelationship implements IRelationship + { + public Messaging.SingleEmailMessage email; + public SObject relatedTo; + + public void resolve() + { + this.email.setWhatId( this.relatedTo.Id ); + } + } + + /** + * UnitOfWork Exception + **/ + @NamespaceAccessible + public class UnitOfWorkException extends Exception {} + + /** + * Internal implementation of Messaging.sendEmail, see outer class registerEmail method + **/ + @NamespaceAccessible + public interface IEmailWork extends IDoWork + { + void registerEmail(Messaging.Email email); + } + + private class SendEmailWork implements IEmailWork + { + private List emails; + + public SendEmailWork() + { + this.emails = new List(); + } + + public void registerEmail(Messaging.Email email) + { + this.emails.add(email); + } + + public void doWork() + { + if (emails.size() > 0) Messaging.sendEmail(emails); + } + } } diff --git a/sfdx-source/apex-common/main/classes/fflib_SObjects.cls b/sfdx-source/apex-common/main/classes/fflib_SObjects.cls index 95c48ccf01..7d03348e2c 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SObjects.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SObjects.cls @@ -23,16 +23,19 @@ * 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. **/ +@NamespaceAccessible public virtual class fflib_SObjects extends fflib_Objects implements fflib_ISObjects { + @NamespaceAccessible public Schema.DescribeSObjectResult SObjectDescribe {get; private set;} /** * Useful during unit testing to assert at a more granular and robust level for errors raised during the various trigger events **/ + @NamespaceAccessible public static ErrorFactory Errors {get; private set;} static @@ -43,32 +46,38 @@ public virtual class fflib_SObjects /** * Class constructor */ + @NamespaceAccessible public fflib_SObjects(List records) { super(records); } + @NamespaceAccessible public fflib_SObjects(List records, Schema.SObjectType sObjectType) { super(records); SObjectDescribe = sObjectType.getDescribe(); } + @NamespaceAccessible public virtual List getRecords() { return (List) getObjects(); } + @NamespaceAccessible public virtual Set getRecordIds() { return new Map(getRecords()).keySet(); } + @NamespaceAccessible public virtual override Object getType() { return getSObjectType(); } + @NamespaceAccessible public virtual SObjectType getSObjectType() { return SObjectDescribe.getSObjectType(); @@ -79,6 +88,7 @@ public virtual class fflib_SObjects * * @param message The error message to add to each record */ + @NamespaceAccessible protected void addError(String message) { for (SObject record : getRecords()) @@ -94,6 +104,7 @@ public virtual class fflib_SObjects * @param message The error message to add to the given field on each record */ @TestVisible + @NamespaceAccessible protected virtual void addError(Schema.SObjectField field, String message) { for (SObject record : getRecords()) @@ -107,6 +118,7 @@ public virtual class fflib_SObjects * @param field The field to nullify */ @TestVisible + @NamespaceAccessible protected virtual void clearField(Schema.SObjectField field) { clearFields(new Set{ field }); @@ -117,6 +129,7 @@ public virtual class fflib_SObjects * @param fields The fields to nullify */ @TestVisible + @NamespaceAccessible protected virtual void clearFields(Set fields) { for (SObject record : getRecords()) @@ -136,6 +149,7 @@ public virtual class fflib_SObjects * * @return Returns the Error message **/ + @NamespaceAccessible protected virtual String error(String message, SObject record) { return Errors.error(this, message, record); @@ -150,6 +164,7 @@ public virtual class fflib_SObjects * * @return Returns the Error message **/ + @NamespaceAccessible protected virtual String error(String message, SObject record, Schema.SObjectField field) { return fflib_SObjects.Errors.error(this, message, record, field); @@ -161,6 +176,7 @@ public virtual class fflib_SObjects * @return Return a set with all the Id values of the given field */ @TestVisible + @NamespaceAccessible protected Set getIdFieldValues(Schema.SObjectField field) { Set result = new Set(); @@ -177,6 +193,7 @@ public virtual class fflib_SObjects * @return Return a set with all the String values of the given field */ @TestVisible + @NamespaceAccessible protected Set getStringFieldValues(Schema.SObjectField field) { Set result = new Set(); @@ -193,6 +210,7 @@ public virtual class fflib_SObjects * @return Return a set with all the values of the given field */ @TestVisible + @NamespaceAccessible protected virtual Set getFieldValues(Schema.SObjectField field) { Set result = new Set(); @@ -209,6 +227,7 @@ public virtual class fflib_SObjects * * @return A list with only the SObjects where the given field has the provided value */ + @NamespaceAccessible protected virtual List getRecordsByFieldValue(Schema.SObjectField field, Object value) { return getRecordsByFieldValues(field, new Set{value}); @@ -220,6 +239,7 @@ public virtual class fflib_SObjects * * @return A list with only the SObjects where the given field value is part of the provided values */ + @NamespaceAccessible protected virtual List getRecordsByFieldValues(Schema.SObjectField field, Set values) { List result = new List(); @@ -238,6 +258,7 @@ public virtual class fflib_SObjects * * @return A list with only the SObjects where the given field value is either null or '') */ + @NamespaceAccessible protected virtual List getRecordsWithBlankFieldValues(Schema.SObjectField field) { return getRecordsWithBlankFieldValues( @@ -250,6 +271,7 @@ public virtual class fflib_SObjects * * @return A list with only the SObjects where the at least one given field value is either null or '') */ + @NamespaceAccessible protected virtual List getRecordsWithBlankFieldValues(Set fields) { List result = new List(); @@ -271,6 +293,7 @@ public virtual class fflib_SObjects * * @return A list with only the SObjects where all given field values are either null or '' */ + @NamespaceAccessible protected virtual List getRecordsWithAllBlankFieldValues(Set fields) { List result = new List(); @@ -295,6 +318,7 @@ public virtual class fflib_SObjects * * @return A list with only the SObjects where the given field value is not null or '' */ + @NamespaceAccessible protected virtual List getRecordsWithNotBlankFieldValues(Schema.SObjectField field) { return getRecordsWithNotBlankFieldValues( @@ -307,6 +331,7 @@ public virtual class fflib_SObjects * * @return A list with only the SObjects where the at least one given field value not null or '' */ + @NamespaceAccessible protected virtual List getRecordsWithNotBlankFieldValues(Set fields) { List result = new List(); @@ -329,6 +354,7 @@ public virtual class fflib_SObjects * * @return A list with only the SObjects where all given field values are not null or '' */ + @NamespaceAccessible protected virtual List getRecordsWithAllNotBlankFieldValues(Set fields) { List result = new List(); @@ -354,6 +380,7 @@ public virtual class fflib_SObjects * @param field The reference to the SObjectField to be modified * @param value The value to store in the given SObjectField */ + @NamespaceAccessible protected virtual void setFieldValue(Schema.SObjectField field, Object value) { for (SObject record : getRecords()) @@ -367,6 +394,7 @@ public virtual class fflib_SObjects * @param fieldToUpdate The SObjectField to store the mapped value when the key matches the value in the fieldToUpdate field * @param values Map of values to store by the fieldToCheck fields value */ + @NamespaceAccessible protected virtual void setFieldValueByMap( Schema.SObjectField fieldToCheck, Schema.SObjectField fieldToUpdate, @@ -383,20 +411,23 @@ public virtual class fflib_SObjects } /** - * Ensures logging of errors in the Domain context for later assertions in tests - **/ + * Ensures logging of errors in the Domain context for later assertions in tests + **/ + @NamespaceAccessible public virtual class ErrorFactory { private List errorList = new List(); private ErrorFactory() { } - public String error(String message, SObject record) + @NamespaceAccessible + public String error(String message, SObject record) { return error(null, message, record); } - public String error(fflib_SObjects domain, String message, SObject record) + @NamespaceAccessible + public String error(fflib_SObjects domain, String message, SObject record) { ObjectError objectError = new ObjectError(); objectError.domain = domain; @@ -406,12 +437,14 @@ public virtual class fflib_SObjects return message; } - public String error(String message, SObject record, SObjectField field) + @NamespaceAccessible + public String error(String message, SObject record, SObjectField field) { return error(null, message, record, field); } - public String error(fflib_ISObjects domain, String message, SObject record, SObjectField field) + @NamespaceAccessible + public String error(fflib_ISObjects domain, String message, SObject record, SObjectField field) { FieldError fieldError = new FieldError(); fieldError.domain = domain; @@ -422,12 +455,14 @@ public virtual class fflib_SObjects return message; } - public List getAll() + @NamespaceAccessible + public List getAll() { return errorList.clone(); } - public void clearAll() + @NamespaceAccessible + public void clearAll() { errorList.clear(); } @@ -436,29 +471,38 @@ public virtual class fflib_SObjects /** * Ensures logging of errors in the Domain context for later assertions in tests **/ + @NamespaceAccessible public virtual class FieldError extends ObjectError { - public SObjectField field; + @NamespaceAccessible + public SObjectField field; - public FieldError() { } + @NamespaceAccessible + public FieldError() { } } /** * Ensures logging of errors in the Domain context for later assertions in tests **/ + @NamespaceAccessible public virtual class ObjectError extends Error { - public SObject record; + @NamespaceAccessible + public SObject record; - public ObjectError() { } + @NamespaceAccessible + public ObjectError() { } } /** * Ensures logging of errors in the Domain context for later assertions in tests **/ + @NamespaceAccessible public abstract class Error { - public String message; - public fflib_ISObjects domain; + @NamespaceAccessible + public String message; + @NamespaceAccessible + public fflib_ISObjects domain; } } \ No newline at end of file diff --git a/sfdx-source/apex-common/main/classes/fflib_SecurityUtils.cls b/sfdx-source/apex-common/main/classes/fflib_SecurityUtils.cls index eaa84cc032..9f60a78e3e 100644 --- a/sfdx-source/apex-common/main/classes/fflib_SecurityUtils.cls +++ b/sfdx-source/apex-common/main/classes/fflib_SecurityUtils.cls @@ -28,321 +28,347 @@ * Utility class for checking FLS/CRUD. NOTE: all "check" methods will throw a SecurityException (or subclass) if the * user does not have the proper security granted. **/ +@NamespaceAccessible public class fflib_SecurityUtils { - @TestVisible - private enum OperationType { CREATE, READ, MODIFY, DEL } //UPDATE and DELETE are reserved words + @TestVisible + private enum OperationType { CREATE, READ, MODIFY, DEL } //UPDATE and DELETE are reserved words - /** - * SecurityException is never be thrown directly by fflib_SecurityUtils, instead all - * forms of CRUD and FLD violations throw subclasses of it. It is provided as a convenience - * in the event you wish to handle CRUD and FLS violations the same way (e.g. die and display an error) - **/ - public virtual class SecurityException extends Exception { - protected OperationType m_operation; - protected Schema.SObjectType m_objectType; - } + /** + * SecurityException is never be thrown directly by fflib_SecurityUtils, instead all + * forms of CRUD and FLD violations throw subclasses of it. It is provided as a convenience + * in the event you wish to handle CRUD and FLS violations the same way (e.g. die and display an error) + **/ + @NamespaceAccessible + public virtual class SecurityException extends Exception { + @NamespaceAccessible + protected OperationType m_operation; + @NamespaceAccessible + protected Schema.SObjectType m_objectType; + } - /** - * CrudException represents a running user's lack of read/create/update/delete access at a profile (or permission set) - * level. Sharing and field level security issues will never cause this. - **/ - public class CrudException extends SecurityException{ - - private CrudException(OperationType operation, Schema.SObjectType objectType){ - this.m_operation = operation; - this.m_objectType = objectType; - if(operation == OperationType.CREATE) - this.setMessage(System.Label.fflib_security_error_object_not_insertable); - else if(operation == OperationType.READ) - this.setMessage(System.Label.fflib_security_error_object_not_readable); - else if(operation == OperationType.MODIFY) - this.setMessage(System.Label.fflib_security_error_object_not_updateable); - else if(operation == OperationType.DEL) - this.setMessage(System.Label.fflib_security_error_object_not_deletable); + /** + * CrudException represents a running user's lack of read/create/update/delete access at a profile (or permission set) + * level. Sharing and field level security issues will never cause this. + **/ + @NamespaceAccessible + public class CrudException extends SecurityException{ + + private CrudException(OperationType operation, Schema.SObjectType objectType){ + this.m_operation = operation; + this.m_objectType = objectType; + if(operation == OperationType.CREATE) + this.setMessage(System.Label.fflib_security_error_object_not_insertable); + else if(operation == OperationType.READ) + this.setMessage(System.Label.fflib_security_error_object_not_readable); + else if(operation == OperationType.MODIFY) + this.setMessage(System.Label.fflib_security_error_object_not_updateable); + else if(operation == OperationType.DEL) + this.setMessage(System.Label.fflib_security_error_object_not_deletable); - this.setMessage( - String.format( - this.getMessage(), - new List{ - objectType.getDescribe().getName() - } - ) - ); - } - } - /** - * FlsException represents a running user's lack of field level security to a specific field at a profile (or permission set) level - * Sharing and CRUD security issues will never cause this to be thrown. - **/ - public class FlsException extends SecurityException{ - private Schema.SObjectField m_fieldToken; + this.setMessage( + String.format( + this.getMessage(), + new List{ + objectType.getDescribe().getName() + } + ) + ); + } + } + /** + * FlsException represents a running user's lack of field level security to a specific field at a profile (or permission set) level + * Sharing and CRUD security issues will never cause this to be thrown. + **/ + @NamespaceAccessible + public class FlsException extends SecurityException{ + private Schema.SObjectField m_fieldToken; - private FlsException(OperationType operation, Schema.SObjectType objectType, Schema.SObjectField fieldToken){ - this.m_operation = operation; - this.m_objectType = objectType; - this.m_fieldToken = fieldToken; - if(operation == OperationType.CREATE) - this.setMessage(System.Label.fflib_security_error_field_not_insertable); - else if(operation == OperationType.READ) - this.setMessage(System.Label.fflib_security_error_field_not_readable); - else if(operation == OperationType.MODIFY) - this.setMessage(System.Label.fflib_security_error_field_not_updateable); + private FlsException(OperationType operation, Schema.SObjectType objectType, Schema.SObjectField fieldToken){ + this.m_operation = operation; + this.m_objectType = objectType; + this.m_fieldToken = fieldToken; + if(operation == OperationType.CREATE) + this.setMessage(System.Label.fflib_security_error_field_not_insertable); + else if(operation == OperationType.READ) + this.setMessage(System.Label.fflib_security_error_field_not_readable); + else if(operation == OperationType.MODIFY) + this.setMessage(System.Label.fflib_security_error_field_not_updateable); - this.setMessage( - String.format( - this.getMessage(), - new List{ - objectType.getDescribe().getName(), - fieldToken.getDescribe().getName() - } - ) - ); - } - } - - /** - * If set to true all check methods will always return void, and never throw exceptions. - * This should really only be set to true if an app-wide setting to disable in-apex - * FLS and CRUD checks exists and is enabled. - * Per security best practices setting BYPASS should be an a opt-in, and not the default behavior. - **/ - public static Boolean BYPASS_INTERNAL_FLS_AND_CRUD = false; + this.setMessage( + String.format( + this.getMessage(), + new List{ + objectType.getDescribe().getName(), + fieldToken.getDescribe().getName() + } + ) + ); + } + } + + /** + * If set to true all check methods will always return void, and never throw exceptions. + * This should really only be set to true if an app-wide setting to disable in-apex + * FLS and CRUD checks exists and is enabled. + * Per security best practices setting BYPASS should be an a opt-in, and not the default behavior. + **/ + @NamespaceAccessible + public static Boolean BYPASS_INTERNAL_FLS_AND_CRUD = false; - /** + /** * Check{Insert,Read,Update} methods check both FLS and CRUD - **/ - - /** - * Checks both insert FLS and CRUD for the specified object type and fields. - * @exception FlsException if the running user does not have insert rights to any fields in {@code fieldNames}. - * @exception CrudException if the running user does not have insert rights to {@code objType} - **/ - public static void checkInsert(SObjectType objType, List fieldNames) - { - checkObjectIsInsertable(objType); - for (String fieldName : fieldNames) - { - checkFieldIsInsertable(objType, fieldName); - } - } - - /** - * Identical to {@link #checkInsert(SObjectType,List)}, except with SObjectField instead of String field references. - * @exception FlsException if the running user does not have insert rights to any fields in {@code fieldTokens}. - * @exception CrudException if the running user does not have insert rights to {@code objType} - **/ - public static void checkInsert(SObjectType objType, List fieldTokens) - { - checkObjectIsInsertable(objType); - for (SObjectField fieldToken : fieldTokens) - { - checkFieldIsInsertable(objType, fieldToken); - } - } + **/ + + /** + * Checks both insert FLS and CRUD for the specified object type and fields. + * @exception FlsException if the running user does not have insert rights to any fields in {@code fieldNames}. + * @exception CrudException if the running user does not have insert rights to {@code objType} + **/ + @NamespaceAccessible + public static void checkInsert(SObjectType objType, List fieldNames) + { + checkObjectIsInsertable(objType); + for (String fieldName : fieldNames) + { + checkFieldIsInsertable(objType, fieldName); + } + } + + /** + * Identical to {@link #checkInsert(SObjectType,List)}, except with SObjectField instead of String field references. + * @exception FlsException if the running user does not have insert rights to any fields in {@code fieldTokens}. + * @exception CrudException if the running user does not have insert rights to {@code objType} + **/ + @NamespaceAccessible + public static void checkInsert(SObjectType objType, List fieldTokens) + { + checkObjectIsInsertable(objType); + for (SObjectField fieldToken : fieldTokens) + { + checkFieldIsInsertable(objType, fieldToken); + } + } - /** - * Checks both read FLS and CRUD for the specified object type and fields. - * @exception FlsException if the running user does not have read rights to any fields in {@code fieldNames}. - * @exception CrudException if the running user does not have read rights to {@code objType} - **/ - public static void checkRead(SObjectType objType, List fieldNames) - { - checkObjectIsReadable(objType); - for (String fieldName : fieldNames) - { - checkFieldIsReadable(objType, fieldName); - } - } - - /** - * Identical to {@link #checkRead(SObjectType,List)}, except with SObjectField instead of String field references. - * @exception FlsException if the running user does not have read rights to any fields in {@code fieldTokens}. - * @exception CrudException if the running user does not have read rights to {@code objType} - **/ - public static void checkRead(SObjectType objType, List fieldTokens) - { - checkObjectIsReadable(objType); - for (SObjectField fieldToken : fieldTokens) - { - checkFieldIsReadable(objType, fieldToken); - } - } + /** + * Checks both read FLS and CRUD for the specified object type and fields. + * @exception FlsException if the running user does not have read rights to any fields in {@code fieldNames}. + * @exception CrudException if the running user does not have read rights to {@code objType} + **/ + @NamespaceAccessible + public static void checkRead(SObjectType objType, List fieldNames) + { + checkObjectIsReadable(objType); + for (String fieldName : fieldNames) + { + checkFieldIsReadable(objType, fieldName); + } + } + + /** + * Identical to {@link #checkRead(SObjectType,List)}, except with SObjectField instead of String field references. + * @exception FlsException if the running user does not have read rights to any fields in {@code fieldTokens}. + * @exception CrudException if the running user does not have read rights to {@code objType} + **/ + @NamespaceAccessible + public static void checkRead(SObjectType objType, List fieldTokens) + { + checkObjectIsReadable(objType); + for (SObjectField fieldToken : fieldTokens) + { + checkFieldIsReadable(objType, fieldToken); + } + } - /** - * Checks both update FLS and CRUD for the specified object type and fields. - * @exception FlsException if the running user does not have update rights to any fields in {@code fieldNames}. - * @exception CrudException if the running user does not have update rights to {@code objType} - **/ - public static void checkUpdate(SObjectType objType, List fieldNames) - { - checkObjectIsUpdateable(objType); - for (String fieldName : fieldNames) - { - checkFieldIsUpdateable(objType, fieldName); - } - } - - /** - * Identical to {@link #checkUpdate(SObjectType,List)}, except with SObjectField instead of String field references. - * @exception FlsException if the running user does not have update rights to any fields in {@code fieldTokens}. - * @exception CrudException if the running user does not have update rights to {@code objType} - **/ - public static void checkUpdate(SObjectType objType, List fieldTokens) - { - checkObjectIsUpdateable(objType); - for (SObjectField fieldToken : fieldTokens) - { - checkFieldIsUpdateable(objType, fieldToken); - } - } + /** + * Checks both update FLS and CRUD for the specified object type and fields. + * @exception FlsException if the running user does not have update rights to any fields in {@code fieldNames}. + * @exception CrudException if the running user does not have update rights to {@code objType} + **/ + @NamespaceAccessible + public static void checkUpdate(SObjectType objType, List fieldNames) + { + checkObjectIsUpdateable(objType); + for (String fieldName : fieldNames) + { + checkFieldIsUpdateable(objType, fieldName); + } + } + + /** + * Identical to {@link #checkUpdate(SObjectType,List)}, except with SObjectField instead of String field references. + * @exception FlsException if the running user does not have update rights to any fields in {@code fieldTokens}. + * @exception CrudException if the running user does not have update rights to {@code objType} + **/ + @NamespaceAccessible + public static void checkUpdate(SObjectType objType, List fieldTokens) + { + checkObjectIsUpdateable(objType); + for (SObjectField fieldToken : fieldTokens) + { + checkFieldIsUpdateable(objType, fieldToken); + } + } - /** + /** * CheckFieldIs* method check only FLS - **/ + **/ - /** - * Checks insert field level security only (no CRUD) for the specified fields on {@code objType} - * @exception FlsException if the running user does not have insert rights to the {@code fieldName} field. - **/ - public static void checkFieldIsInsertable(SObjectType objType, String fieldName) - { - checkFieldIsInsertable(objType, fflib_SObjectDescribe.getDescribe(objType).getField(fieldName)); - } + /** + * Checks insert field level security only (no CRUD) for the specified fields on {@code objType} + * @exception FlsException if the running user does not have insert rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsInsertable(SObjectType objType, String fieldName) + { + checkFieldIsInsertable(objType, fflib_SObjectDescribe.getDescribe(objType).getField(fieldName)); + } - /** - * Identical to {@link #checkFieldIsInsertable(SObjectType,String)}, except with SObjectField instead of String field reference. - * @exception FlsException if the running user does not have insert rights to the {@code fieldName} field. - **/ - public static void checkFieldIsInsertable(SObjectType objType, SObjectField fieldToken) - { - checkFieldIsInsertable(objType, fieldToken.getDescribe()); - } + /** + * Identical to {@link #checkFieldIsInsertable(SObjectType,String)}, except with SObjectField instead of String field reference. + * @exception FlsException if the running user does not have insert rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsInsertable(SObjectType objType, SObjectField fieldToken) + { + checkFieldIsInsertable(objType, fieldToken.getDescribe()); + } - /** - * Identical to {@link #checkFieldIsInsertable(SObjectType,String)}, except with DescribeFieldResult instead of String field reference. - * @exception FlsException if the running user does not have insert rights to the {@code fieldName} field. - **/ - public static void checkFieldIsInsertable(SObjectType objType, DescribeFieldResult fieldDescribe) - { - if (BYPASS_INTERNAL_FLS_AND_CRUD) - return; - if (!fieldDescribe.isCreateable()) - throw new FlsException(OperationType.CREATE, objType, fieldDescribe.getSObjectField()); - } - - /** - * Checks read field level security only (no CRUD) for the specified fields on {@code objType} - * @exception FlsException if the running user does not have read rights to the {@code fieldName} field. - **/ - public static void checkFieldIsReadable(SObjectType objType, String fieldName) - { - checkFieldIsReadable(objType, fflib_SObjectDescribe.getDescribe(objType).getField(fieldName)); - } + /** + * Identical to {@link #checkFieldIsInsertable(SObjectType,String)}, except with DescribeFieldResult instead of String field reference. + * @exception FlsException if the running user does not have insert rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsInsertable(SObjectType objType, DescribeFieldResult fieldDescribe) + { + if (BYPASS_INTERNAL_FLS_AND_CRUD) + return; + if (!fieldDescribe.isCreateable()) + throw new FlsException(OperationType.CREATE, objType, fieldDescribe.getSObjectField()); + } + + /** + * Checks read field level security only (no CRUD) for the specified fields on {@code objType} + * @exception FlsException if the running user does not have read rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsReadable(SObjectType objType, String fieldName) + { + checkFieldIsReadable(objType, fflib_SObjectDescribe.getDescribe(objType).getField(fieldName)); + } - /** - * Identical to {@link #checkFieldIsReadable(SObjectType,String)}, except with SObjectField instead of String field reference. - * @exception FlsException if the running user does not have read rights to the {@code fieldName} field. - **/ - public static void checkFieldIsReadable(SObjectType objType, SObjectField fieldToken) - { - checkFieldIsReadable(objType, fieldToken.getDescribe()); - } + /** + * Identical to {@link #checkFieldIsReadable(SObjectType,String)}, except with SObjectField instead of String field reference. + * @exception FlsException if the running user does not have read rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsReadable(SObjectType objType, SObjectField fieldToken) + { + checkFieldIsReadable(objType, fieldToken.getDescribe()); + } - /** - * Identical to {@link #checkFieldIsReadable(SObjectType,String)}, except with DescribeFieldResult instead of String field reference. - * @exception FlsException if the running user does not have read rights to the {@code fieldName} field. - **/ - public static void checkFieldIsReadable(SObjectType objType, DescribeFieldResult fieldDescribe) - { - if (BYPASS_INTERNAL_FLS_AND_CRUD) - return; - if (!fieldDescribe.isAccessible()) - throw new FlsException(OperationType.READ, objType, fieldDescribe.getSObjectField()); - } - + /** + * Identical to {@link #checkFieldIsReadable(SObjectType,String)}, except with DescribeFieldResult instead of String field reference. + * @exception FlsException if the running user does not have read rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsReadable(SObjectType objType, DescribeFieldResult fieldDescribe) + { + if (BYPASS_INTERNAL_FLS_AND_CRUD) + return; + if (!fieldDescribe.isAccessible()) + throw new FlsException(OperationType.READ, objType, fieldDescribe.getSObjectField()); + } + - /** - * Checks update field level security only (no CRUD) for the specified fields on {@code objType} - * @exception FlsException if the running user does not have update rights to the {@code fieldName} field. - **/ - public static void checkFieldIsUpdateable(SObjectType objType, String fieldName) - { - checkFieldIsUpdateable(objType, fflib_SObjectDescribe.getDescribe(objType).getField(fieldName)); - } + /** + * Checks update field level security only (no CRUD) for the specified fields on {@code objType} + * @exception FlsException if the running user does not have update rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsUpdateable(SObjectType objType, String fieldName) + { + checkFieldIsUpdateable(objType, fflib_SObjectDescribe.getDescribe(objType).getField(fieldName)); + } - /** - * Identical to {@link #checkFieldIsUpdateable(SObjectType,String)}, except with SObjectField instead of String field reference. - * @exception FlsException if the running user does not have update rights to the {@code fieldName} field. - **/ - public static void checkFieldIsUpdateable(SObjectType objType, SObjectField fieldToken) - { - checkFieldIsUpdateable(objType, fieldToken.getDescribe()); - } + /** + * Identical to {@link #checkFieldIsUpdateable(SObjectType,String)}, except with SObjectField instead of String field reference. + * @exception FlsException if the running user does not have update rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsUpdateable(SObjectType objType, SObjectField fieldToken) + { + checkFieldIsUpdateable(objType, fieldToken.getDescribe()); + } - /** - * Identical to {@link #checkFieldIsUpdateable(SObjectType,String)}, except with DescribeFieldResult instead of String field reference. - * @exception FlsException if the running user does not have update rights to the {@code fieldName} field. - **/ - public static void checkFieldIsUpdateable(SObjectType objType, DescribeFieldResult fieldDescribe) - { - if (BYPASS_INTERNAL_FLS_AND_CRUD) - return; - if (!fieldDescribe.isUpdateable()) - throw new FlsException(OperationType.MODIFY, objType, fieldDescribe.getSObjectField()); + /** + * Identical to {@link #checkFieldIsUpdateable(SObjectType,String)}, except with DescribeFieldResult instead of String field reference. + * @exception FlsException if the running user does not have update rights to the {@code fieldName} field. + **/ + @NamespaceAccessible + public static void checkFieldIsUpdateable(SObjectType objType, DescribeFieldResult fieldDescribe) + { + if (BYPASS_INTERNAL_FLS_AND_CRUD) + return; + if (!fieldDescribe.isUpdateable()) + throw new FlsException(OperationType.MODIFY, objType, fieldDescribe.getSObjectField()); } /** * CheckObjectIs* methods check only CRUD **/ - - /** - * Checks insert CRUD for the specified object type. - * @exception CrudException if the running user does not have insert rights to the {@code objType} SObject. - **/ - public static void checkObjectIsInsertable(SObjectType objType) - { - if (BYPASS_INTERNAL_FLS_AND_CRUD) - return; - if (!objType.getDescribe().isCreateable()) - { - throw new CrudException(OperationType.CREATE, objType); - } - } - - /** - * Checks read CRUD for the specified object type. - * @exception CrudException if the running user does not have read rights to the {@code objType} SObject. - **/ - public static void checkObjectIsReadable(SObjectType objType) - { - if (BYPASS_INTERNAL_FLS_AND_CRUD) - return; - if (!objType.getDescribe().isAccessible()) - throw new CrudException(OperationType.READ, objType); - } + + /** + * Checks insert CRUD for the specified object type. + * @exception CrudException if the running user does not have insert rights to the {@code objType} SObject. + **/ + @NamespaceAccessible + public static void checkObjectIsInsertable(SObjectType objType) + { + if (BYPASS_INTERNAL_FLS_AND_CRUD) + return; + if (!objType.getDescribe().isCreateable()) + { + throw new CrudException(OperationType.CREATE, objType); + } + } + + /** + * Checks read CRUD for the specified object type. + * @exception CrudException if the running user does not have read rights to the {@code objType} SObject. + **/ + @NamespaceAccessible + public static void checkObjectIsReadable(SObjectType objType) + { + if (BYPASS_INTERNAL_FLS_AND_CRUD) + return; + if (!objType.getDescribe().isAccessible()) + throw new CrudException(OperationType.READ, objType); + } - /** - * Checks update CRUD for the specified object type. - * @exception CrudException if the running user does not have update rights to the {@code objType} SObject. - **/ - public static void checkObjectIsUpdateable(SObjectType objType) - { - if (BYPASS_INTERNAL_FLS_AND_CRUD) - return; - if (!objType.getDescribe().isUpdateable()) - throw new CrudException(OperationType.MODIFY, objType); - } + /** + * Checks update CRUD for the specified object type. + * @exception CrudException if the running user does not have update rights to the {@code objType} SObject. + **/ + @NamespaceAccessible + public static void checkObjectIsUpdateable(SObjectType objType) + { + if (BYPASS_INTERNAL_FLS_AND_CRUD) + return; + if (!objType.getDescribe().isUpdateable()) + throw new CrudException(OperationType.MODIFY, objType); + } - /** - * Checks delete CRUD for the specified object type. - * @exception CrudException if the running user does not have delete rights to the {@code objType} SObject. - **/ - public static void checkObjectIsDeletable(SObjectType objType) - { - if (BYPASS_INTERNAL_FLS_AND_CRUD) - return; - if (!objType.getDescribe().isDeletable()) - throw new CrudException(OperationType.DEL, objType); - } + /** + * Checks delete CRUD for the specified object type. + * @exception CrudException if the running user does not have delete rights to the {@code objType} SObject. + **/ + @NamespaceAccessible + public static void checkObjectIsDeletable(SObjectType objType) + { + if (BYPASS_INTERNAL_FLS_AND_CRUD) + return; + if (!objType.getDescribe().isDeletable()) + throw new CrudException(OperationType.DEL, objType); + } } \ No newline at end of file diff --git a/sfdx-source/apex-common/main/classes/fflib_StringBuilder.cls b/sfdx-source/apex-common/main/classes/fflib_StringBuilder.cls index 5bb97f3b71..8e079197b8 100644 --- a/sfdx-source/apex-common/main/classes/fflib_StringBuilder.cls +++ b/sfdx-source/apex-common/main/classes/fflib_StringBuilder.cls @@ -29,6 +29,7 @@ * * NOTE: Aspects of this were developed before recent improvements to String handling, as such could likely be enhanced at this stage. **/ +@NamespaceAccessible public virtual class fflib_StringBuilder { protected List buffer = new List(); @@ -36,11 +37,13 @@ public virtual class fflib_StringBuilder /** * Construct an empty StringBuilder **/ + @NamespaceAccessible public fflib_StringBuilder() {} /** * Construct a StringBuilder with the given values **/ + @NamespaceAccessible public fflib_StringBuilder(List values) { add(values); @@ -49,6 +52,7 @@ public virtual class fflib_StringBuilder /** * Add the given values to the StringBuilder **/ + @NamespaceAccessible public virtual void add(List values) { buffer.addAll(values); @@ -57,11 +61,13 @@ public virtual class fflib_StringBuilder /** * Add the given value to the StringBuilder **/ + @NamespaceAccessible public virtual void add(String value) { buffer.add(value); } + @NamespaceAccessible public virtual override String toString() { return String.join(buffer, ''); @@ -70,6 +76,7 @@ public virtual class fflib_StringBuilder /** * Return the state of the StringBuilder **/ + @NamespaceAccessible public virtual String getStringValue() { return toString(); @@ -78,35 +85,42 @@ public virtual class fflib_StringBuilder /** * Subclasses the StringBuilder to produce a comma delimited concatenation of strings **/ + @NamespaceAccessible public virtual with sharing class CommaDelimitedListBuilder extends fflib_StringBuilder { String itemPrefix = ''; String delimiter = ','; - public CommaDelimitedListBuilder() {} + @NamespaceAccessible + public CommaDelimitedListBuilder() {} - public CommaDelimitedListBuilder(List values) + @NamespaceAccessible + public CommaDelimitedListBuilder(List values) { super(values); } - public void setItemPrefix(String itemPrefix) + @NamespaceAccessible + public void setItemPrefix(String itemPrefix) { this.itemPrefix = itemPrefix; } - public void setDelimiter(String delimiter) + @NamespaceAccessible + public void setDelimiter(String delimiter) { this.delimiter = delimiter; } - public String getStringValue(String itemPrefix) + @NamespaceAccessible + public String getStringValue(String itemPrefix) { setItemPrefix(itemPrefix); return toString(); } - public override String toString() + @NamespaceAccessible + public override String toString() { return itemPrefix + String.join(buffer, delimiter + itemPrefix); } @@ -115,14 +129,17 @@ public virtual class fflib_StringBuilder /** * Subclasses the StringCommaDelimitedBuilder to accept native SObjectField tokens and optional FieldSet definitions to concatinate when building queries **/ + @NamespaceAccessible public virtual with sharing class FieldListBuilder extends CommaDelimitedListBuilder { - public FieldListBuilder(List values) + @NamespaceAccessible + public FieldListBuilder(List values) { this(values, null); } - public FieldListBuilder(List values, List fieldSets) + @NamespaceAccessible + public FieldListBuilder(List values, List fieldSets) { // Create a distinct set of fields (or field paths) to select for(Schema.SObjectField value : values) @@ -138,14 +155,17 @@ public virtual class fflib_StringBuilder /** * Subclasses the FieldListBuilder to auto sense and include when needed the CurrencyIsoCode field in the field list **/ + @NamespaceAccessible public with sharing class MultiCurrencyFieldListBuilder extends FieldListBuilder { - public MultiCurrencyFieldListBuilder(List values) + @NamespaceAccessible + public MultiCurrencyFieldListBuilder(List values) { this(values, null); } - public MultiCurrencyFieldListBuilder(List values, List fieldSets) + @NamespaceAccessible + public MultiCurrencyFieldListBuilder(List values, List fieldSets) { super(values, fieldSets);