Skip to content

Commit

Permalink
Merge pull request apex-enterprise-patterns#8 from OrtooApps/feature/…
Browse files Browse the repository at this point in the history
…filter-and-paging-backend

Feature/filter and paging backend
  • Loading branch information
rob-baillie-ortoo authored Jan 24, 2022
2 parents 6f868a8 + 3448786 commit f1d6420
Show file tree
Hide file tree
Showing 72 changed files with 3,169 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public virtual with sharing class fflib_Criteria
this.type = 'AND';
}

/**
* Adds the given evalutor onto the formula stack
*/
protected void addEvaluator( Evaluator evaluator )
{
evaluators.add( evaluator );
}

/**
* Changes the default comparator for each criteria to OR
*
Expand Down Expand Up @@ -295,7 +303,7 @@ public virtual with sharing class fflib_Criteria
}
}

private class FormulaNumberEvaluator implements IFormulaEvaluator
public class FormulaNumberEvaluator implements IFormulaEvaluator
{
private Integer expressionNumber;

Expand Down Expand Up @@ -737,7 +745,7 @@ public virtual with sharing class fflib_Criteria
else if (operator == fflib_Operator.GREATER_THAN_OR_EQUAL_TO)
return '>=';
else if (operator == fflib_Operator.LIKEx)
return ' like ';
return ' LIKE ';
else if (operator == fflib_Operator.INx)
return ' IN ';
else if (operator == fflib_Operator.NOT_IN)
Expand Down Expand Up @@ -817,7 +825,7 @@ public virtual with sharing class fflib_Criteria
return result + ')';
}

private interface Evaluator
public interface Evaluator
{
Boolean evaluate(Object obj);
String toSOQL();
Expand All @@ -826,7 +834,7 @@ public virtual with sharing class fflib_Criteria
/**
* Generic criteria handler for comparing against sets
*/
private virtual class FieldSetEvaluator implements Evaluator
public virtual class FieldSetEvaluator implements Evaluator
{
private Schema.SObjectField sObjectField;
private fflib_Objects values;
Expand All @@ -851,22 +859,22 @@ public virtual with sharing class fflib_Criteria

public String toSOQL()
{
return String.format(
'{0} {1} {2}',
new List<String>
{
this.sObjectField.getDescribe().getName(),
operatorToString(this.operator),
toLiteral(this.values)
}
return String.join(
new List<String>
{
this.sObjectField.getDescribe().getName(),
operatorToString(this.operator),
toLiteral(this.values)
},
''
);
}
}

/**
* Generic field Evaluator
*/
private class FieldEvaluator implements Evaluator
public class FieldEvaluator implements Evaluator
{
private Schema.SObjectField sObjectField;
private Object value;
Expand Down Expand Up @@ -903,7 +911,7 @@ public virtual with sharing class fflib_Criteria
}
}

private abstract class AbstractRelatedFieldEvaluator implements Evaluator
public abstract class AbstractRelatedFieldEvaluator implements Evaluator
{
protected String fieldName;
protected fflib_Operator operator;
Expand All @@ -926,7 +934,7 @@ public virtual with sharing class fflib_Criteria

}

private class RelatedFieldEvaluator extends AbstractRelatedFieldEvaluator
public class RelatedFieldEvaluator extends AbstractRelatedFieldEvaluator
{
private Object value;

Expand All @@ -950,18 +958,18 @@ public virtual with sharing class fflib_Criteria
public String toSOQL()
{
return String.join(
new List<String>
{
this.fieldName,
operatorToString(this.operator),
toLiteral(this.value)
},
''
new List<String>
{
this.fieldName,
operatorToString(this.operator),
toLiteral(this.value)
},
''
);
}
}

