├── .eslintignore ├── .forceignore ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── fetch-repo-stats.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── config ├── linters │ └── pmd-ruleset.xml └── scratch-orgs │ ├── base-scratch-def.json │ └── platform-cache-scratch-def.json ├── images ├── btn-install-unlocked-package-production.png └── btn-install-unlocked-package-sandbox.png ├── lint-staged.config.js ├── nebula-cache-manager ├── core │ ├── cachePartitions │ │ └── CacheManagerPartition.cachePartition-meta.xml │ ├── classes │ │ ├── CacheManager.cls │ │ ├── CacheManager.cls-meta.xml │ │ ├── CacheManager_Tests.cls │ │ └── CacheManager_Tests.cls-meta.xml │ ├── customMetadata │ │ ├── CacheConfiguration.Organization.md-meta.xml │ │ ├── CacheConfiguration.Session.md-meta.xml │ │ └── CacheConfiguration.Transaction.md-meta.xml │ ├── layouts │ │ ├── CacheConfiguration__mdt-Cache Configuration Layout.layout-meta.xml │ │ └── CacheValue__mdt-Cache Value Layout.layout-meta.xml │ ├── objects │ │ ├── CacheConfiguration__mdt │ │ │ ├── CacheConfiguration__mdt.object-meta.xml │ │ │ ├── fields │ │ │ │ ├── IsEnabled__c.field-meta.xml │ │ │ │ ├── IsImmutable__c.field-meta.xml │ │ │ │ ├── PlatformCachePartitionName__c.field-meta.xml │ │ │ │ ├── PlatformCacheTimeToLive__c.field-meta.xml │ │ │ │ └── PlatformCacheVisibility__c.field-meta.xml │ │ │ └── listViews │ │ │ │ └── All.listView-meta.xml │ │ └── CacheValue__mdt │ │ │ ├── CacheValue__mdt.object-meta.xml │ │ │ ├── fields │ │ │ ├── Cache__c.field-meta.xml │ │ │ ├── DataType__c.field-meta.xml │ │ │ ├── IsEnabled__c.field-meta.xml │ │ │ ├── Key__c.field-meta.xml │ │ │ └── Value__c.field-meta.xml │ │ │ ├── listViews │ │ │ └── All.listView-meta.xml │ │ │ └── validationRules │ │ │ └── KeyMustBeAlphanumeric.validationRule-meta.xml │ └── permissionsets │ │ └── CacheManagerAdmin.permissionset-meta.xml └── recipes │ └── classes │ ├── RecordSelector.cls │ └── RecordSelector.cls-meta.xml ├── package-lock.json ├── package.json ├── scripts └── build │ ├── sync-package-version-number.ps1 │ ├── validate-access-to-namespaced-package.apex │ └── validate-access-to-no-namespace-package.apex └── sfdx-project.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/lwc/**/*.css 2 | **/lwc/**/*.html 3 | **/lwc/**/*.json 4 | **/lwc/**/*.svg 5 | **/lwc/**/*.xml 6 | **/aura/**/*.auradoc 7 | **/aura/**/*.cmp 8 | **/aura/**/*.css 9 | **/aura/**/*.design 10 | **/aura/**/*.evt 11 | **/aura/**/*.json 12 | **/aura/**/*.svg 13 | **/aura/**/*.tokens 14 | **/aura/**/*.xml 15 | **/aura/**/*.app 16 | .sfdx 17 | -------------------------------------------------------------------------------- /.forceignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status 2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm 3 | # 4 | 5 | package.xml 6 | 7 | # LWC configuration files 8 | **/jsconfig.json 9 | **/.eslintrc.json 10 | 11 | # LWC Jest 12 | **/__tests__/** 13 | 14 | # Metadata 15 | **.profile-meta.xml -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default line return behavior, in case people don't have core.autocrlf set. 2 | * text=auto eol=lf 3 | 4 | # Common git file types 5 | .gitattributes text eol=lf 6 | .gitignore text eol=lf 7 | *.md text eol=lf 8 | 9 | # Salesforce-specfic file types 10 | *.app text eol=lf 11 | *.cls text eol=lf 12 | *.cmp text eol=lf 13 | *.component text eol=lf 14 | *.css text eol=lf 15 | *.html text eol=lf 16 | *.js text eol=lf 17 | *.page text eol=lf 18 | *.trigger text eol=lf 19 | *.xml text eol=lf -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Pipeline for Nebula Cache Manager 2 | name: Build 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths-ignore: 9 | - 'content/**' 10 | - 'docs/**' 11 | - 'examples/**' 12 | - 'packages/**' 13 | - '.forceignore' 14 | - '.gitignore' 15 | - '.prettierignore' 16 | - '.prettierrc' 17 | - 'CONTRIBUTING.md' 18 | - 'LICENSE' 19 | - 'package.json' 20 | - 'README.md' 21 | - './**/README.md' 22 | - 'sfdx-project.json' 23 | pull_request: 24 | types: [opened, synchronize, reopened] 25 | paths-ignore: 26 | - 'content/**' 27 | - 'docs/**' 28 | - 'examples/**' 29 | - 'packages/**' 30 | - '.forceignore' 31 | - '.gitignore' 32 | - '.prettierignore' 33 | - '.prettierrc' 34 | - 'CONTRIBUTING.md' 35 | - 'LICENSE' 36 | - 'package.json' 37 | - 'README.md' 38 | - './**/README.md' 39 | - 'sfdx-project.json' 40 | 41 | jobs: 42 | code-quality-tests: 43 | name: 'Run Code Quality Tests' 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: 'Checkout source code' 47 | uses: actions/checkout@v3 48 | 49 | - name: 'Restore node_modules cache' 50 | id: cache-npm 51 | uses: actions/cache@v3 52 | with: 53 | path: node_modules 54 | key: npm-${{ hashFiles('**/package-lock.json') }} 55 | restore-keys: | 56 | npm-${{ env.cache-name }}- 57 | npm- 58 | 59 | - name: 'Install npm dependencies' 60 | if: steps.cache-npm.outputs.cache-hit != 'true' 61 | run: npm ci 62 | 63 | - name: 'Verify Apex with SFDX Scanner' 64 | run: | 65 | npm run sfdx:plugins:link:scanner 66 | npm run scan:apex 67 | 68 | - name: 'Verify formatting with Prettier' 69 | run: npm run prettier:verify 70 | 71 | base-scratch-org-tests: 72 | name: 'Run Base Scratch Org Tests' 73 | needs: [code-quality-tests] 74 | runs-on: ubuntu-latest 75 | environment: 'Base Scratch Org' 76 | steps: 77 | - name: 'Checkout source code' 78 | uses: actions/checkout@v3 79 | 80 | - name: 'Restore node_modules cache' 81 | id: cache-npm 82 | uses: actions/cache@v3 83 | with: 84 | path: node_modules 85 | key: npm-${{ hashFiles('**/package-lock.json') }} 86 | restore-keys: | 87 | npm-${{ env.cache-name }}- 88 | npm- 89 | 90 | - name: 'Install npm dependencies' 91 | if: steps.cache-npm.outputs.cache-hit != 'true' 92 | run: npm ci 93 | 94 | - name: 'Authorize Dev Hub' 95 | shell: bash 96 | run: | 97 | echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key 98 | npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername 99 | env: 100 | DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} 101 | DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} 102 | DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} 103 | DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} 104 | 105 | - name: 'Create Base Scratch Org' 106 | run: npx sfdx force:org:create --durationdays 1 --definitionfile ./config/scratch-orgs/base-scratch-def.json --wait 20 --setdefaultusername --json 107 | 108 | - name: 'Deploy Source to Scratch Org' 109 | run: npx sfdx force:source:deploy --sourcepath ./nebula-cache-manager/ 110 | 111 | - name: 'Assign Cache Manager Admin Permission Set' 112 | run: npm run permset:assign:admin 113 | 114 | # Nebula Cache Manager has functionality that use the session partition of platform cache, which only works when the current user has an active session. 115 | # The code should work with or without an active session, so the pipeline runs the tests twice - asynchronously and synchronously. 116 | # Running the Apex tests sync & async serves as an extra level of integration testing to ensure that everything works with or without an active session. 117 | - name: 'Run Apex Tests Asynchronously' 118 | run: npm run test:apex:nocoverage 119 | 120 | - name: 'Run Apex Tests Synchronously' 121 | run: npm run test:apex:nocoverage -- --synchronous 122 | 123 | - name: 'Delete Base Scratch Org' 124 | run: npx sfdx force:org:delete --json --noprompt 125 | if: ${{ always() }} 126 | 127 | platform-cache-scratch-org-tests: 128 | name: 'Run Platform Cache Scratch Org Tests' 129 | needs: [code-quality-tests] 130 | runs-on: ubuntu-latest 131 | environment: 'Platform Cache Scratch Org' 132 | steps: 133 | - name: 'Checkout source code' 134 | uses: actions/checkout@v3 135 | 136 | - name: 'Restore node_modules cache' 137 | id: cache-npm 138 | uses: actions/cache@v3 139 | with: 140 | path: node_modules 141 | key: npm-${{ hashFiles('**/package-lock.json') }} 142 | restore-keys: | 143 | npm-${{ env.cache-name }}- 144 | npm- 145 | 146 | - name: 'Install npm dependencies' 147 | if: steps.cache-npm.outputs.cache-hit != 'true' 148 | run: npm ci 149 | 150 | - name: 'Authorize Dev Hub' 151 | shell: bash 152 | run: | 153 | echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key 154 | npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername 155 | env: 156 | DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} 157 | DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} 158 | DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} 159 | DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} 160 | 161 | - name: 'Create Platform Cache Scratch Org' 162 | run: npx sfdx force:org:create --durationdays 1 --definitionfile ./config/scratch-orgs/platform-cache-scratch-def.json --wait 20 --setdefaultusername --json 163 | 164 | - name: 'Deploy Source to Scratch Org' 165 | run: npx sfdx force:source:deploy --sourcepath ./nebula-cache-manager/ 166 | 167 | - name: 'Assign Cache Manager Admin Permission Set' 168 | run: npm run permset:assign:admin 169 | 170 | # Nebula Cache Manager has functionality that use the session partition of platform cache, which only works when the current user has an active session. 171 | # The code should work with or without an active session, so the pipeline runs the tests twice - asynchronously and synchronously. 172 | # Running the Apex tests sync & async serves as an extra level of integration testing to ensure that everything works with or without an active session. 173 | - name: 'Run Apex Tests Asynchronously' 174 | run: npm run test:apex:nocoverage 175 | 176 | - name: 'Run Apex Tests Synchronously' 177 | run: npm run test:apex -- --synchronous 178 | 179 | - name: 'Upload Apex test code coverage to Codecov.io' 180 | uses: codecov/codecov-action@v3 181 | with: 182 | token: ${{ secrets.CODECOV_TOKEN }} 183 | flags: Apex 184 | 185 | - name: 'Delete Platform Cache Scratch Org' 186 | run: npx sfdx force:org:delete --json --noprompt 187 | if: ${{ always() }} 188 | 189 | create-unlocked-package-versions: 190 | name: 'Create Package Versions' 191 | needs: [base-scratch-org-tests, platform-cache-scratch-org-tests] 192 | if: ${{ github.ref != 'refs/heads/main' }} 193 | runs-on: ubuntu-latest 194 | outputs: 195 | noNamespacePackageVersionId: ${{ steps.createNoNamespace.outputs.noNamespacePackageVersionId }} 196 | withNamespacePackageVersionId: ${{ steps.createWithNamespace.outputs.withNamespacePackageVersionId }} 197 | steps: 198 | - name: 'Checkout source code' 199 | uses: actions/checkout@v3 200 | with: 201 | ref: ${{ github.event.pull_request.head.ref }} 202 | 203 | - name: 'Restore node_modules cache' 204 | id: cache-npm 205 | uses: actions/cache@v3 206 | with: 207 | path: node_modules 208 | key: npm-${{ hashFiles('**/package-lock.json') }} 209 | restore-keys: | 210 | npm-${{ env.cache-name }}- 211 | npm- 212 | 213 | - name: Set environment variables 214 | run: | 215 | echo 'SFDX_DISABLE_AUTOUPDATE=true' >> $GITHUB_ENV 216 | echo 'SFDX_DISABLE_SOURCE_MEMBER_POLLING=true' >> $GITHUB_ENV 217 | echo 'SFDX_PROJECT_AUTOUPDATE_DISABLE_FOR_PACKAGE_VERSION_CREATE=true' >> $GITHUB_ENV 218 | # echo 'SFDX_DISABLE_TELEMETRY=true' >> $GITHUB_ENV 219 | 220 | - name: 'Install npm dependencies' 221 | if: steps.cache-npm.outputs.cache-hit != 'true' 222 | run: npm ci 223 | 224 | - name: 'Authorize Dev Hub' 225 | shell: bash 226 | run: | 227 | echo '${{ env.DEV_HUB_JWT_SERVER_KEY }}' > ./jwt-server.key 228 | npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername 229 | # rm ./jwt-server.key 230 | env: 231 | DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} 232 | DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} 233 | DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} 234 | DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} 235 | 236 | - name: 'Create No-Namespace Package Version' 237 | id: createNoNamespace 238 | run: | 239 | noNamespacePackageVersionId=$(npx sfdx force:package:version:create --package "Nebula Cache Manager (no namespace)" --wait 120 --codecoverage --skipancestorcheck --installationkeybypass --json | jq --exit-status --raw-output ".result.SubscriberPackageVersionId") 240 | echo "noNamespacePackageVersionId=$noNamespacePackageVersionId" >> $GITHUB_ENV 241 | echo "noNamespacePackageVersionId=$noNamespacePackageVersionId" >> $GITHUB_OUTPUT 242 | echo "Created package version $noNamespacePackageVersionId" 243 | 244 | - name: 'Create Namespaced Package Version' 245 | id: createWithNamespace 246 | run: | 247 | withNamespacePackageVersionId=$(npx sfdx force:package:version:create --package "Nebula Cache Manager (Nebula namespace)" --wait 120 --codecoverage --skipancestorcheck --installationkeybypass --json | jq --exit-status --raw-output ".result.SubscriberPackageVersionId") 248 | echo "withNamespacePackageVersionId=$withNamespacePackageVersionId" >> $GITHUB_ENV 249 | echo "withNamespacePackageVersionId=$withNamespacePackageVersionId" >> $GITHUB_OUTPUT 250 | echo "Created package version $withNamespacePackageVersionId" 251 | 252 | - name: 'Create Platform Cache Scratch Org' 253 | run: npx sfdx force:org:create --definitionfile ./config/scratch-orgs/platform-cache-scratch-def.json --setdefaultusername --durationdays 1 254 | 255 | - name: 'Install Namespaced Package Version' 256 | run: npx sfdx package:install --package ${{ env.withNamespacePackageVersionId }} --wait 20 257 | 258 | - name: 'Install No-Namespace Package Version' 259 | run: npx sfdx package:install --package ${{ env.noNamespacePackageVersionId }} --wait 20 260 | 261 | - name: 'Validate Namespaced Package Access' 262 | run: npx sfdx apex:run --file ./scripts/build/validate-access-to-no-namespace-package.apex 263 | 264 | - name: 'Validate No-Namespace Package Access' 265 | run: npx sfdx apex:run --file ./scripts/build/validate-access-to-namespaced-package.apex 266 | 267 | - name: 'Delete Platform Cache Scratch Org' 268 | run: npx sfdx force:org:delete --json --noprompt 269 | if: ${{ always() }} 270 | 271 | # - name: 'Commit New Package Versions' 272 | # run: | 273 | # git config --local user.email "action@github.com" 274 | # git config --local user.name "GitHub Action Bot" 275 | # # npm run sfdx:plugins:link:bummer 276 | # # npx sfdx bummer:package:aliases:sort 277 | # rm ./jwt-server.key 278 | # npx prettier --write ./sfdx-project.json 279 | # git add ./sfdx-project.json 280 | # git status 281 | # git commit -m "Created new package versions" 282 | # git push 283 | -------------------------------------------------------------------------------- /.github/workflows/fetch-repo-stats.yml: -------------------------------------------------------------------------------- 1 | name: Fetch Repo Stats 2 | 3 | on: 4 | schedule: 5 | # Run this once per day, towards the end of the day for keeping the most 6 | # recent data point most meaningful (hours are interpreted in UTC). 7 | - cron: '0 23 * * *' 8 | workflow_dispatch: # Allow for running this manually. 9 | 10 | jobs: 11 | j1: 12 | name: store-repo-stats 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: run-ghrs 16 | uses: jgehrcke/github-repo-stats@RELEASE 17 | with: 18 | ghtoken: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used for Git repositories to specify intentionally untracked files that Git should ignore. 2 | # If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore 3 | # For useful gitignore templates see: https://github.com/github/gitignore 4 | 5 | # Salesforce cache 6 | .sf/ 7 | .sfdx/ 8 | .localdevserver/ 9 | .vscode/ 10 | deploy-options.json 11 | 12 | # LWC VSCode autocomplete 13 | **/lwc/jsconfig.json 14 | 15 | # Code coverage reports 16 | test-coverage/ 17 | 18 | # Salesforce metadata 19 | **/customMetadata/CacheValue.**.md-meta.xml 20 | **.profile-meta.xml 21 | 22 | # Logs 23 | logs 24 | *.log 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # Dependency directories 30 | node_modules/ 31 | 32 | # Eslint cache 33 | .eslintcache 34 | 35 | # MacOS system files 36 | .DS_Store 37 | 38 | # Windows system files 39 | Thumbs.db 40 | ehthumbs.db 41 | [Dd]esktop.ini 42 | $RECYCLE.BIN/ 43 | 44 | # Local environment variables 45 | .env -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run precommit -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running prettier 2 | # More information: https://prettier.io/docs/en/ignore.html 3 | # 4 | 5 | **/staticresources/** 6 | .localdevserver/ 7 | .sf/ 8 | .sfdx/ 9 | .husky/ 10 | coverage/ 11 | node_modules/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "printWidth": 160, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "none" 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jonathan Gillespie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nebula Cache Manager 2 | 3 | A flexible cache management system for Salesforce Apex developers. Built to be scalable & configurable. 4 | 5 | Learn more about the history & implementation of this repo in [the Joys of Apex article 'Iteratively Building a Flexible Caching System for Apex'](https://www.jamessimone.net/blog/joys-of-apex/iteratively-building-a-flexible-caching-system/) 6 | 7 | ## Unlocked Package - `Nebula` Namespace - v1.0.2 8 | 9 | [![Install Unlocked Package (Nebula namespace) in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015nCfQAI) 10 | [![Install Unlocked Package (Nebula namespace) in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015nCfQAI) 11 | 12 | ## Unlocked Package - No Namespace - v1.0.2 13 | 14 | [![Install Unlocked Package (no namespace) in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015nCaQAI) 15 | [![Install Unlocked Package (no namespace) in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015nCaQAI) 16 | 17 | --- 18 | 19 | ## Cache Manager for Apex: Quick Start 20 | 21 | For Apex developers, the `CacheManager` class has several methods that can be used to cache data in 1 of the 3 supported cache types - transaction, organization platform cache, and session platform cache. Each cache type implements the interface `CacheManager.Cacheable` - regardless of which cache type you choose, the way you interact with each cache type is consistent. 22 | 23 | ```java 24 | // This will cache a Map that contains all queues in the current org (if the data has not been cached) 25 | // or it will return the cached version of the data (if the data has previously been cached) 26 | public static Map getQueues() { 27 | String cacheKey = 'queues'; 28 | Map queueDeveloperNameToQueueGroup; 29 | if (CacheManager.getOrganization().contains(cacheKey)) { 30 | queueDeveloperNameToQueueGroup = (Map) CacheManager.getOrganization().get(cacheKey); 31 | } else { 32 | queueDeveloperNameToQueueGroup = new Map(); 33 | for (Group queueGroup : [SELECT Id, DeveloperName, Email, Name FROM Group WHERE Type = 'Queue']) { 34 | queueDeveloperNameToQueueGroup.put(queueGroup.DeveloperName, queueGroup); 35 | } 36 | CacheManager.getOrganization().put(cacheKey, queueDeveloperNameToQueueGroup); 37 | } 38 | return queueDeveloperNameToQueueGroup; 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /config/linters/pmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Nebula Cache Manager custom PMD ruleset 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /config/scratch-orgs/base-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "Nebula Cache Manager - Base Scratch Org", 3 | "edition": "Enterprise", 4 | "hasSampleData": true, 5 | "country": "US", 6 | "language": "en_US", 7 | "features": [], 8 | "settings": { 9 | "securitySettings": { 10 | "enableAdminLoginAsAnyUser": true 11 | }, 12 | "userManagementSettings": { 13 | "enableEnhancedPermsetMgmt": true, 14 | "enableEnhancedProfileMgmt": true, 15 | "enableNewProfileUI": true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/scratch-orgs/platform-cache-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "Nebula Cache Manager - Platform Cache Scratch Org", 3 | "edition": "Enterprise", 4 | "hasSampleData": true, 5 | "country": "US", 6 | "language": "en_US", 7 | "features": ["PlatformCache"], 8 | "settings": { 9 | "securitySettings": { 10 | "enableAdminLoginAsAnyUser": true 11 | }, 12 | "userManagementSettings": { 13 | "enableEnhancedPermsetMgmt": true, 14 | "enableEnhancedProfileMgmt": true, 15 | "enableNewProfileUI": true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /images/btn-install-unlocked-package-production.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongpie/NebulaCacheManager/3dbf694f3afe3b1aae414bb84997453342285cc5/images/btn-install-unlocked-package-production.png -------------------------------------------------------------------------------- /images/btn-install-unlocked-package-sandbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jongpie/NebulaCacheManager/3dbf694f3afe3b1aae414bb84997453342285cc5/images/btn-install-unlocked-package-sandbox.png -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'sfdx-project.json': () => { 3 | return `npm run package:version:number:sync`; 4 | }, 5 | '*.{cls,cmp,component,css,html,js,json,md,page,trigger,yaml,yml}': filenames => filenames.map(filename => `prettier --write '${filename}'`), 6 | '*.{cls,trigger}': () => { 7 | return [`npm run scan:apex`]; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/cachePartitions/CacheManagerPartition.cachePartition-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | CacheManagerPartition 5 | 6 | 0 7 | 0 8 | 0 9 | 0 10 | Session 11 | 12 | 13 | 0 14 | 0 15 | 0 16 | 0 17 | Organization 18 | 19 | 20 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/classes/CacheManager.cls: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------------// 2 | // This file is part of the Nebula Cache Manager project, released under the MIT License. // 3 | // See LICENSE file or go to https://github.com/jongpie/NebulaCacheManager for full license details. // 4 | //---------------------------------------------------------------------------------------------------// 5 | 6 | @SuppressWarnings('PMD.ApexDoc, PMD.AvoidDebugStatements, PMD.AvoidGlobalModifier, PMD.ExcessivePublicCount, PMD.PropertyNamingConventions') 7 | global without sharing class CacheManager { 8 | @TestVisible 9 | private static final Map CONFIGURATION_DEVELOPER_NAME_TO_CACHEABLE_INSTANCE = new Map(); 10 | @TestVisible 11 | private static final List CONFIGURED_CACHE_VALUES = Schema.CacheValue__mdt.getAll().values(); 12 | @TestVisible 13 | private static final String CURRENT_VERSION_NUMBER = 'v1.0.2'; 14 | @TestVisible 15 | private static final String PLATFORM_CACHE_NULL_VALUE = '<{(CACHE_VALUE_IS_NULL)}>'; // Presumably, no one will ever use this as an actual value 16 | @TestVisible 17 | private static final CacheConfiguration__mdt ORGANIZATION_CACHE_CONFIGURATION = Schema.CacheConfiguration__mdt.getInstance('Organization').clone(); 18 | @TestVisible 19 | private static final CacheConfiguration__mdt SESSION_CACHE_CONFIGURATION = Schema.CacheConfiguration__mdt.getInstance('Session').clone(); 20 | @TestVisible 21 | private static final CacheConfiguration__mdt TRANSACTION_CACHE_CONFIGURATION = Schema.CacheConfiguration__mdt.getInstance('Transaction').clone(); 22 | 23 | private static Map cacheTypeToMockPartitionProxy = new Map(); 24 | 25 | private static final System.Pattern ALPHANUMERIC_REGEX_PATTERN { 26 | get { 27 | if (ALPHANUMERIC_REGEX_PATTERN == null) { 28 | ALPHANUMERIC_REGEX_PATTERN = System.Pattern.compile('^[a-zA-Z0-9]+$'); 29 | } 30 | return ALPHANUMERIC_REGEX_PATTERN; 31 | } 32 | private set; 33 | } 34 | 35 | static { 36 | System.debug(System.LoggingLevel.INFO, 'Nebula Cache Manager - Version Number: ' + CURRENT_VERSION_NUMBER); 37 | } 38 | 39 | @TestVisible 40 | private enum PlatformCacheType { 41 | ORGANIZATION, 42 | SESSION 43 | } 44 | 45 | global interface Cacheable { 46 | Boolean contains(String key); 47 | Map contains(Set keys); 48 | Boolean containsAll(Set keys); 49 | Object get(String key); 50 | Object get(String key, System.Type cacheBuilderClass); 51 | Map get(Set keys); 52 | Map getAll(); 53 | Set getKeys(); 54 | Boolean isAvailable(); 55 | Boolean isEnabled(); 56 | Boolean isImmutable(); 57 | void put(String key, Object value); 58 | void put(Map keyToValue); 59 | void remove(String key); 60 | void remove(Set keys); 61 | void removeAll(); 62 | } 63 | 64 | global static Cacheable getOrganizationCache() { 65 | return getOrganizationCache(ORGANIZATION_CACHE_CONFIGURATION); 66 | } 67 | 68 | global static Cacheable getOrganizationCache(CacheConfiguration__mdt configuration) { 69 | return getPlatformCache(configuration, PlatformCacheType.ORGANIZATION); 70 | } 71 | 72 | global static Cacheable getSessionCache() { 73 | return getSessionCache(SESSION_CACHE_CONFIGURATION); 74 | } 75 | 76 | global static Cacheable getSessionCache(CacheConfiguration__mdt configuration) { 77 | return getPlatformCache(configuration, PlatformCacheType.SESSION); 78 | } 79 | 80 | global static Cacheable getTransactionCache() { 81 | if (CONFIGURATION_DEVELOPER_NAME_TO_CACHEABLE_INSTANCE.containsKey(TRANSACTION_CACHE_CONFIGURATION.DeveloperName)) { 82 | return CONFIGURATION_DEVELOPER_NAME_TO_CACHEABLE_INSTANCE.get(TRANSACTION_CACHE_CONFIGURATION.DeveloperName); 83 | } 84 | 85 | TransactionCache transactionCacheInstance = new TransactionCache(TRANSACTION_CACHE_CONFIGURATION); 86 | CONFIGURATION_DEVELOPER_NAME_TO_CACHEABLE_INSTANCE.put(TRANSACTION_CACHE_CONFIGURATION.DeveloperName, transactionCacheInstance); 87 | 88 | return transactionCacheInstance; 89 | } 90 | 91 | @TestVisible 92 | private static void setMockPartitionProxy(PlatformCacheType cacheType, PlatformCachePartitionProxy mockPartitionProxy) { 93 | cacheTypeToMockPartitionProxy.put(cacheType, mockPartitionProxy); 94 | } 95 | 96 | @TestVisible 97 | private static void validateKey(String key) { 98 | Matcher regexMatcher = ALPHANUMERIC_REGEX_PATTERN.matcher(key); 99 | if (regexMatcher.matches() == false) { 100 | throw new IllegalArgumentException('Key must be alphanumeric, received key: ' + key); 101 | } 102 | } 103 | 104 | private static Cacheable getPlatformCache(CacheConfiguration__mdt configuration, PlatformCacheType cacheType) { 105 | if (CONFIGURATION_DEVELOPER_NAME_TO_CACHEABLE_INSTANCE.containsKey(configuration.DeveloperName)) { 106 | return CONFIGURATION_DEVELOPER_NAME_TO_CACHEABLE_INSTANCE.get(configuration.DeveloperName); 107 | } 108 | 109 | PlatformCachePartitionProxy partitionProxy = new PlatformCachePartitionProxy(cacheType, configuration.PlatformCachePartitionName__c); 110 | if (cacheTypeToMockPartitionProxy.containsKey(cacheType)) { 111 | partitionProxy = cacheTypeToMockPartitionProxy.get(cacheType); 112 | } 113 | 114 | CacheConfiguration__mdt localTransactionCacheConfiguration = new CacheConfiguration__mdt( 115 | IsEnabled__c = true, 116 | IsImmutable__c = configuration.IsImmutable__c 117 | ); 118 | 119 | PlatformCache platformCache = new PlatformCache(configuration, new TransactionCache(localTransactionCacheConfiguration), partitionProxy); 120 | CONFIGURATION_DEVELOPER_NAME_TO_CACHEABLE_INSTANCE.put(configuration.DeveloperName, platformCache); 121 | 122 | return platformCache; 123 | } 124 | 125 | private static Map loadConfiguredCacheValues(CacheConfiguration__mdt cacheConfiguration) { 126 | Map keyToCacheValue = new Map(); 127 | if (cacheConfiguration.IsEnabled__c == false) { 128 | return keyToCacheValue; 129 | } 130 | 131 | for (CacheValue__mdt configuredCacheValue : CONFIGURED_CACHE_VALUES) { 132 | if (configuredCacheValue.Cache__c == cacheConfiguration.Id && configuredCacheValue.IsEnabled__c == true) { 133 | System.Type dataType = System.Type.forName(configuredCacheValue.DataType__c); 134 | Boolean isString = configuredCacheValue.DataType__c == String.class.getName(); 135 | Object castedValue = isString ? configuredCacheValue.Value__c : System.JSON.deserialize(configuredCacheValue.Value__c, dataType); 136 | keyToCacheValue.put(configuredCacheValue.Key__c, castedValue); 137 | } 138 | } 139 | return keyToCacheValue; 140 | } 141 | 142 | @SuppressWarnings('PMD.CognitiveComplexity, PMD.CyclomaticComplexity') 143 | private class PlatformCache implements Cacheable { 144 | private final PlatformCachePartitionProxy cachePartitionProxy; 145 | private final CacheConfiguration__mdt configuration; 146 | private final TransactionCache fallbackTransactionCache; 147 | 148 | private PlatformCache(CacheConfiguration__mdt configuration, transactionCache transactionCache, PlatformCachePartitionProxy cachePartitionProxy) { 149 | this.configuration = configuration; 150 | this.fallbackTransactionCache = transactionCache; 151 | this.cachePartitionProxy = cachePartitionProxy; 152 | 153 | this.put(loadConfiguredCacheValues(this.configuration)); 154 | } 155 | 156 | public Boolean contains(String key) { 157 | if (this.configuration.IsEnabled__c == false || this.fallbackTransactionCache.contains(key) == true || this.cachePartitionProxy.isAvailable() == false) { 158 | return this.fallbackTransactionCache.contains(key); 159 | } else { 160 | return this.cachePartitionProxy.contains(key); 161 | } 162 | } 163 | 164 | public Map contains(Set keys) { 165 | Map keyToContainsResult = this.cachePartitionProxy.contains(keys); 166 | keyToContainsResult.putAll(this.fallbackTransactionCache.contains(keys)); 167 | return keyToContainsResult; 168 | } 169 | 170 | public Boolean containsAll(Set keys) { 171 | Map keyToContainsResult = this.contains(keys); 172 | if (keyToContainsResult.isEmpty() == true) { 173 | return false; 174 | } 175 | 176 | for (String key : keyToContainsResult.keySet()) { 177 | Boolean containsKey = keyToContainsResult.get(key); 178 | if (containsKey == false) { 179 | return false; 180 | } 181 | } 182 | return true; 183 | } 184 | 185 | public Object get(String key) { 186 | if (this.fallbackTransactionCache.contains(key) || this.cachePartitionProxy.isAvailable() == false) { 187 | return this.fallbackTransactionCache.get(key); 188 | } else { 189 | Object value = this.cachePartitionProxy.get(key); 190 | // Platform cache does not support storing null values, so a predefined value is used as a substitute 191 | if (value == PLATFORM_CACHE_NULL_VALUE) { 192 | value = null; 193 | } 194 | this.fallbackTransactionCache.put(key, value); 195 | return value; 196 | } 197 | } 198 | 199 | public Object get(String key, System.Type cacheBuilderClass) { 200 | if (this.fallbackTransactionCache.contains(key)) { 201 | return this.fallbackTransactionCache.get(key); 202 | } else if (this.cachePartitionProxy.isAvailable() == false) { 203 | return this.fallbackTransactionCache.get(key, cacheBuilderClass); 204 | } else { 205 | // Cache.CacheBuilder.doLoad method can return null 206 | Object value = this.cachePartitionProxy.get(key, cacheBuilderClass); 207 | this.fallbackTransactionCache.put(key, value); 208 | return value; 209 | } 210 | } 211 | 212 | public Map get(Set keys) { 213 | Map keyToValue = this.cachePartitionProxy.get(keys); 214 | if (keyToValue == null) { 215 | keyToValue = new Map(); 216 | } 217 | keyToValue.putAll(this.fallbackTransactionCache.get(keys)); 218 | return keyToValue; 219 | } 220 | 221 | public Map getAll() { 222 | return this.get(this.getKeys()); 223 | } 224 | 225 | public Set getKeys() { 226 | Set keys = this.cachePartitionProxy.getKeys(); 227 | if (keys == null) { 228 | keys = new Set(); 229 | } 230 | keys.addAll(this.fallbackTransactionCache.getKeys()); 231 | return keys; 232 | } 233 | 234 | public Boolean isAvailable() { 235 | return this.isEnabled() && this.cachePartitionProxy.isAvailable() == true; 236 | } 237 | 238 | public Boolean isEnabled() { 239 | return this.configuration?.IsEnabled__c == true; 240 | } 241 | 242 | public Boolean isImmutable() { 243 | return this.configuration?.IsImmutable__c == true; 244 | } 245 | 246 | public void put(String key, Object value) { 247 | if (this.isEnabled() == false) { 248 | return; 249 | } 250 | 251 | validateKey(key); 252 | this.fallbackTransactionCache.put(key, value); 253 | 254 | if (this.isAvailable() == true && this.isImmutable() == false || this.contains(key) == false) { 255 | // Platform cache does not support storing null values, so a predefined value is used as a substitute 256 | if (value == null) { 257 | value = PLATFORM_CACHE_NULL_VALUE; 258 | } 259 | Cache.Visibility visibility = Cache.Visibility.valueOf(this.configuration.PlatformCacheVisibility__c.toUpperCase()); 260 | this.cachePartitionProxy.put(key, value, this.configuration.PlatformCacheTimeToLive__c.intValue(), visibility, this.configuration.IsImmutable__c); 261 | } 262 | } 263 | 264 | public void put(Map keyToValue) { 265 | for (String key : keyToValue.keySet()) { 266 | this.put(key, keyToValue.get(key)); 267 | } 268 | } 269 | 270 | public void remove(String key) { 271 | if (this.isImmutable() == true) { 272 | return; 273 | } 274 | 275 | this.fallbackTransactionCache.remove(key); 276 | if (this.isAvailable() == true) { 277 | this.cachePartitionProxy.remove(key); 278 | } 279 | } 280 | 281 | public void remove(Set keys) { 282 | for (String key : keys) { 283 | this.remove(key); 284 | } 285 | } 286 | 287 | public void removeAll() { 288 | for (String key : this.getKeys()) { 289 | this.remove(key); 290 | } 291 | } 292 | } 293 | 294 | @TestVisible 295 | private virtual class PlatformCachePartitionProxy { 296 | private final Cache.Partition platformCachePartition; 297 | 298 | @SuppressWarnings('PMD.EmptyCatchBlock') 299 | protected PlatformCachePartitionProxy(PlatformCacheType cacheType, String partitionName) { 300 | // If the specified partition name is not found, the platform automatically throws a runtime exception, which isn't ideal. 301 | // It seems better to eat the exceptions & fallback to the transaction cache (which doesn't rely on Platform Cache). 302 | try { 303 | switch on cacheType { 304 | when ORGANIZATION { 305 | this.platformCachePartition = Cache.Org.getPartition(partitionName); 306 | } 307 | when SESSION { 308 | this.platformCachePartition = Cache.Session.getPartition(partitionName); 309 | } 310 | } 311 | } catch (Cache.Org.OrgCacheException orgCacheException) { 312 | // No-op if the partition can't be found - the rest of the code will fallback to using the transaction cache 313 | } catch (Cache.Session.SessionCacheException sessionCacheException) { 314 | // No-op if the partition can't be found - the rest of the code will fallback to using the transaction cache 315 | } 316 | } 317 | 318 | public virtual Boolean contains(String key) { 319 | return this.platformCachePartition?.contains(key) == true; 320 | } 321 | 322 | public Map contains(Set keys) { 323 | Map keyToContainsResult = this.platformCachePartition?.contains(keys); 324 | if (keyToContainsResult == null) { 325 | keyToContainsResult = new Map(); 326 | } 327 | if (keyToContainsResult.isEmpty() == true) { 328 | for (String key : keys) { 329 | keyToContainsResult.put(key, false); 330 | } 331 | } 332 | return keyToContainsResult; 333 | } 334 | 335 | public virtual Object get(String key) { 336 | return this.platformCachePartition?.get(key); 337 | } 338 | 339 | public virtual Object get(String key, System.Type cacheBuilderClass) { 340 | return this.platformCachePartition?.get(cacheBuilderClass, key); 341 | } 342 | 343 | public virtual Map get(Set keys) { 344 | return this.platformCachePartition?.get(keys); 345 | } 346 | 347 | public virtual Set getKeys() { 348 | return this.platformCachePartition?.getKeys(); 349 | } 350 | 351 | public virtual Boolean isAvailable() { 352 | return this.platformCachePartition?.isAvailable() == true; 353 | } 354 | 355 | @SuppressWarnings('PMD.ExcessiveParameterList') 356 | public virtual void put(String key, Object value, Integer cacheTtlSeconds, Cache.Visibility cacheVisiblity, Boolean isCacheImmutable) { 357 | this.platformCachePartition?.put(key, value, cacheTtlSeconds, cacheVisiblity, isCacheImmutable); 358 | } 359 | 360 | public virtual void remove(String key) { 361 | this.platformCachePartition?.remove(key); 362 | } 363 | } 364 | 365 | private class TransactionCache implements Cacheable { 366 | private final CacheConfiguration__mdt configuration; 367 | private final Map keyToValue = new Map(); 368 | 369 | private TransactionCache(CacheConfiguration__mdt configuration) { 370 | this.configuration = configuration; 371 | 372 | this.put(loadConfiguredCacheValues(this.configuration)); 373 | } 374 | 375 | public Boolean contains(String key) { 376 | return this.keyToValue.containsKey(key); 377 | } 378 | 379 | public Map contains(Set keys) { 380 | Map keyToContainsResult = new Map(); 381 | for (String key : keys) { 382 | keyToContainsResult.put(key, this.contains(key)); 383 | } 384 | return keyToContainsResult; 385 | } 386 | 387 | public Boolean containsAll(Set keys) { 388 | return this.keyToValue.keySet().containsAll(keys); 389 | } 390 | 391 | public Object get(String key) { 392 | return this.keyToValue.get(key); 393 | } 394 | 395 | public Object get(String key, System.Type cacheBuilderClass) { 396 | if (this.contains(key) == false) { 397 | Cache.CacheBuilder cacheBuilder = (Cache.CacheBuilder) cacheBuilderClass.newInstance(); 398 | Object value = cacheBuilder.doLoad(key); 399 | this.put(key, value); 400 | } 401 | return this.get(key); 402 | } 403 | 404 | public Map get(Set keys) { 405 | Map matchingKeyToValue = new Map(); 406 | for (String key : keys) { 407 | matchingKeyToValue.put(key, this.get(key)); 408 | } 409 | return matchingKeyToValue; 410 | } 411 | 412 | public Map getAll() { 413 | return this.keyToValue.clone(); 414 | } 415 | 416 | public Set getKeys() { 417 | return this.keyToValue.keySet(); 418 | } 419 | 420 | public Boolean isAvailable() { 421 | return this.isEnabled() == true; 422 | } 423 | 424 | public Boolean isEnabled() { 425 | return this.configuration?.IsEnabled__c == true; 426 | } 427 | 428 | public Boolean isImmutable() { 429 | return this.configuration?.IsImmutable__c == true; 430 | } 431 | 432 | public void put(String key, Object value) { 433 | if (this.isEnabled() == true || (this.isImmutable() == false || this.contains(key) == false)) { 434 | validateKey(key); 435 | this.keyToValue.put(key, value); 436 | } 437 | } 438 | 439 | public void put(Map keyToValue) { 440 | for (String key : keyToValue.keySet()) { 441 | this.put(key, keyToValue.get(key)); 442 | } 443 | } 444 | 445 | public void remove(String key) { 446 | if (this.isEnabled() == true && this.isImmutable() == false) { 447 | this.keyToValue.remove(key); 448 | } 449 | } 450 | 451 | public void remove(Set keys) { 452 | for (String key : keys) { 453 | this.remove(key); 454 | } 455 | } 456 | 457 | public void removeAll() { 458 | if (this.isEnabled() == true && this.isImmutable() == false) { 459 | this.keyToValue.clear(); 460 | } 461 | } 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/classes/CacheManager.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/classes/CacheManager_Tests.cls: -------------------------------------------------------------------------------- 1 | //---------------------------------------------------------------------------------------------------// 2 | // This file is part of the Nebula Cache Manager project, released under the MIT License. // 3 | // See LICENSE file or go to https://github.com/jongpie/NebulaCacheManager for full license details. // 4 | //---------------------------------------------------------------------------------------------------// 5 | 6 | /** 7 | * @description When testing Platform Cache partitions, there is no way to directly mock the partitions. Furthermore, the partitions 8 | * configured in the org are actually used in test contexts, so if a partition exists but does not have storage space 9 | * allocated in the org, then any tests that try to assert that data is cached in the partitions will fail. 10 | * To help overcome this platform limitation, a mock class - `MockPlatformCachePartitionProxy` - is used 11 | * to simulate how the code would behave with different partition configurations. 12 | * Additional integration tests (that actually test real platform cache partitions) are used in Nebula Logger's pipeline 13 | * but are not included in core package since those tests may fail in some orgs. 14 | */ 15 | @SuppressWarnings('PMD.ApexDoc, PMD.ApexAssertionsShouldIncludeMessage, PMD.CyclomaticComplexity, PMD.MethodNamingConventions') 16 | @IsTest(IsParallel=true) 17 | private class CacheManager_Tests { 18 | static { 19 | CacheManager.CONFIGURED_CACHE_VALUES.clear(); 20 | } 21 | 22 | @IsTest 23 | static void it_does_not_put_key_in_organisation_cache_when_configuration_is_disabled() { 24 | String mockKey = 'SomeKey'; 25 | CacheConfiguration__mdt disabledConfiguration = new CacheConfiguration__mdt( 26 | IsEnabled__c = false, 27 | IsImmutable__c = true, 28 | PlatformCachePartitionName__c = 'Mock' 29 | ); 30 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 31 | CacheManager.Cacheable cache = CacheManager.getOrganizationCache(disabledConfiguration); 32 | System.Assert.isFalse(cache.contains(mockKey)); 33 | 34 | cache.put(mockKey, mockValue); 35 | 36 | System.Assert.isFalse(cache.contains(mockKey)); 37 | } 38 | 39 | @IsTest 40 | static void it_does_not_put_key_in_session_cache_when_configuration_is_disabled() { 41 | String mockKey = 'SomeKey'; 42 | CacheConfiguration__mdt disabledConfiguration = new CacheConfiguration__mdt( 43 | IsEnabled__c = false, 44 | IsImmutable__c = true, 45 | PlatformCachePartitionName__c = 'Mock' 46 | ); 47 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 48 | CacheManager.Cacheable cache = CacheManager.getSessionCache(disabledConfiguration); 49 | System.Assert.isFalse(cache.contains(mockKey)); 50 | 51 | cache.put(mockKey, mockValue); 52 | 53 | System.Assert.isFalse(cache.contains(mockKey)); 54 | } 55 | 56 | @IsTest 57 | static void it_adds_new_key_to_transaction_cache() { 58 | String mockKey = 'SomeKey'; 59 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 60 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(mockKey)); 61 | 62 | CacheManager.getTransactionCache().put(mockKey, mockValue); 63 | 64 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); 65 | System.Assert.areEqual(mockValue, CacheManager.getTransactionCache().get(mockKey)); 66 | } 67 | 68 | @IsTest 69 | static void it_adds_new_key_with_null_value_to_transaction_cache() { 70 | String mockKey = 'SomeKey'; 71 | User mockValue = null; 72 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(mockKey)); 73 | 74 | CacheManager.getTransactionCache().put(mockKey, mockValue); 75 | 76 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); 77 | System.Assert.areEqual(mockValue, CacheManager.getTransactionCache().get(mockKey)); 78 | } 79 | 80 | @IsTest 81 | static void it_adds_new_key_with_cachebuilder_to_transaction_cache_when_get_key() { 82 | String mockKey = 'SomeKey'; 83 | System.Type cacheBuilderStubType = CacheBuilderStub.class; 84 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(mockKey)); 85 | 86 | Object generatedValue = CacheManager.getTransactionCache().get(mockKey, cacheBuilderStubType); 87 | 88 | System.Assert.isNotNull(generatedValue); 89 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); 90 | 91 | Object cachedValue = CacheManager.getTransactionCache().get(mockKey, cacheBuilderStubType); 92 | 93 | System.Assert.isNotNull(cachedValue); 94 | System.Assert.areEqual(generatedValue, cachedValue); 95 | } 96 | 97 | @IsTest 98 | static void it_adds_configured_key_and_value_to_transaction_cache() { 99 | String mockKey = 'SomeKey'; 100 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 101 | CacheValue__mdt configuredCacheValue = new CacheValue__mdt( 102 | Cache__c = CacheManager.TRANSACTION_CACHE_CONFIGURATION.Id, 103 | DataType__c = Schema.User.class.getName(), 104 | IsEnabled__c = true, 105 | Key__c = mockKey, 106 | Value__c = JSON.serialize(mockValue) 107 | ); 108 | CacheManager.CONFIGURED_CACHE_VALUES.add(configuredCacheValue); 109 | 110 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); 111 | Object returnedValue = CacheManager.getTransactionCache().get(mockKey); 112 | 113 | System.Assert.areEqual(mockValue, returnedValue); 114 | System.Assert.isInstanceOfType(returnedValue, Schema.User.class); 115 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); 116 | System.Assert.areEqual(mockValue, CacheManager.getTransactionCache().get(mockKey)); 117 | } 118 | 119 | @IsTest 120 | static void it_updates_value_for_existing_key_in_transaction_cache() { 121 | String mockKey = 'SomeKey'; 122 | User oldMockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 123 | CacheManager.getTransactionCache().put(mockKey, oldMockValue); 124 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); 125 | System.Assert.areEqual(oldMockValue, CacheManager.getTransactionCache().get(mockKey)); 126 | Account newMockValue = new Account(Name = 'Some fake account'); 127 | 128 | CacheManager.getTransactionCache().put(mockKey, newMockValue); 129 | 130 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); 131 | System.Assert.areEqual(newMockValue, CacheManager.getTransactionCache().get(mockKey)); 132 | } 133 | 134 | @IsTest 135 | static void it_removes_value_for_existing_key_in_transaction_cache() { 136 | String mockKey = 'SomeKey'; 137 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 138 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(mockKey)); 139 | CacheManager.getTransactionCache().put(mockKey, mockValue); 140 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(mockKey)); 141 | System.Assert.areEqual(mockValue, CacheManager.getTransactionCache().get(mockKey)); 142 | 143 | CacheManager.getTransactionCache().remove(mockKey); 144 | 145 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(mockKey)); 146 | } 147 | 148 | @IsTest 149 | static void it_supports_bulk_operations_in_transaction_cache() { 150 | System.Assert.isTrue(CacheManager.getTransactionCache().containsAll(new Set())); 151 | System.Assert.isTrue(CacheManager.getTransactionCache().isAvailable()); 152 | Map keyToValue = new Map{ 153 | 'SomeDate' => Date.newInstance(1999, 9, 9), 154 | 'SomeString' => 'hello, world', 155 | 'SomeSObject' => new User(Id = System.UserInfo.getUserId()) 156 | }; 157 | System.Assert.isFalse(CacheManager.getTransactionCache().containsAll(keyToValue.keySet())); 158 | CacheManager.getTransactionCache().put(keyToValue); 159 | for (String key : keyToValue.keySet()) { 160 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(key)); 161 | } 162 | Map keyToContainsResult = CacheManager.getTransactionCache().contains(keyToValue.keySet()); 163 | for (String key : keyToContainsResult.keySet()) { 164 | Boolean containsResult = keyToContainsResult.get(key); 165 | System.Assert.isTrue(containsResult, 'Cache did not contain key: ' + key); 166 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(key)); 167 | } 168 | Map returnedKeyToValue = CacheManager.getTransactionCache().getAll(); 169 | System.Assert.areEqual(keyToValue, returnedKeyToValue); 170 | System.Assert.isTrue(CacheManager.getTransactionCache().containsAll(keyToValue.keySet()), '' + CacheManager.getTransactionCache().getKeys()); 171 | CacheManager.getTransactionCache().remove(keyToValue.keySet()); 172 | System.Assert.isFalse(CacheManager.getTransactionCache().containsAll(keyToValue.keySet())); 173 | } 174 | 175 | @IsTest 176 | static void it_remove_alls_keys_in_transaction_cache_when_remove_all_method_is_called() { 177 | System.Assert.isTrue(CacheManager.getTransactionCache().isAvailable()); 178 | Map keyToValue = new Map{ 179 | 'SomeDate' => Date.newInstance(1999, 9, 9), 180 | 'SomeString' => 'hello, world', 181 | 'SomeSObject' => new User(Id = System.UserInfo.getUserId()) 182 | }; 183 | CacheManager.getTransactionCache().put(keyToValue); 184 | System.Assert.areEqual(keyToValue.keySet(), CacheManager.getTransactionCache().getKeys()); 185 | 186 | CacheManager.getTransactionCache().removeAll(); 187 | 188 | System.Assert.isTrue(CacheManager.getTransactionCache().getKeys().isEmpty()); 189 | } 190 | 191 | @IsTest 192 | static void it_adds_new_key_to_organization_cache_when_organization_platform_cache_is_available() { 193 | String mockKey = 'SomeKey'; 194 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 195 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 196 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 197 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 198 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 199 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 200 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.containsMethodCallCount); 201 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.putMethodCallCount); 202 | 203 | CacheManager.getOrganizationCache().put(mockKey, mockValue); 204 | 205 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.putMethodCallCount); 206 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 207 | System.Assert.areEqual(mockValue, mockOrganizationPartitionProxy.get(mockKey)); 208 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 209 | System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); 210 | } 211 | 212 | @IsTest 213 | static void it_gets_value_from_organization_cache_when_transaction_cache_miss_and_organization_platform_cache_is_available() { 214 | String mockKey = 'SomeKey'; 215 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 216 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 217 | mockOrganizationPartitionProxy.put(mockKey, mockValue, 3600, Cache.Visibility.ALL, false); 218 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 219 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 220 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 221 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 222 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.containsMethodCallCount); 223 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.getMethodCallCount); 224 | 225 | Object actualResult = CacheManager.getOrganizationCache().get(mockKey); 226 | 227 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.getMethodCallCount); 228 | System.Assert.areEqual(mockValue, actualResult); 229 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 230 | } 231 | 232 | @IsTest 233 | static void it_gets_value_from_session_cache_when_transaction_cache_miss_and_session_platform_cache_is_available() { 234 | String mockKey = 'SomeKey'; 235 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 236 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 237 | mockSessionPartitionProxy.put(mockKey, mockValue, 3600, Cache.Visibility.ALL, false); 238 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 239 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 240 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 241 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 242 | System.Assert.areEqual(1, mockSessionPartitionProxy.containsMethodCallCount); 243 | System.Assert.areEqual(0, mockSessionPartitionProxy.getMethodCallCount); 244 | 245 | Object actualResult = CacheManager.getSessionCache().get(mockKey); 246 | 247 | System.Assert.areEqual(1, mockSessionPartitionProxy.getMethodCallCount); 248 | System.Assert.areEqual(mockValue, actualResult); 249 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 250 | } 251 | 252 | @IsTest 253 | static void it_gets_null_from_organization_cache_when_transaction_cache_miss_and_organization_platform_cache_is_available() { 254 | String mockKey = 'SomeKey'; 255 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 256 | mockOrganizationPartitionProxy.put(mockKey, CacheManager.PLATFORM_CACHE_NULL_VALUE, 3600, Cache.Visibility.ALL, false); 257 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 258 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 259 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 260 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 261 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.containsMethodCallCount); 262 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.getMethodCallCount); 263 | 264 | Object actualResult = CacheManager.getOrganizationCache().get(mockKey); 265 | 266 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.getMethodCallCount); 267 | System.Assert.areEqual(null, actualResult); 268 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 269 | } 270 | 271 | @IsTest 272 | static void it_gets_null_from_session_cache_when_transaction_cache_miss_and_session_platform_cache_is_available() { 273 | String mockKey = 'SomeKey'; 274 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 275 | mockSessionPartitionProxy.put(mockKey, CacheManager.PLATFORM_CACHE_NULL_VALUE, 3600, Cache.Visibility.ALL, false); 276 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 277 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 278 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 279 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 280 | System.Assert.areEqual(1, mockSessionPartitionProxy.containsMethodCallCount); 281 | System.Assert.areEqual(0, mockSessionPartitionProxy.getMethodCallCount); 282 | 283 | Object actualResult = CacheManager.getSessionCache().get(mockKey); 284 | 285 | System.Assert.areEqual(1, mockSessionPartitionProxy.getMethodCallCount); 286 | System.Assert.areEqual(null, actualResult); 287 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 288 | } 289 | 290 | @IsTest 291 | static void it_adds_new_key_to_fallback_transaction_cache_when_organization_platform_cache_is_not_available() { 292 | String mockKey = 'SomeKey'; 293 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 294 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(false); 295 | System.Assert.isFalse(mockOrganizationPartitionProxy.isAvailable()); 296 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 297 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 298 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 299 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 300 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.putMethodCallCount); 301 | 302 | CacheManager.getOrganizationCache().put(mockKey, mockValue); 303 | 304 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.putMethodCallCount); 305 | System.Assert.isFalse(mockOrganizationPartitionProxy.contains(mockKey)); 306 | System.Assert.isNull(mockOrganizationPartitionProxy.get(mockKey)); 307 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 308 | System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); 309 | } 310 | 311 | @IsTest 312 | static void it_adds_new_key_to_organization_cache_with_cachebuilder_when_organization_platform_cache_is_available() { 313 | String mockKey = 'SomeKey'; 314 | System.Type cacheBuilderStubType = CacheBuilderStub.class; 315 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 316 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 317 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 318 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 319 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 320 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.containsMethodCallCount); 321 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); 322 | 323 | Object generatedValue = CacheManager.getOrganizationCache().get(mockKey, cacheBuilderStubType); 324 | 325 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); 326 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 327 | System.Assert.isNotNull(generatedValue); 328 | 329 | Object cachedValue = CacheManager.getOrganizationCache().get(mockKey, cacheBuilderStubType); 330 | 331 | System.Assert.isNotNull(cachedValue); 332 | System.Assert.areEqual(generatedValue, cachedValue); 333 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); 334 | System.Assert.areEqual(cachedValue, mockOrganizationPartitionProxy.get(mockKey)); 335 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 336 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 337 | System.Assert.areEqual(cachedValue, CacheManager.getOrganizationCache().get(mockKey)); 338 | } 339 | 340 | @IsTest 341 | static void it_adds_new_key_to_fallback_transaction_cache_with_cachebuilder_when_organization_platform_cache_is_not_available() { 342 | String mockKey = 'SomeKey'; 343 | System.Type cacheBuilderStubType = CacheBuilderStub.class; 344 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(false); 345 | System.Assert.isFalse(mockOrganizationPartitionProxy.isAvailable()); 346 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 347 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 348 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 349 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 350 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); 351 | 352 | Object generatedValue = CacheManager.getOrganizationCache().get(mockKey, cacheBuilderStubType); 353 | 354 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); 355 | System.Assert.isFalse(mockOrganizationPartitionProxy.contains(mockKey)); 356 | System.Assert.isNotNull(generatedValue); 357 | 358 | Object cachedValue = CacheManager.getOrganizationCache().get(mockKey, cacheBuilderStubType); 359 | 360 | System.Assert.isNotNull(cachedValue); 361 | System.Assert.areEqual(generatedValue, cachedValue); 362 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.getCacheBuilderMethodCallCount); 363 | System.Assert.isNull(mockOrganizationPartitionProxy.get(mockKey)); 364 | System.Assert.isFalse(mockOrganizationPartitionProxy.contains(mockKey)); 365 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 366 | System.Assert.areEqual(cachedValue, CacheManager.getOrganizationCache().get(mockKey)); 367 | } 368 | 369 | @IsTest 370 | static void it_adds_configured_key_and_value_to_organization_cache_when_organization_platform_cache_is_available() { 371 | String mockKey = 'SomeKey'; 372 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 373 | CacheValue__mdt configuredCacheValue = new CacheValue__mdt( 374 | Cache__c = CacheManager.ORGANIZATION_CACHE_CONFIGURATION.Id, 375 | DataType__c = Schema.User.class.getName(), 376 | IsEnabled__c = true, 377 | Key__c = mockKey, 378 | Value__c = JSON.serialize(mockValue) 379 | ); 380 | CacheManager.CONFIGURED_CACHE_VALUES.add(configuredCacheValue); 381 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 382 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 383 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 384 | 385 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 386 | Object returnedValue = CacheManager.getOrganizationCache().get(mockKey); 387 | 388 | System.Assert.areEqual(mockValue, returnedValue); 389 | System.Assert.isInstanceOfType(returnedValue, Schema.User.class); 390 | System.Assert.areEqual(mockValue, mockOrganizationPartitionProxy.get(mockKey)); 391 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 392 | System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); 393 | } 394 | 395 | @IsTest 396 | static void it_adds_configured_key_with_null_value_to_organization_cache_when_organization_platform_cache_is_available() { 397 | String mockKey = 'SomeKey'; 398 | CacheValue__mdt configuredCacheValue = new CacheValue__mdt( 399 | Cache__c = CacheManager.ORGANIZATION_CACHE_CONFIGURATION.Id, 400 | DataType__c = String.class.getName(), 401 | IsEnabled__c = true, 402 | Key__c = mockKey, 403 | Value__c = null 404 | ); 405 | CacheManager.CONFIGURED_CACHE_VALUES.add(configuredCacheValue); 406 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 407 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 408 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 409 | 410 | Object returnedValue = CacheManager.getOrganizationCache().get(mockKey); 411 | 412 | System.Assert.areEqual(null, returnedValue); 413 | System.Assert.areEqual(CacheManager.PLATFORM_CACHE_NULL_VALUE, mockOrganizationPartitionProxy.get(mockKey)); 414 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 415 | System.Assert.areEqual(null, CacheManager.getOrganizationCache().get(mockKey)); 416 | } 417 | 418 | @IsTest 419 | static void it_adds_new_key_with_null_value_to_organization_cache_when_organization_platform_cache_is_available() { 420 | String mockKey = 'SomeKey'; 421 | User mockValue = null; 422 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 423 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 424 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 425 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.containsMethodCallCount); 426 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 427 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.containsMethodCallCount); 428 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.putMethodCallCount); 429 | 430 | CacheManager.getOrganizationCache().put(mockKey, mockValue); 431 | 432 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.putMethodCallCount); 433 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 434 | System.Assert.areEqual(CacheManager.PLATFORM_CACHE_NULL_VALUE, mockOrganizationPartitionProxy.get(mockKey)); 435 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 436 | System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); 437 | } 438 | 439 | @IsTest 440 | static void it_updates_value_for_existing_key_in_organization_cache_when_organization_platform_cache_is_available() { 441 | String mockKey = 'SomeKey'; 442 | User oldMockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 443 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 444 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 445 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 446 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.putMethodCallCount); 447 | CacheManager.getOrganizationCache().put(mockKey, oldMockValue); 448 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.putMethodCallCount); 449 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 450 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 451 | System.Assert.areEqual(oldMockValue, CacheManager.getOrganizationCache().get(mockKey)); 452 | Account newMockValue = new Account(Name = 'Some fake account'); 453 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.putMethodCallCount); 454 | 455 | CacheManager.getOrganizationCache().put(mockKey, newMockValue); 456 | 457 | System.Assert.areEqual(2, mockOrganizationPartitionProxy.putMethodCallCount); 458 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 459 | System.Assert.areEqual(newMockValue, mockOrganizationPartitionProxy.get(mockKey)); 460 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 461 | System.Assert.areEqual(newMockValue, CacheManager.getOrganizationCache().get(mockKey)); 462 | } 463 | 464 | @IsTest 465 | static void it_removes_value_for_existing_key_in_organization_cache_when_organization_platform_cache_is_available() { 466 | String mockKey = 'SomeKey'; 467 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 468 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 469 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 470 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 471 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 472 | CacheManager.getOrganizationCache().put(mockKey, mockValue); 473 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 474 | System.Assert.areEqual(mockValue, mockOrganizationPartitionProxy.get(mockKey)); 475 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 476 | System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); 477 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.removeMethodCallCount); 478 | 479 | CacheManager.getOrganizationCache().remove(mockKey); 480 | 481 | System.Assert.areEqual(1, mockOrganizationPartitionProxy.removeMethodCallCount); 482 | System.Assert.isFalse(mockOrganizationPartitionProxy.contains(mockKey)); 483 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 484 | } 485 | 486 | @IsTest 487 | static void it_removes_value_for_existing_key_in_organization_cache_when_organization_platform_cache_is_not_available() { 488 | String mockKey = 'SomeKey'; 489 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 490 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(false); 491 | System.Assert.isFalse(mockOrganizationPartitionProxy.isAvailable()); 492 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 493 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 494 | CacheManager.getOrganizationCache().put(mockKey, mockValue); 495 | System.Assert.isFalse(mockOrganizationPartitionProxy.contains(mockKey)); 496 | System.Assert.isNull(mockOrganizationPartitionProxy.get(mockKey)); 497 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 498 | System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); 499 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.removeMethodCallCount); 500 | 501 | CacheManager.getOrganizationCache().remove(mockKey); 502 | 503 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.removeMethodCallCount); 504 | System.Assert.isFalse(mockOrganizationPartitionProxy.contains(mockKey)); 505 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 506 | } 507 | 508 | @IsTest 509 | static void it_does_not_remove_value_for_existing_key_in_organization_cache_when_organization_platform_cache_is_immutable() { 510 | String mockKey = 'SomeKey'; 511 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 512 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 513 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 514 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 515 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(mockKey)); 516 | CacheManager.getOrganizationCache().put(mockKey, mockValue); 517 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 518 | System.Assert.areEqual(mockValue, mockOrganizationPartitionProxy.get(mockKey)); 519 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 520 | System.Assert.areEqual(mockValue, CacheManager.getOrganizationCache().get(mockKey)); 521 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.removeMethodCallCount); 522 | System.Assert.isFalse(CacheManager.getOrganizationCache().isImmutable()); 523 | CacheManager.ORGANIZATION_CACHE_CONFIGURATION.IsImmutable__c = true; 524 | System.Assert.isTrue(CacheManager.getOrganizationCache().isImmutable()); 525 | 526 | CacheManager.getOrganizationCache().remove(mockKey); 527 | 528 | System.Assert.areEqual(0, mockOrganizationPartitionProxy.removeMethodCallCount); 529 | System.Assert.isTrue(mockOrganizationPartitionProxy.contains(mockKey)); 530 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(mockKey)); 531 | } 532 | 533 | @IsTest 534 | static void it_supports_bulk_operations_in_organization_cache() { 535 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 536 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 537 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 538 | System.Assert.isTrue(CacheManager.getOrganizationCache().isAvailable()); 539 | Map keyToValue = new Map{ 540 | 'SomeDate' => Date.newInstance(1999, 9, 9), 541 | 'SomeString' => 'hello, world', 542 | 'SomeSObject' => new User(Id = System.UserInfo.getUserId()) 543 | }; 544 | System.Assert.isFalse(CacheManager.getOrganizationCache().containsAll(new Set())); 545 | System.Assert.isFalse(CacheManager.getOrganizationCache().containsAll(keyToValue.keySet())); 546 | CacheManager.getOrganizationCache().put(keyToValue); 547 | for (String key : keyToValue.keySet()) { 548 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(key)); 549 | } 550 | System.Assert.isFalse(CacheManager.getOrganizationCache().containsAll(new Set())); 551 | Map keyToContainsResult = CacheManager.getOrganizationCache().contains(keyToValue.keySet()); 552 | for (String key : keyToContainsResult.keySet()) { 553 | Boolean containsResult = keyToContainsResult.get(key); 554 | System.Assert.isTrue(containsResult, 'Cache did not contain key: ' + key); 555 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(key)); 556 | } 557 | Map returnedKeyToValue = CacheManager.getOrganizationCache().getAll(); 558 | System.Assert.areEqual(keyToValue, returnedKeyToValue); 559 | System.Assert.isTrue(CacheManager.getOrganizationCache().containsAll(keyToValue.keySet()), '' + CacheManager.getOrganizationCache().getKeys()); 560 | CacheManager.getOrganizationCache().remove(keyToValue.keySet()); 561 | System.Assert.isFalse(CacheManager.getOrganizationCache().containsAll(keyToValue.keySet())); 562 | } 563 | 564 | @IsTest 565 | static void it_remove_alls_keys_in_organization_cache_when_remove_all_method_is_called() { 566 | MockPlatformCachePartitionProxy mockOrganizationPartitionProxy = new MockPlatformCachePartitionProxy(true); 567 | System.Assert.isTrue(mockOrganizationPartitionProxy.isAvailable()); 568 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.ORGANIZATION, mockOrganizationPartitionProxy); 569 | System.Assert.isTrue(CacheManager.getOrganizationCache().isAvailable()); 570 | Map keyToValue = new Map{ 571 | 'SomeDate' => Date.newInstance(1999, 9, 9), 572 | 'SomeString' => 'hello, world', 573 | 'SomeSObject' => new User(Id = System.UserInfo.getUserId()) 574 | }; 575 | CacheManager.getOrganizationCache().put(keyToValue); 576 | System.Assert.areEqual(keyToValue.keySet(), CacheManager.getOrganizationCache().getKeys()); 577 | 578 | CacheManager.getOrganizationCache().removeAll(); 579 | 580 | System.Assert.isTrue(CacheManager.getOrganizationCache().getKeys().isEmpty()); 581 | } 582 | 583 | @IsTest 584 | static void it_adds_new_key_to_session_cache_with_cachebuilder_when_session_platform_cache_is_available() { 585 | String mockKey = 'SomeKey'; 586 | System.Type cacheBuilderStubType = CacheBuilderStub.class; 587 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 588 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 589 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 590 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 591 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 592 | System.Assert.areEqual(1, mockSessionPartitionProxy.containsMethodCallCount); 593 | System.Assert.areEqual(0, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); 594 | 595 | Object generatedValue = CacheManager.getSessionCache().get(mockKey, cacheBuilderStubType); 596 | 597 | System.Assert.areEqual(1, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); 598 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 599 | System.Assert.isNotNull(generatedValue); 600 | 601 | Object cachedValue = CacheManager.getSessionCache().get(mockKey, cacheBuilderStubType); 602 | 603 | System.Assert.isNotNull(cachedValue); 604 | System.Assert.areEqual(generatedValue, cachedValue); 605 | System.Assert.areEqual(1, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); 606 | System.Assert.areEqual(cachedValue, mockSessionPartitionProxy.get(mockKey)); 607 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 608 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 609 | System.Assert.areEqual(cachedValue, CacheManager.getSessionCache().get(mockKey)); 610 | } 611 | 612 | @IsTest 613 | static void it_adds_new_key_to_fallback_transaction_cache_with_cachebuilder_when_session_platform_cache_is_not_available() { 614 | String mockKey = 'SomeKey'; 615 | System.Type cacheBuilderStubType = CacheBuilderStub.class; 616 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(false); 617 | System.Assert.isFalse(mockSessionPartitionProxy.isAvailable()); 618 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 619 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 620 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 621 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 622 | System.Assert.areEqual(0, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); 623 | 624 | Object generatedValue = CacheManager.getSessionCache().get(mockKey, cacheBuilderStubType); 625 | 626 | System.Assert.areEqual(0, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); 627 | System.Assert.isFalse(mockSessionPartitionProxy.contains(mockKey)); 628 | System.Assert.isNotNull(generatedValue); 629 | 630 | Object cachedValue = CacheManager.getSessionCache().get(mockKey, cacheBuilderStubType); 631 | 632 | System.Assert.isNotNull(cachedValue); 633 | System.Assert.areEqual(generatedValue, cachedValue); 634 | System.Assert.areEqual(0, mockSessionPartitionProxy.getCacheBuilderMethodCallCount); 635 | System.Assert.isNull(mockSessionPartitionProxy.get(mockKey)); 636 | System.Assert.isFalse(mockSessionPartitionProxy.contains(mockKey)); 637 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 638 | System.Assert.areEqual(cachedValue, CacheManager.getSessionCache().get(mockKey)); 639 | } 640 | 641 | @IsTest 642 | static void it_adds_new_key_to_session_cache_when_session_platform_cache_is_available() { 643 | String mockKey = 'SomeKey'; 644 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 645 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 646 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 647 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 648 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 649 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 650 | System.Assert.areEqual(1, mockSessionPartitionProxy.containsMethodCallCount); 651 | System.Assert.areEqual(0, mockSessionPartitionProxy.putMethodCallCount); 652 | 653 | CacheManager.getSessionCache().put(mockKey, mockValue); 654 | 655 | System.Assert.areEqual(1, mockSessionPartitionProxy.putMethodCallCount); 656 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 657 | System.Assert.areEqual(mockValue, mockSessionPartitionProxy.get(mockKey)); 658 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 659 | System.Assert.areEqual(mockValue, CacheManager.getSessionCache().get(mockKey)); 660 | } 661 | 662 | @IsTest 663 | static void it_adds_new_key_to_fallback_transaction_cache_when_session_platform_cache_is_not_available() { 664 | String mockKey = 'SomeKey'; 665 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 666 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(false); 667 | System.Assert.isFalse(mockSessionPartitionProxy.isAvailable()); 668 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 669 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 670 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 671 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 672 | System.Assert.areEqual(0, mockSessionPartitionProxy.putMethodCallCount); 673 | 674 | CacheManager.getSessionCache().put(mockKey, mockValue); 675 | 676 | System.Assert.areEqual(0, mockSessionPartitionProxy.putMethodCallCount); 677 | System.Assert.isFalse(mockSessionPartitionProxy.contains(mockKey)); 678 | System.Assert.isNull(mockSessionPartitionProxy.get(mockKey)); 679 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 680 | System.Assert.areEqual(mockValue, CacheManager.getSessionCache().get(mockKey)); 681 | } 682 | 683 | @IsTest 684 | static void it_adds_configured_key_and_value_to_session_cache_when_session_platform_cache_is_available() { 685 | String mockKey = 'SomeKey'; 686 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 687 | CacheValue__mdt configuredCacheValue = new CacheValue__mdt( 688 | Cache__c = CacheManager.SESSION_CACHE_CONFIGURATION.Id, 689 | DataType__c = Schema.User.class.getName(), 690 | IsEnabled__c = true, 691 | Key__c = mockKey, 692 | Value__c = JSON.serialize(mockValue) 693 | ); 694 | CacheManager.CONFIGURED_CACHE_VALUES.add(configuredCacheValue); 695 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 696 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 697 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 698 | 699 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 700 | Object returnedValue = CacheManager.getSessionCache().get(mockKey); 701 | 702 | System.Assert.areEqual(mockValue, returnedValue); 703 | System.Assert.isInstanceOfType(returnedValue, Schema.User.class); 704 | System.Assert.areEqual(mockValue, mockSessionPartitionProxy.get(mockKey)); 705 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 706 | System.Assert.areEqual(mockValue, CacheManager.getSessionCache().get(mockKey)); 707 | } 708 | 709 | @IsTest 710 | static void it_adds_configured_key_with_null_value_to_session_cache_when_session_platform_cache_is_available() { 711 | String mockKey = 'SomeKey'; 712 | CacheValue__mdt configuredCacheValue = new CacheValue__mdt( 713 | Cache__c = CacheManager.SESSION_CACHE_CONFIGURATION.Id, 714 | DataType__c = String.class.getName(), 715 | IsEnabled__c = true, 716 | Key__c = mockKey, 717 | Value__c = null 718 | ); 719 | CacheManager.CONFIGURED_CACHE_VALUES.add(configuredCacheValue); 720 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 721 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 722 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 723 | 724 | Object returnedValue = CacheManager.getSessionCache().get(mockKey); 725 | 726 | System.Assert.areEqual(null, returnedValue); 727 | System.Assert.areEqual(CacheManager.PLATFORM_CACHE_NULL_VALUE, mockSessionPartitionProxy.get(mockKey)); 728 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 729 | System.Assert.areEqual(null, CacheManager.getSessionCache().get(mockKey)); 730 | } 731 | 732 | @IsTest 733 | static void it_adds_new_key_with_null_value_to_session_cache_when_session_platform_cache_is_available() { 734 | String mockKey = 'SomeKey'; 735 | User mockValue = null; 736 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 737 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 738 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 739 | System.Assert.areEqual(0, mockSessionPartitionProxy.containsMethodCallCount); 740 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 741 | System.Assert.areEqual(1, mockSessionPartitionProxy.containsMethodCallCount); 742 | System.Assert.areEqual(0, mockSessionPartitionProxy.putMethodCallCount); 743 | 744 | CacheManager.getSessionCache().put(mockKey, mockValue); 745 | 746 | System.Assert.areEqual(1, mockSessionPartitionProxy.putMethodCallCount); 747 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 748 | System.Assert.areEqual(CacheManager.PLATFORM_CACHE_NULL_VALUE, mockSessionPartitionProxy.get(mockKey)); 749 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 750 | System.Assert.areEqual(mockValue, CacheManager.getSessionCache().get(mockKey)); 751 | } 752 | 753 | @IsTest 754 | static void it_updates_value_for_existing_key_in_session_cache_when_session_platform_cache_is_available() { 755 | String mockKey = 'SomeKey'; 756 | User oldMockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 757 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 758 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 759 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 760 | System.Assert.areEqual(0, mockSessionPartitionProxy.putMethodCallCount); 761 | CacheManager.getSessionCache().put(mockKey, oldMockValue); 762 | System.Assert.areEqual(1, mockSessionPartitionProxy.putMethodCallCount); 763 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 764 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 765 | System.Assert.areEqual(oldMockValue, CacheManager.getSessionCache().get(mockKey)); 766 | Account newMockValue = new Account(Name = 'Some fake account'); 767 | System.Assert.areEqual(1, mockSessionPartitionProxy.putMethodCallCount); 768 | 769 | CacheManager.getSessionCache().put(mockKey, newMockValue); 770 | 771 | System.Assert.areEqual(2, mockSessionPartitionProxy.putMethodCallCount); 772 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 773 | System.Assert.areEqual(newMockValue, mockSessionPartitionProxy.get(mockKey)); 774 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 775 | System.Assert.areEqual(newMockValue, CacheManager.getSessionCache().get(mockKey)); 776 | } 777 | 778 | @IsTest 779 | static void it_removes_value_for_existing_key_in_session_cache_when_session_platform_cache_is_available() { 780 | String mockKey = 'SomeKey'; 781 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 782 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 783 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 784 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 785 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 786 | CacheManager.getSessionCache().put(mockKey, mockValue); 787 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 788 | System.Assert.areEqual(mockValue, mockSessionPartitionProxy.get(mockKey)); 789 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 790 | System.Assert.areEqual(mockValue, CacheManager.getSessionCache().get(mockKey)); 791 | System.Assert.areEqual(0, mockSessionPartitionProxy.removeMethodCallCount); 792 | 793 | CacheManager.getSessionCache().remove(mockKey); 794 | 795 | System.Assert.areEqual(1, mockSessionPartitionProxy.removeMethodCallCount); 796 | System.Assert.isFalse(mockSessionPartitionProxy.contains(mockKey)); 797 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 798 | } 799 | 800 | @IsTest 801 | static void it_removes_value_for_existing_key_in_session_cache_when_session_platform_cache_is_not_available() { 802 | String mockKey = 'SomeKey'; 803 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 804 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(false); 805 | System.Assert.isFalse(mockSessionPartitionProxy.isAvailable()); 806 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 807 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 808 | CacheManager.getSessionCache().put(mockKey, mockValue); 809 | System.Assert.isFalse(mockSessionPartitionProxy.contains(mockKey)); 810 | System.Assert.isNull(mockSessionPartitionProxy.get(mockKey)); 811 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 812 | System.Assert.areEqual(mockValue, CacheManager.getSessionCache().get(mockKey)); 813 | System.Assert.areEqual(0, mockSessionPartitionProxy.removeMethodCallCount); 814 | 815 | CacheManager.getSessionCache().remove(mockKey); 816 | 817 | System.Assert.areEqual(0, mockSessionPartitionProxy.removeMethodCallCount); 818 | System.Assert.isFalse(mockSessionPartitionProxy.contains(mockKey)); 819 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 820 | } 821 | 822 | @IsTest 823 | static void it_does_not_remove_value_for_existing_key_in_session_cache_when_session_platform_cache_is_immutable() { 824 | String mockKey = 'SomeKey'; 825 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 826 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 827 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 828 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 829 | System.Assert.isFalse(CacheManager.getSessionCache().contains(mockKey)); 830 | CacheManager.getSessionCache().put(mockKey, mockValue); 831 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 832 | System.Assert.areEqual(mockValue, mockSessionPartitionProxy.get(mockKey)); 833 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 834 | System.Assert.areEqual(mockValue, CacheManager.getSessionCache().get(mockKey)); 835 | System.Assert.areEqual(0, mockSessionPartitionProxy.removeMethodCallCount); 836 | System.Assert.isFalse(CacheManager.getSessionCache().isImmutable()); 837 | CacheManager.SESSION_CACHE_CONFIGURATION.IsImmutable__c = true; 838 | System.Assert.isTrue(CacheManager.getSessionCache().isImmutable()); 839 | 840 | CacheManager.getSessionCache().remove(mockKey); 841 | 842 | System.Assert.areEqual(0, mockSessionPartitionProxy.removeMethodCallCount); 843 | System.Assert.isTrue(mockSessionPartitionProxy.contains(mockKey)); 844 | System.Assert.isTrue(CacheManager.getSessionCache().contains(mockKey)); 845 | } 846 | 847 | @IsTest 848 | static void it_supports_bulk_operations_in_session_cache() { 849 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 850 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 851 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 852 | System.Assert.isTrue(CacheManager.getSessionCache().isAvailable()); 853 | Map keyToValue = new Map{ 854 | 'SomeDate' => Date.newInstance(1999, 9, 9), 855 | 'SomeString' => 'hello, world', 856 | 'SomeSObject' => new User(Id = System.UserInfo.getUserId()) 857 | }; 858 | System.Assert.isFalse(CacheManager.getSessionCache().containsAll(new Set())); 859 | System.Assert.isFalse(CacheManager.getSessionCache().containsAll(keyToValue.keySet())); 860 | CacheManager.getSessionCache().put(keyToValue); 861 | for (String key : keyToValue.keySet()) { 862 | System.Assert.isTrue(CacheManager.getSessionCache().contains(key)); 863 | } 864 | System.Assert.isFalse(CacheManager.getSessionCache().containsAll(new Set())); 865 | Map keyToContainsResult = CacheManager.getSessionCache().contains(keyToValue.keySet()); 866 | for (String key : keyToContainsResult.keySet()) { 867 | Boolean containsResult = keyToContainsResult.get(key); 868 | System.Assert.isTrue(containsResult, 'Cache did not contain key: ' + key); 869 | System.Assert.isTrue(CacheManager.getSessionCache().contains(key)); 870 | } 871 | Map returnedKeyToValue = CacheManager.getSessionCache().getAll(); 872 | System.Assert.areEqual(keyToValue, returnedKeyToValue); 873 | System.Assert.isTrue(CacheManager.getSessionCache().containsAll(keyToValue.keySet()), '' + CacheManager.getSessionCache().getKeys()); 874 | CacheManager.getSessionCache().remove(keyToValue.keySet()); 875 | System.Assert.isFalse(CacheManager.getSessionCache().containsAll(keyToValue.keySet())); 876 | } 877 | 878 | @IsTest 879 | static void it_remove_alls_keys_in_session_cache_when_remove_all_method_is_called() { 880 | MockPlatformCachePartitionProxy mockSessionPartitionProxy = new MockPlatformCachePartitionProxy(true); 881 | System.Assert.isTrue(mockSessionPartitionProxy.isAvailable()); 882 | CacheManager.setMockPartitionProxy(CacheManager.PlatformCacheType.SESSION, mockSessionPartitionProxy); 883 | System.Assert.isTrue(CacheManager.getSessionCache().isAvailable()); 884 | Map keyToValue = new Map{ 885 | 'SomeDate' => Date.newInstance(1999, 9, 9), 886 | 'SomeString' => 'hello, world', 887 | 'SomeSObject' => new User(Id = System.UserInfo.getUserId()) 888 | }; 889 | CacheManager.getSessionCache().put(keyToValue); 890 | System.Assert.areEqual(keyToValue.keySet(), CacheManager.getSessionCache().getKeys()); 891 | 892 | CacheManager.getSessionCache().removeAll(); 893 | 894 | System.Assert.isTrue(CacheManager.getSessionCache().getKeys().isEmpty()); 895 | } 896 | 897 | @IsTest 898 | static void it_does_not_impact_other_caches_when_organization_cache_is_updated() { 899 | String key = 'SomeKey'; 900 | Object value = System.now(); 901 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(key)); 902 | System.Assert.isFalse(CacheManager.getSessionCache().contains(key)); 903 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(key)); 904 | 905 | CacheManager.getOrganizationCache().put(key, value); 906 | 907 | System.Assert.isTrue(CacheManager.getOrganizationCache().contains(key)); 908 | System.Assert.areEqual(value, CacheManager.getOrganizationCache().get(key)); 909 | System.Assert.isFalse(CacheManager.getSessionCache().contains(key)); 910 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(key)); 911 | } 912 | 913 | @IsTest 914 | static void it_does_not_impact_other_caches_when_session_cache_is_updated() { 915 | String key = 'SomeKey'; 916 | Object value = System.now(); 917 | System.Assert.isFalse(CacheManager.getSessionCache().contains(key)); 918 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(key)); 919 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(key)); 920 | 921 | CacheManager.getSessionCache().put(key, value); 922 | 923 | System.Assert.isTrue(CacheManager.getSessionCache().contains(key)); 924 | System.Assert.areEqual(value, CacheManager.getSessionCache().get(key)); 925 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(key)); 926 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(key)); 927 | } 928 | 929 | @IsTest 930 | static void it_does_not_impact_other_caches_when_transaction_cache_is_updated() { 931 | String key = 'SomeKey'; 932 | Object value = System.now(); 933 | System.Assert.isFalse(CacheManager.getTransactionCache().contains(key)); 934 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(key)); 935 | System.Assert.isFalse(CacheManager.getSessionCache().contains(key)); 936 | 937 | CacheManager.getTransactionCache().put(key, value); 938 | 939 | System.Assert.isTrue(CacheManager.getTransactionCache().contains(key)); 940 | System.Assert.areEqual(value, CacheManager.getTransactionCache().get(key)); 941 | System.Assert.isFalse(CacheManager.getOrganizationCache().contains(key)); 942 | System.Assert.isFalse(CacheManager.getSessionCache().contains(key)); 943 | } 944 | 945 | @IsTest 946 | static void validateKey_does_not_throw_when_called_with_number() { 947 | try { 948 | for (Integer i = 0; i < 50; ++i) { 949 | CacheManager.validateKey('' + i); 950 | } 951 | System.Assert.isTrue(true); 952 | } catch (IllegalArgumentException ex) { 953 | System.Assert.fail(); 954 | } 955 | } 956 | 957 | @IsTest 958 | static void validateKey_does_not_throw_when_called_with_letters() { 959 | String alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 960 | try { 961 | for (String key : alphabet.split('')) { 962 | CacheManager.validateKey(key); 963 | } 964 | System.Assert.isTrue(true); 965 | } catch (IllegalArgumentException ex) { 966 | System.Assert.fail(); 967 | } 968 | } 969 | 970 | @IsTest 971 | static void validateKey_throws_when_called_with_not_letters_nor_numbers() { 972 | String otherChars = 'ù%`$€"\'(§!)-&@"#éèàç*¨ %£+/=.?,\\'; 973 | 974 | for (String key : otherChars.split('')) { 975 | try { 976 | CacheManager.validateKey(key); 977 | System.Assert.fail(); 978 | } catch (Exception ex) { 979 | System.Assert.isInstanceOfType(ex, IllegalArgumentException.class); 980 | } 981 | } 982 | } 983 | 984 | @IsTest 985 | static void validateKey_throws_when_called_with_not_letters_nor_numbers_and_letter_numbers() { 986 | List phrase = new List{ 'I throw !!', 'I throw 2 times for sure!!', 'No, you throw' }; 987 | 988 | for (String key : phrase) { 989 | try { 990 | CacheManager.validateKey(key); 991 | System.Assert.fail(); 992 | } catch (Exception ex) { 993 | System.Assert.isInstanceOfType(ex, IllegalArgumentException.class); 994 | } 995 | } 996 | } 997 | 998 | // Since the class `Cache.Partition` can't have be mocked & can't have its methods overridden, 999 | // the `CacheManager` class internally uses a proxy to help abstract out the usage of the partition, 1000 | // which lets us mock the proxy within (true) unit tests. 1001 | private class MockPlatformCachePartitionProxy extends CacheManager.PlatformCachePartitionProxy { 1002 | private final Boolean isAvailable; 1003 | // Since `Cache.Partition` can't be mocked, this mock proxy uses a map as a substitute 1004 | private final Map keyToValue = new Map(); 1005 | 1006 | public Integer isAvailableMethodCallCount = 0; 1007 | public Integer containsMethodCallCount = 0; 1008 | public Integer getMethodCallCount = 0; 1009 | public Integer getCacheBuilderMethodCallCount = 0; 1010 | public Integer putMethodCallCount = 0; 1011 | public Integer removeMethodCallCount = 0; 1012 | 1013 | private MockPlatformCachePartitionProxy(Boolean isAvailable) { 1014 | super(null, null); 1015 | this.isAvailable = isAvailable; 1016 | } 1017 | 1018 | public override Boolean isAvailable() { 1019 | this.isAvailableMethodCallCount++; 1020 | return this.isAvailable; 1021 | } 1022 | 1023 | public override Boolean contains(String key) { 1024 | this.containsMethodCallCount++; 1025 | return this.keyToValue.containsKey(key); 1026 | } 1027 | 1028 | public override Object get(String key) { 1029 | this.getMethodCallCount++; 1030 | return this.keyToValue.get(key); 1031 | } 1032 | 1033 | public override Object get(String key, System.Type cacheBuilderClass) { 1034 | this.getCacheBuilderMethodCallCount++; 1035 | if (this.keyToValue.containsKey(key) == false) { 1036 | Cache.CacheBuilder cacheBuilder = (Cache.CacheBuilder) cacheBuilderClass.newInstance(); 1037 | Object value = cacheBuilder.doLoad(key); 1038 | this.keyToValue.put(key, value); 1039 | } 1040 | return this.keyToValue.get(key); 1041 | } 1042 | 1043 | @SuppressWarnings('PMD.ExcessiveParameterList') 1044 | public override void put(String key, Object value, Integer cacheTtlSeconds, Cache.Visibility cacheVisiblity, Boolean isCacheImmutable) { 1045 | this.putMethodCallCount++; 1046 | this.keyToValue.put(key, value); 1047 | } 1048 | 1049 | public override void remove(String key) { 1050 | this.removeMethodCallCount++; 1051 | this.keyToValue.remove(key); 1052 | } 1053 | } 1054 | 1055 | private class CacheBuilderStub implements Cache.CacheBuilder { 1056 | public Object doLoad(String key) { 1057 | User mockValue = new User(Id = System.UserInfo.getUserId(), ProfileId = System.UserInfo.getProfileId()); 1058 | return mockValue; 1059 | } 1060 | } 1061 | } 1062 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/classes/CacheManager_Tests.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/customMetadata/CacheConfiguration.Organization.md-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | false 9 | 10 | IsEnabled__c 11 | true 12 | 13 | 14 | IsImmutable__c 15 | false 16 | 17 | 18 | PlatformCachePartitionName__c 19 | CacheManagerPartition 20 | 21 | 22 | PlatformCacheTimeToLive__c 23 | 86400.0 24 | 25 | 26 | PlatformCacheVisibility__c 27 | All 28 | 29 | 30 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/customMetadata/CacheConfiguration.Session.md-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | false 9 | 10 | IsEnabled__c 11 | true 12 | 13 | 14 | IsImmutable__c 15 | false 16 | 17 | 18 | PlatformCachePartitionName__c 19 | CacheManagerPartition 20 | 21 | 22 | PlatformCacheTimeToLive__c 23 | 28800.0 24 | 25 | 26 | PlatformCacheVisibility__c 27 | All 28 | 29 | 30 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/customMetadata/CacheConfiguration.Transaction.md-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | false 9 | 10 | IsEnabled__c 11 | true 12 | 13 | 14 | IsImmutable__c 15 | false 16 | 17 | 18 | PlatformCachePartitionName__c 19 | 20 | 21 | 22 | PlatformCacheTimeToLive__c 23 | 24 | 25 | 26 | PlatformCacheVisibility__c 27 | All 28 | 29 | 30 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/layouts/CacheConfiguration__mdt-Cache Configuration Layout.layout-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | true 7 | 8 | 9 | 10 | Required 11 | MasterLabel 12 | 13 | 14 | Required 15 | DeveloperName 16 | 17 | 18 | 19 | 20 | Edit 21 | IsEnabled__c 22 | 23 | 24 | Edit 25 | IsImmutable__c 26 | 27 | 28 | 29 | 30 | 31 | true 32 | true 33 | true 34 | 35 | 36 | 37 | Edit 38 | PlatformCachePartitionName__c 39 | 40 | 41 | Edit 42 | PlatformCacheTimeToLive__c 43 | 44 | 45 | 46 | 47 | Edit 48 | PlatformCacheVisibility__c 49 | 50 | 51 | 52 | 53 | 54 | false 55 | true 56 | true 57 | 58 | 59 | 60 | Edit 61 | IsProtected 62 | 63 | 64 | Readonly 65 | CreatedById 66 | 67 | 68 | 69 | 70 | Required 71 | NamespacePrefix 72 | 73 | 74 | Readonly 75 | LastModifiedById 76 | 77 | 78 | 79 | 80 | 81 | true 82 | true 83 | false 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | MasterLabel 92 | DeveloperName 93 | IsEnabled__c 94 | Value__c 95 | CacheValue__mdt.Cache__c 96 | DeveloperName 97 | Asc 98 | 99 | false 100 | false 101 | false 102 | false 103 | false 104 | 105 | 00h9A0000037ZwX 106 | 4 107 | 0 108 | Default 109 | 110 | 111 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/layouts/CacheValue__mdt-Cache Value Layout.layout-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | true 7 | 8 | 9 | 10 | Required 11 | MasterLabel 12 | 13 | 14 | Required 15 | DeveloperName 16 | 17 | 18 | 19 | 20 | Edit 21 | IsEnabled__c 22 | 23 | 24 | 25 | 26 | 27 | true 28 | true 29 | true 30 | 31 | 32 | 33 | Required 34 | Cache__c 35 | 36 | 37 | Required 38 | Key__c 39 | 40 | 41 | Required 42 | DataType__c 43 | 44 | 45 | Required 46 | Value__c 47 | 48 | 49 | 50 | 51 | 52 | false 53 | true 54 | true 55 | 56 | 57 | 58 | Edit 59 | IsProtected 60 | 61 | 62 | Readonly 63 | CreatedById 64 | 65 | 66 | 67 | 68 | Required 69 | NamespacePrefix 70 | 71 | 72 | Readonly 73 | LastModifiedById 74 | 75 | 76 | 77 | 78 | 79 | true 80 | true 81 | false 82 | 83 | 84 | 85 | 86 | 87 | 88 | false 89 | false 90 | false 91 | false 92 | false 93 | 94 | 00h9A000003SpuW 95 | 4 96 | 0 97 | Default 98 | 99 | 100 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheConfiguration__mdt/CacheConfiguration__mdt.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Used by Nebula Cache Manager to control the runtime behavior of different caches 4 | 5 | Cache Configurations 6 | Public 7 | 8 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheConfiguration__mdt/fields/IsEnabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | IsEnabled__c 4 | true 5 | false 6 | SubscriberControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheConfiguration__mdt/fields/IsImmutable__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | IsImmutable__c 4 | false 5 | Indicates if cache values can be changed once set (IsImmutable__c = false) or if existing values are locked until they expire (IsImmutable__c = true) 7 | false 8 | DeveloperControlled 9 | 10 | Checkbox 11 | 12 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheConfiguration__mdt/fields/PlatformCachePartitionName__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PlatformCachePartitionName__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | false 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheConfiguration__mdt/fields/PlatformCacheTimeToLive__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PlatformCacheTimeToLive__c 4 | false 5 | DeveloperControlled 6 | 7 | 18 8 | false 9 | 0 10 | Number 11 | false 12 | 13 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheConfiguration__mdt/fields/PlatformCacheVisibility__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PlatformCacheVisibility__c 4 | false 5 | DeveloperControlled 6 | 7 | false 8 | Picklist 9 | 10 | true 11 | 12 | false 13 | 14 | All 15 | true 16 | 17 | 18 | 19 | Namespace 20 | false 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheConfiguration__mdt/listViews/All.listView-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | All 4 | MasterLabel 5 | DeveloperName 6 | IsEnabled__c 7 | IsImmutable__c 8 | PlatformCachePartitionName__c 9 | PlatformCacheTimeToLive__c 10 | PlatformCacheVisibility__c 11 | Everything 12 | 13 | 14 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheValue__mdt/CacheValue__mdt.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Used by Nebula Cache Manager to automatically load configured cache values into the specified cache at runtime 4 | 5 | Cache Values 6 | Public 7 | 8 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheValue__mdt/fields/Cache__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cache__c 4 | false 5 | DeveloperControlled 6 | 7 | CacheConfiguration__mdt 8 | Cache Values 9 | CacheValues 10 | true 11 | MetadataRelationship 12 | false 13 | 14 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheValue__mdt/fields/DataType__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DataType__c 4 | false 5 | DeveloperControlled 6 | The Apex type of the provided value. This can be any valid Apex type that supports JSON deserialization, such as a primitive data type like String, Boolean, Datetime, or a custom Apex class 8 | 9 | 255 10 | true 11 | Text 12 | false 13 | 14 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheValue__mdt/fields/IsEnabled__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | IsEnabled__c 4 | true 5 | false 6 | DeveloperControlled 7 | 8 | Checkbox 9 | 10 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheValue__mdt/fields/Key__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Key__c 4 | false 5 | DeveloperControlled 6 | 7 | 255 8 | true 9 | Text 10 | false 11 | 12 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheValue__mdt/fields/Value__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Value__c 4 | false 5 | DeveloperControlled 6 | 7 | 131072 8 | LongTextArea 9 | 5 10 | 11 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheValue__mdt/listViews/All.listView-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | All 4 | MasterLabel 5 | DeveloperName 6 | IsEnabled__c 7 | Cache__c 8 | Key__c 9 | DataType__c 10 | Value__c 11 | Everything 12 | 13 | 14 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/objects/CacheValue__mdt/validationRules/KeyMustBeAlphanumeric.validationRule-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | KeyMustBeAlphanumeric 4 | true 5 | NOT(REGEX(Key__c, '^[a-zA-Z0-9]*$')) 6 | Key__c 7 | Key must be alphanumeric 8 | 9 | -------------------------------------------------------------------------------- /nebula-cache-manager/core/permissionsets/CacheManagerAdmin.permissionset-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CacheManager 5 | true 6 | 7 | 8 | true 9 | CacheConfiguration__mdt 10 | 11 | 12 | true 13 | CacheValue__mdt 14 | 15 | false 16 | 17 | 18 | -------------------------------------------------------------------------------- /nebula-cache-manager/recipes/classes/RecordSelector.cls: -------------------------------------------------------------------------------- 1 | @SuppressWarnings('PMD.ApexCRUDViolation, PMD.ApexDoc, PMD.EmptyStatementBlock') 2 | public without sharing class RecordSelector { 3 | public RecordSelector() { 4 | } 5 | 6 | public User getCurrentUser() { 7 | String cacheKey = 'user_' + System.UserInfo.getUserId(); 8 | if (CacheManager.getSessionCache().contains(cacheKey) == true) { 9 | return (User) CacheManager.getSessionCache().get(cacheKey); 10 | } 11 | 12 | User currentUser = [ 13 | SELECT Id, Email, FirstName, LastName, ProfileId, Profile.Name, Username, UserRoleId, UserRole.Name 14 | FROM User 15 | WHERE Id = :System.UserInfo.getUserId() 16 | ]; 17 | CacheManager.getSessionCache().put(cacheKey, currentUser); 18 | return currentUser; 19 | } 20 | 21 | public List getQueues() { 22 | String cacheKey = 'queues'; 23 | if (CacheManager.getOrganizationCache().contains(cacheKey) == true) { 24 | return (List) CacheManager.getOrganizationCache().get(cacheKey); 25 | } 26 | 27 | List queues = [SELECT Id FROM Group WHERE Type = 'Queue']; 28 | CacheManager.getOrganizationCache().put(cacheKey, queues); 29 | return queues; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nebula-cache-manager/recipes/classes/RecordSelector.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 56.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nebula-cache-manager", 3 | "version": "1.0.2", 4 | "description": "A flexible cache management system for Salesforce Apex developers. Built to be scalable & configurable.", 5 | "scripts": { 6 | "lint": "eslint **/{aura,lwc}/**", 7 | "package:aliases:sort": "sfdx bummer:package:aliases:sort", 8 | "package:version:create:nonamespace": "sfdx force:package:version:create --json --package \"Nebula Cache Manager (no namespace)\" --skipancestorcheck --codecoverage --installationkeybypass --wait 30", 9 | "package:version:create:withnamespace": "sfdx force:package:version:create --json --package \"Nebula Cache Manager (Nebula namespace)\" --skipancestorcheck --codecoverage --installationkeybypass --wait 30", 10 | "package:version:number:sync": "pwsh ./scripts/build/sync-package-version-number.ps1", 11 | "permset:assign:admin": "sfdx force:user:permset:assign --permsetname CacheManagerAdmin", 12 | "postinstall": "husky install", 13 | "precommit": "lint-staged", 14 | "prettier:fix": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 15 | "prettier:verify": "prettier --list-different \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 16 | "scan:apex": "sfdx scanner:run --pmdconfig ./config/linters/pmd-ruleset.xml --target ./nebula-cache-manager/ --engine pmd --severity-threshold 3", 17 | "sfdx:plugins:link:bummer": "npx sfdx plugins:link ./node_modules/@jongpie/sfdx-bummer-plugin", 18 | "sfdx:plugins:link:prettier": "npx sfdx plugins:link ./node_modules/@jayree/sfdx-plugin-prettier", 19 | "sfdx:plugins:link:scanner": "npx sfdx plugins:link ./node_modules/@salesforce/sfdx-scanner", 20 | "test:apex": "sfdx apex:test:run --testlevel RunLocalTests --wait 30 --resultformat human --codecoverage --detailedcoverage --outputdir ./test-coverage/apex", 21 | "test:apex:nocoverage": "sfdx apex:test:run --testlevel RunLocalTests --wait 30 --resultformat human --outputdir ./test-coverage/apex" 22 | }, 23 | "devDependencies": { 24 | "@jayree/sfdx-plugin-prettier": "^1.2.28", 25 | "@jongpie/sfdx-bummer-plugin": "^0.0.18", 26 | "@lwc/eslint-plugin-lwc": "^1.1.2", 27 | "@prettier/plugin-xml": "^2.0.1", 28 | "@salesforce/sfdx-scanner": "^3.9.0", 29 | "eslint": "^8.11.0", 30 | "eslint-plugin-import": "^2.25.4", 31 | "husky": "^7.0.4", 32 | "lint-staged": "^12.3.7", 33 | "prettier": "^2.6.0", 34 | "prettier-plugin-apex": "^1.10.0", 35 | "pwsh": "^0.3.0", 36 | "sfdx-cli": "^7.190.2" 37 | }, 38 | "keywords": [ 39 | "apex", 40 | "cache", 41 | "cache management", 42 | "caching", 43 | "platform cache", 44 | "salesforce" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /scripts/build/sync-package-version-number.ps1: -------------------------------------------------------------------------------- 1 | # This script is used to automatically sync the package version numbers for Nebula Cache Manager's 2 unlocked packages 2 | # (stored in sfdx-project.json) and update other files - package.json and CacheManager.cls - to ensure that all 3 files have the same version number 3 | 4 | $sfdxProjectJsonPath = "./sfdx-project.json" 5 | $packageJsonPath = "./package.json" 6 | $readmeClassPath = "./README.md" 7 | $cacheManagerClassPath = "./nebula-cache-manager/core/classes/CacheManager.cls" 8 | 9 | function Get-SFDX-Project-JSON { 10 | Get-Content -Path $sfdxProjectJsonPath | ConvertFrom-Json 11 | } 12 | 13 | function Get-Version-Number { 14 | $projectJSON = Get-SFDX-Project-JSON 15 | $versionNumber = ($projectJSON).packageDirectories[0].versionNumber 16 | $versionNumber = $versionNumber.substring(0, $versionNumber.LastIndexOf('.')) 17 | return $versionNumber 18 | } 19 | 20 | function Get-Package-JSON { 21 | Get-Content -Raw -Path $packageJsonPath | ConvertFrom-Json 22 | } 23 | 24 | function Update-Package-JSON { 25 | param ( 26 | $versionNumber 27 | ) 28 | $packageJson = Get-Package-JSON 29 | Write-Output "Bumping package.json version number to: $versionNumber" 30 | 31 | $packageJson.version = $versionNumber 32 | ConvertTo-Json -InputObject $packageJson | Set-Content -Path $packageJsonPath -NoNewline 33 | npx prettier --write $packageJsonPath 34 | git add $packageJsonPath 35 | } 36 | 37 | function Get-README { 38 | Get-Content -Raw -Path $readmeClassPath 39 | } 40 | 41 | function Update-README { 42 | param ( 43 | $versionNumber 44 | ) 45 | $versionNumber = "v" + $versionNumber 46 | $readmeContents = Get-README 47 | Write-Output "Bumping README unlocked package version numbers to: $versionNumber" 48 | 49 | $targetRegEx = "(.+ Namespace - )(.+)" 50 | $replacementRegEx = '$1' + $versionNumber 51 | $readmeContents -replace $targetRegEx, $replacementRegEx | Set-Content -Path $readmeClassPath -NoNewline 52 | npx prettier --write $readmeClassPath 53 | git add $readmeClassPath 54 | } 55 | 56 | function Get-Cache-Manager-Class { 57 | Get-Content -Raw -Path $cacheManagerClassPath 58 | } 59 | 60 | function Update-Cache-Manager-Class { 61 | param ( 62 | $versionNumber 63 | ) 64 | $versionNumber = "v" + $versionNumber 65 | $cacheManagerClassContents = Get-Cache-Manager-Class 66 | Write-Output "Bumping CacheManager.cls version number to: $versionNumber" 67 | 68 | $targetRegEx = "(.+CURRENT_VERSION_NUMBER = ')(.+)(';)" 69 | $replacementRegEx = '$1' + $versionNumber + '$3' 70 | $cacheManagerClassContents -replace $targetRegEx, $replacementRegEx | Set-Content -Path $cacheManagerClassPath -NoNewline 71 | npx prettier --write $cacheManagerClassPath 72 | git add $cacheManagerClassPath 73 | } 74 | 75 | $versionNumber = Get-Version-Number 76 | Write-Output "Target Version Number: $versionNumber" 77 | 78 | Update-Package-JSON $versionNumber 79 | Update-README $versionNumber 80 | Update-Cache-Manager-Class $versionNumber 81 | -------------------------------------------------------------------------------- /scripts/build/validate-access-to-namespaced-package.apex: -------------------------------------------------------------------------------- 1 | List defaultCaches = new List{ 2 | Nebula.CacheManager.getOrganizationCache(), 3 | Nebula.CacheManager.getSessionCache(), 4 | Nebula.CacheManager.getTransactionCache() 5 | }; 6 | 7 | String key = 'someKey'; 8 | User value = [SELECT Id, Username, Email FROM User WHERE Id = :UserInfo.getUserId()]; 9 | for (Nebula.CacheManager.Cacheable cache : defaultCaches) { 10 | System.Assert.isFalse(cache.contains(key)); 11 | cache.put(key, value); 12 | System.Assert.isTrue(cache.contains(key)); 13 | System.Assert.areEqual(value, cache.get(key)); 14 | cache.remove(key); 15 | System.Assert.isFalse(cache.contains(key)); 16 | } -------------------------------------------------------------------------------- /scripts/build/validate-access-to-no-namespace-package.apex: -------------------------------------------------------------------------------- 1 | List defaultCaches = new List{ 2 | CacheManager.getOrganizationCache(), 3 | CacheManager.getSessionCache(), 4 | CacheManager.getTransactionCache() 5 | }; 6 | 7 | String key = 'someKey'; 8 | User value = [SELECT Id, Username, Email FROM User WHERE Id = :UserInfo.getUserId()]; 9 | for (CacheManager.Cacheable cache : defaultCaches) { 10 | System.Assert.isFalse(cache.contains(key)); 11 | cache.put(key, value); 12 | System.Assert.isTrue(cache.contains(key)); 13 | System.Assert.areEqual(value, cache.get(key)); 14 | cache.remove(key); 15 | System.Assert.isFalse(cache.contains(key)); 16 | } -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Nebula Cache Manager", 3 | "namespace": "", 4 | "sfdcLoginUrl": "https://login.salesforce.com", 5 | "sourceApiVersion": "56.0", 6 | "pushPackageDirectoriesSequentially": true, 7 | "packageDirectories": [ 8 | { 9 | "package": "Nebula Cache Manager (no namespace)", 10 | "path": "./nebula-cache-manager/core/", 11 | "definitionFile": "./config/scratch-orgs/base-scratch-def.json", 12 | "versionNumber": "1.0.2.0", 13 | "versionName": "Updated CacheManager methods from public to global", 14 | "versionDescription": "All relevant methods in CacheManager (and its inner classses) have been changed to global so that they're accessible in the namespaced package", 15 | "releaseNotesUrl": "https://github.com/jongpie/NebulaCacheManager/releases", 16 | "default": false 17 | }, 18 | { 19 | "package": "Nebula Cache Manager (Nebula namespace)", 20 | "path": "./nebula-cache-manager/core/", 21 | "definitionFile": "./config/scratch-orgs/base-scratch-def.json", 22 | "versionNumber": "1.0.2.0", 23 | "versionName": "Updated CacheManager methods from public to global", 24 | "versionDescription": "All relevant methods in CacheManager (and its inner classses) have been changed to global so that they're accessible in the namespaced package", 25 | "releaseNotesUrl": "https://github.com/jongpie/NebulaCacheManager/releases", 26 | "default": false 27 | }, 28 | { 29 | "path": "./nebula-cache-manager/recipes/", 30 | "default": true 31 | } 32 | ], 33 | "plugins": { 34 | "sfdx-plugin-prettier": { 35 | "enabled": true 36 | } 37 | }, 38 | "packageAliases": { 39 | "Nebula Cache Manager (Nebula namespace)": "0Ho5Y000000fxvlSAA", 40 | "Nebula Cache Manager (Nebula namespace)@1.0.0": "04t5Y0000015n2AQAQ", 41 | "Nebula Cache Manager (Nebula namespace)@1.0.1": "04t5Y0000015n6rQAA", 42 | "Nebula Cache Manager (Nebula namespace)@1.0.2": "04t5Y0000015nCfQAI", 43 | "Nebula Cache Manager (no namespace)": "0Ho5Y000000fxvqSAA", 44 | "Nebula Cache Manager (no namespace)@1.0.0": "04t5Y0000015n25QAA", 45 | "Nebula Cache Manager (no namespace)@1.0.1": "04t5Y0000015n6hQAA", 46 | "Nebula Cache Manager (no namespace)@1.0.2": "04t5Y0000015nCaQAI" 47 | } 48 | } 49 | --------------------------------------------------------------------------------