Add Dynamic Application Factory

This enabled realtime dependency injection for the fflib application.
By default the configuration comes from the Custom Metadata object fflib_AppBinding__mdt, but can be fully customised and extended.

Initial stuff
This commit is contained in:
Wim Velzeboer
2021-07-02 13:57:11 +01:00
parent 46276b564f
commit 039e82d73c
73 changed files with 3418 additions and 4 deletions
-1
View File
@@ -1,7 +1,6 @@
name: Create a Scratch Org, Push Source and Run Apex Tests
on:
push:
pull_request_target:
jobs:
+20
View File
@@ -0,0 +1,20 @@
################################################################################
# ALLOW all files and folders in this directory, other than...
# - local-config.sh
# - custom_*
#
# This file (local-config.sh) is used by the shell scripts found in ./dev-tools
# to provide customized settings for each developer working on an SFDX-Falcon
# based project. Because these variables are customized per developer, this
# local-config file should NOT be tracked in source control.
#
# The same holds for any custom libraries that an indivisual developer would
# like to make. If you want to build something that wont' be tracked, just
# prepend the filename with "custom_" and it will be ignored.
################################################################################
#Ignore local-config.sh
local-config.sh
# Ignore any files or folders that start with "custom_" (case insensitive)
custom_*
+551
View File
@@ -0,0 +1,551 @@
#!/bin/bash
####################################################################################################
#
# FILENAME: shared-functions.sh
#
# PURPOSE: Common header file for all dev-tools scripts. Contains shared functions.
#
# DESCRIPTION: Contains shared functions used by all of the dev-tools shell scripts.
#
# INSTRUCTIONS: This script should be sourced at the top of all dev-tools shell scripts.
# Do not source this script manually from the command line. If you do, all of the
# shell variables set by this script (including those from local-config.sh) will be
# set in the user's shell, and any changes to local-config.sh may not be picked up
# until the user manually exits their shell and opens a new one.
#
#
####################################################################################################
#
##
###
#### PREVENT RELOAD OF THIS LIBRARY ################################################################
###
##
#
if [ "$SFDX_FALCON_FRAMEWORK_SHELL_VARS_SET" = "true" ]; then
# The SFDX_FALCON_FRAMEWORK_SHELL_VARS_SET variable is defined as part of
# this project's local configuration script (local-config.sh). If this
# variable holds the string "true" then it means that local-config.sh has
# already been sourced (loaded). Since this is exactly what THIS script
# (shared-functions.sh) is supposed to do it means that this library has
# already been loaded.
#
# We DO NOT want to load shared-functions.sh a second time, so we'll RETURN
# (not EXIT) gracefully so the caller can continue as if they were the
# first to load this.
return 0
fi
#
##
###
#### FUNCTION: askUserForStringValue ###############################################################
###
##
#
askUserForStringValue () {
# If a second argument was provided, echo its
# value before asking the user for input.
if [ "$2" != "" ]; then
echo $2 "\n"
fi
# Create a local variable to store the value of
# the variable provided by the first argument.
eval "local LOCAL_VALUE=\$$1"
# Create a local variable to store the default error message.
local LOCAL_ERROR_MSG="You must provide a value at the prompt."
# Do not allow the user to continue unless they
# provide a value when prompted.
while [ "$LOCAL_VALUE" = "" ]; do
eval "read -p \"$1: \" $1"
eval "LOCAL_VALUE=\$$1"
if [ "$LOCAL_VALUE" = "" ]; then
# If the caller specified a custom error message, use it.
if [ "$3" != "" ]; then
eval "LOCAL_ERROR_MSG=\"$3\""
fi
echoErrorMsg "$LOCAL_ERROR_MSG"
fi
done
}
#
##
###
#### FUNCTION: confirmChoice #######################################################################
###
##
#
confirmChoice () {
# Local variable will store the user's response.
local USER_RESPONSE=""
# Show the question being asked.
echo "`tput rev`$2`tput sgr0`"
# Prompt the user for a response and read their input.
read -p "$3" USER_RESPONSE
# Save the user's response to the variable provided by the caller.
eval "$1=\"$USER_RESPONSE\""
# Add a line break
echo ""
}
#
##
###
#### FUNCTION: confirmScriptExecution ##############################################################
###
##
#
confirmScriptExecution () {
if [ ! -z "$CONFIRM_EXECUTION" ]; then
# The user has already been asked to confirm execution.
# If confirmScriptExecution() is getting called again, it means that
# one or more scripts are being sourced and run by a parent script.
# We don't want to keep throwing confirmation messages to the user,
# so we'll just return 0 to silently continue.
return 0
fi
echo "`tput rev`$1`tput sgr0`"
read -p "(type YES to confirm, or hit ENTER to cancel) " CONFIRM_EXECUTION
if [ "$CONFIRM_EXECUTION" != "YES" ]; then
echo "\nScript aborted\n"
exit 0
fi
echo ""
}
#
##
###
#### FUNCTION: createScratchOrg () #################################################################
###
##
#
createScratchOrg() {
# Declare a local variable to store the Alias of the org to CREATE
local ORG_ALIAS_TO_CREATE=""
# Check if a value was passed to this function in Argument 1.
# If there was we will make that the org alias to CREATE.
if [ ! -z $1 ]; then
ORG_ALIAS_TO_CREATE="$1"
elif [ ! -z $TARGET_ORG_ALIAS ]; then
ORG_ALIAS_TO_CREATE="$TARGET_ORG_ALIAS"
else
# Something went wrong. No argument was provided and the TARGET_ORG_ALIAS
# has not yet been set or is an empty string. Raise an error message and
# then exit 1 to kill the script.
echoErrorMsg "Could not execute createScratchOrg(). Unknown target org alias."
exit 1
fi
# Create a new scratch org using the scratch-def.json locally configured for this project.
echoStepMsg "Create a new $ORG_ALIAS_TO_CREATE scratch org"
echo "Executing force:org:create -f $SCRATCH_ORG_CONFIG -a $ORG_ALIAS_TO_CREATE -v $DEV_HUB_ALIAS -s -d 29"
(cd $PROJECT_ROOT && exec sfdx force:org:create -f $SCRATCH_ORG_CONFIG -a $ORG_ALIAS_TO_CREATE -v $DEV_HUB_ALIAS -s -d 29)
if [ $? -ne 0 ]; then
echoErrorMsg "Scratch org \"$ORG_ALIAS_TO_CREATE\"could not be created. Aborting Script."
exit 1
fi
}
#
##
###
#### FUNCTION: deleteScratchOrg () #################################################################
###
##
#
deleteScratchOrg () {
# Declare a local variable to store the Alias of the org to delete
local ORG_ALIAS_TO_DELETE=""
# Check if a value was passed to this function in Argument 1.
# If there was we will make that the org alias to delete.
if [ ! -z $1 ]; then
ORG_ALIAS_TO_DELETE="$1"
elif [ ! -z $TARGET_ORG_ALIAS ]; then
ORG_ALIAS_TO_DELETE="$TARGET_ORG_ALIAS"
else
# Something went wrong. No argument was provided and the TARGET_ORG_ALIAS
# has not yet been set or is an empty string. Raise an error message and
# then exit 1 to kill the script.
echoErrorMsg "Could not execute deleteScratchOrg(). Unknown target org alias."
exit 1
fi
# Delete the current scratch org.
echoStepMsg "Delete the $ORG_ALIAS_TO_DELETE scratch org"
echo "Executing force:org:delete -p -u $ORG_ALIAS_TO_DELETE -v $DEV_HUB_ALIAS"
(cd $PROJECT_ROOT && exec sfdx force:org:delete -p -u $ORG_ALIAS_TO_DELETE -v $DEV_HUB_ALIAS )
}
#
##
###
#### FUNCTION: determineTargetOrgAlias () ##########################################################
###
##
#
determineTargetOrgAlias () {
# Start by clearing TARGET_ORG_ALIAS so we'll know for sure if a new value was provided
TARGET_ORG_ALIAS=""
# If no value was provided for $REQUESTED_OPERATION, set defaults and return success.
if [ -z "$REQUESTED_OPERATION" ]; then
PARENT_OPERATION="NOT_SPECIFIED"
TARGET_ORG_ALIAS="NOT_SPECIFIED"
return 0
else
case "$REQUESTED_OPERATION" in
"REBUILD_SCRATCH_ORG")
TARGET_ORG_ALIAS="$SCRATCH_ORG_ALIAS"
;;
"VALIDATE_PACKAGE_SOURCE")
TARGET_ORG_ALIAS="$PACKAGING_ORG_ALIAS"
;;
"DEPLOY_PACKAGE_SOURCE")
TARGET_ORG_ALIAS="$PACKAGING_ORG_ALIAS"
;;
"INSTALL_PACKAGE")
TARGET_ORG_ALIAS="$SUBSCRIBER_ORG_ALIAS"
;;
esac
# Make sure that TARGET_ORG_ALIAS was set. If not, it means an unexpected PARENT_OPERATION
# was provided. In that case, raise an error and abort the script.
if [ -z "$TARGET_ORG_ALIAS" ]; then
echo "\nFATAL ERROR: `tput sgr0``tput setaf 1`\"$REQUESTED_OPERATION\" is not a valid installation option.\n"
exit 1
fi
# If we get this far, it means that the REQUESTED_OPERATION was valid.
# We can now assign that to the PARENT_OPERATION variable and return success.
PARENT_OPERATION="$REQUESTED_OPERATION"
return 0
fi
}
#
##
###
#### FUNCTION: echoConfigVariables () ##############################################################
###
##
#
echoConfigVariables () {
echo ""
echo "`tput setaf 7`PROJECT_ROOT -------------->`tput sgr0` " $PROJECT_ROOT
echo "`tput setaf 7`NAMESPACE_PREFIX ---------->`tput sgr0` " $NAMESPACE_PREFIX
echo "`tput setaf 7`PACKAGE_NAME -------------->`tput sgr0` " $PACKAGE_NAME
echo "`tput setaf 7`DEFAULT_PACKAGE_DIR_NAME -->`tput sgr0` " $DEFAULT_PACKAGE_DIR_NAME
echo "`tput setaf 7`TARGET_ORG_ALIAS ---------->`tput sgr0` " $TARGET_ORG_ALIAS
echo "`tput setaf 7`DEV_HUB_ALIAS ------------->`tput sgr0` " $DEV_HUB_ALIAS
echo "`tput setaf 7`SCRATCH_ORG_ALIAS --------->`tput sgr0` " $SCRATCH_ORG_ALIAS
echo "`tput setaf 7`PACKAGING_ORG_ALIAS ------->`tput sgr0` " $PACKAGING_ORG_ALIAS
echo "`tput setaf 7`SUBSCRIBER_ORG_ALIAS ------>`tput sgr0` " $SUBSCRIBER_ORG_ALIAS
echo "`tput setaf 7`METADATA_PACKAGE_ID ------->`tput sgr0` " $METADATA_PACKAGE_ID
echo "`tput setaf 7`PACKAGE_VERSION_ID -------->`tput sgr0` " $PACKAGE_VERSION_ID
echo "`tput setaf 7`SCRATCH_ORG_CONFIG -------->`tput sgr0` " $SCRATCH_ORG_CONFIG
echo "`tput setaf 7`GIT_REMOTE_URI ------------>`tput sgr0` " $GIT_REMOTE_URI
echo "`tput setaf 7`ECHO_LOCAL_CONFIG_VARS ---->`tput sgr0` " $ECHO_LOCAL_CONFIG_VARS
echo ""
}
#
##
###
#### FUNCTION: echoErrorMsg () #####################################################################
###
##
#
echoErrorMsg () {
tput sgr 0; tput setaf 7; tput bold;
printf "\n\nERROR: "
tput sgr 0; tput setaf 1;
printf "%b\n\n" "$1"
tput sgr 0;
}
#
##
###
#### FUNCTION: echoQuestion () #####################################################################
###
##
#
echoQuestion () {
tput sgr 0; tput rev;
printf "\nQuestion $CURRENT_QUESTION of $TOTAL_QUESTIONS:"
printf " %b\n\n" "$1"
tput sgr 0;
let CURRENT_QUESTION++
}
#
##
###
#### FUNCTION: echoScriptCompleteMsg () ############################################################
###
##
#
echoScriptCompleteMsg () {
tput sgr 0; tput setaf 7; tput bold;
printf "\n\nScript Complete: "
tput sgr 0;
printf "%b\n\n" "$1"
tput sgr 0;
}
#
##
###
#### FUNCTION: echoStepMsg () ######################################################################
###
##
#
echoStepMsg () {
tput sgr 0; tput setaf 7; tput bold;
if [ $TOTAL_STEPS -gt 0 ]; then
## This is one of a sequence of steps
printf "\nStep $CURRENT_STEP of $TOTAL_STEPS:"
else
# This is likely a preliminary step, coming before a sequence.
printf "\nPreliminary Step $CURRENT_STEP:"
fi
tput sgr 0;
printf " %b\n\n" "$1"
tput sgr 0;
let CURRENT_STEP++
}
#
##
###
#### FUNCTION: echoWarningMsg () ###################################################################
###
##
#
echoWarningMsg () {
tput sgr 0; tput setaf 7; tput bold;
printf "\n\nWARNING: "
tput sgr 0;
printf "%b\n\n" "$1"
tput sgr 0;
}
#
##
###
#### FUNCTION: findProjectRoot () ##################################################################
###
##
#
findProjectRoot () {
# Detect the path to the directory that the running script was called from.
local PATH_TO_RUNNING_SCRIPT="$( cd "$(dirname "$0")" ; pwd -P )"
# Grab the last 10 characters of the detected path. This should be "/dev-tools".
local DEV_TOOLS_SLICE=${PATH_TO_RUNNING_SCRIPT: -10}
# Make sure the last 10 chars of the path are "/dev-tools".
# Kill the script with an error if not.
if [[ $DEV_TOOLS_SLICE != "/dev-tools" ]]; then
echoErrorMsg "Script was not executed within the <project-root>/dev-tools directory."
tput sgr 0; tput bold;
echo "Shell scripts that utilize FALCON Developer Tools must be executed from"
echo "inside the dev-tools directory found at the root of your SFDX project.\n"
exit 1
fi
# Calculate the Project Root path by going up one level from the path currently
# held in the PATH_TO_RUNNING_SCRIPT variable
local PATH_TO_PROJECT_ROOT="$( cd "$PATH_TO_RUNNING_SCRIPT" ; cd .. ; pwd -P )"
# Pass the value of the "detected path" back out to the caller by setting the
# value of the first argument provided when the function was called.
eval "$1=\"$PATH_TO_PROJECT_ROOT\""
}
#
##
###
#### FUNCTION: initializeHelperVariables () ########################################################
###
##
#
initializeHelperVariables () {
CONFIRM_EXECUTION="" # Indicates the user's choice whether to execute a script or not
PROJECT_ROOT="" # Path to the root of this SFDX project
TARGET_ORG_ALIAS="" # Target of all Salesforce CLI commands during this run
LOCAL_CONFIG_FILE_NAME=dev-tools/lib/local-config.sh # Name of the file that contains local config variables
CURRENT_STEP=1 # Used by echoStepMsg() to indicate the current step
TOTAL_STEPS=0 # Used by echoStepMsg() to indicate total num of steps
CURRENT_QUESTION=1 # Used by echoQuestion() to indicate the current question
TOTAL_QUESTIONS=1 # Used by echoQuestion() to indicate total num of questions
# Call findProjectRoot() to dynamically determine
# the path to the root of this SFDX project
findProjectRoot PROJECT_ROOT
}
#
##
###
#### FUNCTION: installPackage () ###################################################################
###
##
#
installPackage () {
# Delcare a local variable to store the Alias of the target install org.
local PACKAGE_INSTALL_TARGET_ALIAS=""
# Check if a target org was provided as the FOURTH argument. If there
# is no FOURTH argument, go with whatever is set in the TARGET_ORG_ALIAS
# variable. If that is empty, then exit with an error.
if [ ! -z $4 ]; then
PACKAGE_INSTALL_TARGET_ALIAS="$4"
elif [ ! -z $TARGET_ORG_ALIAS ]; then
PACKAGE_INSTALL_TARGET_ALIAS="$TARGET_ORG_ALIAS"
else
# Something went wrong. No FOURTH argument was provided and the TARGET_ORG_ALIAS
# has not yet been set or is an empty string. Raise an error message and
# then exit 1 to kill the script.
echoErrorMsg "Could not execute installPackage(). Unknown target org alias."
exit 1
fi
# Echo the string provided by argument three. This string should provide the
# user with an easy-to-understand idea of what package is being installed.
echoStepMsg "$3"
# Print the time (HH:MM:SS) when the installation started.
echo "Executing force:package:install --package $1 --publishwait 5 --wait 10 -u $PACKAGE_INSTALL_TARGET_ALIAS"
echo "\n`tput bold`Package installation started at `date +%T``tput sgr0`\n"
local startTime=`date +%s`
# Perform the package installation. If the installation fails abort the script.
(cd $PROJECT_ROOT && exec sfdx force:package:install --package $1 --publishwait 5 --wait 10 -u $PACKAGE_INSTALL_TARGET_ALIAS)
if [ $? -ne 0 ]; then
echoErrorMsg "$2 could not be installed. Aborting Script."
exit 1
fi
# Print the time (HH:MM:SS) when the installation completed.
echo "\n`tput bold`Package installation completed at `date +%T``tput sgr0`"
local endTime=`date +%s`
# Determine the total runtime (in seconds) and show the user.
local totalRuntime=$((endTime-startTime))
echo "Total runtime for package installation was $totalRuntime seconds."
}
#
##
###
#### FUNCTION: resetQuestionCounter () #############################################################
###
##
#
resetQuestionCounter () {
CURRENT_QUESTION=1
TOTAL_QUESTIONS=$1
}
#
##
###
#### FUNCTION: resetStepMsgCounter () ##############################################################
###
##
#
resetStepMsgCounter () {
CURRENT_STEP=1
TOTAL_STEPS=$1
}
#
##
###
#### FUNCTION: showPressAnyKeyPrompt ###############################################################
###
##
#
showPressAnyKeyPrompt () {
read -n 1 -sr -p "-- Press any Key to Continue --"
}
#
##
###
#### FUNCTION: suggestDefaultValue #################################################################
###
##
#
suggestDefaultValue () {
# Make sure a value was provided for the
# second argument of this function.
if [ "$2" = "" ]; then
echoErrorMsg "You must provide two arguments to suggestDefaultValue. Terminating script."
exit 1
fi
# Create local variables to store the value of
# the variable provided by the first argument and the
# value of the second argument (proposed default)
eval "local LOCAL_VALUE=\$$1"
eval "local LOCAL_DEFAULT=\"$2\""
# Set a defualt prompt message in case one is not provided.
local INTERNAL_USER_PROMPT="Would you like to accept the following default value?"
# If the caller supplied a third argument, it means they want
# a specific message to be shown before accepting the default.
# If they did now, we will use owr own "default" message.
if [ ! -z "$3" ]; then
INTERNAL_USER_PROMPT="$3"
fi
# Show prompt and display what the default var assignment would be.
echo $INTERNAL_USER_PROMPT
echo "\n"$1=$LOCAL_DEFAULT"\n"
# Ask user to confirm or reject the proposed value.
local ACCEPT_DEFAULT=""
read -p "(type YES to accept, NO to provide a different value) " ACCEPT_DEFAULT
if [ "$ACCEPT_DEFAULT" != "YES" ]; then
return 1
fi
# Store the value from arg 2 into arg 1, basically
# using the "default" value for the main value.
eval "$1=\"$2\""
return 0
}
#
##
###
#### BEGIN MAIN EXECUTION BLOCK ####################################################################
###
##
#
# INITIALIZE HELPER VARIABLES
initializeHelperVariables
# CHECK IF LOCAL CONFIG SHOULD BE SUPPRESSED.
# If $SUPPRESS_LOCAL_CONFIG has been set to "true" DO NOT load the local configuration
# variables. A script that includes shared-functions.sh can set this variable to
# force this behavior (dev-tools/setup-core-project for example).
if [ "$SUPPRESS_LOCAL_CONFIG" = "true" ]; then
# Comment out the following line unless you're debugging setup-core-project.
# echo "Local dev-tools configuration (local-config.sh) has been suppressed"
return 0
fi
# CHECK IF LOCAL CONFIG FILE EXISTS
# Look for the local config variables script local-config.sh. If the developer has not created a
# local-config.sh file in dev-tools/lib then EXIT from the shell script with an error message.
if [ ! -r "$PROJECT_ROOT/$LOCAL_CONFIG_FILE_NAME" ]; then
echoErrorMsg "Local dev-tools configuration file not found"
tput sgr 0; tput bold;
echo "Please create a local-config.sh file in your dev-tools/lib directory by copying"
echo "dev-tools/templates/local-config-template.sh and customizing it with your local settings\n"
exit 1
fi
# LOAD THE LOCAL CONFIG VARIABLES
# The local-config.sh file was found and is readable. Source (execute) it the current shell process.
# This will make all the variables defined in local-config.sh available to all commands that come
# after it in this shell.
source "$PROJECT_ROOT/$LOCAL_CONFIG_FILE_NAME"
# MARK THAT LOCAL CONFIG VARIABLES HAVE BEEN SET.
# Indicates that local config variables have been successfully set.
SFDX_FALCON_FRAMEWORK_SHELL_VARS_SET="true"
##END##
+262
View File
@@ -0,0 +1,262 @@
#!/bin/bash
####################################################################################################
#
# FILENAME: rebuild-scratch-org
#
# PURPOSE: Deletes then recreates a scratch org based on the SFDX source in this project.
#
# DESCRIPTION: Executing this script will first delete the exisisting default scratch org for
# this project (if it exists), then create a new one using the source and config
# information defined in your dev-tools/lib/local-config.sh file.
#
# INSTRUCTIONS: Execute the following command from the root of your SFDX project directory.
# ./dev-tools/rebuild-scratch-org
#
####################################################################################################
#
##
###
#### LOAD SHARED FUNCTIONS LIBRARY #################################################################
###
##
#
# Make sure that the shared-functions.sh script exists.
if [ ! -r `dirname $0`/lib/shared-functions.sh ]; then
echo "\nFATAL ERROR: `tput sgr0``tput setaf 1`Could not load dev-tools/lib/shared-functions.sh. File not found.\n"
exit 1
fi
# Indicate the operation being carried out by this script.
REQUESTED_OPERATION="REBUILD_SCRATCH_ORG"
# Load the shared-functions.sh library. This action will also cause the
# config variables from dev-tools/lib/local-config.sh to be loaded.
source `dirname $0`/lib/shared-functions.sh
#
##
###
#### CONFIRM SCRIPT EXECUTION ######################################################################
###
##
#
confirmScriptExecution "Do you want to rebuild your scratch org?"
#
##
###
#### CREATE LOCAL VARIABLES ########################################################################
###
##
#
# The default version of this script does not require additional local
# variables. If your customized script does require them, this is where
# you would define and initialize them.
#
##
###
#### FUNCTION: assignPermset () ####################################################################
###
##
#
assignPermset () {
# Assign permission sets to the scratch org's Admin user.
echoStepMsg "Assign the $1 permission set to the scratch org's Admin user"
echo \
"Executing force:user:permset:assign \\
--permsetname "$1" \\
--targetusername $SCRATCH_ORG_ALIAS \\
--loglevel error\n"
(cd $PROJECT_ROOT && exec sfdx force:user:permset:assign \
--permsetname "$1" \
--targetusername $SCRATCH_ORG_ALIAS \
--loglevel error)
if [ $? -ne 0 ]; then
echoErrorMsg "Permission set \"$1\" could not be assigned to the admin user. Aborting Script."
exit 1
fi
}
#
##
###
#### FUNCTION: createScratchOrg () #################################################################
###
##
#
createScratchOrg() {
# Create a new scratch org using the scratch-def.json locally configured for this project.
echoStepMsg "Create a new $SCRATCH_ORG_ALIAS scratch org"
echo "Executing force:org:create -f $SCRATCH_ORG_CONFIG -a $SCRATCH_ORG_ALIAS -v $DEV_HUB_ALIAS -s -d 29"
(cd $PROJECT_ROOT && exec sfdx force:org:create -f $SCRATCH_ORG_CONFIG -a $SCRATCH_ORG_ALIAS -v $DEV_HUB_ALIAS -s -d 29)
if [ $? -ne 0 ]; then
echoErrorMsg "Scratch org could not be created. Aborting Script."
exit 1
fi
}
#
##
###
#### FUNCTION: deleteScratchOrg () #################################################################
###
##
#
deleteScratchOrg () {
# Delete the current scratch org.
echoStepMsg "Delete the $SCRATCH_ORG_ALIAS scratch org"
echo "Executing force:org:delete -p -u $SCRATCH_ORG_ALIAS -v $DEV_HUB_ALIAS"
(cd $PROJECT_ROOT && exec sfdx force:org:delete -p -u $SCRATCH_ORG_ALIAS -v $DEV_HUB_ALIAS )
}
#
##
###
#### FUNCTION: importData () #######################################################################
###
##
#
importData () {
# Setup development data
echoStepMsg "Import data from $1"
echo \
"Executing force:data:tree:import \\
--plan \"$1\" \\
--targetusername $SCRATCH_ORG_ALIAS \\
--loglevel error)\n"
(cd $PROJECT_ROOT && exec sfdx force:data:tree:import \
--plan "$1" \
--targetusername $SCRATCH_ORG_ALIAS \
--loglevel error)
if [ $? -ne 0 ]; then
echoErrorMsg "Data import failed. Aborting Script."
exit 1
fi
}
#
##
###
#### FUNCTION: installPackage () ###################################################################
###
##
#
installPackage () {
# Echo the string provided by argument three. This string should provide the
# user with an easy-to-understand idea of what package is being installed.
echoStepMsg "$3"
# Print the time (HH:MM:SS) when the installation started.
echo "Executing force:package:install -i $1 -p 5 -w 10 -u $SCRATCH_ORG_ALIAS"
echo "\n`tput bold`Package installation started at `date +%T``tput sgr0`\n"
local startTime=`date +%s`
# Perform the package installation. If the installation fails abort the script.
(cd $PROJECT_ROOT && exec sfdx force:package:install -i $1 -p 5 -w 10 -u $SCRATCH_ORG_ALIAS)
if [ $? -ne 0 ]; then
echoErrorMsg "$2 could not be installed. Aborting Script."
exit 1
fi
# Print the time (HH:MM:SS) when the installation completed.
echo "\n`tput bold`Package installation completed at `date +%T``tput sgr0`"
local endTime=`date +%s`
# Determine the total runtime (in seconds) and show the user.
local totalRuntime=$((endTime-startTime))
echo "Total runtime for package installation was $totalRuntime seconds."
}
#
##
###
#### FUNCTION: pushMetadata () #####################################################################
###
##
#
pushMetadata () {
# Push metadata to the new Scratch Org.
echoStepMsg "Push metadata to the new scratch org"
echo "Executing force:source:push -u $SCRATCH_ORG_ALIAS"
(cd $PROJECT_ROOT && exec sfdx force:source:push -u $SCRATCH_ORG_ALIAS)
if [ $? -ne 0 ]; then
echoErrorMsg "SFDX source could not be pushed to the scratch org. Aborting Script."
exit 1
fi
}
#
##
###
#### FUNCTION: validateScratchOrgDeletion () #######################################################
###
##
#
validateScratchOrgDeletion () {
# Confirm that the scratch org is no longer the default username in SFDX local config.
echoStepMsg "Validate deletion of the scratch org"
echo "Executing sfdx force:config:list to confirm deletion from project local config"
(cd $PROJECT_ROOT && exec sfdx force:config:list)
}
#
##
###
#### SCRATCH ORG SETUP (DELETE/CREATE/PUSH) ########################################################
###
##
#
# STEP 0
# Reset the Step Message counter to reflect the number of TOTAL STEPS
# in your rebuild process. For the baseline SFDX-Falcon template it's 4.
resetStepMsgCounter 5
# STEPS 1 and 2
# Delete the current scratch org.
deleteScratchOrg
# STEP 3
# Create a new scratch org using the scratch-def.json locally configured for this project.
createScratchOrg
# STEP 4 through ???
# Install any packages (managed or unmanaged).
# Template for calling this function:
# installPackage #PACKAGE_VERSION_ID# \
# "#PACKAGE_NAME#" \
# "#STEP_MESSAGE#"
echoStepMsg "Installing fflib-apex-mocks and fflib-apex-common to $SCRATCH_ORG_ALIAS scratch org"
mkdir $PROJECT_ROOT/temp
cd $PROJECT_ROOT/temp
rm -Rf fflib-apex-mocks/
rm -Rf fflib-apex-common/
git clone https://github.com/apex-enterprise-patterns/fflib-apex-mocks.git
git clone https://github.com/apex-enterprise-patterns/fflib-apex-common.git
sfdx force:source:deploy -p ./fflib-apex-mocks/sfdx-source -u $SCRATCH_ORG_ALIAS
sfdx force:source:deploy -p ./fflib-apex-common/sfdx-source -u $SCRATCH_ORG_ALIAS
# STEP 5 through ???
# Assign any permission sets that were added by installed packages.
# Template for calling this function:
# assignPermset #PACKAGED_PERMSET_NAME#
# STEP 6
# Push metadata to the new Scratch Org.
pushMetadata
# STEP 7 through ????
# Assign any permission sets that were added by your Source Push.
# Template for calling this function:
# assignPermset #PERMSET_NAME#
# STEP 8 through ????
# Import data used during development. You may need to make multiple calls
# Template for calling this function:
# importData "$PROJECT_ROOT/data/#DATA_PLAN_JSON#"
#
##
###
#### ECHO CLOSING MESSAGE ##########################################################################
###
##
#
echoScriptCompleteMsg \
"Rebuild of scratch org $SCRATCH_ORG_ALIAS completed successfully."
exit 0
##END##
@@ -0,0 +1,87 @@
#!/bin/bash
#<%%#
####################################################################################################
#
# FILENAME: local-config-template.sh.ejs
#
# PURPOSE: Template for creating a personalized local-config.sh file.
#
# DESCRIPTION: All shell scripts in the dev-tools directory require several configuration values
# to run correctly (eg. the path to your project's root directory or the alias of
# the DevHub that you want to use. These customizations can be specific to each
# individual developer, and therefore should not be tracked by the project's VCS.
#
# This file serves as a template that Release Managers can use to establish baseline
# values for their project. When individaul developers clone the repo and initialize
# their local, personal version of the project this template is used to create a
# customized local-config.sh file at <project-root>/dev-tools/lib/local-config.sh
#
# INSTRUCTIONS: 1. Inside of the dev-tools directory, execute the following command
# cp ./templates/local-config-template.sh.ejs ./lib/local-config.sh
# 2. Edit the default values in your local-config.sh to meet the needs of your local
# environment and project setup.
#
####################################################################################################
#%>
##
###
#### DEFINE LOCAL CONFIGURATION VARIABLES ##########################################################
###
##
#
# Alias for the Dev Hub that should be used when creating scratch orgs for this project.
# This variable will always need to be customized for individual developers.
DEV_HUB_ALIAS="<%%-userAnswers.devHubAlias%>"
# Namespace Prefix. Set to empty string ("") if this project is not building a managed package.
NAMESPACE_PREFIX="<%-userAnswers.namespacePrefix%>"
# Package Name. Specified as part of the Package Detail info in your packaging org.
# Surround this value with double-quotes if your package name contains space characters.
# Set to empty string ("") if this project is not building a managed package.
PACKAGE_NAME="<%-userAnswers.packageName%>"
# Metadata Package ID. Refers to the metadata package as a whole. Must begin with "033".
# Set to empty string ("") if this project is not building a managed package.
METADATA_PACKAGE_ID="<%-userAnswers.metadataPackageId%>"
# Package Version ID. Refers to a specific, installable version of a package. Must begin with "04t".
# Set to empty string ("") if this project is not building a managed package.
PACKAGE_VERSION_ID="<%-userAnswers.packageVersionId%>"
# Default Package Directory. Should match what is set in sfdx-project.json.
DEFAULT_PACKAGE_DIR_NAME="<%-userAnswers.packageDirectory%>"
# Alias for the primary Scratch Org used by this project.
SCRATCH_ORG_ALIAS="$NAMESPACE_PREFIX-SCRATCH"
# Alias for the packaging org for this project.
PACKAGING_ORG_ALIAS="$NAMESPACE_PREFIX-PACKAGE"
# Alias for the subscriber test org used to test managed-beta package installs.
SUBSCRIBER_ORG_ALIAS="$NAMESPACE_PREFIX-SUBSCRIBER"
# Git Remote URI. SSH or HTTPS URI that points to the Git remote repo used by this project.
# GitHub is used as an example here, but any Git remote (ie. BitBucket) can be used.
# Set to empty string ("") if this project is not being tracked in a remote repository.
GIT_REMOTE_URI="<%-userAnswers.gitRemoteUri%>"
# Location of the primary scratch-def.json file that should be used by SFDX-Falcon scripts that
# create scratch orgs (eg. rebuild-scratch-org).
SCRATCH_ORG_CONFIG="$PROJECT_ROOT/config/project-scratch-def.json"
# Echo the variables set by this script prior to exiting. Specify "false" to suppress the
# display of local config that normally occurs when executing SFDX-Falcon based scripts.
ECHO_LOCAL_CONFIG_VARS="true"
#
##
###
#### ECHO ALL VARIABLES ############################################################################
###
##
#
if [ "$ECHO_LOCAL_CONFIG_VARS" = "true" ]; then
echo "\n`tput setaf 7``tput bold`Local configuration variables set by `dirname $0`/lib/local-config.sh`tput sgr0`\n"
echoConfigVariables
fi
##END##
@@ -150,7 +150,7 @@ public virtual class fflib_ClassicDomainFactory
* avoids the need to stub the mock to return its ObjectType
* @param mockDomain The instance of the Domain mock
*/
public virtual void setMock(Type domainType, fflib_IDomain mockDomain)
public virtual void setMock(Object domainType, fflib_IDomain mockDomain)
{
mockDomainByObject.put(domainType, mockDomain);
}
@@ -0,0 +1,131 @@
/**
* File Name: fflib_AppBinding
* Description: Definition for an Application Binding
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public virtual class fflib_AppBinding
implements fflib_IAppBinding, Comparable
{
protected Object bindingObject { get; set; }
protected Decimal bindingSequence { get; set; }
protected fflib_AppBindingType bindingType { get; set; }
protected System.Type implementationType { get; set; }
protected System.Type interfaceType { get; set; }
protected String namespacePrefix { get; set; }
protected fflib_SharingMode sharingMode { get; set; }
public Integer compareTo(Object compareTo)
{
fflib_IAppBinding compareToEmp = (fflib_IAppBinding) compareTo;
if (getSequence() == compareToEmp.getSequence()) return 0;
if (getSequence() > compareToEmp.getSequence()) return 1;
return -1;
}
public Object getBindingObject()
{
return this.bindingObject;
}
public virtual fflib_AppBindingType getBindingType()
{
return this.bindingType;
}
public virtual System.Type getImplementationType()
{
return this.implementationType;
}
public Type getInterfaceType()
{
return this.interfaceType;
}
public String getNamespacePrefix()
{
return this.namespacePrefix;
}
public Decimal getSequence()
{
return this.bindingSequence;
}
public fflib_SharingMode getSharingMode()
{
return this.sharingMode;
}
public virtual Object newImplInstance()
{
return implementationType.newInstance();
}
public virtual fflib_IAppBinding setBindingObject(Object bindingObject)
{
this.bindingObject = bindingObject;
return this;
}
public virtual fflib_IAppBinding setBindingType(fflib_AppBindingType bindingType)
{
this.bindingType = bindingType;
return this;
}
public virtual fflib_IAppBinding setNamespacePrefix(String namespacePrefix)
{
this.namespacePrefix = namespacePrefix;
return this;
}
public virtual fflib_IAppBinding setImplementationType(System.Type implementationType)
{
this.implementationType = implementationType;
return this;
}
public virtual fflib_IAppBinding setInterfaceType(Type interfaceType)
{
this.interfaceType = interfaceType;
return this;
}
public virtual fflib_IAppBinding setSequence(Decimal sequence)
{
this.bindingSequence = sequence;
return this;
}
public virtual fflib_IAppBinding setSharingMode(fflib_SharingMode sharingMode)
{
this.sharingMode = sharingMode;
return this;
}
}
@@ -0,0 +1,139 @@
/**
* File Name: fflib_AppBindingMetaDataModule
* Description: An application module responsible for loading the application bindings
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public with sharing class fflib_AppBindingMetaDataModule
extends fflib_AppBindingModule
{
public override void init()
{
this.bindings = new List<fflib_IAppBinding>();
for (fflib_AppBinding__mdt binding : fflib_AppBindingsSelector.newInstance().selectAll())
{
addBinding(
new fflib_AppBinding()
.setBindingObject(getBindingObject(binding))
.setBindingType(getBindingType(binding))
.setImplementationType(getSystemType(binding, fflib_AppBinding__mdt.ImplementationType__c))
.setInterfaceType(getSystemType(binding, fflib_AppBinding__mdt.InterfaceType__c))
.setNamespacePrefix(binding.NamespacePrefix)
.setSequence(binding.BindingSequence__c)
.setSharingMode(getSharingModeFromString(binding.SharingMode__c))
);
}
}
private Object getBindingObject(fflib_AppBinding__mdt binding)
{
if (String.isBlank(binding.BindingObject__c) && String.isBlank(binding.BindingObjectAlternate__c)) return null;
String bindingObjectApiName = String.isNotBlank(binding.BindingObject__c)
? binding.BindingObject__r.QualifiedApiName.toLowerCase().trim()
: binding.BindingObjectAlternate__c.toLowerCase().trim();
// Assume its an SObjectType
List<Schema.DescribeSObjectResult> schemaDescribes = Schema.describeSObjects(new List<String> {bindingObjectApiName});
if (schemaDescribes.size() == 1)
{
return schemaDescribes.get(0).getSObjectType();
}
else
{
// check if its an Object (class)
Type objectType = System.Type.forName(bindingObjectApiName);
if (objectType == null)
throw new DeveloperException('Failed to find (S)Object ' + bindingObjectApiName + ' referenced by binding ' + binding.DeveloperName);
return objectType;
}
}
private fflib_AppBindingType getBindingType(fflib_AppBinding__mdt binding)
{
Map<String, fflib_AppBindingType> bindingTypeByStrings = new Map<String, fflib_AppBindingType>();
for (fflib_AppBindingType enumValue : fflib_AppBindingType.values())
{
bindingTypeByStrings.put(String.valueOf(enumValue).toUpperCase(), enumValue);
}
if (bindingTypeByStrings.containsKey(binding.Type__c.toUpperCase()) == false)
throw new DeveloperException(
String.format(
'Unable to identify binding type {0} referenced by binding {1}',
new List<String>{ binding.Type__c, binding.DeveloperName}
)
);
return bindingTypeByStrings.get(binding.Type__c.toUpperCase());
}
private fflib_SharingMode getSharingModeFromString(String sharingMode)
{
switch on sharingMode.toUpperCase()
{
when 'WITH SHARING'
{
return fflib_SharingMode.WithSharing;
}
when 'WITHOUT SHARING'
{
return fflib_SharingMode.WithoutSharing;
}
when 'INHERITED SHARING'
{
return fflib_SharingMode.WithoutSharing;
}
when 'NONE'
{
return null;
}
}
return null;
}
private Type getSystemType(fflib_AppBinding__mdt binding, Schema.SObjectField sObjectField)
{
String systemTypeName = String.valueOf(binding.get(sObjectField));
if (null == systemTypeName) return null;
Type systemType = System.Type.forName(systemTypeName);
if (null == systemType)
throw new DeveloperException(
String.format(
'Unable to find {0} referenced by binding {1} for {2}',
new List<String>{ systemTypeName, binding.DeveloperName, sObjectField.getDescribe().getName()}
)
);
return systemType;
}
public class DeveloperException extends Exception
{
}
}
@@ -0,0 +1,71 @@
/**
* File Name: fflib_AppBindingModule
* Description: An application module is responsible for loading the application bindings
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public virtual class fflib_AppBindingModule
implements fflib_IAppBindingModule
{
protected List<fflib_IAppBinding> bindings;
public fflib_AppBindingModule(){ }
public fflib_AppBindingModule(List<fflib_IAppBinding> bindings)
{
this.bindings = bindings;
}
public virtual void addBinding(fflib_IAppBinding binding)
{
if (null == this.bindings) this.bindings = new List<fflib_IAppBinding>();
this.bindings.add(binding);
}
public virtual void addBindings(List<fflib_IAppBinding> bindings)
{
if (null == this.bindings)
{
this.bindings = bindings;
}
else
{
this.bindings.addAll(bindings);
}
}
public List<fflib_IAppBinding> getBindings()
{
return this.bindings;
}
public virtual void init()
{
if (null == this.bindings) this.bindings = new List<fflib_IAppBinding>();
}
}
@@ -0,0 +1,318 @@
/**
* File Name: fflib_AppBindingResolver
* Description: Resolver for returning the requested binding(s)
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public virtual class fflib_AppBindingResolver
implements fflib_IAppBindingResolver
{
// allow for dependency injection inside the resolver
protected InternalBindings internalBindings
{
get
{
if (null == internalBindings)
{
internalBindings = new InternalBindings();
}
return internalBindings;
}
private set;
}
protected fflib_IAppBinding bindingToResolve
{
get
{
if (bindingToResolve == null)
{
bindingToResolve = internalBindings.newAppBinding();
}
return bindingToResolve;
}
private set;
}
protected fflib_IAppBindings bindings;
private List<fflib_IAppBindingModule> modules;
public static fflib_IAppBindingResolver newInstance()
{
return new fflib_AppBindingResolver();
}
/**
* Constructors
*/
public fflib_AppBindingResolver()
{
}
/**
* Providing routes to the AppBindingResolver constructor provides dependency injection inside the resolver
*
* @param routes The routes
*/
public fflib_AppBindingResolver(fflib_IAppBindingRouter routes)
{
this(routes.getRoutes());
}
public fflib_AppBindingResolver(Map<System.Type, System.Type> internalBindings)
{
this.internalBindings = new InternalBindings(internalBindings);
}
public fflib_IAppBindingResolver addModule(fflib_IAppBindingModule bindingModule)
{
if (null == this.modules) this.modules = new List<fflib_IAppBindingModule>();
this.modules.add(bindingModule);
return this;
}
public fflib_IAppBindingResolver byObject(Object bindingObject)
{
this.bindingToResolve.setBindingObject(bindingObject);
return this;
}
public fflib_IAppBindingResolver bySequence(Integer sequence)
{
this.bindingToResolve.setSequence(sequence);
return this;
}
public fflib_IAppBindingResolver byType(fflib_AppBindingType bindingType)
{
this.bindingToResolve.setBindingType(bindingType);
return this;
}
public fflib_IAppBindingResolver byInterfaceType(Type interfaceType)
{
this.bindingToResolve.setInterfaceType(interfaceType);
return this;
}
public fflib_IAppBindingResolver bySharingMode(fflib_SharingMode sharingMode)
{
this.bindingToResolve.setSharingMode(sharingMode);
return this;
}
public fflib_IAppBindingResolver byNamespacePrefix(String namespacePrefix)
{
this.bindingToResolve.setNamespacePrefix(namespacePrefix);
return this;
}
/**
* Handles lazy loading of modules, if no custom module is configured (via addModule) the default module is returned
*
* @return Returns the configured modules
*/
public List<fflib_IAppBindingModule> getModules()
{
if (null == this.modules)
{
// Lazy load the default Binding module is none other is set.
modules = new List<fflib_IAppBindingModule>
{
internalBindings.newAppBindingModule()
};
}
return modules;
}
public List<fflib_IAppBinding> resolve()
{
if (!hasBindingsLoaded()) loadBindings();
fflib_IAppBindings resolvedBindings =
this.bindings
.selectByNamespacePrefix(bindingToResolve.getNamespacePrefix())
.selectByType(bindingToResolve.getBindingType())
.selectByInterfaceType(bindingToResolve.getInterfaceType())
.selectByObject(bindingToResolve.getBindingObject())
.selectBySharingMode(bindingToResolve.getSharingMode())
.selectBySequence(bindingToResolve.getSequence());
this.bindingToResolve = internalBindings.newAppBinding();
return resolvedBindings.getBindings();
}
protected Boolean hasBindingsLoaded()
{
return (this.bindings != null);
}
/**
* Prepares a domain to store the bindings,
* retrieves the bindings from the modules
* and stores the bindings in this.bindings
*/
protected virtual void loadBindings()
{
this.bindings = internalBindings.newAppBindings();
List<fflib_IAppBindingModule> modules = getModules();
List<fflib_IAppBinding> appBindings = loadModules(modules);
this.bindings.addBindings(
appBindings
);
}
/**
* Initialises all the modules and retrieves their binding.
* Any child modules in all nested will also be initialised and processed
*
* @param modules The modules to load their binding
*
* @return Returns the bindings configured in the given modules
*/
protected virtual List<fflib_IAppBinding> loadModules(List<fflib_IAppBindingModule> modules)
{
List<fflib_IAppBinding> result = new List<fflib_IAppBinding>();
for (fflib_IAppBindingModule module : getModules())
{
module.init();
List<fflib_IAppBinding> bindings = module.getBindings();
List<fflib_IAppBindingModule> subModules = getModulesFromBindings(bindings);
if (subModules.isEmpty() == false)
{
result.addAll(
loadModules(subModules)
);
}
result.addAll(bindings);
}
return result;
}
/**
* Retrieves the bindings of the type module from the given list and returns their instance
*
* @param bindings The bindings to filter
*
* @return Returns the instances of Module bindings
*/
private List<fflib_IAppBindingModule> getModulesFromBindings(List<fflib_IAppBinding> bindings)
{
List<fflib_IAppBindingModule> result = new List<fflib_IAppBindingModule>();
for (fflib_IAppBinding binding : bindings)
{
if (binding.getBindingType() != fflib_AppBindingType.Module) continue;
Object instance = binding.newImplInstance();
if (!(instance instanceof fflib_IAppBindingModule))
throw new DeveloperException(
'Binding module should be an instance of fflib_IAppBindingModule, instead found: '
+ binding.getImplementationType().getName()
);
result.add(
(fflib_IAppBindingModule) instance
);
}
return result;
}
public class InternalBindings
{
private Map<System.Type, System.Type> bindings;
public InternalBindings()
{
}
public InternalBindings(Map<System.Type, System.Type> internalBindings)
{
this.bindings = internalBindings;
}
public fflib_IAppBinding newAppBinding()
{
System.Type interfaceType = fflib_IAppBinding.class;
if (hasInternalBinding(interfaceType))
{
Object instance = getInternalBinding(interfaceType);
if (!(instance instanceof fflib_IAppBinding))
throw new DeveloperException('Invalid internal binding for fflib_IAppBinding');
return (fflib_IAppBinding) instance;
}
return new fflib_AppBinding();
}
public fflib_IAppBindings newAppBindings()
{
System.Type interfaceType = fflib_IAppBindings.class;
if (hasInternalBinding(interfaceType))
{
Object instance = getInternalBinding(interfaceType);
if (!(instance instanceof fflib_IAppBinding))
throw new DeveloperException('Invalid internal binding for fflib_IAppBindings');
return (fflib_IAppBindings) instance;
}
return new fflib_AppBindings();
}
private Boolean hasInternalBinding(System.Type interfaceType)
{
return this.bindings != null && this.bindings.containsKey(interfaceType);
}
private Object getInternalBinding(System.Type interfaceType)
{
return this.bindings.get(interfaceType).newInstance();
}
private fflib_IAppBindingModule newAppBindingModule()
{
System.Type interfaceType = fflib_IAppBindingModule.class;
if (hasInternalBinding(interfaceType))
{
Object instance = getInternalBinding(interfaceType);
if (!(instance instanceof fflib_IAppBindingModule))
throw new DeveloperException('Invalid internal binding for fflib_IAppBindingModule');
return (fflib_IAppBindingModule) instance;
}
return new fflib_AppBindingMetaDataModule();
}
}
public class DeveloperException extends Exception {}
}
@@ -0,0 +1,34 @@
/**
* File Name: fflib_AppBindingType
* Description: Enum for Binding Types
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public enum fflib_AppBindingType
{
Domain, Service, Selector, Controller, Module, Misc
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,178 @@
/**
* File Name: fflib_AppBindings
* Description: Domain class for fflib_AppBinding objects
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public virtual class fflib_AppBindings
implements fflib_IAppBindings
{
protected List<fflib_IAppBinding> bindings;
public fflib_AppBindings() { }
public fflib_AppBindings(List<fflib_IAppBinding> bindings)
{
this.bindings = bindings;
}
public void addBinding(fflib_IAppBinding binding)
{
if (null == this.bindings)
{
this.bindings = new List<fflib_IAppBinding>{ binding };
}
{
this.bindings.add(binding);
}
}
public void addBindings(List<fflib_IAppBinding> bindings)
{
if (null == this.bindings)
{
this.bindings = bindings;
}
else
{
this.bindings.addAll(bindings);
}
}
public fflib_IAppBindings selectByObject(Object bindingObject)
{
if (null == bindingObject) return this;
List<fflib_IAppBinding> result = new List<fflib_IAppBinding>();
for (fflib_IAppBinding binding : bindings)
{
if (binding.getBindingObject() != bindingObject) continue;
result.add(binding);
}
return new fflib_AppBindings(result);
}
public fflib_IAppBindings selectBySequence(Decimal sequence)
{
bindings.sort();
if (null == sequence) return this;
List<fflib_IAppBinding> result = new List<fflib_IAppBinding>();
for (fflib_IAppBinding binding : bindings)
{
if (binding.getSequence() != sequence) continue;
result.add(binding);
}
return new fflib_AppBindings(result);
}
public fflib_IAppBindings selectByType(fflib_AppBindingType bindingType)
{
if (null == bindingType) return this;
List<fflib_IAppBinding> result = new List<fflib_IAppBinding>();
for (fflib_IAppBinding binding : bindings)
{
if (binding.getBindingType() != bindingType) continue;
result.add(binding);
}
return new fflib_AppBindings(result);
}
public fflib_IAppBindings selectByInterfaceType(Type interfaceType)
{
if (null == interfaceType) return this;
List<fflib_IAppBinding> result = new List<fflib_IAppBinding>();
for (fflib_IAppBinding binding : bindings)
{
if (binding.getInterfaceType() != interfaceType) continue;
result.add(binding);
}
return new fflib_AppBindings(result);
}
public fflib_IAppBindings selectBySharingMode(fflib_SharingMode sharingMode)
{
if (null == sharingMode) return this;
List<fflib_IAppBinding> result = new List<fflib_IAppBinding>();
for (fflib_IAppBinding binding : bindings)
{
if (binding.getSharingMode() != sharingMode) continue;
result.add(binding);
}
return new fflib_AppBindings(result);
}
public fflib_IAppBindings selectByNamespacePrefix(String namespacePrefix)
{
if (null == namespacePrefix) return this;
List<fflib_IAppBinding> result = new List<fflib_IAppBinding>();
for (fflib_IAppBinding binding : bindings)
{
if (binding.getNamespacePrefix() != namespacePrefix) continue;
result.add(binding);
}
return new fflib_AppBindings(result);
}
public List<fflib_IAppBinding> getBindings()
{
return this.bindings;
}
public List<System.Type> getImplementations()
{
List<System.Type> result = new List<System.Type>();
for (fflib_IAppBinding binding : bindings)
{
result.add(
binding.getImplementationType()
);
}
return result;
}
public List<Object> getObjects()
{
return getBindings();
}
public Object getType()
{
return fflib_IAppBinding.class;
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,93 @@
/**
* File Name: fflib_AppBindingsSelector
* Description: Selector for fflib_AppBinding__mdt records
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public virtual without sharing class fflib_AppBindingsSelector extends fflib_SObjectSelector
{
// Used for mocking
@TestVisible
private static System.Type SELECTOR_IMPL_TYPE = fflib_AppBindingsSelector.class;
public List<Schema.SObjectField> getSObjectFieldList()
{
return new List<Schema.SObjectField>
{
fflib_AppBinding__mdt.Id,
fflib_AppBinding__mdt.BindingObject__c,
fflib_AppBinding__mdt.BindingObjectAlternate__c,
fflib_AppBinding__mdt.BindingSequence__c,
fflib_AppBinding__mdt.DeveloperName,
fflib_AppBinding__mdt.NamespacePrefix,
fflib_AppBinding__mdt.ImplementationType__c,
fflib_AppBinding__mdt.InterfaceType__c,
fflib_AppBinding__mdt.SharingMode__c,
fflib_AppBinding__mdt.Type__c
};
}
public static fflib_AppBindingsSelector newInstance()
{
return (fflib_AppBindingsSelector) SELECTOR_IMPL_TYPE.newInstance();
}
public Schema.SObjectType getSObjectType()
{
return fflib_AppBinding__mdt.SObjectType;
}
public virtual List<fflib_AppBinding__mdt> selectAll()
{
/**
Todo - Waiting to uncomment the following section
until fflib-apex-common fixes the getFieldPath issue
Currently failing on the BindingObject__r field,
thinking its not a relationship field
Error on line 119, column 1: fflib_QueryFactory.NonReferenceFieldException:
fflib_AppBinding__mdt.BindingObject__r is not a lookup or master-detail
field but is used in a cross-object query field.
return (List<fflib_AppBinding__mdt>) Database.query(
newQueryFactory()
.selectField('BindingObject__r.QualifiedApiName')
.toSOQL());
*/
return (List<fflib_AppBinding__mdt>) Database.query(
String.format(
'SELECT {0} FROM {1}',
new List<String>
{
getFieldListString() + ', BindingObject__r.QualifiedApiName',
getSObjectType().getDescribe().getName()
}
)
);
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,56 @@
/**
* File Name: fflib_DynamicAppFactory
* Description: Abstract class with common functionality for Dynamic Bindings
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public abstract class fflib_DynamicAppFactory
{
protected fflib_SharingMode sharingMode;
protected fflib_IAppBindingResolver resolver;
protected Map<System.Type, System.Type> routes;
protected Map<Object, Object> mockImplByObjectType = new Map<Object, Object>();
protected void setMock(Object objectType, Object mock)
{
this.mockImplByObjectType.put(
objectType,
mock
);
}
protected Object getMock(Object objectType)
{
return this.mockImplByObjectType.get(objectType);
}
protected Boolean hasMock(Object objectType)
{
return this.mockImplByObjectType.containsKey(objectType);
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,123 @@
/**
* File Name: fflib_DynamicDomainFactory
* Description:
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public virtual class fflib_DynamicDomainFactory
extends fflib_DynamicAppFactory
implements fflib_IDomainFactory
{
public static final fflib_AppBindingType DOMAIN = fflib_AppBindingType.Domain;
protected Map<Object, fflib_IDomain> mockDomainByObjectType = new Map<Object, fflib_IDomain>();
protected fflib_ISelectorFactory selectorFactory;
public fflib_DynamicDomainFactory(fflib_IAppBindingResolver resolver, fflib_ISelectorFactory selectorFactory)
{
this.resolver = resolver;
this.selectorFactory = selectorFactory;
}
public fflib_IDomain newInstance(Set<Id> recordIds)
{
return newInstance(this.selectorFactory.selectById(recordIds));
}
public fflib_IDomain newInstance(Set<Id> recordIds, SObjectType sObjectType)
{
return newInstance(this.selectorFactory.selectById(recordIds, sObjectType));
}
public fflib_IDomain newInstance(List<SObject> records)
{
SObjectType sObjectType = records.getSObjectType();
if (sObjectType == null)
throw new DeveloperException('Could not determine the SObjectType of the provided records');
return newInstance(
(List<Object>) records,
(Object) sObjectType
);
}
public fflib_IDomain newInstance(List<Object> objects, Object objectType)
{
if (hasMock(objectType)) return (fflib_IDomain) getMock(objectType);
List<fflib_IAppBinding> bindings = resolver.byObject(objectType)
.byType(DOMAIN)
.resolve();
if (bindings.isEmpty())
throw new DeveloperException('Unable to find domain binding ' + objectType);
System.Type domainImplementationType = bindings.get(0).getImplementationType();
Object instance = domainImplementationType.newInstance();
if (!(instance instanceof fflib_IDomainConstructor))
throw new DeveloperException(
String.format(
'Domain implementation {0} should be an instance of fflib_IDomainConstructor',
new List<String> { domainImplementationType.getName() }
)
);
return ((fflib_IDomainConstructor) domainImplementationType.newInstance())
.construct(objects);
}
public fflib_IDomain newInstance(List<SObject> records, SObjectType domainSObjectType)
{
return newInstance((List<Object>) records, (Object) domainSObjectType);
}
public void setMock(fflib_ISObjectDomain mockDomain)
{
super.setMock(mockDomain.getType(), mockDomain);
}
public void setMock(Schema.SObjectType sObjectType, fflib_ISObjectDomain mockDomain)
{
super.setMock(sObjectType, mockDomain);
}
public void setMock(fflib_IDomain mockDomain)
{
super.setMock(mockDomain.getType(), mockDomain);
}
public void setMock(Object domainType, fflib_IDomain mockDomain)
{
super.setMock(domainType, mockDomain);
}
public class DeveloperException extends Exception {}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,137 @@
/**
* File Name: fflib_DynamicSelectorFactory
* Description:
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public virtual class fflib_DynamicSelectorFactory
extends fflib_DynamicAppFactory
implements fflib_ISelectorFactory
{
public static final fflib_AppBindingType SELECTOR_BINDING_TYPE = fflib_AppBindingType.Selector;
protected Map<Object, Object> mockSelectorImplByObjectType = new Map<Object, Object>();
public fflib_DynamicSelectorFactory(fflib_IAppBindingResolver resolver)
{
this.resolver = resolver;
}
public fflib_ISObjectSelector newInstance(SObjectType sObjectType)
{
if (hasMock(sObjectType)) return (fflib_ISObjectSelector) getMock(sObjectType);
List<fflib_IAppBinding> bindings = resolver
.byType(SELECTOR_BINDING_TYPE)
.byObject(sObjectType)
.bySharingMode(sharingMode)
.resolve();
if (bindings.isEmpty())
throw new DeveloperException(
String.format(
'Unable to find selector binding {0} with sharing more {0}',
new List<String>
{
sObjectType.getDescribe().getName(),
sharingMode.name()
}
)
);
return (fflib_ISObjectSelector) bindings.get(0).newImplInstance();
}
public List<SObject> selectById(Set<Id> recordIds)
{
if (null == recordIds || recordIds.size() == 0)
throw new fflib_Application.DeveloperException('Invalid record Id\'s set');
return selectById(recordIds, new List<Id>(recordIds)[0].getSObjectType());
}
public List<SObject> selectById(Set<Id> recordIds, SObjectType sObjectType)
{
if (null == recordIds || recordIds.size() == 0)
throw new fflib_Application.DeveloperException('Invalid record Id\'s set');
return newInstance(sObjectType)
.selectSObjectsById(recordIds);
}
public List<SObject> selectByRelationship(List<SObject> relatedRecords, SObjectField relationshipField)
{
Set<Id> relatedIds =
new RelatedRecordsDomain(relatedRecords)
.getRelatedIds(relationshipField);
if (relatedIds.isEmpty()) return new List<SObject>();
return selectById(relatedIds);
}
public void replaceWith(SObjectType sObjectType, Type replacementImplType)
{
resolver.byType(fflib_AppBindingType.Selector)
.byObject(sObjectType)
.bySharingMode(this.sharingMode)
.resolve()
.get(0)
.setImplementationType(replacementImplType);
}
public void setMock(fflib_ISObjectSelector selectorInstance)
{
setMock(selectorInstance.sObjectType(), selectorInstance);
}
public void setMock(SObjectType sObjectType, fflib_ISObjectSelector selectorInstance)
{
super.setMock(sObjectType, selectorInstance);
}
public fflib_DynamicSelectorFactory setSharingMode(fflib_SharingMode sharingMode)
{
this.sharingMode = sharingMode;
return this;
}
private class RelatedRecordsDomain extends SObjects
{
public RelatedRecordsDomain(List<SObject> records)
{
super(records);
}
public Set<Id> getRelatedIds(Schema.SObjectField relationshipField)
{
return getIdFieldValues(relationshipField).getIds();
}
}
public class DeveloperException extends Exception {}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,91 @@
/**
* File Name: fflib_DynamicServiceFactory
* Description: Service Factory to return service class instances in a dynamic way
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public virtual class fflib_DynamicServiceFactory
extends fflib_DynamicAppFactory
implements fflib_IServiceFactory
{
public static final fflib_AppBindingType SERVICE_BINDING_TYPE = fflib_AppBindingType.Service;
protected Map<System.Type, Object> mockServiceImplByObjectType = new Map<System.Type, Object>();
public fflib_DynamicServiceFactory(fflib_IAppBindingResolver resolver)
{
this.resolver = resolver;
}
public Object newInstance(Type serviceInterfaceType)
{
if (hasMock(serviceInterfaceType)) return getMock(serviceInterfaceType);
List<fflib_IAppBinding> bindings = resolver
.byType(SERVICE_BINDING_TYPE)
.byInterfaceType(serviceInterfaceType)
.bySharingMode(sharingMode)
.resolve();
if (bindings.isEmpty())
throw new DeveloperException(
String.format(
'Unable to find service binding {0} with sharing more {0}',
new List<String>
{
serviceInterfaceType.getName(),
sharingMode.name()
}
)
);
return bindings.get(0).newImplInstance();
}
public void replaceWith(Type serviceInterfaceType, Type replacementImplType)
{
resolver.byType(SERVICE_BINDING_TYPE)
.byInterfaceType(serviceInterfaceType)
.bySharingMode(sharingMode)
.resolve()
.get(0) // TODO - validate that there are any resolved bindings
.setImplementationType(replacementImplType);
}
public void setMock(Type serviceInterfaceType, Object serviceImpl)
{
super.setMock(serviceInterfaceType, serviceImpl);
}
public fflib_DynamicServiceFactory setSharingMode(fflib_SharingMode sharingMode)
{
this.sharingMode = sharingMode;
return this;
}
public class DeveloperException extends Exception {}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,34 @@
/**
* File Name: fflib_SharingMode
* Description: Enum with the Application Binding Sharing Modes
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public enum fflib_SharingMode
{
WithSharing, WithoutSharing, InheritedSharing, None
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,50 @@
/**
* File Name: fflib_IAppBinding
* Description: Interface for Application Binding Data Object
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public interface fflib_IAppBinding
{
Object getBindingObject();
fflib_AppBindingType getBindingType();
System.Type getImplementationType();
System.Type getInterfaceType();
String getNamespacePrefix();
Decimal getSequence();
fflib_SharingMode getSharingMode();
Object newImplInstance();
fflib_IAppBinding setBindingObject(Object bindingObject);
fflib_IAppBinding setBindingType(fflib_AppBindingType bindingType);
fflib_IAppBinding setNamespacePrefix(String namespacePrefix);
fflib_IAppBinding setImplementationType(System.Type implementationType);
fflib_IAppBinding setInterfaceType(System.Type interfaceType);
fflib_IAppBinding setSequence(Decimal sequence);
fflib_IAppBinding setSharingMode(fflib_SharingMode sharingMode);
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,37 @@
/**
* File Name: fflib_IAppBindingModule
* Description: Interface for the Application Module
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public interface fflib_IAppBindingModule
{
void init();
void addBinding(fflib_IAppBinding binding);
void addBindings(List<fflib_IAppBinding> bindings);
List<fflib_IAppBinding> getBindings();
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,41 @@
/**
* File Name: fflib_IAppBindingResolver
* Description:
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public interface fflib_IAppBindingResolver
{
fflib_IAppBindingResolver addModule(fflib_IAppBindingModule bindingModule);
fflib_IAppBindingResolver byObject(Object bindingObject);
fflib_IAppBindingResolver bySequence(Integer sequence);
fflib_IAppBindingResolver byType(fflib_AppBindingType bindingType);
fflib_IAppBindingResolver byInterfaceType(System.Type interfaceType);
fflib_IAppBindingResolver bySharingMode(fflib_SharingMode sharingMode);
fflib_IAppBindingResolver byNamespacePrefix(String namespacePrefix);
List<fflib_IAppBinding> resolve();
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,34 @@
/**
* File Name: fflib_IAppBindingRouter
* Description:
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public interface fflib_IAppBindingRouter
{
Map<System.Type, System.Type> getRoutes();
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,46 @@
/**
* File Name: fflib_IAppBindings
* Description: Domain class interface signatures for fflib_AppBindings
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public interface fflib_IAppBindings
extends fflib_IDomain
{
void addBinding(fflib_IAppBinding binding);
void addBindings(List<fflib_IAppBinding> bindings);
fflib_IAppBindings selectByObject(Object bindingObject);
fflib_IAppBindings selectBySequence(Decimal sequence);
fflib_IAppBindings selectByType(fflib_AppBindingType bindingType);
fflib_IAppBindings selectByInterfaceType(System.Type interfaceType);
fflib_IAppBindings selectBySharingMode(fflib_SharingMode sharingMode);
fflib_IAppBindings selectByNamespacePrefix(String namespacePrefix);
List<fflib_IAppBinding> getBindings();
List<System.Type> getImplementations();
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<Layout xmlns="http://soap.sforce.com/2006/04/metadata">
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>false</detailHeading>
<editHeading>true</editHeading>
<label>Information</label>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>MasterLabel</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>DeveloperName</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>Type__c</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>IsProtected</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>NamespacePrefix</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>true</customLabel>
<detailHeading>true</detailHeading>
<editHeading>true</editHeading>
<label>Binding</label>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>BindingObject__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>BindingObjectAlternate__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>BindingSequence__c</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>InterfaceType__c</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>ImplementationType__c</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>SharingMode__c</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsLeftToRight</style>
</layoutSections>
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>false</detailHeading>
<editHeading>true</editHeading>
<label>System Information</label>
<layoutColumns>
<layoutItems>
<behavior>Readonly</behavior>
<field>CreatedById</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Readonly</behavior>
<field>LastModifiedById</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>true</customLabel>
<detailHeading>true</detailHeading>
<editHeading>false</editHeading>
<label>Custom Links</label>
<layoutColumns/>
<layoutColumns/>
<layoutColumns/>
<style>CustomLinks</style>
</layoutSections>
<showEmailCheckbox>false</showEmailCheckbox>
<showHighlightsPanel>false</showHighlightsPanel>
<showInteractionLogPanel>false</showInteractionLogPanel>
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
<summaryLayout>
<masterLabel>00h55000002F8hB</masterLabel>
<sizeX>4</sizeX>
<sizeY>0</sizeY>
<summaryLayoutStyle>Default</summaryLayoutStyle>
</summaryLayout>
</Layout>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<label>Application Binding</label>
<pluralLabel>Application Bindings</pluralLabel>
<visibility>Public</visibility>
</CustomObject>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>BindingObjectAlternate__c</fullName>
<description>Use this field to specify the SObject API name to bind to when the &quot;Binding Object&quot; field does not list that particular SObject. Only specify the &quot;Binding Object&quot; field or this one; not both.</description>
<externalId>false</externalId>
<fieldManageability>SubscriberControlled</fieldManageability>
<inlineHelpText>Use this field to specify the SObject API name to bind to when the &quot;Binding Object&quot; field does not list that particular SObject. Only specify the &quot;Binding Object&quot; field or this one; not both.</inlineHelpText>
<label>Binding Object Alternate</label>
<length>255</length>
<required>false</required>
<type>Text</type>
<unique>false</unique>
</CustomField>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>BindingObject__c</fullName>
<externalId>false</externalId>
<fieldManageability>SubscriberControlled</fieldManageability>
<label>Binding Object</label>
<referenceTo>EntityDefinition</referenceTo>
<relationshipLabel>Bindings</relationshipLabel>
<relationshipName>Bindings</relationshipName>
<required>false</required>
<type>MetadataRelationship</type>
<unique>false</unique>
</CustomField>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>BindingSequence__c</fullName>
<externalId>false</externalId>
<fieldManageability>SubscriberControlled</fieldManageability>
<label>Binding Sequence</label>
<precision>18</precision>
<required>false</required>
<scale>0</scale>
<type>Number</type>
<unique>false</unique>
</CustomField>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>ImplementationType__c</fullName>
<externalId>false</externalId>
<fieldManageability>SubscriberControlled</fieldManageability>
<label>Implementation Type</label>
<length>255</length>
<required>true</required>
<type>Text</type>
<unique>false</unique>
</CustomField>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>InterfaceType__c</fullName>
<externalId>false</externalId>
<fieldManageability>SubscriberControlled</fieldManageability>
<label>Interface Type</label>
<length>255</length>
<required>false</required>
<type>Text</type>
<unique>false</unique>
</CustomField>
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>SharingMode__c</fullName>
<defaultValue>&quot;With Sharing&quot;</defaultValue>
<externalId>false</externalId>
<fieldManageability>DeveloperControlled</fieldManageability>
<label>Sharing mode</label>
<required>true</required>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value>
<fullName>With Sharing</fullName>
<default>true</default>
<label>With Sharing</label>
</value>
<value>
<fullName>Without Sharing</fullName>
<default>false</default>
<label>Without Sharing</label>
</value>
<value>
<fullName>Inherited Sharing</fullName>
<default>false</default>
<label>Inherited Sharing</label>
</value>
<value>
<fullName>None</fullName>
<default>false</default>
<label>None</label>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Type__c</fullName>
<externalId>false</externalId>
<fieldManageability>DeveloperControlled</fieldManageability>
<inlineHelpText>Type of binding or Logical layers of functionality</inlineHelpText>
<label>Binding Type</label>
<required>true</required>
<type>Picklist</type>
<valueSet>
<restricted>true</restricted>
<valueSetDefinition>
<sorted>false</sorted>
<value>
<fullName>Domain</fullName>
<default>true</default>
<label>Domain</label>
</value>
<value>
<fullName>Service</fullName>
<default>false</default>
<label>Service</label>
</value>
<value>
<fullName>Selector</fullName>
<default>false</default>
<label>Selector</label>
</value>
<value>
<fullName>Controller</fullName>
<default>false</default>
<label>Controller</label>
</value>
<value>
<fullName>Module</fullName>
<default>false</default>
<label>Module</label>
</value>
<value>
<fullName>EventListener</fullName>
<default>false</default>
<label>Event Listener</label>
</value>
<value>
<fullName>Miscellaneous</fullName>
<default>false</default>
<label>Miscellaneous</label>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<ListView xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>All</fullName>
<columns>MasterLabel</columns>
<columns>BindingObject__c</columns>
<columns>BindingObjectAlternate__c</columns>
<columns>BindingSequence__c</columns>
<columns>InterfaceType__c</columns>
<columns>ImplementationType__c</columns>
<filterScope>Everything</filterScope>
<label>All</label>
</ListView>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<ValidationRule xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>BindObj_Or_BindObjAlt</fullName>
<active>true</active>
<description>This validation rule ensures that only the &quot;Binding Object&quot; or the &quot;Binding Object Alternate&quot; field is specified and not both.</description>
<errorConditionFormula>NOT( ISBLANK( BindingObject__c ) ) &amp;&amp; NOT( ISBLANK( BindingObjectAlternate__c ) )</errorConditionFormula>
<errorMessage>Only specify the &quot;Binding Object&quot; field or the &quot;Binding Object Alternate&quot; field; not both.</errorMessage>
</ValidationRule>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<ValidationRule xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Require_Interface_Type_For_Services</fullName>
<active>true</active>
<description>An Interface Type is required for Service class Bindings</description>
<errorConditionFormula>AND(ISPICKVAL(Type__c,&quot;Service&quot;), ISBLANK( InterfaceType__c ) )</errorConditionFormula>
<errorDisplayField>ImplementationType__c</errorDisplayField>
<errorMessage>An Interface Type is required for Service class Bindings</errorMessage>
</ValidationRule>
@@ -42,7 +42,7 @@ public interface fflib_IDomainFactory
**/
fflib_IDomain newInstance(Set<Id> recordIds);
fflib_IDomain newInstance(Set<Id> recordIds, Schema.SObjectType objectType);
fflib_IDomain newInstance(Set<Id> recordIds, Schema.SObjectType sObjectType);
/**
* Dynamically constructs an instance of the Domain class for the given records
@@ -95,5 +95,5 @@ public interface fflib_IDomainFactory
* avoids the need to stub the mock to return its ObjectType
* @param mockDomain The instance of the Domain mock
*/
void setMock(Type domainType, fflib_IDomain mockDomain);
void setMock(Object domainType, fflib_IDomain mockDomain);
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,147 @@
/**
* File Name: fflib_DynamicDomainFactoryTest
* Description:
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@IsTest(IsParallel=true)
public class fflib_DynamicDomainFactoryTest
{
private static final fflib_IAppBindingResolver bindingResolver =
new fflib_AppBindingResolver();
private static final fflib_IDomainFactory Domain =
new fflib_DynamicDomainFactory(
bindingResolver,
fflib_DynamicSelectorFactoryTest.Selector);
private static final SObjectType ACCOUNT_OBJECT_TYPE = Schema.Account.SObjectType;
@IsTest
static void itShouldReturnDomain()
{
// Mock the App Bindings selector
fflib_AppBindingsSelector.SELECTOR_IMPL_TYPE = AppBindingsSelectorMock.class;
final List<Account> records = generateAccountRecords();
System.Test.startTest();
Object instance = Domain.newInstance(records);
System.Test.stopTest();
System.assert(instance instanceof fflib_SObjects);
}
@IsTest
static void itShouldReturnDomainById()
{
// Mock the App Bindings selector
fflib_AppBindingsSelector.SELECTOR_IMPL_TYPE = AppBindingsSelectorMock.class;
final List<Account> records = generateAccountRecords();
final Set<Id> ids = new fflib_SObjects(records).getRecordIds();
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_ISObjectSelector selectorMock = (fflib_ISObjectSelector) mocks.mock(fflib_ISObjectSelector.class);
mocks.startStubbing();
mocks.when(selectorMock.selectSObjectsById(ids)).thenReturn(records);
mocks.stopStubbing();
fflib_DynamicSelectorFactoryTest.Selector.setMock(ACCOUNT_OBJECT_TYPE, selectorMock);
System.Test.startTest();
Object instance = Domain.newInstance(ids);
System.Test.stopTest();
System.assert(instance instanceof fflib_SObjects);
System.assertEquals(records, ((fflib_SObjects) instance).getRecords());
}
@IsTest
static void itShouldReturnTheMockDomain()
{
// Mock the App Bindings selector
fflib_AppBindingsSelector.SELECTOR_IMPL_TYPE = AppBindingsSelectorMock.class;
final List<Account> records = generateAccountRecords();
final Set<Id> ids = new fflib_SObjects(records).getRecordIds();
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_SObjects domainMock = (fflib_SObjects) mocks.mock(fflib_SObjects.class);
mocks.startStubbing();
mocks.when(domainMock.getType()).thenReturn(ACCOUNT_OBJECT_TYPE);
mocks.when(domainMock.getRecords()).thenReturn(new List<Account>());
mocks.stopStubbing();
System.Test.startTest();
fflib_SObjects instanceA = (fflib_SObjects) Domain.newInstance(records);
Domain.setMock(domainMock);
fflib_SObjects instanceB = (fflib_SObjects) Domain.newInstance(records, ACCOUNT_OBJECT_TYPE);
System.Test.stopTest();
System.assertEquals(records, instanceA.getRecords());
System.assert(instanceB.getRecords().isEmpty());
}
private static List<Account> generateAccountRecords()
{
List<Account> records = new List<Account>
{
new Account(Id = fflib_IDGenerator.generate(ACCOUNT_OBJECT_TYPE)),
new Account(Id = fflib_IDGenerator.generate(ACCOUNT_OBJECT_TYPE)),
new Account(Id = fflib_IDGenerator.generate(ACCOUNT_OBJECT_TYPE))
};
return records;
}
public class MyDomainConstructor implements fflib_IDomainConstructor
{
public fflib_IDomain construct(List<Object> objects)
{
return new fflib_SObjects((List<SObject>) objects);
}
}
private class AppBindingsSelectorMock extends fflib_AppBindingsSelector
{
public override List<fflib_AppBinding__mdt> selectAll()
{
return new List<fflib_AppBinding__mdt>
{
new fflib_AppBinding__mdt(
Type__c = 'Domain',
BindingObject__c = '',
BindingObjectAlternate__c = ACCOUNT_OBJECT_TYPE.getDescribe().getName(),
ImplementationType__c = MyDomainConstructor.class.getName(),
SharingMode__c = 'None'
)
};
}
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,156 @@
/**
* File Name: fflib_DynamicSelectorFactoryTest
* Description:
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@IsTest(IsParallel=true)
public class fflib_DynamicSelectorFactoryTest
{
private static fflib_IAppBindingResolver bindingResolver =
new fflib_AppBindingResolver();
@TestVisible
private static final fflib_ISelectorFactory Selector =
new fflib_DynamicSelectorFactory(bindingResolver)
.setSharingMode(fflib_SharingMode.WithSharing);
private static final Type MY_SELECTOR_TYPE = fflib_DynamicSelectorFactoryTest.MySelector.class;
@IsTest
static void itShouldReturnTheSelectorByBindingObject()
{
// Mock the App Bindings selector
fflib_AppBindingsSelector.SELECTOR_IMPL_TYPE = AppBindingsSelectorMock.class;
System.Test.startTest();
Object instance = Selector.newInstance(Schema.Account.SObjectType);
System.Test.stopTest();
System.assert(instance instanceof fflib_DynamicSelectorFactoryTest.MySelector);
}
@IsTest
static void itShouldReturnTheAlternativeSelector()
{
// Mock the App Bindings selector
fflib_AppBindingsSelector.SELECTOR_IMPL_TYPE = AppBindingsSelectorMock.class;
System.Test.startTest();
Object instanceA = Selector.newInstance(Schema.Account.SObjectType);
Selector.replaceWith(Schema.Account.SObjectType, MyMockSelector.class);
Object instanceB = Selector.newInstance(Schema.Account.SObjectType);
System.Test.stopTest();
System.assert(instanceA instanceof fflib_DynamicSelectorFactoryTest.MySelector);
System.assert(instanceB instanceof fflib_DynamicSelectorFactoryTest.MyMockSelector);
}
@IsTest
static void itShouldReturnTheRecordsById()
{
Id id = fflib_IDGenerator.generate(Schema.Account.SObjectType);
Set<Id> ids = new Set<Id>{ id };
final List<Account> records = new List<Account>{ new Account(Name='Test') };
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_ISObjectSelector selectorMock = (fflib_ISObjectSelector) mocks.mock(fflib_ISObjectSelector.class);
mocks.startStubbing();
mocks.when(selectorMock.selectSObjectsById(ids)).thenReturn(records);
mocks.stopStubbing();
Selector.setMock(Schema.Account.SObjectType, selectorMock);
System.Test.startTest();
List<SObject> result = Selector.selectById(ids);
System.Test.stopTest();
System.assert(result == records);
((fflib_ISObjectSelector) mocks.verify(selectorMock)).selectSObjectsById(ids);
}
@IsTest
static void itShouldReturnTheRecordsByRelatedId()
{
Id id = fflib_IDGenerator.generate(Schema.Account.SObjectType);
Set<Id> ids = new Set<Id>{ id };
final List<Account> accounts = new List<Account>{ new Account(Id = id, Name='Test') };
final List<Contact> contacts = new List<Contact> { new Contact(AccountId = id)};
fflib_ApexMocks mocks = new fflib_ApexMocks();
fflib_ISObjectSelector selectorMock = (fflib_ISObjectSelector) mocks.mock(fflib_ISObjectSelector.class);
mocks.startStubbing();
mocks.when(selectorMock.sObjectType()).thenReturn(Schema.Account.SObjectType);
mocks.when(selectorMock.selectSObjectsById(ids)).thenReturn(accounts);
mocks.stopStubbing();
Selector.setMock(selectorMock);
System.Test.startTest();
List<SObject> result = Selector.selectByRelationship(contacts, Schema.Contact.AccountId);
System.Test.stopTest();
System.assert(result == accounts);
((fflib_ISObjectSelector) mocks.verify(selectorMock)).selectSObjectsById(ids);
}
public interface IMySelector extends fflib_ISObjectSelector {}
private abstract class MySelectorAbstract extends fflib_SObjectSelector implements IMySelector
{
public List<Schema.SObjectField> getSObjectFieldList()
{
return new List<Schema.SObjectField>{ Account.Id, Account.Name };
}
public SObjectType getSObjectType()
{
return Schema.Account.SObjectType;
}
}
public class MySelector extends MySelectorAbstract {}
public class MyMockSelector extends MySelectorAbstract {}
private class AppBindingsSelectorMock extends fflib_AppBindingsSelector
{
public override List<fflib_AppBinding__mdt> selectAll()
{
return new List<fflib_AppBinding__mdt>
{
new fflib_AppBinding__mdt(
Type__c = 'Selector',
BindingObject__c = '',
BindingObjectAlternate__c = Schema.Account.SObjectType.getDescribe().getName(),
ImplementationType__c = MY_SELECTOR_TYPE.getName(),
SharingMode__c = 'With Sharing'
)
};
}
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
@@ -0,0 +1,188 @@
/**
* File Name: fflib_DynamicServiceFactoryTest
* Description:
*
* @author : architect ir. Wilhelmus G.J. Velzeboer
*
* Copyright (c), W.G.J. Velzeboer,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above author notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@IsTest(IsParallel=true)
public class fflib_DynamicServiceFactoryTest
{
private static final Type MY_SERVICE_INTERFACE_TYPE = fflib_DynamicServiceFactoryTest.IMyService.class;
private static final Type MY_SERVICE_TYPE = fflib_DynamicServiceFactoryTest.MyService.class;
private static final Type MY_ALT_SERVICE_TYPE = fflib_DynamicServiceFactoryTest.MyALtService.class;
@IsTest
static void itShouldReturnTheCorrectInstance()
{
fflib_AppBindingModule module = new fflib_AppBindingModule();
module.addBinding(createBindingForMyService());
bindingResolver.addModule(module);
System.Test.startTest();
Object instance = Service.newInstance(MY_SERVICE_TYPE);
System.Test.stopTest();
System.assert(instance instanceof fflib_DynamicServiceFactoryTest.MyService);
}
@IsTest
static void itShouldReturnTheAlternativeInstance()
{
fflib_AppBindingModule module = new fflib_AppBindingModule();
module.addBinding(createBindingForMyService());
bindingResolver.addModule(module);
Service.replaceWith(MyService.class, MyAltService.class);
System.Test.startTest();
Object instance = Service.newInstance(MY_SERVICE_TYPE);
System.Test.stopTest();
System.assert(instance instanceof fflib_DynamicServiceFactoryTest.MyAltService);
}
@IsTest
static void itShouldReturnTheMockInstance()
{
fflib_AppBindingModule module = new fflib_AppBindingModule();
module.addBinding(createBindingForMyService());
bindingResolver.addModule(module);
IMyService mock = new MyAltService();
Service.setMock(MyService.class, mock);
System.Test.startTest();
Object instance = Service.newInstance(MY_SERVICE_TYPE);
System.Test.stopTest();
System.assert(instance instanceof fflib_DynamicServiceFactoryTest.MyAltService);
}
@IsTest
static void itShouldReturnTheFirstBindingInstance()
{
fflib_AppBindingsSelector.SELECTOR_IMPL_TYPE = AppBindingsSelectorMock.class;
System.Test.startTest();
Object instance = Service.newInstance(MY_SERVICE_INTERFACE_TYPE);
System.Test.stopTest();
System.assert(instance instanceof fflib_DynamicServiceFactoryTest.MyService);
}
@IsTest
static void itShouldReturnTheSecondBindingInstance()
{
fflib_AppBindingsSelector.SELECTOR_IMPL_TYPE = reverseAppBindingsSelectorMock.class;
System.Test.startTest();
Object instance = Service.newInstance(MY_SERVICE_INTERFACE_TYPE);
System.Test.stopTest();
System.assert(instance instanceof fflib_DynamicServiceFactoryTest.MyAltService);
}
private static fflib_AppBinding createBindingForMyService()
{
fflib_AppBinding result = new fflib_AppBinding();
result.setBindingType(fflib_AppBindingType.Service);
result.setInterfaceType(MY_SERVICE_TYPE);
result.setImplementationType(MY_SERVICE_TYPE);
result.setSharingMode(fflib_SharingMode.WithSharing);
result.setSequence(2);
return result;
}
private static fflib_AppBinding createBindingForMyAltService()
{
fflib_AppBinding result = createBindingForMyService();
result.setImplementationType(MyAltService.class);
result.setSequence(1);
return result;
}
private static fflib_IAppBindingResolver bindingResolver =
new fflib_AppBindingResolver();
private static final fflib_IServiceFactory Service =
new fflib_DynamicServiceFactory(bindingResolver)
.setSharingMode(fflib_SharingMode.WithSharing);
public interface IMyService {}
public class MyService implements IMyService { }
public class MyAltService implements IMyService { }
private class AppBindingsSelectorMock extends fflib_AppBindingsSelector
{
public override List<fflib_AppBinding__mdt> selectAll()
{
return new List<fflib_AppBinding__mdt>
{
new fflib_AppBinding__mdt(
Type__c = 'Service',
InterfaceType__c = MY_SERVICE_INTERFACE_TYPE.getName(),
ImplementationType__c = MY_SERVICE_TYPE.getName(),
SharingMode__c = 'With Sharing',
BindingSequence__c = 1
),
new fflib_AppBinding__mdt(
Type__c = 'Service',
InterfaceType__c = MY_SERVICE_INTERFACE_TYPE.getName(),
ImplementationType__c = MY_ALT_SERVICE_TYPE.getName(),
SharingMode__c = 'With Sharing',
BindingSequence__c = 2
)
};
}
}
private class reverseAppBindingsSelectorMock extends fflib_AppBindingsSelector
{
public override List<fflib_AppBinding__mdt> selectAll()
{
return new List<fflib_AppBinding__mdt>
{
new fflib_AppBinding__mdt(
Type__c = 'Service',
InterfaceType__c = MY_SERVICE_INTERFACE_TYPE.getName(),
ImplementationType__c = MY_SERVICE_TYPE.getName(),
SharingMode__c = 'With Sharing',
BindingSequence__c = 2
),
new fflib_AppBinding__mdt(
Type__c = 'Service',
InterfaceType__c = MY_SERVICE_INTERFACE_TYPE.getName(),
ImplementationType__c = MY_ALT_SERVICE_TYPE.getName(),
SharingMode__c = 'With Sharing',
BindingSequence__c = 1
)
};
}
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>