private class RelatedFieldSetEvaluator extends AbstractRelatedFieldEvaluator
public class RelatedFieldSetEvaluator extends AbstractRelatedFieldEvaluator
{
private fflib_Objects values;

Expand All @@ -984,19 +992,19 @@ public virtual with sharing class fflib_Criteria

public String toSOQL()
{
return String.format(
'{0} {1} {2}',
new List<String>
{
this.fieldName,
operatorToString(this.operator),
toLiteral(this.values)
}
return String.join(
new List<String>
{
this.fieldName,
operatorToString(this.operator),
toLiteral(this.values)
},
''
);
}
}

private class PropertyEvaluator implements Evaluator
public class PropertyEvaluator implements Evaluator
{
private Object property;
private Object value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public abstract with sharing class fflib_SObjectSelector
* Implement this method to inform the base class of the SObject (custom or standard) to be queried
**/
abstract Schema.SObjectType getSObjectType();

/**
* Implement this method to inform the base class of the common fields to be queried or listed by the base class methods
**/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public inherited sharing class ApplicationMockRegistrar
* @param Type The domain type (class) that the mock should be generated as
* @return Amoss_Instance The controller for the mock Domain
*/
private static Amoss_Instance registerMockDomain( SobjectType sobjectType, Type domainType )
public static Amoss_Instance registerMockDomain( SobjectType sobjectType, Type domainType )
{
Amoss_Instance mockDomainController = new Amoss_Instance( domainType );
mockDomainController
Expand Down Expand Up @@ -187,7 +187,7 @@ public inherited sharing class ApplicationMockRegistrar
* @param Type The selector type (class) that the mock should be generated as
* @return Amoss_Instance The controller for the mock selector
*/
private static Amoss_Instance registerMockSelector( SobjectType sobjectType, Type selectorType )
public static Amoss_Instance registerMockSelector( SobjectType sobjectType, Type selectorType )
{
Amoss_Instance mockSelectorController = new Amoss_Instance( selectorType );
mockSelectorController
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ public inherited sharing class FrameworkErrorCodes {
public final static String DML_UPDATE_NOT_ALLOWED = '00002';
public final static String DML_DELETE_NOT_ALLOWED = '00003';
public final static String DML_PUBLISH_NOT_ALLOWED = '00004';

public final static String SELECTOR_UNBOUND_COUNT_QUERY = '0000000';

}
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ public class StackTrace {
return stackTraceEntries[0];
}

@testVisible
private StackTraceEntry getEntryStackTraceEntry()
{
if ( stackTraceEntries.isEmpty() )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
*
* @group fflib Extension
*/
public inherited sharing virtual class ortoo_Criteria extends fflib_Criteria // NOPMD: specified a mini-namespace to differentiate from fflib versions
public inherited sharing virtual class ortoo_Criteria extends fflib_Criteria implements ISearchCriteria // NOPMD: specified a mini-namespace to differentiate from fflib versions
{
public virtual ortoo_Criteria likeString( Schema.SObjectField field, Object value )
{
addEvaluator( new FieldEvaluator( field, fflib_Operator.LIKEx, value ) );
return this;
}

public virtual ortoo_Criteria likeString( String relatedField, Object value )
{
addEvaluator( new RelatedFieldEvaluator( relatedField, fflib_Operator.LIKEx, value ) );
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,100 @@
*
* @group fflib Extension
*/
public abstract inherited sharing class ortoo_SobjectSelector extends fflib_SobjectSelector // NOPMD: specified a mini-namespace to differentiate from fflib versions
public abstract inherited sharing class ortoo_SobjectSelector extends fflib_SobjectSelector implements ISearchSelector // NOPMD: specified a mini-namespace to differentiate from fflib versions
{
public ortoo_SobjectSelector()
{
super();
enforceFLS();
}

/**
* Configure this instance to ignore FLS when selecting data.
*
* @return fflib_SObjectSelector Itself, allowing for a fluent interface.
*/
public fflib_SObjectSelector ignoreFls()
{
m_enforceFLS = false;
return this;
}
public class UnboundCountQueryException extends Exceptions.SelectorException {}

public ortoo_SobjectSelector()
{
super();
enforceFLS();
}

/**
* Configure this instance to ignore FLS when selecting data.
*
* @return fflib_SObjectSelector Itself, allowing for a fluent interface.
*/
public fflib_SObjectSelector ignoreFls()
{
m_enforceFLS = false;
return this;
}

/**
* Given a configuration, search criteria, a window and an order by, will perform the defined search.
*
* The results includes a total record count as well as the windowed search results.
*
* WIll perform a maximum of 2 SOQL queries.
*
* @param ISearchConfiguration The configuration defining the additional fields that should be returned.
* @param ISearchCriteria The criteria defining the records that should be returned.
* @param SearchWindow Defines the subset of the full result set that should be returned.
* @param SearchOrderBy The order in which the records should be returned (is applied prior to the window).
* @return SearchResults The resulting total record count and result set.
*/
public SearchResults selectBySearchCriteria( ISearchConfiguration searchConfiguration, ISearchCriteria criteria, SearchWindow window, SearchOrderBy orderBy )
{
Contract.requires( searchConfiguration != null, 'selectBySearchCriteria called with a null searchConfiguration' );
Contract.requires( criteria != null, 'selectBySearchCriteria called with a null criteria' );
Contract.requires( window != null, 'selectBySearchCriteria called with a null window' );
Contract.requires( orderBy != null, 'selectBySearchCriteria called with a null orderBy' );

Integer countOfRecords = getCountOfRecords( criteria );

List<Sobject> resultsRecords = new List<Sobject>();

if ( countOfRecords > 0 )
{
fflib_QueryFactory queryFactory = newQueryFactory().setCondition( criteria.toSOQL() );

queryFactory.selectFields( searchConfiguration.getRequiredFields() );
queryFactory.setOffset( window.offset );
queryFactory.setLimit( window.length );

if ( orderBy.isConfigured() )
{
queryFactory.setOrdering( orderBy.fieldName , orderBy.direction == 'desc' ? fflib_QueryFactory.SortOrder.DESCENDING : fflib_QueryFactory.SortOrder.ASCENDING );
}

resultsRecords = Database.query( queryFactory.toSOQL() );
}

return new SearchResults( countOfRecords, resultsRecords );
}

/**
* Given a set of search criteria, will return the number of records that meet that criteria
*
* Will perform a maximum of 1 SOQL query
*
* @param ISearchCriteria The criteria defining the records that should be returned.
* @return Integer The total number of records that match the criteria
*/
protected Integer getCountOfRecords( ISearchCriteria criteria )
{
Contract.requires( criteria != null, 'getCountOfRecords called with a null criteria' );

String whereClause = criteria.toSOQL();

if ( String.isBlank( whereClause ) )
{
throw new UnboundCountQueryException( 'Attempted to perform the count of an unbound query against ' + getSObjectName() )
.setErrorCode( FrameworkErrorCodes.SELECTOR_UNBOUND_COUNT_QUERY )
.addContext( 'SObjectType', getSObjectName() );
}

if ( isEnforcingCRUD() && ! SobjectUtils.isAccessible( sObjectType() ) )
{
throw new fflib_SObjectDomain.DomainException( 'Permission to access ' + getSObjectName() + ' denied.' ); // to match the exception thrown by fflib
}

String query = 'SELECT COUNT(Id) recordCount FROM ' + getSObjectName() + ' WHERE ' + whereClause;

AggregateResult result = (AggregateResult)Database.query( query ); // NOPMD: variables come from a trusted source

return (Integer)result.get( 'recordCount' );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@isTest
private without sharing class ortoo_CriteriaTest
{
@isTest
private static void likeString_field_whenCalled_addsALikeToTheGeneratedSoql() // NOPMD: Test method name format
{
ortoo_Criteria criteria = new ortoo_Criteria();

Test.startTest();
criteria.likeString( Contact.Name, 'thing%' );
Test.stopTest();

String expected = 'Name LIKE \'thing%\'';
String got = criteria.toSOQL();

System.assertEquals( expected, got, 'likeString, when called with a field, will add a LIKE to the Generated SOQL' );
}

@isTest
private static void likeString_stringName_whenCalled_addsALikeToTheGeneratedSoql() // NOPMD: Test method name format
{
ortoo_Criteria criteria = new ortoo_Criteria();

Test.startTest();
criteria.likeString( 'Account.Name', 'thing%' );
Test.stopTest();

String expected = 'Account.Name LIKE \'thing%\'';
String got = criteria.toSOQL();

System.assertEquals( expected, got, 'likeString, when called with a string name for a field will add a LIKE to the Generated SOQL' );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading

0 comments on commit f1d6420

Please sign in to comment.