From 0f5127db2b3c37461ece6aa78acf594b1df4377d Mon Sep 17 00:00:00 2001 From: Bob Laskowski Date: Tue, 27 Jul 2021 12:34:22 -0700 Subject: [PATCH] initial commit --- .github/dependabot.yml | 12 + .github/workflows/build.yml | 36 +++ .github/workflows/publish.yml | 45 +++ .gitignore | 31 +++ .whitesource | 3 + CODE_OF_CONDUCT.md | 33 +++ CONTRIBUTING.md | 15 + LICENSE.md | 20 ++ README.md | 164 +++++++++++ build.gradle | 123 +++++++++ example-projects/README.md | 61 +++++ .../media/connected_app_oauth_settings.png | Bin 0 -> 108216 bytes example-projects/webflux-example/build.gradle | 46 ++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + example-projects/webflux-example/gradlew | 185 +++++++++++++ example-projects/webflux-example/gradlew.bat | 89 ++++++ .../webflux-example/settings.gradle | 3 + .../webflux/WebfluxTokenManagerExample.java | 12 + .../crm/webflux/client/SalesforceClient.java | 41 +++ .../webflux/controller/WebfluxController.java | 26 ++ .../com/tgt/crm/webflux/vo/QueryResponse.java | 21 ++ .../src/main/resources/application.yml | 15 + example-projects/webmvc-example/build.gradle | 46 ++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + example-projects/webmvc-example/gradlew | 185 +++++++++++++ example-projects/webmvc-example/gradlew.bat | 89 ++++++ .../webmvc-example/settings.gradle | 3 + .../tgt/crm/mvc/MvcTokenManagerExample.java | 12 + .../tgt/crm/mvc/client/SalesforceClient.java | 42 +++ .../tgt/crm/mvc/controller/MvcController.java | 28 ++ .../com/tgt/crm/mvc/vo/QueryResponse.java | 21 ++ .../src/main/resources/application.yml | 22 ++ gradle/checks.gradle | 145 ++++++++++ gradle/config/pmd/pmd.xml | 120 ++++++++ gradle/spotless.gradle | 44 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 +++++++++++++ gradlew.bat | 89 ++++++ settings.gradle | 6 + .../build.gradle | 18 ++ .../settings.gradle | 1 + .../tgt/crm/token/core/HttpClientConfig.java | 13 + .../token/core/SalesforceAuthResponse.java | 35 +++ .../tgt/crm/token/core/SalesforceConfig.java | 49 ++++ .../crm/token/core/SalesforceConstants.java | 15 + .../crm/token/core/BaseIntegrationTest.java | 73 +++++ .../tgt/crm/token/core/MockResponseUtil.java | 74 +++++ .../testFixtures/resources/sfAuthError.json | 4 + .../resources/sfAuthRefreshed.json | 8 + .../testFixtures/resources/sfAuthSuccess.json | 8 + .../sfQueryUnauthorizedResponse.json | 6 + .../build.gradle | 95 +++++++ .../settings.gradle | 1 + .../tgt/crm/token/webflux/AuthWebClient.java | 51 ++++ .../SalesforceLibraryAutoConfiguration.java | 12 + .../token/webflux/SalesforceWebClient.java | 132 +++++++++ .../webflux/SalesforceWebfluxAuthClient.java | 87 ++++++ .../main/resources/META-INF/spring.factories | 1 + .../SalesforceWebfluxAuthClientTest.java | 217 +++++++++++++++ .../resources/salesforceAuthResponse.json | 8 + .../salesforceAuthResponseRefreshed.json | 8 + .../resources/sfSobjectSuccessResponse.json | 5 + .../token/webflux/WebfluxIntegrationTest.java | 256 ++++++++++++++++++ .../testintegration/resources/application.yml | 10 + .../build.gradle | 98 +++++++ .../settings.gradle | 1 + .../tgt/crm/token/mvc/AuthRestTemplate.java | 27 ++ .../SalesforceLibraryAutoConfiguration.java | 18 ++ .../token/mvc/SalesforceMvcAuthClient.java | 109 ++++++++ .../crm/token/mvc/SalesforceRestTemplate.java | 84 ++++++ .../SalesforceRestTemplateInterceptor.java | 88 ++++++ .../crm/token/mvc/WebMvcHttpClientConfig.java | 13 + .../token/mvc/WebMvcHttpClientConstants.java | 12 + .../main/resources/META-INF/spring.factories | 1 + .../mvc/SalesforceMvcAuthClientTest.java | 135 +++++++++ ...SalesforceRestTemplateInterceptorTest.java | 110 ++++++++ .../tgt/crm/token/mvc/MvcIntegrationTest.java | 222 +++++++++++++++ .../testintegration/resources/application.yml | 10 + 81 files changed, 4148 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .gitignore create mode 100644 .whitesource create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 build.gradle create mode 100644 example-projects/README.md create mode 100644 example-projects/media/connected_app_oauth_settings.png create mode 100644 example-projects/webflux-example/build.gradle create mode 100644 example-projects/webflux-example/gradle/wrapper/gradle-wrapper.jar create mode 100644 example-projects/webflux-example/gradle/wrapper/gradle-wrapper.properties create mode 100755 example-projects/webflux-example/gradlew create mode 100644 example-projects/webflux-example/gradlew.bat create mode 100644 example-projects/webflux-example/settings.gradle create mode 100644 example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/WebfluxTokenManagerExample.java create mode 100644 example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/client/SalesforceClient.java create mode 100644 example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/controller/WebfluxController.java create mode 100644 example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/vo/QueryResponse.java create mode 100644 example-projects/webflux-example/src/main/resources/application.yml create mode 100644 example-projects/webmvc-example/build.gradle create mode 100644 example-projects/webmvc-example/gradle/wrapper/gradle-wrapper.jar create mode 100644 example-projects/webmvc-example/gradle/wrapper/gradle-wrapper.properties create mode 100755 example-projects/webmvc-example/gradlew create mode 100644 example-projects/webmvc-example/gradlew.bat create mode 100644 example-projects/webmvc-example/settings.gradle create mode 100644 example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/MvcTokenManagerExample.java create mode 100644 example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/client/SalesforceClient.java create mode 100644 example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/controller/MvcController.java create mode 100644 example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/vo/QueryResponse.java create mode 100644 example-projects/webmvc-example/src/main/resources/application.yml create mode 100644 gradle/checks.gradle create mode 100644 gradle/config/pmd/pmd.xml create mode 100644 gradle/spotless.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 token-manager-for-salesforce-core/build.gradle create mode 100644 token-manager-for-salesforce-core/settings.gradle create mode 100644 token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/HttpClientConfig.java create mode 100644 token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceAuthResponse.java create mode 100644 token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceConfig.java create mode 100644 token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceConstants.java create mode 100644 token-manager-for-salesforce-core/src/testFixtures/java/com/tgt/crm/token/core/BaseIntegrationTest.java create mode 100644 token-manager-for-salesforce-core/src/testFixtures/java/com/tgt/crm/token/core/MockResponseUtil.java create mode 100644 token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthError.json create mode 100644 token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthRefreshed.json create mode 100644 token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthSuccess.json create mode 100644 token-manager-for-salesforce-core/src/testFixtures/resources/sfQueryUnauthorizedResponse.json create mode 100644 token-manager-for-salesforce-webflux/build.gradle create mode 100644 token-manager-for-salesforce-webflux/settings.gradle create mode 100644 token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/AuthWebClient.java create mode 100644 token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceLibraryAutoConfiguration.java create mode 100644 token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceWebClient.java create mode 100644 token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceWebfluxAuthClient.java create mode 100644 token-manager-for-salesforce-webflux/src/main/resources/META-INF/spring.factories create mode 100644 token-manager-for-salesforce-webflux/src/test/java/com/tgt/crm/token/webflux/SalesforceWebfluxAuthClientTest.java create mode 100644 token-manager-for-salesforce-webflux/src/test/resources/salesforceAuthResponse.json create mode 100644 token-manager-for-salesforce-webflux/src/test/resources/salesforceAuthResponseRefreshed.json create mode 100644 token-manager-for-salesforce-webflux/src/test/resources/sfSobjectSuccessResponse.json create mode 100644 token-manager-for-salesforce-webflux/src/testintegration/java/com/tgt/crm/token/webflux/WebfluxIntegrationTest.java create mode 100644 token-manager-for-salesforce-webflux/src/testintegration/resources/application.yml create mode 100644 token-manager-for-salesforce-webmvc/build.gradle create mode 100644 token-manager-for-salesforce-webmvc/settings.gradle create mode 100644 token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/AuthRestTemplate.java create mode 100644 token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceLibraryAutoConfiguration.java create mode 100644 token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceMvcAuthClient.java create mode 100644 token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceRestTemplate.java create mode 100644 token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceRestTemplateInterceptor.java create mode 100644 token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/WebMvcHttpClientConfig.java create mode 100644 token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/WebMvcHttpClientConstants.java create mode 100644 token-manager-for-salesforce-webmvc/src/main/resources/META-INF/spring.factories create mode 100644 token-manager-for-salesforce-webmvc/src/test/java/com/tgt/crm/token/mvc/SalesforceMvcAuthClientTest.java create mode 100644 token-manager-for-salesforce-webmvc/src/test/java/com/tgt/crm/token/mvc/SalesforceRestTemplateInterceptorTest.java create mode 100644 token-manager-for-salesforce-webmvc/src/testintegration/java/com/tgt/crm/token/mvc/MvcIntegrationTest.java create mode 100644 token-manager-for-salesforce-webmvc/src/testintegration/resources/application.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b67a4c1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "weekly" + day: "monday" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..e0d39cc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,36 @@ +name: PR Validation + +on: + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Build with Gradle + run: ./gradlew build --info -Dorg.gradle.daemon=false + - name: Cleanup Gradle Cache + # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. + # Restoring these files from a GitHub Actions cache might cause problems for future builds. + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..905c187 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,45 @@ +name: Publish to Github Packages on Release +on: + release: + branches: + - main + types: + - created +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Build with Gradle + run: ./gradlew build --info -Dorg.gradle.daemon=false + - name: Cleanup Gradle Cache + # Remove some files from the Gradle cache, so they aren't cached by GitHub Actions. + # Restoring these files from a GitHub Actions cache might cause problems for future builds. + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties + - name: Publish package + run: | + NEW_VERSION=$(echo "${GITHUB_REF}" | cut -d "/" -f3) + echo "Publishing new version: ${NEW_VERSION}" + ./gradlew -Pversion=${NEW_VERSION} publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54f1b35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +*.class +*.log +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear +#*.jar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# output +target/ +build/ +/bin/ +out/ + +# eclipse +.classpath +.project +.settings/ + +# idea +*.iml +*.ipr +*.iws +.idea +.DS_Store +.gradle/ \ No newline at end of file diff --git a/.whitesource b/.whitesource new file mode 100644 index 0000000..7a52500 --- /dev/null +++ b/.whitesource @@ -0,0 +1,3 @@ +{ + "settingsInheritedFrom": "whitesource-config/whitesource-config@main" +} \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ccaaee8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,33 @@ +# Contributor Covenant Code of Conduct + +# Our Pledge +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +# Our Standards +Examples of behavior that contributes to creating a positive environment include: +Using welcoming and inclusive language +Being respectful of differing viewpoints and experiences +Gracefully accepting constructive criticism +Focusing on what is best for the community +Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: +The use of sexualized language or imagery and unwelcome sexual attention or advances +Trolling, insulting/derogatory comments, and personal or political attacks +Public or private harassment +Publishing others' private information, such as a physical or electronic address, without explicit permission +Other conduct which could reasonably be considered inappropriate in a professional setting + +# Our Responsibilities +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +# Scope +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +# Enforcement +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at TTS-OpenSource-Office@target.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +# Attribution +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..49aeae1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing to reuse + +### Issues + +Feel free to submit bugs or feature requests as issues. + +### Pull Requests + +These rules must be followed for any contributions to be merged into master. + +1. Fork this repo +1. Make any desired changes +1. Validate your changes meet your desired use case +1. Ensure documentation has been updated +1. Open a pull request diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..cb490df --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (c) 2021 Target Brands, Inc. + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c86a24 --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# Token Manager for Salesforce + +This project makes Salesforce API calls with Spring a breeze. It exposes either a RestTemplate or WebClient that handles generating, refreshing and attaching an authorization token header for every API call. Just pass in your instance and user credentials via `application.yml`, autowire your desired bean and it takes care of the rest. + +## Usage + +To use this library, the following repository declaration and one of the dependencies to your project's `build.gradle` file. This will include the core module as well automatically. + +```groovy +repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/target/token-manager-for-salesforce") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("USERNAME") + password = project.findProperty("gpr.token") ?: System.getenv("TOKEN") + } + } +} +dependencies { + // for reactive applications + implementation "com.tgt.crm:token-manager-for-salesforce-webflux:${libraryVersion}" + // for non-reactive applications + implementation "com.tgt.crm:token-manager-for-salesforce-webmvc:${libraryVersion}" +} +``` + +Find the latest version in the "packages" section of this repo. You will need access to the Github repository and will need to make your user and a personal access token available as a project or system property to authorize with Github and download the package. More details can be found [here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry). + +Add the following to your `application.yml` file to pass in Salesforce properties from TAP secrets/environment variables. You can set explicit values for these properties instead of environment variables if you prefer. Ensure you don't check any secrets into Git. + +```yaml +salesforce: + host: ${SALESFORCE_HOST} + username: ${SALESFORCE_USERNAME} + password: ${SALESFORCE_PASSWORD} + client-id: ${SALESFORCE_CLIENT_ID} + client-secret: ${SALESFORCE_CLIENT_SECRET} + auth-uri: /services/oauth2/token # optional + retry-backoff-delay: 1000 # optional, configures retry for auth token requests only + max-auth-token-retries: 3 # optional, configures retry for auth token requests only + retry-backoff-multiplier: 2 # optional, configures retry for auth token requests only, only used by MVC, see SalesforceConfig for more info + httpclient: + max-conn-per-route: 20 # optional + read-timeout: 30000 # optional, in milliseconds + connection-timeout: 60000 # optional, in milliseconds + mvc: # configs in this block only available for webmvc + max-pools: 50 # optional + connection-request-timeout: 30000 # optional, in milliseconds + retries: 3 # optional, MVC only, configures default # of retries for all requests except auth token + retry-interval: 2000 # optional, in milliseconds, configures default retry interval for all requests except auth token +``` + +You should then be able to autowire the RestTemplate or WebClient bean in any component in your project and use it to make API calls to Salesforce. + +```java +@Service +public class SalesforceClient { + + private final RestTemplate salesforceRestTemplate; + + public SalesforceClient(@Qualifier("sfRestTemplate") final RestTemplate salesForceRestTemplate) { + this.salesForceRestTemplate = salesForceRestTemplate; + } + + public String querySalesforce() { + ResponseEntity sfResponse = + salesForceRestTemplate.exchange( + "/services/data/v50.0/query&q={query}", + HttpMethod.GET, + HttpEntity.EMPTY, + String.class, + "SELECT Id FROM Case"); + + return sfResponse.getBody(); + } +} +``` + +```java +@Service +public class SalesforceClient { + + private final WebClient salesforceWebClient; + + public SalesforceClient(@Qualifier("sfWebClient") final WebClient salesforceWebClient) { + this.salesforceWebClient = salesforceWebClient; + } + + public Mono querySalesforce() { + return webClient + .get() + .uri("/services/data/v50.0/query&q={query}", "SELECT Id FROM Case") + .retrieve() + .toEntity(String.class) + .map(HttpEntity::getBody); + } +} +``` + +### Minimum Requirements + +In your project, the following minimum versions of Spring Boot are required to use this library: + +* token-manager-for-salesforce-webflux: Spring Boot > 2.2.6.RELEASE + * Spring Boot > 2.4.0 is recommended to support Wiretap for WebClient debug logging +* token-manager-for-salesforce-webmvc: Spring Boot > 2.2.0.RELEASE + +### Metrics + +The application also emits one micrometer metric. If a token refresh fails, a counter is incremented. The counter is called `exception_counter` and has one tag `exception_type` with value `token_refresh_exception`. This can be used to set up an alert in Grafana if a token refresh ever fails. + +### How does it work? + +This library follows the [OAuth 2.0 Username-Password Flow](https://help.salesforce.com/articleView?id=remoteaccess_oauth_username_password_flow.htm&type=5) and is intended to be used with first-party applications. + +When your application starts up it will not have a token. The first time it makes an API call, the library intercepts the request, makes the appropriate API call to `/services/oauth2/token` for your configured instance as documented [here](https://help.salesforce.com/articleView?id=remoteaccess_oauth_endpoints.htm&type=5). The generated token is attached to the request as an `Authorization` header and the request proceeds. It is also cached in memory. + +Subsequent requests use the cached token and do not try to request a new token unless a 401 response is received. When a 401 is received, it attempts to generate a new token and retries the request. We use this behavior because Salesforce auth tokens do not return an `expires_in` property and the length of time they are valid for can vary from instance to instance based on admin settings. + +### Debugging Requests + +It is possible and occasionally useful to log complete HTTP requests and responses including URLs, query params, headers and bodies. Be careful as this has the potential to expose sensitive data such as passwords, auth tokens or API keys. It is recommended to only use this when running the application locally. + +To enable request/response logging for either mvc or webflux library, add the following to your `application.yml`: + +```yaml +logging: + level: + com.tgt.crm.token: TRACE + org.springframework.web.client.RestTemplate: DEBUG # additional RestTemplate debug logs, only applies to MVC. WARNING: logs sensitive info + org.apache.http: DEBUG # additional detailed logging for RestTemplate, only applies to MVC. WARNING: logs sensitive info + reactor.netty: TRACE # additional detailed logging for WebClient, only applies to WebFlux +``` + +Note that setting log level to `DEBUG` will enable all debug logging except request/response logging. This is by design to help prevent unintentional sensitive data exposure. + +## Local Development + +If you make changes to this library locally and want to test those changes with another application, you can use [Gradle Composite Builds](https://docs.gradle.org/current/userguide/composite_builds.html). Essentially you just tell your other application to point to this project on your local file system instead of downloading the dependency from a repository. + +In the `settings.gradle` file in the root of the other project (you may have to create the file if it doesn't exist in that project), add the following line: + +``` +includeBuild '../token-manager-for-salesforce' +``` + +Where `../token-manager-for-salesforce` is the relative path to this project on your local file system. Now when you build the other project, it should use your local copy of `token-manager-for-salesforce`. Be sure to remove this change before committing. + +## Publish a new version + +Make the desired changes locally. Open a PR against the master branch, get it reviewed and merge. Tag this commit with a tag following [semantic versioning](https://semver.org/). This will trigger a new deployment of the library to [Github Packages](https://github.com/orgs/target/packages?repo_name=token-manager-for-salesforce). + +## Troubleshooting + +Problem: When I try to start my application I am getting a `NoClassDefFoundError` or a `NoSuchMethodError`. + +Solution: Check the version of Spring Boot you are using in your app and that it meets the minimum requirement listed above in the README. + +## License + +token-manager-for-salesforce is licensed under the [MIT License](LICENSE.md) + +Salesforce is a trademark of Salesforce.com, inc., and is used here with permission. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6341b13 --- /dev/null +++ b/build.gradle @@ -0,0 +1,123 @@ +buildscript { + ext { + // libraries + springBootVersion = '2.5.3' + lombokVersion = '1.18.20' + apacheCommonsVersion = '3.12.0' + hibernateValidatorVersion = '6.1.7.Final' + mockWebserverVersion = '4.9.1' + spotbugsAnnotationsVersion = '4.3.0' + httpComponentsVersion = '4.5.13' + reactorTestVersion = '3.4.8' + + // plugins + spotlessPluginVersion = '5.14.2' + dependencyManagementPluginVersion = '1.0.11.RELEASE' + dependencyUpdatesPluginVersion = '0.39.0' + jacocoPluginVersion = '0.8.7' + libraryGradlePluginVersion = '2.1.0' + qualityPluginVersion = '4.6.0' + } + repositories { + mavenCentral() + google() + } +} + +plugins { + id 'java-library' + id 'idea' + id 'jacoco' + id 'maven-publish' + id 'com.github.ben-manes.versions' version "${dependencyUpdatesPluginVersion}" + id 'com.diffplug.spotless' version "${spotlessPluginVersion}" + id 'io.spring.dependency-management' version "${dependencyManagementPluginVersion}" + id 'ru.vyarus.quality' version "${qualityPluginVersion}" +} + +apply from: 'gradle/spotless.gradle' + +subprojects { + group 'com.tgt.crm' + sourceCompatibility = 11 + + repositories { + mavenCentral() + google() + } + + jar { + enabled = true + } + + apply plugin: 'java-library' + apply plugin: 'io.spring.dependency-management' + apply plugin: 'jacoco' + apply plugin: 'idea' + apply plugin: 'maven-publish' + apply plugin: 'ru.vyarus.quality' + apply plugin: 'com.diffplug.spotless' + + apply from: '../gradle/spotless.gradle' + apply from: '../gradle/checks.gradle' + + publishing { + publications { + gpr(MavenPublication) { + from(components.java) + } + } + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/target/token-manager-for-salesforce" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + } + + dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${springBootVersion}") + } + } + + test { + // use junit5 + useJUnitPlatform() + } + + dependencies { + // used to copy & log headers on requests if debug enabled + implementation "org.apache.commons:commons-lang3:${apacheCommonsVersion}" + // used for bean validation + implementation "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}" + implementation "org.apache.httpcomponents:httpclient:${httpComponentsVersion}" + implementation 'io.micrometer:micrometer-core' + + compileOnly "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + testCompileOnly "org.projectlombok:lombok:${lombokVersion}" + testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" + + testImplementation "org.springframework.boot:spring-boot-starter-test" + testImplementation "com.squareup.okhttp3:mockwebserver:${mockWebserverVersion}" + testImplementation "com.squareup.okhttp3:okhttp:${mockWebserverVersion}" + } +} + +def isNonStable = { String version -> + def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) } + def regex = /^[0-9,.v-]+(-r)?$/ + return !stableKeyword && !(version ==~ regex) +} + +tasks.named("dependencyUpdates").configure { + rejectVersionIf { + isNonStable(it.candidate.version) + } +} diff --git a/example-projects/README.md b/example-projects/README.md new file mode 100644 index 0000000..3572c8d --- /dev/null +++ b/example-projects/README.md @@ -0,0 +1,61 @@ +# Example Projects + +Since these are standalone projects distinct from the `token-manager-for-salesforce project`, you must help IntelliJ recognize them. Right-click on the `settings.gradle` file within each project and select the "Import Gradle Project" option. + +These small sample projects each expose a single Rest endpoint `GET /salesforce/query?q={SOQL QUERY HERE}`. This endpoint takes in a SOQL query as the query parameter and executes this query against the configured Salesforce instance. It uses the `token-manager-for-salesforce` library to handle authenticating with the Salesforce instance to execute the request. It calls the Salesforce [SOQL Query API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm) and simply passes through the response. + +Note that in `settings.gradle` we use [Gradle Composite Builds](https://docs.gradle.org/current/userguide/composite_builds.html) to point to the local version of this library. This way you can use this project to easily test changes to the library. If you'd prefer to point to a deployed version, simple remove the `includeBuild '../../../token-manager-for-salesforce'` line from `settings.gradle`. + +### How to Run + +To run the application you will need to pass in environment variables with credentials for your Salesforce environment. In [application.yml](webflux-example/src/main/resources/application.yml) you can find the required secrets. + +You can use IntelliJ's run configurations to set environment variables or run the application directly from the commandline using Gradle. Ensure you are in the example project's directory and execute, substituting in your real secrets. + +```shell +./gradlew bootRun --args='--salesforce.host=secret_host --salesforce.username=secret_username --salesforce.password=secret_password --salesforce.client-id=secret_id --salesforce.client-secret=secret_secret' +``` + +Examples of what these values may look like for your org: + +``` +SF_HOST: https://your_org--sandbox_name.my.salesforce.com +SF_USERNAME: some_username@your_org.com.service.sandbox_name +SF_PASSWORD: password for the above account +SF_CLIENT_ID: long string of numbers and letters +SF_CLIENT_SECRET: long string of numbers and letters +``` + +If you already have a connected app set up, you can find the client ID and client secret in Setup > App Manager > Right click, View on your connected app. + +If you do not have a connected app, you can follow instructions [here](https://help.salesforce.com/articleView?id=sf.connected_app_create_api_integration.htm&type=5) to create a new one. Fill out the basic Connected App information and use the following for the oAuth settings to get started. You may want to customize these later to limit access. After you create the connected app, a client ID and secret will be generated. + +![Connected App oAuth Settings](media/connected_app_oauth_settings.png) + +### How to Test + +Once you have the application running, you should be able to execute the following cURL to hit the Rest endpoint: + +```shell +curl --request GET \ + --url 'http://localhost:8080/salesforce/query?q=SELECT%20ID%2C%20Name%20FROM%20ACCOUNT%20LIMIT%201' +``` + +Note that the SOQL query is URL encoded. The query being executed is: `SELECT ID, Name FROM ACCOUNT LIMIT 1`. You should get a response with something like: + +```json +{ + "totalSize": 1, + "done": true, + "records": [ + { + "attributes": { + "type": "Account", + "url": "/services/data/v51.0/sobjects/Account/001P000001opKSMIA2" + }, + "Id": "001P000001opKSMIA2", + "Name": "Francis Johnson" + } + ] +} +``` diff --git a/example-projects/media/connected_app_oauth_settings.png b/example-projects/media/connected_app_oauth_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..b269c21505a753daa899d24615e0cc1cfb8b7b63 GIT binary patch literal 108216 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

9GT9^=K|O)uI|6b%9VPYu;6kV+LPUjmi{>i8 zTkrj`O?}5F^zOTD2If2!&Ag`Tpbr-BRyb+9P2@|vu1M+@sZF0M@(%w}NG;$4RYf~0 zFLo@fNIa%}0=MK`bO^SU;PbTp@()tzrv1hD{_bxXjdjs>azF#|CNH zLMMz|v*UB=9!+9l;s3R>f5<8#%+T>b>1Y0bQ6E(=(Nt@tGo)Fi15XLo+uCVHyYWZd z38|jY`rPuxTYsS#H71SR!J&!zBs@!0wz`X8Y5Hz{iqX2{h$}lL)AMcuoi2`x5o8`$ zV6>tB+-G%OfMJ|#AWF^7Jl%`H`^DJaXsadqA?+M%BrCFDOwJjgyI(a!%36Zk+BOic zr0KAv20RoW&gx+^m(4U~n#me&B3wx|Dmc8aT}p)*y8 z8MIB7x}iLAX3AmbqM_)tv-ifpwk4kvbvNvku=gFiRoSi}L1#BHC@ST$g#hz+1NZja zARo@()x)3HOr9BFJW;QTV9=+MBC<%MY{B~0=&G&f1SVXFuHs^1HU7oMU-Ix|>Ms6mG#_rq(3p!)JaYVMTE@8QP zwU^OY|(%+OW6#V>1LUtp&E*j!1xrm zgR=frghpe3;kTaqog}k%?HUS__}9MYFr!nco^%S>2SR|LB=ie5wCY^8bY{;qffOr=a6OaZTO))+Mz9TZhQ z7u2Lu4z}W*;qc&%Q$>qgtNWwh@@yfDDv)JLQ&jl^da3W-oG}HZyrDfbcls-i&dhhE z3_k;^m+L+!e;isjV&71)uxt)%h2AJ>fGkVk z%H6D&Fy1{gbR2;vu3eO!tDuo6DMwNeb0?5QIVdSFP(P%TlK>0h%A^G(W}-7-ZfmRU zEN)v|1)35apr*SvS)EQD698A$O8Qq}=?9oQW3Jr?>+Js* z$)gTyrVJX?%l+Y3B>LAz@ahT^H?7EiqI)^uYEhwA4&EjsHa;?^f-TENPNidnwt!Xd zD=NoFgCwo`kxcivq3#QRiX`w8@nV`No*_MczS%xBi8i#to_9y2sKgFdy7$8OL|`_);8Ue(=T@&szFptoN+^}b zyc<^tPRMh2Enf4All_Sdt0N}wtEZvn){QY*J{+To^?&raZ}dNBUr^85yHixhUx(U? zYJC0s+*3xYxVc|YOR=>zi$1gCF@$W|biVcO3qiOs_O`zyd_psz8ncNStnDD*4 zH!f#t5m~NsQNJH8TR)i47^%tnQfW%+ai7J0`W7RXotOs;gUMw#Yfd2x+tbsj0?D&J z6@BiA-ZDGcWY?p0(|b>2GTE;Wm?O9Y2)mvz&!F*gaRpzvzQ2YC==H^=pKKcDz9(*k zG5XS|d5tHmj7k_ZBRvhEAR1b_n~X7~(5Oc$%AlOcVJZa4m}jQCi#a}p9UX>MXg6Cu z`Chvc4sOaF8tprKw;ny^{uY-x`2RZj^#NG<^`s8je&mv_p+*seS@Y(e(YCO zo@BYQflJnZGKxR@T{a46EV5BdNB_oRveA*@PnHyz0GQo;keDuyPXF6lGM^B|6cduB zA<}=6ve$fr>FYjyKKg%Du83oVH~fJ$!(xH{|E)#;&xr~BWdaQd2qxRP3#0z;MgNDE z|MeB2pQyNY%oD!;zqHE#-K78Wv@0`$V^|qCn)S~qk36nQ;1vs#viFNNpk=y_K`m5-{W3NpsSAG2FDnC;Hah}t< z@3sK8!4{K3-`P;(IdH+#)Z?#Fr)%9_Z)SiR)UtNs$G*^D-5z##pw#l;f~?!pGoWMO zYXE9ogz-mbkTM4GAokG$xgN@ee61iMuQ=sG1$m;2wKv8*)( zeR*bPwBW97fB*QrwrKU^U1NLTCjKL?tzEZfuqV0lC?`r#C#AWOQj+oiU)41zg+PaT z1Mzb=#wGNBN+xQbW}tR6W7ULUV~~DL=TE>elk;{K=r##CT)}-_E#hU5u%ZWF#;yA) z#)U`s1i0E3Md7b23YZSv-J+)Z0Q$8&xfdIJrAK7QZ| zc#gvE01w<-K1``-m}T!VMoGN>F>tDzR309!W0rVZbhjB5atvEozRvzg+rlsMM9&M5 z^y(!C6AJae`L>ujw~hlhF2CMfNU*qm#t;tiNwa#RdJVj(WS*z<7#x-4Jn4d^;uP#O z4HXAI6;u@DvZ`AY9sg)}izW$s*+VO(W)lertMp&eYX;D}zA9=z{)01fL?6=6V3ewD z0~hFe?GOtkZ@i_U14xRUK=}b=%c(2Ntn@Tr0Z)5?yh&-C{@%p{mQ28tcXSxFf3Sk; zAq?mUnVp%&#W3p8;QZDe_Q1UED)D|4Hg5y)0z5d3C}_4;tthZh$@c0+;aUGr+-O zttbDFAea55^z%v*HyBhL0DPI;N__qNh9Fz?A(qlMNn{!j!zHD@M|PqGB$LY+q#Xs> z$**jnF-xd$5@o#Z_8zumnET-mg}5VdkvNNCf@#%cdaCAKR{-(p!utducrHl5XSN#k zOT2`Rk;a0;Lb!rZs@)NiS8Lt?fhkE|s*`S;85psK9a(|4Zv8q9bamEBnmVSjWuU^E zY~1`*@um?-aIO391_-0r0?bEGw;h+Sj}YqZHzol|U~6&k42rORli`6T%VBQ|I2LZ+ zc;QF0m@DWWX5z0?oA4;P8Q;OEx*+O#|HsP}BoBW^GtV!hcplmgKjdtb*yG^`FrhbL zA2E-(;}f1L(7y4FO}y&3G-NUY)26#;il}L9elQ~; zue1TvFwHG|T5U@XUg>;Csek$Lzqe3$5ZFTD!q%2m&{=9Q!tAYn44mA2%w$F`(aP|L z4|^-(L;?H_No(Qz1p6clWRu7vKO+J4`bC<0h=7?$PC#}V2pkyT6R)y~f+aS~+Cmj& z$e=Y5M_{vOAvEN*bi%Z9iBzS zdkN@p&UWlrIIG~5++K(-O^I+mT{xMIN$H+wxpo`vDwW|(8YuVtg(+pW={*jA1~2#w?4B z99X?nqRwT7QA{^wST1Y1@u872j!Ag%*m@8G5r!svwo!vh7|70pg{d$yG_nU4deU2R zb<$W^J6k)i>@gVnZi2=vI}yxL`pQ@6-pc`|Dkcs!6PbkUKEE>J8kd?RO&v5p5>=L?Nr^y8{cPkG}iT;ZyTgZvK4y)ai1{E1s^>~Em5 zKiE+eabWaEPOrq>f`+hXuF(oQfX4CVU8aKLq7CQ?>{$8H1e{2%yA?!KLskpj8dECX z9Px*8Y>itMe=Jez?0A38-4yx@QWPH)6f`bjac?>cNUHvS3Zg1I=#byp)7bfQ z$m2=P0S$5O+cH+k{=oCZ-Hl_WRSGmNS*Dz{E-=2=v{yAyxLdIMC8lu+DVWiz%;K#^ zD}%cl^_JZS+ILvW)gERavZ@BW4Ik|U4Dj-(j_w3*W+?VBYBJ8mg6CjCI@8)IqO5($ z+l~D#)A(-=Uk)_p93jNH>*wV-J}$i>8QBFFJ@W0r(1}r0qsZNw)1y3EHDa0d2#{tI zX9G+X{Plr#xFgNpD|*4PcnuJvcFqQ*BS$P`}}I5=^ObR|ZO_7MxoV4aFdTR%$mj znw2`Os|b{u#OiiLQ7R1SpzOrwAXPLX9d(%*a!$)e`$TrshXIon)$V3b!pSLoCr)}) zK655H`#bx8S;apuz;PHTSc)<;qOxXJ0Pe=8UadSk~jlwdy*UMA(R>yBi~>+8Uv-areq?tTs9Pz{d8e@@Wt z*saie7v~P?^w9gNk%#SQr7vE!a$P1_Fs$36EW~<-2EWb{D8)%8ZXMl7p}5;(6~^JB zw)Y@Y#dRerdT=UW=8b5ER z-4U;#ly;s@ahvt{fv|FEaW~zb0xqMU3hqfRw{{fp3#6Bq$@o3u4& zy+vM!`%uO9--hBVc_9T16c<#Fk7S{>%dJibs80^1^3Z7{%ZE2D_VLZxpV@cA*miOJ zVe&e-pToj15bD%J)C>!;nz}tBKMG`+j`yv(^$J6ZNR9-GOx&}ufEuTsB^<+iqjO>mNoBRx8wwk4ttp^6GWDwk`S2Q z1U=LyJ~InuZ+!UPQMQq&mVmj22;rLKcdO)K^BE8;)pP_jR9J--uNsD`PNltW#mZwg zyLRSQf$YFeoL3howH-K}+3dR`o%c|2C^~5cM193Q7tj_%-%@2!Mwr*4+-=3y;6K2{ z!6$!P7T59<{af;>GNR$wK&bxp1uzM6eB7?^4ph+^wZ=<{m+VqzIWmdvB$%7f4vQiXzOwYyU$XGbG{K2 zv(>4NLudNhRb1L`{;g%+9NV}l+b84fR!qyg$l*akTp{O|o^kALrHh~$?LR7Bc_5op zY}hCoK4r|(HYPE?V{Cy^sFN3%t30NX;dunlY?8Z;Lp3|(&2MTiQX0ekb->r{WI0w3ZMG9oaZ)Uz@d3AT*!T%rX-ZCu8wu>58gc}42r39qJAVd_T zQ&2!uLKr$lLSX1dQc|Q;KuKwsp<(Do0qLB9p}U)*<2}ba9-rIiJ>Fm6kMEC%C^J`` z*SYsvd+oJ7d&&w4yn1QP@tYzapKA{1-e|^l3R|!|K8(+}*NL1>q`5Hnh>vvI#}>V+ zDk9pfsc!JS{<`;(bhAra=b|_aN~Q8Sz~$j6Yqy5;LSKf0?v+X;)%=Ci(K{mf`V?VS z2Dd>saq)TF{UhL-7zj<>iV4#pt9QCdWE66&6{j|yS<&P~P4#3oO+1B=f8Z|{-c=%O zfqiLJ2U}j>tJK_`Q*-#c>$h71m+3PTnwBy=*JxIWv301*Yo%>iBBHn^LRYt@(9H$FiglCLO4Hnl#=b!f2BJ zqFsPN1Je=cS3erlT@_CKl(4m&Us(uf{GM5TJMleobo;@taPn(4mPx68pL+P4 zMjkHBRGDQqGFa$lBNYE1Nv0&Q90MN_eV=^z6ZiibMc^{}O*AH#3`2O41gkT&dlY}) zI1p_XSOGt<#>$f8@h3mv1;%*mzNYJ}21yzu4G0O{98Z7{hiKCUl1y@H00QeIsE?TQ zvQC)n0^t83RWv8Zig;1w0^tE@er*Ip;jh%WzWTl~+1Lnp4!Rd_L_&ef9kIwdR?a~i zTnEtO)j5DA?|r9GzA8ve(;#4(Y>acfne3i9Mr z8{cWHqh2ce4QD>4?r|WC)BcA}k%RDURRQFd&d@pb)7%eO5nJxbQsbwN0=4EfELPsP zb(Pl`v-&gx@@^B?u%CpAE05iae#;M7KfXzz@x5eINiUw;lc%2ZH1^;C$O138Z%=Gv z)u%XX;>R0OU&r5f0c9<%DUlMAf9-7i>_ksL!}M?uL?s9!jBg$j&kf8Y-794n3nKz( zFx^c8rts2_B6mJ})q|%VdkgOO+73PMr`+`rpPpiO+|#r6Rdb=x_Ms|Ys0hzQTSOJZ zj0;H0dtK&HvD~3WokHdd$Q&&JFgsGD@?S+&EJX6hO0x|VVN76cfMiE{0(5*`)TwkN z2F`{>mt(LaPju*;aP)yBnFKpI{j=**RwDp{mIrRTD>Q&PC@OvrB&uBP0F5us>sGsB z^}O6CE4Rt&GFufB?-sUPH)v(cxO8%e*&dz*0EFP9yiiso@9F+@fE0<~jcQbeLSlE8 z^6g3#XzsQK+yz=xB^f|3E8uJ>i1X?i%s3DT$2JaT{%l_MW&7V;N!E4zCl>_Ef$f=% z*;Nsc&rE_|TT-kxn{9ew8>BZ3U`;YS{kR9u9L4HG2A=2v5dc+VV-Dn>?qaH`?tMxZFx@7$hxZWNI&~^(VjbyzeW-F+5>b!gw0t;LF>z=g-E%E{}4DiE5p;- zXub3S<$0`yTjJtvLkV%`=LwH}u0M+%t|^|XRnWfKQMJbkE6TPqnsx>)+P+|Ma_y{- zi({28s>D{@8_(K7|M6}W6mV9HQwqw>HUNQ9M9E27 zs9wP-{y^5?$i-a69%P2<%wl1Na0IcJeh9CM^d(SO-uMX0b@_!rfTen6EB^zl8%O18 zN$l+N{=&#dA<%i?U?*`?>82WJ>OX&U4D6g#a%)drg=>Lgg(Il2*cNbET3T29>~1nY z;{ax9j|=qK?PhfG%IZ$Y00HpwO9=H9@~j>OJe zb5-t}2F?Z0y(`@Pxlo|=iv(9wEm>K2r$9gdDCh%!$Mo&TtMZ?wRL{WnruXDcRnF;m zDy{bX^R-~1H@Av_B}yFp%BD_?M+{ee9I)4p3E|7Tf*PioHwdR&0bOg7h)>F3u}8ym zPp(C;(aSY6x@EWH?&--gaQ-W@{qQ}2(5Rk8O;w zs;N^&Z5rdvxa~ul#*|Q@RGq_TOusB3va*zUm z+^L8JVWA5)Ih2%m@j&kh!D%bt-%SU(@dMLCS-@6j_|pf+g#fUFw9~y zM1rtNKaQ&i?sr2#Q_gV%S6h~#A|Gsr!=kNE2%ajlq@;hpHIo*tOPHyv#9%TFs8ZsK zo96Fc^;cNBdvoHn8sN0y0`eq+EGj))9(=T0lIV-(RXhjDtNwr%Zs9Wq@>2drK%rdC zzgn;Av+di>=SD;^#Oe|T^xH*`BqC$O->51`-ib|I2Q9obO$=@pMQ~YJ$x5{yBDf#6 z7r+86FJ|W5MqnKf3oB;?jMC*oN(3Dq|J9qmscdCR=IcQ7Itf0URk$Pom~Ubt>)S7R z3)CFJf$S7ZKwIZ<@8b|`i7geg8Yr2Ig3njUAu z;DzU%OnZ#A=?o7aOW%PK8oR8@QPF3QT8OCovbC`dD|3o;J%U3HS{IY%uDM0lgkpNqKLEgV} zeQHAJwDYts!9VH-84+}C>1~mv(lbnB|HNrOy)WJFEdn88}EEq^ccNDjE((`^c z1ltcfcI#$eVNwM2<3>JM0x?E7xJ;>*N)k)tL1=;JQ&gmDqVZ$0^6${AM0o1<&iW>j z&Vmy>psjWg0la4GLc_lf)xmHJUBM7U1uJj!II1=h($S{~*11imc~4C7a2W_au{ z6(Uc8!BR5CcyC>s?kgQ?Y1}FMR{!#?jB;~?30bY}?}f6j5kN!K(x4LcC%!Kh$5rG^ zNq9WkE^Tvq;bN>h(fj;*?e}$neW%!<^k3PQxESL4Q8rtk0ldOS;;hD-Cy#mG>G9$k z@iFC9I;~qWe0M%ewvfNWaEAt$9cIjD_C2x8k z62S3&TUJv6O!D=TPFp*fcgET&t~=-+rt7{{zqyeu?Gif}nd;!Ze(`pk{*pZ()weTR zZDz^6ArnTHG^J?r$Ddse5<-i^8*K6?CY5~IaBb|+(Kn&Y;TrF~S3q1L@3KkGv`VN= zU?JlE22k*aso*WTH?ia*RuiB3xnh+Qw*HH(`}xB)P$3QV{VR_M4?YJo?*A|-jx~W? zB&B1x5+q}1DR%c(O{3#FnCit-c-83s3?Cl@uH0+cWk7E5W*7I$Nk294zHqdZs#(%Y!EBleUKwT>?-R122FO4dqKZ^F=QkmDU z_jlYm0Ub%)KJ?eg@92Dw?V$4vJyPxRcLq~TjPtLa{}RTDN2e({(@VO-n}5H4?fa@o zUDYL?rnhC_HG6K>ED{dN;@? z^He0J=*bFCEd8x_-LMYgqS(%YdM!!kA^+Fc$o(Aro`b;!*O9FoT^27}jC~&08wXq5 z`+R)A*p@ucL|8$@jk66n(#0U70f_vj`z`mjuj9=EpZwyPv{egNnH#Sh{2Z<06MaXD zh^fn?$%;xAlg14j#mDd5V^+w?@yOFEwT%4c>T3b1pYz@cvbSLW%nF0r33oMD`%>h# zPBXTm<5}PJ*|G&Y7SXWufXN=i&wAP>WogF)Bc9_Pv^RP5M}!~keledw0(0XB9MesI zL@)$xAvdP%N~{w6Syod-?(OJHHTv!-EaU8ba~rYj3w>3IFgAP} zLPrnMyaZK}+E(n2(c5q)W>6$M_fAG5-pC!!8!AGoA9qJl9Rf0Gw}I{YTQL^2PaZg2 zJJCp9#csqg0bYC$4jC=qgzeXI#ss9={;4?toA43A!Z_Z5p}-%EAJ5uZ|H`M{GT(EC zb-oUZW>l?yO&^<-6a!TO%*O!dyX8UYr1II@XXU)YA)WEU_@$PCZOLLQd}ZspRUV9$ zBzMS7pYoN7bqY+Uw$AN~?QO7yp1@ATqHeLOwo$YeUle~$Q8#w$&8MNS_jivm+(pfW zj)rtTT}4OICfii*O%1+$N1ydkUJY02KBYg>9XFe8HhX(IKlwvcrN6kbU~8-O9t)$K zTdC}($m3>ikWPx_Edww^k~zO#Hcg_OcZ4YvY6Ht}JCwD*9yFrLFG3Uetv3puTYZXq z)f`buFu22`(a!cGO$$V&Pt0}W0afeeg(4%EbNp z9Fd%V@}|Ia`K0oztHs%U_3WBlg};Lut^YY+4dtND!dB+ww$>D}Qu~&u3yX0wvL8ue z1Cv=R)sR$NVSGkyUSxM!3PriV(UBfxZTdGTpnz;;!QQQs?Zsjp2p%-$p} z*9g9R6Ti*f-rCw8)i}@ntgV&ZJQ39>c|o`H_y!DGtF39GG2V+~E5Aj>@@~#kes|}E zNT~Wk6)5RX0*jTW>&rjcTmX6gL-uyF;?A&*vfhA%{=)xW#~qX^djWWu+8Kbp$CT_mF#ZPA=Rv({qO$)*nb0;KuGtsjAML z4Qkz!|L8=Pw{gY*49^F?N`7)_Q_<1~HlP?v2+V@iEvrG(2vh)YM60?$EOB9+{C{Li zG~i0xx_dIAVh>WIe0FNPm;D|47eGQ@n?*y_J zfhvGOI4wItx*vTd*Crl#LPLL!ss@EW2GAUW+5n*Kk6d1M=d}_*VB7=Q&I4G>t>_CE z&j6;IpToN5H+r4Fu%e4)HQbU}+A-Sq?3MmaX;px+6*|<<;euOg#mt^!!waTWauytyOT!B%rAh@|6QePnXM3Kn%dvK75V1UQE&Q^B{ry2j#?S5`6G`Cw z+n=8RNap49dZ;#15{rWh2|B0mPl-X_zpY^UMBlidkVrkC;S23ikH!QaJQ)_~wal}_ z&iO07VA=+1;g^vhxv~bGT>e;86Kf*V#5$bd+GMaXQIE~m?_*rFnV>Bk>ww@Xe$^fz z(jL?2Ld@9~A{Iw1EuU7*C7`9v=DaJq7{a1qX zD;NKESyMj78u2`S0?*xMKf?~8o&&;=bKa#uH1Qu*$n#2Ur!vUsZ=a07)$;N|BS?DU z2`-K^mgoW?(_Q^l#N!4q{+kAPzRZ_4(mRb@z7=o(g2No!W{DlumP(w?F0dAONaq>$~u@Y z&`EHE{@oB5#1Jy$Uk~hPXu$aL?R;?O0zeAbjphclM*!%JZSMV6%k(-+c#RRswzH1K zWq&aqOi$_o-*LFN&KRX_`=eN(r5`9q+V5Ol;F=E)0A0<|Y+8N3o{5V1>aKC10Q9rC z8}@TFIM8Ph-@X@X;7?$AF<1xykGhpH;2C$BL}-C2 z?>d&;jY0*PsT{mt2LR8+&IBkcay45v-VX@@|Hn7*j|Y$9$<`T+40VusETIC|M}Qyv zd2N26t|(5-%*U2=IXpl?o^P3?$q`JPFEFnXtQ#?YZ%S&o3Y&6mnmik>B)!K5vk#db z4^NqSaGh{EnJF+?Un2QN#8=sSoa{@m!0fMZ^K+DTBuvhi6>)DoxpIS};32Z7t$jHEPVf<06DW(hlt(lYd#V6UI&WV171OLSP_`3jIE ztmT|2fVa6m8jwm(t&(E-c%d(y8kCh_l0-Hefoffrm%@n^Wj724nM{H`p$z5FhJ?es zlcJ9-4`gtrJScrb#Lf_4?;dEb~K*H+YCl z*eCh(k4IOSK82<>@L?adH`)1G$rlelNy9VnH7DVCoHCPgno!#IJ3(`}LKRkiZet|MLxGit+MMBu3y8Fraf! zdtVddRT27N68Dc%j5}iZoHz8Tud;W)*oVKLmG4=6#Dp8~sJR&SKaXoZOwzU%4jw7Di|zbw6`N%C3NP?fk++aN7P1IqSI)tJ zHg;QsSXiV(4Xg(Fe4zqx2ZZ7n8ozYH`aW7hpbAjSWB8nmyhR(~dXB>asZFm8foYba zb@#pV(5CxRtHKYiuQun6VCI*UIBB?rD9Z3jhmQEilqa$;dt5?DLwaDP$!e^5x?6C(@ld1A~q7vtqr;IB}I9O98%S zhF_@|4wdk-T)E)y)K6do*KfRGtx|o84>5*C`me;O?{f5n^lal0K;{_7lUGC2UMum_lcGHml zKN|DCK?XCH%rV)JL{nnFZy~h2wq1~(Y$QI5^m}V4zsd=08)akPD2>_0=6X8nIZWCQ zW5rUsHO_x!j~Mu<4k^)0wCv2JBVcER48X40PYXgeu@1*~?IA?O9crYD8%g>vq>}b) z+>f*qq45E(DqB;X+n)6h&2orF@FU*W3S@m+E z5&@gtXttt3o~xdTapo0O=KO{gHs$oPrQP;7x^^ead9k=l+Ti4L^gLOMkq24tbWpbh ztaq(onRLucF|7OjPB*HqqsF7pGBM`BAC%m3bO~>jQQ6zU6@_-c7q3@wkkUC&d(l`( z`GQr}$-7hM6!EHsFlsa686a$Aj~?zrJe*x1-{;nedxWfaG1w zBl$e{fH_uQ)xdPQ*-(1n3O?$k`#DKSI>MI&FC|2+2Vx#_@=*jHLXzv7RTkjW!~jS2 zdrX5I)gF==ld`1f7gs@(5Sqbmg4Yz2_wG5K9k6>Y6UO&jfem_q#zt)~co*v(RXAz< zN6GcmlcUNB#YiNfGv&8MJ3=p`>!g@cRdtx1nz|Dh&{2lLODsxQt}4FeSIp?KpR_m^ zq?y59gAs(BhbP*nN@yjb9P#=HOHcAuSJ@~8U_M$NFfo+PaY9>6hMfKAC8hdwJp}xo zig$&yCS`>yHtDpju0c3rYHNnzyBHZ)Z?g?Cqpa?s+EebxByoFkDGY6N$jY&}s_7Zw zh*4UXxj130gH+3gBLiA(<36Uk$7H9vuL7Us+QM$0V1>;qSCmk-!=gZiF02B@$n6uh z=q7zwb!z%mmFuu)PYbGMZwYhV6{oJ^qQC9D(Dd0%V>;~p8>bz;^9ybR6%OXqKFdvE z%@vMk`$8s|cjFZf@FNq+lUYnNlP70PhNtw7&H$s?)n7t6c-wAx56JwJ% zCUc@Us#W&--iVUpE6%yMD-3jj23@LzzK!TaA%s1eQr16USf)r1@{Hp>gf3XtJ0Wzw zmXU!17XKFg1UDe%RM51tLAbVXXgpV@IwPUE9xy(8kmuR#=!vnRQ@7KU&L^VYlnsaT zsYuF-hla&sKhOb|hnX9y1~&VO!(Tq za-g?F714qxBll5Pb^A{9%@bp$tb51#4xY3V+RQ z3g4fL?A4fiQAh?>=*0fT%+^iNQz7Ib{7T`k#T?G&oedsgX4;2Sj!=-{8n=4=`1`-u z74!A$@5nq%vyZAwy+Z(2xqzd$YL2D`U|XQgq4do)JiC9#pD$MwnR3oJJLRNKHi5rd z;~6v}@-DmiL+pbN4Dvc%_W{CD_FK*GW*=Uu5@u!$ojor|g}vaqAIuvXo4wxTS%}%_ zI=h7bq|5*O?e+_=UHaQEqTN~kKH6Vr_|K2N1d43*4fRC-mDXVo{nwN7CTEjK+X?D7 z8vpkv|2YUVj&CpXwW@y}?cX2$`%45Oz%TqiPJ&D>zut0o>1lF22?>gjBSdCk{)_R<&WD}V(pZR5&|&63bOQd|(XGcE+4uw8z=L{x9gQ=~)eDr>h|o9;>g7vg+g z%Mqp!Mc05>y~0;P_EL)gu?_&(V{}uFq7+o08tp>>1*ZX5U0pb3BC9%W7b6ANotUG( z`K%9pTOmQ7bv0!p@VA9y!vaI`~@1J-ApCJ(eh{U zhXwI2#x5KXc7)mSM+IYHQiy_{b-fGl439e2FEa6*gG5E!KC~OFa(OoYF-`;pd9-{& zLXURywtL90m#BOd4x0z#aM`VMfsJAaQ3PVLfvAfg0`c7ZE^>5wbQ4)2(AM`9{_Loim!3N{@rb3-eNS{Mn^F$mD>5Q<{APQuV>SM0G5wu!eDg#-%N@2X z5YAXMIMh)N8dc6&d!m`fKm=o>uj6{<_+)Ebk(FNLx?s)Tm^EDc;LiMJk4#^cc_Avd zrozwtX!Sa^nbU%G&XHKIu27f9@c#T>l6BUWYSN34iBZOZ?Ib%=jlF^Zi{;P-MlRL5 zl^@8VRsUe}WCi0tetW&YAtX#ckfSK>h$Y}rgu`xr_r7qXLs@tSU~>y|RBjO%y4L3R zE(YjPJ?Z!?ygK5KB7uUu=x*RMVxm2l`elS_`m@(u1oWV>ExJrmGlvaKFomflxrGiV zaBBHvX=>U%z(PdGHB=EFROqv&JZl-afQNkg5>^~)x!JKPdW0~@!Enjy^IT#-v|6#n z7xdW6sc&q%tsGVr-71rrKK(L`Ct;W+j3=Mt$|L3doMu0w+EUNcB6t7LYKtL4CksUM zUULWdoqP2;^VE6UAwv4rcU&f=W37E6y5eGkM3|T z?~T#THCX8=oZP=I@=4XmWxVFX2-{p&;<&q-PnQ~0m9ok2O1&VO;G9*stp_Mh~B+RW06I}4?J?@g* zK+FpM_I$6gg>M??%Xma_61<}E9X-F`+&%>X}Cm#Y2+z^la35l~OAWHB*|sv2=r%9@BS$H6E+TutNlVILXXXdv zUxg_!-T;@7$7jqD58t z_0IeP?y)Kz1z1*<+0N2hX_Z`rVg!z6WQ-9Py`& zlt5!k!?<1BDqW`T=E2JH;4sxnatNE#igW4nmMF8; z)2pVpIt_VdmH+halhroR-H?-GIjU(HUF>kBzJPC{OzB`m=tu)pe~ ztuRQE4xt@5+jI7=v`J#OqO|^l)UOkm%Q_9zRQyP4&ZbkF3LL&tS%APZ~Hpuft8)-qc_-uzvXh72!6V;83GInc3l_GDMq$Or` zH{j3}o)eIp)IO8On>p|B0NiTIRA9Q>o1=FGiLCO6RW;^N=9}ZHy{#nvvL6|h(qI8` zTMoMcqebm0oiD-J-dGQGqI;rM`KIQi>Ww#w@D?WQ`WFGeX9}zROtCs=wWmEDx}uxS zZ95mYe$Pq;UgHs@DWIP=2AxoXB{{R_dL%YMadG+bg@9*Wp-$GcKI$Fl=-&BN1?=yoEN?x6l_z zcdWe&*d5+BJD?CY-ZP)?ldq&jySfb2RjwulRFo%lmX0FpArIm40qExo#_tIsKt|?B ztth_L@-f#81gS!!r^K-*?imgiP_=y+p$#Tk-aNBfCQZ(y{G2-EyP2EK=6Bwz=?qLz z|LOodW9&u`X%OTf+gd})9ChHdivBTrZ*zCJ(m8T5cI5drci|2Q>D=nDllpvTX~x=v z1*cNW_)(0KFcm_GO)0rdbbq2c3hBxc55`O_{}JHfxn7+8mc;#=G6X3@Ti^VwO(_ws zR~vd%Ci!|O-4edafO*Q#+b_Ejs+>FJw^r_PFnob7bTdfPVP9&Xr>o?Nj#|bZ$VscHW$3; zuDYHDk)Cs4DS{jd`Y7>feYiR}di-e5VM#{B?BPUktyLe@!_+MUPh(OXEdlb*V z){r^UF5U}6_S6m~)q3LfHoJb5T7@fS^haIe*N*E*fW~&@?yx6&?o~v#y9iXWrFxtN zH#(x+k|4dcdr2IhJn5Yu{kD6y;H2V5=nTFC=tMIqmmOYeHO|1T#&z8}5$5p&l%`0!Rp6D@IKfozo7og=8!=B`~FJ6q#P`j44Eg{6yD^o?<{~+4G zUgyvva0<;dpGKJh7S?$DRo%NglJ#@KJ`}yzM4ZTY_*xhHAOG52vjCEPL;e%sUMCb8 z5~?(MV2Z4|oj2$C;<{wv<==J+N!+p?0|I=me?#JbE?GYHoDuZ@2iW*rlKubVGLrtk zCkbW#@^DOiGl^M{;_*SUIs4cGqs6Bx|hW@zYzv>fztwS4T09mP{1(&&O0RrbU>lbKC5Urf; zO6&sDlgt4UD0=cSeHgT6*o>Uxv)KO*=!O};JVY4#Cy%vqHE7d&jkO|Rei9G@5WenD z$ehz^BiuYekHzeZpFiFTA-p&bb{GSoYDkW}qtU>wD#EkRLgSHZHx3j?@wRdgl?SWr z=I_!yF#Q~IbyNOZotkh0&pZ*GfOQ~I1J9eU4{ot5q8JH!iPAoPoJSvG*w#kuLxt2r zK=$)kq!}azhQJs_I5){Y|6?u17cTXU9AQt%*Lr?3|9(5k0;xeY-?_Zkrmz!$1+@E~ zgCo59>{Lc)qtqOAw++-nbF|CR=?aOj5$1Yk5SWUs9hOUhm7Rc{e-%it3xO}s3{Kl@ zn_7k0*mAD9;c%(d9?trvYDQ;T?o^phC0r*n`-E*r1oYYJeqSR+CP}$*5sa@(E^o~$ zIBipYzz+j%Fim*%QvN%34;KO0eL3!4+TzzM6wOuh*<{?a_M2vj1y!binDvAgkzMVX zM*;j!4M-kGH9@+=yot~F-YLLyy?Syt&vW#eK;*->V@_Ql@>AyT@AP$z9CDRSh*Zve zpamd^&uIjZ4VX@cjMzNF>4_u&s{kGG5%%E-eF<0&AGhW$V%&zTaF&Y}s=9(iU*Pw*txa#z?TnXa(X9pd*IEJbtWfchwex z+IBN7UpDn%?rq>i`oZm_g+)|nP{dMusRD7o*L8H}>?DTU$UiOh_@$cO=m;V8UHfl{ zp-QJbP#ySZ(ZS7sx`Mw67+wL_|0p_EOFIBnXO=_XqGDkOV z-uzx!(+CBCRmn*yNfNIT$i=G7KVEgX`{#c9>lVsX1M;uwqeGMO0S;ExjVvb3oXh<$ zrM!2(1u3sYs*M$=rf%gXYp#+52MlrWJ?<6_zHE0d(F;2!Z!12%h$vCy!1wDVR1UaA zWAHeb%4RY(!MrnuSA1xqzH!trt^fm} zPRK>`ahRg&`-y2_BVyZni9eTNLH<2#YjO=4R3>6<}P%i z+&4UHi{)Jy1@gx`2Ic@EGz?@hD|4~ygayuvX}7Dbd3Xf|HV2lZqG-`)v(cz+bh7Z5XY%m(I=kw)0j2Rw@|bMxNT)gqZNlh880Ll*x|J#h#;Vfy60K>>AKSEhk)njeW4^XkkJB^6|lTZlhT3JR#JWD;px<_QS?CvQCkCt z?Sia0qG+hl2tI1tMm&e#`<z;Wko3t2GU(?$j)K9a40J=VdZqvoq zwvZ^Fhns(jWbpT-(1xWhgk*Z|MRT*H=e~ho^{J~KHJXw`wH=sOJt^G?BG0I(!*mJ@ zS9HHfDnh$uM*3@-Tw)mMvaBl5d2}PsOK@~^{LB$SDt>LvN9TFfe1fnxFPz=45pE!C zU)E_)0=sg$1V702^KX!gZyPDsE6>+jhj;kG;s!`H#dRu1CLxryiwxxCPw_i%R~zPO zMNhHk@|Nq>qHw&6b)D8vy%@AJvk0y6dTp=rnuUuVZ@A}j2C$c%+_zsC*3CS7f5yF$ zxuow>dtmIT203o$5!DWazq1d~4-TQ@$49>uIXfo5yN|pE;aB*=jV3A~Ez7UgsYE*N zjh2?5Q+-%;%K9~FteaCychc`Wmp|?aDDcBX!5~b_PBcQivU$g2QD8Ag4JGMatZA_z z(p$lIdf)!^@PZe6I^~88ExNfsi?NasZ+TM6Q(y6(xX1B5HJ98Cmj>S-ozf?zwfj@% z^GRLq2eYW$=v}bJ94a?CQcIzlCPW{bE1+0TAS=$1E^I2AJrz)D@kt71y3*`0o?@O` zzvWO8QB?ayzC2I4K7Rim4Ek~THra!%b{dfKciRVu$#^MN*cP^}{4-;TV6KoPv!TL_ z3PJFOI)qKu9=rQm?dic!eA&Y(1I_mbYK8nY^#NcQ&Z|bEu%sM3ePC1Av1)jEPox9x z0bS^{8Y-M!y9V}LXqsH)MyD|EE5KO?4u_y$X3{!;f7kCwNF!R}NfN=}v?2@bNaDLe z3*vH&n>NBM#7%|h3WP$2*6eAL>W!bAMa--1xqdoE=^}!$Xyn&|m*3g<1Pq&=aVUQE zsyv^HHeR^(o^Wsgc(WrxXT@&Au=ud`!iYVTw_i&H9ay64u4}GJ6b&Ax3uLd7MVIbO zphA__!wTx@wqcmMOJBdfOnTMqc`~@_ndN;z92NlUfwnPM^RHlFtkxRsOh+sVqO@G( z!MeKMd_<^;afL$dTIIBAxr=!N#RGnipAr+V$o6^e3)6J^+eu6a5U>_QzUTTkynr;Z z6R39Yt$U6yqV+R#0zCS)gai-l)ZAf98lDt)wFmJch^Rf*Fb=*Jm z=2n*=mff=F!M-%`md=c&&=nIl7D9Y9xX?4LQ%Qu^x5S(+p&nUCC@@eWHQmm`;T+jF zFzOO^O|S{_q7>@(QS|KCe9isjoufsqL*U_kxQPQOPO5psU`+!i8tzHFD(ADAOLzQ3 zds3R;=3;!2Q6CEHF{wzC-a>-0#9Dt^URY%ZubK7);^9FC^}Y2{LS6lwaL>)DG~J_M zHEm&HylxMDhN{H(VqGn+142^ENLTD4-?W{P9dAyI9!r7sqw>ZlU@7>QuCCO$acVwr zSXEGg(0LmS?}k8%ea!F{VH6%S5w~?DT$|}jU+oV<;Hr)RYPDvy#L^oF z`&|PuEFdY~1#HAnmw2Ajd}>W64L#s1NQ-e@4i}?&UPS=rA&(Ja3g;?y)fC^5Af+yY z-;_>~CbsBGsMcki6CQPgv8$2J%Qn`YMvM`SMUA;qN-tMrj+bg?XJ$C0noqBDq3^$i z&z^KG+~2RXU$2g@N*FaMC)0*%+4FP+bIG5u1h`t6vwk|Y(}I>hueDc}3c+1>)BX?c zclP-WWf8>Pz`3Z4c{Q{3u|L|L_1P}`6N|elRO-RjTnG79NvF}#F8$gysj&@R(^^=GywxM25@a}F z?up|2j46Rn0|6OYhRXmUk|r zdp&+yGEF0Eo*2>DEUu~w9P&866Zf|~{vV_ps_UhAxUjl^0~{?p4e!H;4|fCw1*OdU zMLJA=t;7EHtv$F`NZeS^H^zR-1_H&sKYgO0yZ_QRM-#!mG({;3V9MAPa`Kc;qu+1z zw^9IC-{^U&{*U|c^HASZKw13$?416;;Qik}O1vQg<Q*V5&GZoq&2s`qD5(Qf_}`}Vg0`PY#)IRVxiM7u zRdcnWeHmTF@LV%Xpox>STLQhX79i3A=xo#*x2;yS3Do+-fWxHP>^5wc&)4%9dc{9B zI7UcyXJYPne_AtE#H9l9x~~7Kt^NA*3Bmz;v*|jk!H+{jrVau&NEADfc?wUQb{$ZoW`Lzu zaFWj+2O?bll^&VI_s)X=VIg#@56cm3zPkaEuXCKHn>uz6=Jn7=>tS2twqfX8Mc3Zc z*Op^|YaLkz>9c=3k;nZv`=+scs~;zx|4Y8|^EOUt0_v-*=J*bS_j{tuCE!UYj)>vq z%KyS+q7`x7F9$jdUkT=#8=C9TvY2dGAMFBKwC73dV5=DI%&V=*;ndiA+mUec1+WRN z*y2Cfu?%6kz)s_U50vRRl#)LWQLjfc z5ERU|C0aTxX`5ly^ux+8_<)}Mc@Zc_m%p0@LiLL2#jDEc?fC#i``@vj`j)lkkgHu@ zb?*cay=zP1dws4pKW_3`jFjy`0TF^sYK(VmB!;eH>C--7g*DwUk1+QvX$qnH49j@i z?0o^>Vt?cQn4n1!a1UtOT^*i>7Uo#xBb4A z1X_W}yI*sf&ES2BDAUZfCHyj7V^*(oy^mG(hJ7yE zMvh8WGD<8Z&SCxZ6H)Ts>aZ0SkEnT%jQat}b${E^KxMnw!HhE4UEaHcK)ZI({^iHo zwxHddHRYiDQO%UQSRVDcaU;O#~AubtFjQ&_9N=w1`O|wa}~fK943R;-z4MA-_>EbM-$1aG}06rZwl@l z!g6%YQJEC57oa!mJ=kfxfMc*yDd4Y2?tbP{&;JViYMAKmeJwqDbHmVY3FHkcSA;qN z#B>XD?VmTZmZ3S+zzsA&$K8QQEhOih_W2z4Jj6N{AQR>0l?HR=PWo(vS$1eCQolc7 zAQ?3w%!72S?0KuUg&NWXQ*+sQZvk#?2CrycJw+O$W3_}8D2#R{)^_9d!f5j#%h!RO z$9LVE2Y2%%D-omad(h5D%NsIfn?R~Lss+$w5fPWjxUO@O$yc=!J?3g?pODsP5o8Bk zyXcARY;o;)yBJIoDCyBOho9%L@kYw-i|^mN;y-uoKO1!T>+^_-?v_OHL?UIDRS76B zR?Knn;>Eins%|8BomhgwV7E2Wd?m(Q6HTf1fP3_rO-pzLvS50&1i+=bwRaS3uRvDj zMA;{PR@LbN;H#TL0R|}u8rG>gOcG){D_;agKezbxTT1!rEnUdj>=c#)#dD$Z zdDx07-c}C@g0iQ3lULT_MwqWr%(A?h$Ll z9LVGuP%`PH9fCsBJ%|@RX&4%YGB=M^Rz(-)nc&ub|5Xb2SIC%11x*-YS$FYozpeYh zHL+(yBHrh){6ag=2uhle5=Fj@va%lN!HNhBputqnbvVMm2S>+f+5taFj#8afuH*aG z6J&YBO}HpK4NkHCE{$olxmM+ApQ~=JwnpXgRz-eB6cylSF48N-})Gf zwq-Gy;DlY+ONT-#;KG_KQ-Q+)aM>?yq~Jr_CWxp`^*%eVN(^7Ry81 zmr-t7IihC$nQJAeOdIb}*JbhNk=>N+ag!yBYWaz}@UD`Eom?H^p*{sC)Gd@DTJ++v zTt1?0ez>cR+PQwOzE1Uj%IRXyCGGrhd@R6-D{t#P(a`rKZp|y8 zVkDw_@HitXr~Y?$UzTWbK~l&{zjbdlfZZfIuPNuR4_51M~`FDpNTliq}GY z9B{3Dz!BGF8MUs1%F{ea(?V4Ab|^8O45)2C=X_9nQnd}MNI-#dF8Sd6&SGNOJW2=E zw1moA^`Ic+z(o?1LZB;Q6Z5Af8_mAH{hoeH&ZJ__d0FvK!Wa}PYkKRUQd_fa%aRTd z5fF3Pn`3#+XOKCtc_0GO1Vj%`B`H4;U{*x3DYr6slx1XjmWUuMiAVbtS?b}mTxy$= zd)l?ftKP&SKZS|^oz=)+@y1>MLpwm>gWcoB9AH^R~@0k{oLOeg))5b zh;VmTt;8e3U`-_A^TnYVbT^jf!U%JCxK)&LN|=C_?&6oV@s;lJOo>BvE89$WhqaL* z*S)pXb-pD-<9V4;=Knz+J|#SMQ7gDpE|WO2GoIdpxNV7*)mtcwE02eI@AoCn5s6Nu?Cl+jqNuDbQX!JD8)e@H*~Tt9 zXc11bWlMy{GKHZr*3{8r-_6)+Vr*l{PQ(5DdXDEj_fgMz?!B-3dOfdupZ@Ujhsunf z@Av(AZy&r;F?5?n$Hx9c0yf|4JAhv#9j=mF|GV(?&q2QC$@DIPO>1BJ*QfWEV}Jm} zM(;VU|L&Y1!X*{(OI6RU&c@?h-yD*VH_nK?p=@`qgyP-r0&Ot>+J=r{-v!zPcqK+A ze%rn~uIPxU*mG7lf>l0?&7NHF8oBpJ+A}(aGGg+x@D2`uR*&QQp-fJi|D zxqz9XNfbz94D@gR^g7(uKW-r+?1Xj0l1}~ri%z!QWdx5ed7M5S2(s~=V>Zr2oSukWDRo;LfsPxPI75BFkiLY%$XZ$8#QEabs?W4U;+$I~hkx`ec$R901O{mlGi zq)`Qbyrx$Swuh>=)Ah+kp6)3lqO(2-TlRBf^3rg1j8NuyWT$G_0^AM(-Su5%D#SV7gY#`HWf)3f z-XTLj0>13wcwM3CYpK$PS@0B&!RDlb*<)bl6El=Xh}RuJin(ZSn9e&;UDdA8~VjYnHA`x3ZSjeZoIAd{-P{7f|r&fPrs_=?Y5|A97zY z1{Z=k_z~d3g3CnY7r4)?X^Fb6(?(x~g`ih=F+C*PO_*mh~!Ct?mZk7T#rmnkar)0bDK;|3pOkz`}y~jE7Av7O{OJwI2H*Ze&HFX)N zApmlahCz-5dmpkPV=(5?=*OT6^K{YRTjRmtycKf@Sxkm7y%ieL>Q*Mn4@UEr8Q!Zp zOWysxsvR)tqYubcfJuic`kW`%>V0(WKDAg=ydspBj=^JoPu z`R5**egX$0&-wPu%Tdps5bcy4TBNJ>-doFXptB$2fDAhB8Q`7zCZlFY+`|q;v9v-kNn6zB?BG zi}t0ZbzVnK4vL#AkWu8i|cnjYWSzcEu`{LmAZ=hwYIF9|CPQ@*r@DPymcvIR{ zzM?c?dBxFc=GLVP3$F#w&|1q^ywO}nQD=c>Zz6_V&}la{aZ8s&z7ojEN{E)*g6?pX(AF^6O-i;)OrclOC4rFUu$0Q1 zMqK3T-ZQN-P${{{EE)|oWM85GT=y>CU*O}%i0=M3zmlJx_l@T<7YjuufIj|h!9E8D zgS?D1Vh?u;`G3)GmrMhT!>RFf#!)gERI0YDTMlCeF4<^CYCFFyfpZ!f zJF2mYZaY%W423M&Bp(_7>nQUF$+aJSBdKYBPwZ}yh<+9aV*d}6=Vl=zZrP$XgvWzT z{g>M3UR6GNYlPwm0_dWz# z(sk+bWeex3CFm$?M|sntlRCg-BYGq_B(T?c>OsHvdR3w*la@pQ`L=5xfg91av1iKL z{4d>}r~%m5=GUiWyBC`z=Q-MDXjsfI2rISuXJ4|*m)=<1>izLHts9Lm9=_Oaa#DUb z0M=5j~sHm+x0_G1d!Ea)u5Gc6W# zQ}n<1~^#RL8Jo0M8o&yA&zkkncQ+tHc{}) zgbbpeSm^YCQ23lA$-R4CGk|3BL69zh5XDp6e-OpqJY#=G6hngPlZC}))-T7HI!kQ3 z!QKTzTqJnxJ+i{ii)qS+LWx5+)O_gA6$XO81ka|XKGG4eamD-bJE|T_(@As7_T2cN zEiJ!t!Xx1|K4HW^e#)z#RhWRGgrS@1q6YaS(EvCR$b*eHF;&4PU9{GGa=CurWqJhX zw;0M-MsSS|d=lV-vr1VBRd@TgMVJMz&$hKkno^$x_nTQ>q4+c3BQn9N1^6ey*eUuh z4B6(|B0vB9_I_lf)KTN9qsiZ({?2Yi+<(@Q9$#^=$TG8*mX`giTU!ckTXC6pbw#>? zLc$X+nzj(t{_~UqDEXh^WdY6oL)5rRLVe@4A`I}Yr|@djh$ptM#YkWMWL&*K#83J% z^yR3y!NU6$}q2*AN~>c{-x<(o!yQw8*X_zd8p!h1o?Vq)35W%5m~+; zzoEkYKJnPWnDuI=-)eI8k^VcHM#&!G=|^e-I{ytj|Hn@*;040dkG!w{xOU+B`c(lP zY@cq?KOE;{V*S$1JUia&$XF`JzOq4LHVBedvDa{{0&suJsie`BL7$Bg7+mTGYz15Zt05t|~9vxC$#>{Bk2e+T#;sf9E#+m1jS249`oP z5rQ+x2JVo8V2+U>dm78yCx#1;JWYS$!MQaLqe?K64ucGpDO*KVHCz+j=`bC14mJw^aM}iS+<&3pCtk)e07%uE;kFzL zIF9VwbB&`R@)Uh=p|VacKhW6EQ?RJFDuk25Yf_UxUT$IvzN>tMX3R*gdtMt~)bTQ? zmy|qJmo*&b_B}>?oDN(@y86OhpNzv*T+(JCh*CfMP*v@5%3T`qEOn7zw#O#X zAl`?mOOO}^Q0cwi0|TYcE;q6Mr;{8zo>c+@9x$$Iy*!r;NmOI%Y?9me4l7pmz>gG& zMchz2$pp5ZF;MJ;dk-+CA*|R4CAcMP+$E()HYR8FKV(>^H8Q8H(7->XlMK zRE_x&_=aO>E;Pc14+agXLzu3G%f!xW^tlyGC7!dNBEyR{&KaaC2*1s@X%0rlN(j>O zP|AEmpv~oVk7q`BBCKyZWp({avS>u^<<#J!Fo(<7q=bY~{qTupr^aK$M*G%49Z>k3 zQmg(dCNv%23lZ9ITrA^yuWuqfrwHvvKgd0}Z_l2Ntr~LnD-1_ni59tD^(#Ej6kAM) z{wrwr{wVcPiX(3vE|f6|i5p|PPTD84`RglKo=OGdWhX4xF}j3;~mOt6{(|d$oEVerCx}9Pse5D zy4E)@5j-X(dC$Z2qmDkj6!vt{`}3zEG*@zQ?!pvru@cnkP>Q2P8r!yyJ>4eC_cuH@ zZ6jxzn;hyq{7M*Vq;9S?B-o-_JlEci>zOkj49LPQwc{tv2!Wn5GsYsvSpE~$80YZe z6FFS^sJd>FqJ`JWeN|g%DekM>5J1hu76cnuSld$yYvqR_#rU)mc5zCT@(XWs!W;$% zA-9K##&4l9q}w%qs%LEFM$B+9;GMK*8|OESk2u6 zqQu%e(k?VJDMAR9UoVfTy)Wgk4o@?LG73lVj#MV{%^x{g(lZmQ5joBPGq^8kcwsag zv8H5zHRWni&!WbasAmSg^JYMq*K+>UiS#P3gw%nQs`x;Vvy=! zp1EaAKi|?#;<$tb;@m1VdD^7khC@vk?sAYU|Hd;AsU}iMTHpR5ax~$%AT!sST}-wa zmmeuDEA5Qbmb`fzZ&J+{-AcE&F{)A%~7G$I@K?!cHfCV3@o61!HT@u7P-O zw3xInTET{FQ?BDgW)ROK1~&HB#``^cxLaj&yTk5U!Ly#bbId}1Y$D&o72Mol<8NrW z!NylS74jdJ?)Pw`ZJghrdgo=?&x&9EB8$J}@;#E0>yJ#+w*WtKfa+@i=pQk@qO(>E z`^)2&Pyk?LKmEyvSMV+elGv+jKXq9A8_ECw@Tmam{1dm&|2ZL`Z+sW5jiF+|>c2C?u-Nqi z6yI&zNqJn_QRYkH0E;>z09Yj(A2DBq%gSceaAWRmAW?vyr0(w=ZdEi0V(ZVjPF>W= zGvBy0SxtZ>fva6}dK92&xt#C}qHjy)b)82_ex#3g&e_yW_Rjams7r(8H47BWC^6l( zUV-Ln1lwOyWE8{-MSjc5RILi8FOLg(!pwQGH%)ytsJywvlFR+v~Zkuk@5Yj5nk(&V)f_9NLj` zAPZgk@DQX?5PVNA#PpvwU_nL`(!~K-w`m5m$7Ub*`XqV8Zn9dk;rxk}(O&n~2=SwEN&7&(i8bOjk@0^HbKYYZwFvBKC}rp_O*W`~$m>Cq zMCPH<#9maiEY<2g?`lz;OXLKWh~xpvO^-OE5lEwMvLy*F40Gr-bNM46?_mT-|mA${#sG(MqulI+QR>A>h)FgRuZK9h*+2|-@um}5^%u^Wj3 zE0B12AJ{SA`FBc&txSC4x61mXFRdzEvg1PXLIvRQA<;ai2Ck_a3-)d8# zVZtS)K=?ebM)p;UNSuD*=m|_^_OI#N7xZr0urwXBZJB6ms1=m@F9-xp+?sk1?|)Y9 zvw||A0OH>$0(WF~?yLkK!8Neqki1R}EI1_Lq8TX*&_k0ahPOBE^&`DO=`D>T^Yj4B z8$s`|J)Mn&O_x>gO-y#VgnQG^JT!z<9cFPlu8law$SpqUlZ5WYnpk;{J;(J0n`DBzXIhgQ9g!qf6(zYz0C`Ffe#IdKr-t&DD3BJ{+n_2Y)Tt1QovxqVN z*oMF3;EyO8t~Cn2=t7{D9|mGsKHJJngR)mw6uAF3|IP`d0GO;!crU}5vFqzCCASmXgV>Wl3bnTF z_A&n?_fnBsu4A_aJUm*CN|~Y&>vv^d}QrL$2QiMAA#!IAH*fZNn z?%N@|;CZgDYNZB5DKp#751fQ!jSaQDtubC*FT%Tskj-oCj-8~2TXn(?Nj|NO#xTCq z8cA5i<|*a7(I*vWy4xB!IARXT)_WU`27P(Da_w&0lWlw0h8Rh$4taYKQ$9xZ;`DO; z)2W4T6hHw*UNAmy*L6aa$)7y{)IZ`4y`9hf&>kP^aW;ezRw3LC9Sz#Xt@|jYa|)kC zZ?s;QA#YK z)pIa7h|SsYMM@$zG~(%)a-n?+KM*H_^re06cg`}>c`TPI3FXnD2_gP_x)U0(jbN2n77QmXV&dNAL{&OCkM9o}NvV!tD&ecxL8O~mOw>mCmt-&Grq z2s`TmS8+{|ayykRWeL!Xq6gXagYmqN&hag_|EhqG78$yS^e1(1KdcfKx>7A<8`YRk zV|RyrYsG%`D~xe!IwO?t*x;=*7SnQJ^L;DQqmZ%RF+VPr*7gv@p>x;`RS1*-&7jtC z=VNJM&`e~yI}UrKSv;N6IYt;C1ZU1lHq($<>D!dqRwC3?caw_BDVgDMiNbCnYx#$O zgV5!2cE754vhvOw6_eX03^mS@9X$s56jY+#H8R=wrfa6Olr?-v))*&<5?W0b=|w*h zg=<@DxpvUpA!qcv#@M&rGt4@3M$ zuJ#$>Yil^6bxiw90U})RzkU1^2qI{vdb#E*?ISsbdy0QGg|{xeJ-4}AgXFGj2scmn z^Sj#)jRw^Y=>}$yq~DsEz8heMe<)cG3YtQ6HLDi|jti|}Peq(0 zSnO7DDQ>P;GjjD{w zH}lENr;dU&FbEU?00aa80D$0M0RRB{U*7-#kbjS|qAG&4l5%47zyJ#WrfA#1$1Ctx zf&Z_d{GW=lf^w2#qRJ|CvSJUi(^E3iv~=^Z(zH}F)3Z%V3`@+rNB7gTVU{Bb~90p|f+0(v;nz01EG7yDMX9@S~__vVgv%rS$+?IH+oZ03D5zYrv|^ zC1J)SruYHmCki$jLBlTaE5&dFG9-kq3!^i>^UQL`%gn6)jz54$WDmeYdsBE9;PqZ_ zoGd=P4+|(-u4U1dbAVQrFWoNgNd;0nrghPFbQrJctO>nwDdI`Q^i0XJDUYm|T|RWc zZ3^Qgo_Qk$%Fvjj-G}1NB#ZJqIkh;kX%V{THPqOyiq)d)0+(r9o(qKlSp*hmK#iIY zA^)Vr$-Hz<#SF=0@tL@;dCQsm`V9s1vYNq}K1B)!XSK?=I1)tX+bUV52$YQu*0%fnWEukW>mxkz+%3-S!oguE8u#MGzST8_Dy^#U?fA@S#K$S@9msUiX!gd_ow>08w5)nX{-KxqMOo7d?k2&?Vf z&diGDtZr(0cwPe9z9FAUSD9KC)7(n^lMWuayCfxzy8EZsns%OEblHFSzP=cL6}?J| z0U$H!4S_TVjj<`6dy^2j`V`)mC;cB%* z8{>_%E1^FH!*{>4a7*C1v>~1*@TMcLK{7nEQ!_igZC}ikJ$*<$yHy>7)oy79A~#xE zWavoJOIOC$5b6*q*F_qN1>2#MY)AXVyr$6x4b=$x^*aqF*L?vmj>Mgv+|ITnw_BoW zO?jwHvNy^prH{9$rrik1#fhyU^MpFqF2fYEt(;4`Q&XWOGDH8k6M=%@fics4ajI;st# zCU^r1CK&|jzUhRMv;+W~6N;u<;#DI6cCw-otsc@IsN3MoSD^O`eNflIoR~l4*&-%RBYk@gb^|-JXs&~KuSEmMxB}xSb z@K76cXD=Y|=I&SNC2E+>Zg?R6E%DGCH5J1nU!A|@eX9oS(WPaMm==k2s_ueCqdZw| z&hqHp)47`c{BgwgvY2{xz%OIkY1xDwkw!<0veB#yF4ZKJyabhyyVS`gZepcFIk%e2 zTcrmt2@-8`7i-@5Nz>oQWFuMC_KlroCl(PLSodswHqJ3fn<;gxg9=}~3x_L3P`9Sn zChIf}8vCHvTriz~T2~FamRi?rh?>3bX1j}%bLH+uFX+p&+^aXbOK7clZxdU~6Uxgy z8R=obwO4dL%pmVo*Ktf=lH6hnlz_5k3cG;m8lgaPp~?eD!Yn2kf)tU6PF{kLyn|oI@eQ`F z3IF7~Blqg8-uwUuWZScRKn%c2_}dXB6Dx_&xR*n9M9LXasJhtZdr$vBY!rP{c@=)& z#!?L$2UrkvClwQO>U*fSMs67oSj2mxiJ$t;E|>q%Kh_GzzWWO&3;ufU%2z%ucBU8H z3WIwr$n)cfCXR&>tyB7BcSInK>=ByZA%;cVEJhcg<#6N{aZC4>K41XF>ZgjG`z_u& zGY?;Ad?-sgiOnI`oppF1o1Gurqbi*;#x2>+SSV6|1^G@ooVy@fg?wyf@0Y!UZ4!}nGuLeC^l)6pwkh|oRY`s1Pm$>zZ3u-83T|9 zGaKJIV3_x+u1>cRibsaJpJqhcm%?0-L;2 zitBrdRxNmb0OO2J%Y&Ym(6*`_P3&&5Bw157{o7LFguvxC$4&zTy#U=W*l&(Q2MNO} zfaUwYm{XtILD$3864IA_nn34oVa_g^FRuHL5wdUd)+W-p-iWCKe8m_cMHk+=? zeKX)M?Dt(|{r5t7IenkAXo%&EXIb-i^w+0CX0D=xApC=|Xy(`xy+QG^UyFe z+#J6h_&T5i#sV)hj3D4WN%z;2+jJcZxcI3*CHXGmOF3^)JD5j&wfX)e?-|V0GPuA+ zQFot%aEqGNJJHn$!_}#PaAvQ^{3-Ye7b}rWwrUmX53(|~i0v{}G_sI9uDch_brX&6 zWl5Ndj-AYg(W9CGfQf<6!YmY>Ey)+uYd_JNXH=>|`OH-CDCmcH(0%iD_aLlNHKH z7bcW-^5+QV$jK?R*)wZ>r9t}loM@XN&M-Pw=F#xn(;u3!(3SXXY^@=aoj70;_=QE9 zGghsG3ekq#N||u{4We_25U=y#T*S{4I{++Ku)> zQ!DZW;pVcn>b;&g2;YE#+V`v*Bl&Y-i@X6D*OpNA{G@JAXho&aOk(_j^weW{#3X5Y z%$q_wpb07EYPdmyH(1^09i$ca{O<}7) zRWncXdSPgBE%BM#by!E>tdnc$8RwUJg1*x($6$}ae$e9Knj8gvVZe#bLi!<+&BkFj zg@nOpDneyc+hU9P-;jmOSMN|*H#>^Ez#?;%C3hg_65leSUm;iz)UkW)jX#p)e&S&M z1|a?wDzV5NVnlhRBCd_;F87wp>6c<&nkgvC+!@KGiIqWY4l}=&1w7|r6{oBN8xyzh zG$b#2=RJp_iq6)#t5%yLkKx(0@D=C3w+oiXtSuaQ%I1WIb-eiE$d~!)b@|4XLy!CZ z9p=t=%3ad@Ep+<9003D2KZ5VyP~_n$=;~r&YUg5UZ0KVD&tR1DHy9x)qWtKJp#Kq# zP*8p#W(8JJ_*h_3W}FlvRam?<4Z+-H77^$Lvi+#vmhL9J zJ<1SV45xi;SrO2f=-OB(7#iNA5)x1uNC-yNxUw|!00vcW2PufRm>e~toH;M0Q85MQLWd?3O{i8H+5VkR@l9Dg-ma ze2fZ%>G(u5(k9EHj2L6!;(KZ8%8|*-1V|B#EagbF(rc+5iL_5;Eu)L4Z-V;0HfK4d z*{utLse_rvHZeQ>V5H=f78M3Ntg1BPxFCVD{HbNA6?9*^YIq;B-DJd{Ca2L#)qWP? zvX^NhFmX?CTWw&Ns}lgs;r3i+Bq@y}Ul+U%pzOS0Fcv9~aB(0!>GT0)NO?p=25LjN z2bh>6RhgqD7bQj#k-KOm@JLgMa6>%-ok1WpOe)FS^XOU{c?d5shG(lIn3GiVBxmg`u%-j=)^v&pX1JecJics3&jvPI)mDut52? z3jEA)DM%}BYbxxKrizVYwq?(P&19EXlwD9^-6J+4!}9{ywR9Gk42jjAURAF&EO|~N z)?s>$Da@ikI4|^z0e{r`J8zIs>SpM~Vn^{3fArRu;?+43>lD+^XtUcY1HidJwnR6+ z!;oG2=B6Z_=M%*{z-RaHc(n|1RTKQdNjjV!Pn9lFt^4w|AeN06*j}ZyhqZ^!-=cyGP_ShV1rGxkx8t zB;8`h!S{LD%ot``700d0@Grql(DTt4Awgmi+Yr0@#jbe=2#UkK%rv=OLqF)9D7D1j z!~McAwMYkeaL$~kI~90)5vBhBzWYc3Cj1WI0RS`z000R8-@ET0dA~*r(gSiCJmQMN&4%1D zyVNf0?}sBH8zNbBLn>~(W{d3%@kL_eQ6jEcR{l>C|JK z(R-fA!z|TTRG40|zv}7E@PqCAXP3n`;%|SCQ|ZS%ym$I{`}t3KPL&^l5`3>yah4*6 zifO#{VNz3)?ZL$be;NEaAk9b#{tV?V7 zP|wf5YA*1;s<)9A4~l3BHzG&HH`1xNr#%){4xZ!jq%o=7nN*wMuXlFV{HaiQLJ`5G zBhDi#D(m`Q1pLh@Tq+L;OwuC52RdW7b8}~60WCOK5iYMUad9}7aWBuILb({5=z~YF zt?*Jr5NG+WadM{mDL>GyiByCuR)hd zA=HM?J6l1Xv0Dl+LW@w$OTcEoOda^nFCw*Sy^I@$sSuneMl{4ys)|RY#9&NxW4S)9 zq|%83IpslTLoz~&vTo!Ga@?rj_kw{|k{nv+w&Ku?fyk4Ki4I?);M|5Axm)t+BaE)D zm(`AQ#k^DWrjbuXoJf2{Aj^KT zFb1zMSqxq|vceV+Mf-)$oPflsO$@*A0n0Z!R{&(xh8s}=;t(lIy zv$S8x>m;vQNHuRzoaOo?eiWFe{0;$s`Bc+Osz~}Van${u;g(su`3lJ^TEfo~nERfP z)?aFzpDgnLYiERsKPu|0tq4l2wT)Atr6Qb%m-AUn6HnCue*yWICp7TjW$@sO zm5rm4aTcPQ(rfi7a`xP7cKCFrJD}*&_~xgLyr^-bmsL}y;A5P|al8J3WUoBSjqu%v zxC;mK!g(7r6RRJ852Z~feoC&sD3(6}^5-uLK8o)9{8L_%%rItZK9C){UxB|;G>JbP zsRRtS4-3B*5c+K2kvmgZK8472%l>3cntWUOVHxB|{Ay~aOg5RN;{PJgeVD*H%ac+y!h#wi%o2bF2Ca8IyMyH{>4#{E_8u^@+l-+n=V}Sq?$O z{091@v%Bd*3pk0^2UtiF9Z+(a@wy6 zUdw8J*ze$K#=$48IBi1U%;hmhO>lu!uU;+RS}p&6@rQila7WftH->*A4=5W|Fmtze z)7E}jh@cbmr9iup^i%*(uF%LG&!+Fyl@LFA-}Ca#bxRfDJAiR2dt6644TaYw1Ma79 zt8&DYj31j^5WPNf5P&{)J?WlCe@<3u^78wnd(Ja4^a>{^Tw}W>|Cjt^If|7l^l)^Q zbz|7~CF(k_9~n|h;ysZ+jHzkXf(*O*@5m zLzUmbHp=x!Q|!9NVXyipZ3)^GuIG$k;D)EK!a5=8MFLI_lpf`HPKl=-Ww%z8H_0$j ztJ||IfFG1lE9nmQ0+jPQy zCBdKkjArH@K7jVcMNz);Q(Q^R{d5G?-kk;Uu_IXSyWB)~KGIizZL(^&qF;|1PI7!E zTP`%l)gpX|OFn&)M%txpQ2F!hdA~hX1Cm5)IrdljqzRg!f{mN%G~H1&oqe`5eJCIF zHdD7O;AX-{XEV(a`gBFJ9ews#CVS2y!&>Cm_dm3C8*n3MA*e67(WC?uP@8TXuMroq z{#w$%z@CBIkRM7?}Xib+>hRjy?%G!fiw8! z8(gB+8J~KOU}yO7UGm&1g_MDJ$IXS!`+*b*QW2x)9>K~Y*E&bYMnjl6h!{17_8d!%&9D`a7r&LKZjC<&XOvTRaKJ1 zUY@hl5^R&kZl3lU3njk`3dPzxj$2foOL26r(9zsVF3n_F#v)s5vv3@dgs|lP#eylq62{<-vczqP!RpVBTgI>@O6&sU>W|do17+#OzQ7o5A$ICH z?GqwqnK^n2%LR;$^oZM;)+>$X3s2n}2jZ7CdWIW0lnGK-b#EG01)P@aU`pg}th&J-TrU`tIpb5t((0eu|!u zQz+3ZiOQ^?RxxK4;zs=l8q!-n7X{@jSwK(iqNFiRColuEOg}!7cyZi`iBX4g1pNBj zAPzL?P^Ljhn;1$r8?bc=#n|Ed7wB&oHcw()&*k#SS#h}jO?ZB246EGItsz*;^&tzp zu^YJ0=lwsi`eP_pU8}6JA7MS;9pfD;DsSsLo~ogzMNP70@@;Fm8f0^;>$Z>~}GWRw!W5J3tNX*^2+1f3hz{~rIzJo z6W%J(H!g-eI_J1>0juX$X4Cl6i+3wbc~k146UIX&G22}WE>0ga#WLsn9tY(&29zBvH1$`iWtTe zG2jYl@P!P)eb<5DsR72BdI7-zP&cZNI{7q3e@?N8IKc4DE#UVr->|-ryuJXk^u^>4 z$3wE~=q390;XuOQP~TNoDR?#|NSPJ%sTMInA6*rJ%go|=YjGe!B>z6u$IhgQSwoV* zjy3F2#I>uK{42{&IqP59)Y(1*Z>>#W8rCf4_eVsH)`v!P#^;BgzKDR`ARGEZzkNX+ zJUQu=*-ol=Xqqt5=`=pA@BIn@6a9G8C{c&`i^(i+BxQO9?YZ3iu%$$da&Kb?2kCCo zo7t$UpSFWqmydXf@l3bVJ=%K?SSw)|?srhJ-1ZdFu*5QhL$~-IQS!K1s@XzAtv6*Y zl8@(5BlWYLt1yAWy?rMD&bwze8bC3-GfNH=p zynNFCdxyX?K&G(ZZ)afguQ2|r;XoV^=^(;Cku#qYn4Lus`UeKt6rAlFo_rU`|Rq z&G?~iWMBio<78of-2X(ZYHx~=U0Vz4btyXkctMKdc9UM!vYr~B-(>)(Hc|D zMzkN4!PBg%tZoh+=Gba!0++d193gbMk2&krfDgcbx0jI92cq?FFESVg0D$>F+bil} zY~$)|>1HZsX=5sAZ2WgPB5P=8X#TI+NQ(M~GqyVB53c6IdX=k>Wu@A0Svf5#?uHaF zsYn|koIi3$(%GZ2+G+7Fv^lHTb#5b8sAHSTnL^qWZLM<(1|9|QFw9pnRU{svj}_Al zL)b9>fN{QiA($8peNEJyy`(a{&uh-T4_kdZFIVsKKVM(?05}76EEz?#W za^fiZOAd14IJ4zLX-n7Lq0qlQ^lW8Cvz4UKkV9~P}>sq0?xD3vg+$4vLm~C(+ zM{-3Z#qnZ09bJ>}j?6ry^h+@PfaD7*jZxBEY4)UG&daWb??6)TP+|3#Z&?GL?1i+280CFsE|vIXQbm| zM}Pk!U`U5NsNbyKzkrul-DzwB{X?n3E6?TUHr{M&+R*2%yOiXdW-_2Yd6?38M9Vy^ z*lE%gA{wwoSR~vN0=no}tP2Ul5Gk5M(Xq`$nw#ndFk`tcpd5A=Idue`XZ!FS>Q zG^0w#>P4pPG+*NC9gLP4x2m=cKP}YuS!l^?sHSFftZy{4CoQrb_ z^20(NnG`wAhMI=eq)SsIE~&Gp9Ne0nD4%Xiu|0Fj1UFk?6avDqjdXz{O1nKao*46y zT8~iA%Exu=G#{x=KD;_C&M+Zx4+n`sHT>^>=-1YM;H<72k>$py1?F3#T1*ef9mLZw z5naLQr?n7K;2l+{_uIw*_1nsTn~I|kkCgrn;|G~##hM;9l7Jy$yJfmk+&}W@JeKcF zx@@Woiz8qdi|D%aH3XTx5*wDlbs?dC1_nrFpm^QbG@wM=i2?Zg;$VK!c^Dp8<}BTI zyRhAq@#%2pGV49*Y5_mV4+OICP|%I(dQ7x=6Ob}>EjnB_-_18*xrY?b%-yEDT(wrO z9RY2QT0`_OpGfMObKHV;QLVnrK%mc?$WAdIT`kJQT^n%GuzE7|9@k3ci5fYOh(287 zuIbg!GB3xLg$YN=n)^pHGB0jH+_iIiC=nUcD;G6LuJsjn2VI1cyZx=a?ShCsF==QK z;q~*m&}L<-cb+mDDXzvvrRsybcgQ;Vg21P(uLv5I+eGc7o7tc6`;OA9{soHFOz zT~2?>Ts}gprIX$wRBb4yE>ot<8+*Bv`qbSDv*VtRi|cyWS>)Fjs>fkNOH-+PX&4(~ z&)T8Zam2L6puQl?;5zg9h<}k4#|yH9czHw;1jw-pwBM*O2hUR6yvHATrI%^mvs9q_ z&ccT0>f#eDG<^WG^q@oVqlJrhxH)dcq2cty@l3~|5#UDdExyXUmLQ}f4#;6fI{f^t zDCsgIJ~0`af%YR%Ma5VQq-p21k`vaBu6WE?66+5=XUd%Ay%D$irN>5LhluRWt7 zov-=f>QbMk*G##&DTQyou$s7UqjjW@k6=!I@!k+S{pP8R(2=e@io;N8E`EOB;OGoI zw6Q+{X1_I{OO0HPpBz!X!@`5YQ2)t{+!?M_iH25X(d~-Zx~cXnS9z>u?+If|iNJbx zyFU2d1!ITX64D|lE0Z{dLRqL1Ajj=CCMfC4lD3&mYR_R_VZ>_7_~|<^o*%_&jevU+ zQ4|qzci=0}Jydw|LXLCrOl1_P6Xf@c0$ieK2^7@A9UbF{@V_0p%lqW|L?5k>bVM8|p5v&2g;~r>B8uo<4N+`B zH{J)h;SYiIVx@#jI&p-v3dwL5QNV1oxPr8J%ooezTnLW>i*3Isb49%5i!&ac_dEXv zvXmVUck^QHmyrF8>CGXijC_R-y(Qr{3Zt~EmW)-nC!tiH`wlw5D*W7Pip;T?&j%kX z6DkZX4&}iw>hE(boLyjOoupf6JpvBG8}jIh!!VhnD0>}KSMMo{1#uU6kiFcA04~|7 zVO8eI&x1`g4CZ<2cYUI(n#wz2MtVFHx47yE5eL~8bot~>EHbevSt}LLMQX?odD{Ux zJMnam{d)W4da{l7&y-JrgiU~qY3$~}_F#G7|MxT)e;G{U`In&?`j<5D->}cb{}{T(4DF0BOk-=1195KB-E*o@c?`>y#4=dMtYtSY=&L{!TAjFVcq0y@AH`vH! z$41+u!Ld&}F^COPgL(EE{0X7LY&%D7-(?!kjFF7=qw<;`V{nwWBq<)1QiGJgUc^Vz ztMUlq1bZqKn17|6x6iAHbWc~l1HcmAxr%$Puv!znW)!JiukwIrqQ00|H$Z)OmGG@= zv%A8*4cq}(?qn4rN6o`$Y))(MyXr8R<2S^J+v(wmFmtac!%VOfN?&(8Nr!T@kV`N; z*Q33V3t`^rN&aBiHet)18wy{*wi1=W!B%B-Q6}SCrUl$~Hl{@!95ydml@FK8P=u4s z4e*7gV2s=YxEvskw2Ju!2%{8h01rx-3`NCPc(O zH&J0VH5etNB2KY6k4R@2Wvl^Ck$MoR3=)|SEclT2ccJ!RI9Nuter7u9@;sWf-%um;GfI!=eEIQ2l2p_YWUd{|6EG ze{yO6;lMc>;2tPrsNdi@&1K6(1;|$xe8vLgiouj%QD%gYk`4p{Ktv9|j+!OF-P?@p z;}SV|oIK)iwlBs+`ROXkhd&NK zzo__r!B>tOXpBJMDcv!Mq54P+n4(@dijL^EpO1wdg~q+!DT3lB<>9AANSe!T1XgC=J^)IP0XEZ()_vpu!!3HQyJhwh?r`Ae%Yr~b% zO*NY9t9#qWa@GCPYOF9aron7thfWT`eujS4`t2uG6)~JRTI;f(ZuoRQwjZjp5Pg34 z)rp$)Kr?R+KdJ;IO;pM{$6|2y=k_siqvp%)2||cHTe|b5Ht8&A{wazGNca zX$Ol?H)E_R@SDi~4{d-|8nGFhZPW;Cts1;08TwUvLLv&_2$O6Vt=M)X;g%HUr$&06 zISZb(6)Q3%?;3r~*3~USIg=HcJhFtHhIV(siOwV&QkQe#J%H9&E21!C*d@ln3E@J* zVqRO^<)V^ky-R|%{(9`l-(JXq9J)1r$`uQ8a}$vr9E^nNiI*thK8=&UZ0dsFN_eSl z(q~lnD?EymWLsNa3|1{CRPW60>DSkY9YQ;$4o3W7Ms&@&lv9eH!tk~N&dhqX&>K@} zi1g~GqglxkZ5pEFkllJ)Ta1I^c&Bt6#r(QLQ02yHTaJB~- zCcE=5tmi`UA>@P=1LBfBiqk)HB4t8D?02;9eXj~kVPwv?m{5&!&TFYhu>3=_ zsGmYZ^mo*-j69-42y&Jj0cBLLEulNRZ9vXE)8~mt9C#;tZs;=#M=1*hebkS;7(aGf zcs7zH(I8Eui9UU4L--))yy`&d&$In&VA2?DAEss4LAPCLd>-$i?lpXvn!gu^JJ$(DoUlc6wE98VLZ*z`QGQov5l4Fm_h?V-;mHLYDVOwKz7>e4+%AzeO>P6v}ndPW| zM>m#6Tnp7K?0mbK=>gV}=@k*0Mr_PVAgGMu$j+pWxzq4MAa&jpCDU&-5eH27Iz>m^ zax1?*HhG%pJ((tkR(V(O(L%7v7L%!_X->IjS3H5kuXQT2!ow(;%FDE>16&3r){!ex zhf==oJ!}YU89C9@mfDq!P3S4yx$aGB?rbtVH?sHpg?J5C->!_FHM%Hl3#D4eplxzQ zRA+<@LD%LKSkTk2NyWCg7u=$%F#;SIL44~S_OGR}JqX}X+=bc@swpiClB`Zbz|f!4 z7Ysah7OkR8liXfI`}IIwtEoL}(URrGe;IM8%{>b1SsqXh)~w}P>yiFRaE>}rEnNkT z!HXZUtxUp1NmFm)Dm@-{FI^aRQqpSkz}ZSyKR%Y}YHNzBk)ZIp} zMtS=aMvkgWKm9&oTcU0?S|L~CDqA+sHpOxwnswF-fEG)cXCzUR?ps@tZa$=O)=L+5 zf%m58cq8g_o}3?Bhh+c!w4(7AjxwQ3>WnVi<{{38g7yFboo>q|+7qs<$8CPXUFAN< zG&}BHbbyQ5n|qqSr?U~GY{@GJ{(Jny{bMaOG{|IkUj7tj^9pa9|FB_<+KHLxSxR;@ zHpS$4V)PP+tx}22fWx(Ku9y+}Ap;VZqD0AZW4gCDTPCG=zgJmF{|x;(rvdM|2|9a}cex6xrMkERnkE;}jvU-kmzd%_J50$M`lIPCKf+^*zL=@LW`1SaEc%=m zQ+lT06Gw+wVwvQ9fZ~#qd430v2HndFsBa9WjD0P}K(rZYdAt^5WQIvb%D^Q|pkVE^ zte$&#~zmULFACGfS#g=2OLOnIf2Of-k!(BIHjs77nr!5Q1*I9 z1%?=~#Oss!rV~?-6Gm~BWJiA4mJ5TY&iPm_$)H1_rTltuU1F3I(qTQ^U$S>%$l z)Wx1}R?ij0idp@8w-p!Oz{&*W;v*IA;JFHA9%nUvVDy7Q8woheC#|8QuDZb-L_5@R zOqHwrh|mVL9b=+$nJxM`3eE{O$sCt$UK^2@L$R(r^-_+z?lOo+me-VW=Zw z-Bn>$4ovfWd%SPY`ab-u9{INc*k2h+yH%toDHIyqQ zO68=u`N}RIIs7lsn1D){)~%>ByF<>i@qFb<-axvu(Z+6t7v<^z&gm9McRB~BIaDn$ z#xSGT!rzgad8o>~kyj#h1?7g96tOcCJniQ+*#=b7wPio>|6a1Z?_(TS{)KrPe}(8j z!#&A=k(&Pj^F;r)CI=Z{LVu>uj!_W1q4b`N1}E(i%;BWjbEcnD=mv$FL$l?zS6bW!{$7j1GR5ocn94P2u{ z70tAAcpqtQo<@cXw~@i-@6B23;317|l~S>CB?hR5qJ%J3EFgyBdJd^fHZu7AzHF(BQ!tyAz^L0`X z23S4Fe{2X$W0$zu9gm%rg~A>ijaE#GlYlrF9$ds^QtaszE#4M(OLVP2O-;XdT(XIC zatwzF*)1c+t~c{L=fMG8Z=k5lv>U0;C{caN1NItnuSMp)6G3mbahu>E#sj&oy94KC zpH}8oEw{G@N3pvHhp{^-YaZeH;K+T_1AUv;IKD<=mv^&Ueegrb!yf`4VlRl$M?wsl zZyFol(2|_QM`e_2lYSABpKR{{NlxlDSYQNkS;J66aT#MSiTx~;tUmvs-b*CrR4w=f z8+0;*th6kfZ3|5!Icx3RV11sp=?`0Jy3Fs0N4GZQMN=8HmT6%x9@{Dza)k}UwL6JT zHRDh;%!XwXr6yuuy`4;Xsn0zlR$k%r%9abS1;_v?`HX_hI|+EibVnlyE@3aL5vhQq zlIG?tN^w@0(v9M*&L+{_+RQZw=o|&BRPGB>e5=ys7H`nc8nx)|-g;s7mRc7hg{GJC zAe^vCIJhajmm7C6g! zL&!WAQ~5d_5)00?w_*|*H>3$loHrvFbitw#WvLB!JASO?#5Ig5$Ys10n>e4|3d;tS zELJ0|R4n3Az(Fl3-r^QiV_C;)lQ1_CW{5bKS15U|E9?ZgLec@%kXr84>5jV2a5v=w z?pB1GPdxD$IQL4)G||B_lI+A=08MUFFR4MxfGOu07vfIm+j=z9tp~5i_6jb`tR>qV z$#`=BQ*jpCjm$F0+F)L%xRlnS%#&gro6PiRfu^l!EVan|r3y}AHJQOORGx4~ z&<)3=K-tx518DZyp%|!EqpU!+X3Et7n2AaC5(AtrkW>_57i}$eqs$rupubg0a1+WO zGHZKLN2L0D;ab%{_S1Plm|hx8R?O14*w*f&2&bB050n!R2by zw!@XOQx$SqZ5I<(Qu$V6g>o#A!JVwErWv#(Pjx=KeS0@hxr4?13zj#oWwPS(7Ro|v z>Mp@Kmxo79q|}!5qtX2-O@U&&@6s~!I&)1WQIl?lTnh6UdKT_1R640S4~f=_xoN3- zI+O)$R@RjV$F=>Ti7BlnG1-cFKCC(t|Qjm{SalS~V-tX#+2ekRhwmN zZr`8{QF6y~Z!D|{=1*2D-JUa<(1Z=;!Ei!KiRNH?o{p5o3crFF=_pX9O-YyJchr$~ zRC`+G+8kx~fD2k*ZIiiIGR<8r&M@3H?%JVOfE>)})7ScOd&?OjgAGT@WVNSCZ8N(p zuQG~76GE3%(%h1*vUXg$vH{ua0b`sQ4f0*y=u~lgyb^!#CcPJa2mkSEHGLsnO^kb$ zru5_l#nu=Y{rSMWiYx?nO{8I!gH+?wEj~UM?IrG}E|bRIBUM>UlY<`T1EHpRr36vv zBi&dG8oxS|J$!zoaq{+JpJy+O^W(nt*|#g32bd&K^w-t>!Vu9N!k9eA8r!Xc{utY> zg9aZ(D2E0gL#W0MdjwES-7~Wa8iubPrd?8-$C4BP?*wok&O8+ykOx{P=Izx+G~hM8 z*9?BYz!T8~dzcZr#ux8kS7u7r@A#DogBH8km8Ry4slyie^n|GrTbO|cLhpqgMdsjX zJ_LdmM#I&4LqqsOUIXK8gW;V0B(7^$y#h3h>J0k^WJfAMeYek%Y-Dcb_+0zPJez!GM zAmJ1u;*rK=FNM0Nf}Y!!P9c4)HIkMnq^b;JFd!S3?_Qi2G#LIQ)TF|iHl~WKK6JmK zbv7rPE6VkYr_%_BT}CK8h=?%pk@3cz(UrZ{@h40%XgThP*-Oeo`T0eq9 zA8BnWZKzCy5e&&_GEsU4*;_k}(8l_&al5K-V*BFM=O~;MgRkYsOs%9eOY6s6AtE*<7GQAR2ulC3RAJrG_P1iQK5Z~&B z&f8X<>yJV6)oDGIlS$Y*D^Rj(cszTy5c81a5IwBr`BtnC6_e`ArI8CaTX_%rx7;cn zR-0?J_LFg*?(#n~G8cXut(1nVF0Oka$A$1FGcERU<^ggx;p@CZc?3UB41RY+wLS`LWFNSs~YP zuw1@DNN3lTd|jDL7gjBsd9}wIw}4xT2+8dBQzI00m<@?c2L%>}QLfK5%r!a-iII`p zX@`VEUH)uj^$;7jVUYdADQ2k*!1O3WdfgF?OMtUXNpQ1}QINamBTKDuv19^{$`8A1 zeq%q*O0mi@(%sZU>Xdb0Ru96CFqk9-L3pzLVsMQ`Xpa~N6CR{9Rm2)A|CI21L(%GW zh&)Y$BNHa=FD+=mBw3{qTgw)j0b!Eahs!rZnpu)z!!E$*eXE~##yaXz`KE5(nQM`s zD!$vW9XH)iMxu9R>r$VlLk9oIR%HxpUiW=BK@4U)|1WNQ=mz9a z^!KkO=>GaJ!GBXm{KJj^;kh-MkUlEQ%lza`-G&}C5y1>La1sR6hT=d*NeCnuK%_LV zOXt$}iP6(YJKc9j-Fxq~*ItVUqljQ8?oaysB-EYtFQp9oxZ|5m0^Hq(qV!S+hq#g( z?|i*H2MIr^Kxgz+3vIljQ*Feejy6S4v~jKEPTF~Qhq!(ms5>NGtRgO5vfPPc4Z^AM zTj!`5xEreIN)vaNxa|q6qWdg>+T`Ol0Uz)ckXBXEGvPNEL3R8hB3=C5`@=SYgAju1 z!)UBr{2~=~xa{b8>x2@C7weRAEuatC)3pkRhT#pMPTpSbA|tan%U7NGMvzmF?c!V8 z=pEWxbdXbTAGtWTyI?Fml%lEr-^AE}w#l(<7OIw;ctw}imYax&vR4UYNJZK6P7ZOd zP87XfhnUHxCUHhM@b*NbTi#(-8|wcv%3BGNs#zRCVV(W?1Qj6^PPQa<{yaBwZ`+<`w|;rqUY_C z&AeyKwwf*q#OW-F()lir=T^<^wjK65Lif$puuU5+tk$;e_EJ;Lu+pH>=-8=PDhkBg z8cWt%@$Sc#C6F$Vd+0507;{OOyT7Hs%nKS88q-W!$f~9*WGBpHGgNp}=C*7!RiZ5s zn1L_DbKF@B8kwhDiLKRB@lsXVVLK|ph=w%_`#owlf@s@V(pa`GY$8h%;-#h@TsO|Y8V=n@*!Rog7<7Cid%apR|x zOjhHCyfbIt%+*PCveTEcuiDi%Wx;O;+K=W?OFUV%)%~6;gl?<0%)?snDDqIvkHF{ zyI02)+lI9ov42^hL>ZRrh*HhjF9B$A@=H94iaBESBF=eC_KT$8A@uB^6$~o?3Wm5t1OIaqF^~><2?4e3c&)@wKn9bD? zoeCs;H>b8DL^F&>Xw-xjZEUFFTv>JD^O#1E#)CMBaG4DX9bD(Wtc8Rzq}9soQ8`jf zeSnHOL}<+WVSKp4kkq&?SbETjq6yr@4%SAqOG=9E(3YeLG9dtV+8vmzq+6PFPk{L; z(&d++iu=^F%b+ea$i2UeTC{R*0Isk;vFK!no<;L+(`y`3&H-~VTdKROkdyowo1iqR zbVW(3`+(PQ2>TKY>N!jGmGo7oeoB8O|P_!Ic@ zZ^;3dnuXo;WJ?S+)%P>{Hcg!Jz#2SI(s&dY4QAy_vRlmOh)QHvs_7c&zkJCmJGVvV zX;Mtb>QE+xp`KyciG$Cn*0?AK%-a|=o!+7x&&yzHQOS>8=B*R=niSnta^Pxp1`=md z#;$pS$4WCT?mbiCYU?FcHGZ#)kHVJTTBt^%XE(Q};aaO=Zik0UgLcc0I(tUpt(>|& zcxB_|fxCF7>&~5eJ=Dpn&5Aj{A^cV^^}(7w#p;HG&Q)EaN~~EqrE1qKrMAc&WXIE;>@<&)5;gD2?={Xf@Mvn@OJKw=8Mgn z!JUFMwD+s==JpjhroT&d{$kQAy%+d`a*XxDEVxy3`NHzmITrE`o!;5ClXNPb4t*8P zzAivdr{j_v!=9!^?T3y?gzmqDWX6mkzhIzJ-3S{T5bcCFMr&RPDryMcdwbBuZbsgN zGrp@^i?rcfN7v0NKGzDPGE#4yszxu=I_`MI%Z|10nFjU-UjQXXA?k8Pk|OE<(?ae) zE%vG#eZAlj*E7_3dx#Zz4kMLj>H^;}33UAankJiDy5ZvEhrjr`!9eMD8COp}U*hP+ zF}KIYx@pkccIgyxFm#LNw~G&`;o&5)2`5aogs`1~7cMZQ7zj!%L4E`2yzlQN6REX20&O<9 zKV6fyr)TScJPPzNTC2gL+0x#=u>(({{D7j)c-%tvqls3#Y?Z1m zV5WUE)zdJ{$p>yX;^P!UcXP?UD~YM;IRa#Rs5~l+*$&nO(;Ers`G=0D!twR(0GF@c zHl9E5DQI}Oz74n zfKP>&$q0($T4y$6w(p=ERAFh+>n%iaeRA%!T%<^+pg?M)@ucY<&59$x9M#n+V&>}=nO9wCV{O~lg&v#+jcUj(tQ z`0u1YH)-`U$15a{pBkGyPL0THv1P|4e@pf@3IBZS4dVJPo#H>pWq%Lr0YS-SeWash z8R7=jb28KPMI|_lo#GEO|5B?N_e``H*23{~a!AmUJ+fb4HX-%QI@lSEUxKlGV7z7Q zSKw@-TR>@1RL%w{x}dW#k1NgW+q4yt2Xf1J62Bx*O^WG8OJ|FqI4&@d3_o8Id@*)4 zYrk=>@!wv~mh7YWv*bZhxqSmFh2Xq)o=m;%n$I?GSz49l1$xRpPu_^N(vZ>*>Z<04 z2+rP70oM=NDysd!@fQdM2OcyT?3T^Eb@lIC-UG=Bw{BjQ&P`KCv$AcJ;?`vdZ4){d z&gkoUK{$!$$K`3*O-jyM1~p-7T*qb)Ys>Myt^;#1&a%O@x8A+E>! zY8=eD`ZG)LVagDLBeHg>=atOG?Kr%h4B%E6m@J^C+U|y)XX@f z8oyJDW|9g=<#f<{JRr{y#~euMnv)`7j=%cHWLc}ngjq~7k**6%4u>Px&W%4D94(r* z+akunK}O0DC2A%Xo9jyF;DobX?!1I(7%}@7F>i%&nk*LMO)bMGg2N+1iqtg+r(70q zF5{Msgsm5GS7DT`kBsjMvOrkx&|EU!{{~gL4d2MWrAT=KBQ-^zQCUq{5PD1orxlIL zq;CvlWx#f1NWvh`hg011I%?T_s!e38l*lWVt|~z-PO4~~1g)SrJ|>*tXh=QfXT)%( z+ex+inPvD&O4Ur;JGz>$sUOnWdpSLcm1X%aQDw4{dB!cnj`^muI$CJ2%p&-kULVCE z>$eMR36kN$wCPR+OFDM3-U(VOrp9k3)lI&YVFqd;Kpz~K)@Fa&FRw}L(SoD z9B4a+hQzZT-BnVltst&=kq6Y(f^S4hIGNKYBgMxGJ^;2yrO}P3;r)(-I-CZ)26Y6? z&rzHI_1GCvGkgy-t1E;r^3Le30|%$ebDRu2+gdLG)r=A~Qz`}~&L@aGJ{}vVs_GE* zVUjFnzHiXfKQbpv&bR&}l2bzIjAooB)=-XNcYmrGmBh(&iu@o!^hn0^#}m2yZZUK8 zufVm7Gq0y`Mj;9b>`c?&PZkU0j4>IL=UL&-Lp3j&47B5pAW4JceG{!XCA)kT<%2nqCxj<)uy6XR_uws~>_MEKPOpAQ!H zkn>FKh)<9DwwS*|Y(q?$^N!6(51O0 z^JM~Ax{AI1Oj$fs-S5d4T7Z_i1?{%0SsIuQ&r8#(JA=2iLcTN+?>wOL532%&dMYkT z*T5xepC+V6zxhS@vNbMoi|i)=rpli@R9~P!39tWbSSb904ekv7D#quKbgFEMTb48P zuq(VJ+&L8aWU(_FCD$3^uD!YM%O^K(dvy~Wm2hUuh6bD|#(I39Xt>N1Y{ZqXL`Fg6 zKQ?T2htHN!(Bx;tV2bfTtIj7e)liN-29s1kew>v(D^@)#v;}C4-G=7x#;-dM4yRWm zyY`cS21ulzMK{PoaQ6xChEZ}o_#}X-o}<&0)$1#3we?+QeLt;aVCjeA)hn!}UaKt< zat1fHEx13y-rXNMvpUUmCVzocPmN~-Y4(YJvQ#db)4|%B!rBsgAe+*yor~}FrNH08 z3V!97S}D7d$zbSD{$z;@IYMxM6aHdypIuS*pr_U6;#Y!_?0i|&yU*@16l z*dcMqDQgfNBf}?quiu4e>H)yTVfsp#f+Du0@=Kc41QockXkCkvu>FBd6Q+@FL!(Yx z2`YuX#eMEiLEDhp+9uFqME_E^faV&~9qjBHJkIp~%$x^bN=N)K@kvSVEMdDuzA0sn z88CBG?`RX1@#hQNd`o^V{37)!w|nA)QfiYBE^m=yQKv-fQF+UCMcuEe1d4BH7$?>b zJl-r9@0^Ie=)guO1vOd=i$_4sz>y3x^R7n4ED!5oXL3@5**h(xr%Hv)_gILarO46q+MaDOF%ChaymKoI6JU5Pg;7#2n9-18|S1;AK+ zgsn6;k6-%!QD>D?cFy}8F;r@z8H9xN1jsOBw2vQONVqBVEbkiNUqgw~*!^##ht>w0 zUOykwH=$LwX2j&nLy=@{hr)2O&-wm-NyjW7n~Zs9UlH;P7iP3 zI}S(r0YFVYacnKH(+{*)Tbw)@;6>%=&Th=+Z6NHo_tR|JCI8TJiXv2N7ei7M^Q+RM z?9o`meH$5Yi;@9XaNR#jIK^&{N|DYNNbtdb)XW1Lv2k{E>;?F`#Pq|&_;gm~&~Zc9 zf+6ZE%{x4|{YdtE?a^gKyzr}dA>OxQv+pq|@IXL%WS0CiX!V zm$fCePA%lU{%pTKD7|5NJHeXg=I0jL@$tOF@K*MI$)f?om)D63K*M|r`gb9edD1~Y zc|w7N)Y%do7=0{RC|AziW7#am$)9jciRJ?IWl9PE{G3U+$%FcyKs_0Cgq`=K3@ttV z9g;M!3z~f_?P%y3-ph%vBMeS@p7P&Ea8M@97+%XEj*(1E6vHj==d zjsoviB>j^$_^OI_DEPvFkVo(BGRo%cJeD){6Uckei=~1}>sp299|IRjhXe)%?uP0I zF5+>?0#Ye}T^Y$u_rc4=lPcq4K^D(TZG-w30-YiEM=dcK+4#o*>lJ8&JLi+3UcpZk z!^?95S^C0ja^jwP`|{<+3cBVog$(mRdQmadS+Vh~z zS@|P}=|z3P6uS+&@QsMp0no9Od&27O&14zHXGAOEy zh~OKpymK5C%;LLb467@KgIiVwYbYd6wFxI{0-~MOGfTq$nBTB!{SrWmL9Hs}C&l&l#m?s*{tA?BHS4mVKHAVMqm63H<|c5n0~k)-kbg zXidai&9ZUy0~WFYYKT;oe~rytRk?)r8bptITsWj(@HLI;@=v5|XUnSls7$uaxFRL+ zRVMGuL3w}NbV1`^=Pw*0?>bm8+xfeY(1PikW*PB>>Tq(FR`91N0c2&>lL2sZo5=VD zQY{>7dh_TX98L2)n{2OV=T10~*YzX27i2Q7W86M4$?gZIXZaBq#sA*{PH8){|GUi;oM>e?ua7eF4WFuFYZSG| zze?srg|5Ti8Og{O zeFxuw9!U+zhyk?@w zjsA6(oKD=Ka;A>Ca)oPORxK+kxH#O@zhC!!XS4@=swnuMk>t+JmLmFiE^1aX3f<)D@`%K0FGK^gg1a1j>zi z2KhV>sjU7AX3F$SEqrXSC}fRx64GDoc%!u2Yag68Lw@w9v;xOONf@o)Lc|Uh3<21ctTYu-mFZuHk*+R{GjXHIGq3p)tFtQp%TYqD=j1&y)>@zxoxUJ!G@ zgI0XKmP6MNzw>nRxK$-Gbzs}dyfFzt>#5;f6oR27ql!%+{tr+(`(>%51|k`ML} zY4eE)Lxq|JMas(;JibNQds1bUB&r}ydMQXBY4x(^&fY_&LlQC)3hylc$~8&~|06-D z#T+%66rYbHX%^KuqJED_wuGB+=h`nWA!>1n0)3wZrBG3%`b^Ozv6__dNa@%V14|!D zQ?o$z5u0^8`giv%qE!BzZ!3j;BlDlJDk)h@9{nSQeEk!z9RGW) z${RSF3phEM*ce*>Xdp}585vj$|40=&S{S-GTiE?Op*vY&Lvr9}BO$XWy80IF+6@%n z5*2ueT_g@ofP#u5pxb7n*fv^Xtt7&?SRc{*2Ka-*!BuOpf}neHGCiHy$@Ka1^Dint z;DkmIL$-e)rj4o2WQV%Gy;Xg(_Bh#qeOsTM2f@KEe~4kJ8kNLQ+;(!j^bgJMcNhvklP5Z6I+9Fq@c&D~8Fb-4rmDT!MB5QC{Dsb;BharP*O;SF4& zc$wj-7Oep7#$WZN!1nznc@Vb<_Dn%ga-O#J(l=OGB`dy=Sy&$(5-n3zzu%d7E#^8`T@}V+5B;PP8J14#4cCPw-SQTdGa2gWL0*zKM z#DfSXs_iWOMt)0*+Y>Lkd=LlyoHjublNLefhKBv@JoC>P7N1_#> zv=mLWe96%EY;!ZGSQDbZWb#;tzqAGgx~uk+-$+2_8U`!ypbwXl z^2E-FkM1?lY@yt8=J3%QK+xaZ6ok=-y%=KXCD^0r!5vUneW>95PzCkOPO*t}p$;-> ze5j-BLT_;)cZQzR2CEsm@rU7GZfFtdp*a|g4wDr%8?2QkIGasRfDWT-Dvy*U{?IHT z*}wGnzdlSptl#ZF^sf)KT|BJs&kLG91^A6ls{CzFprZ6-Y!V0Xysh%9p%iMd7HLsS zN+^Un$tDV)T@i!v?3o0Fsx2qI(AX_$dDkBzQ@fRM%n zRXk6hb9Py#JXUs+7)w@eo;g%QQ95Yq!K_d=z{0dGS+pToEI6=Bo8+{k$7&Z zo4>PH(`ce8E-Ps&uv`NQ;U$%t;w~|@E3WVOCi~R4oj5wP?%<*1C%}Jq%a^q~T7u>K zML5AKfQDv6>PuT`{SrKHRAF+^&edg6+5R_#H?Lz3iGoWo#PCEd0DS;)2U({{X#zU^ zw_xv{4x7|t!S)>44J;KfA|DC?;uQ($l+5Vp7oeqf7{GBF9356nx|&B~gs+@N^gSdd zvb*>&W)|u#F{Z_b`f#GVtQ`pYv3#||N{xj1NgB<#=Odt6{eB%#9RLt5v zIi|0u70`#ai}9fJjKv7dE!9ZrOIX!3{$z_K5FBd-Kp-&e4(J$LD-)NMTp^_pB`RT; zftVVlK2g@+1Ahv2$D){@Y#cL#dUj9*&%#6 zd2m9{1NYp>)6=oAvqdCn5#cx{AJ%S8skUgMglu2*IAtd+z1>B&`MuEAS(D(<6X#Lj z?f4CFx$)M&$=7*>9v1ER4b6!SIz-m0e{o0BfkySREchp?WdVPpQCh!q$t>?rL!&Jg zd#heM;&~A}VEm8Dvy&P|J*eAV&w!&Nx6HFV&B8jJFVTmgLaswn!cx$&%JbTsloz!3 zMEz1d`k==`Ueub_JAy_&`!ogbwx27^ZXgFNAbx=g_I~5nO^r)}&myw~+yY*cJl4$I znNJ32M&K=0(2Dj_>@39`3=FX!v3nZHno_@q^!y}%(yw0PqOo=);6Y@&ylVe>nMOZ~ zd>j#QQSBn3oaWd;qy$&5(5H$Ayi)0haAYO6TH>FR?rhqHmNOO+(})NB zLI@B@v0)eq!ug`>G<@htRlp3n!EpU|n+G+AvXFrWSUsLMBfL*ZB`CRsIVHNTR&b?K zxBgsN0BjfB>UVcJ|x%=-zb%OV7lmZc& zxiupadZVF7)6QuhoY;;FK2b*qL0J-Rn-8!X4ZY$-ZSUXV5DFd7`T41c(#lAeLMoeT z4%g655v@7AqT!i@)Edt5JMbN(=Q-6{=L4iG8RA%}w;&pKmtWvI4?G9pVRp|RTw`g0 zD5c12B&A2&P6Ng~8WM2eIW=wxd?r7A*N+&!Be7PX3s|7~z=APxm=A?5 zt>xB4WG|*Td@VX{Rs)PV0|yK`oI3^xn(4c_j&vgxk_Y3o(-`_5o`V zRTghg6%l@(qodXN;dB#+OKJEEvhfcnc#BeO2|E(5df-!fKDZ!%9!^BJ_4)9P+9Dq5 zK1=(v?KmIp34r?z{NEWnLB3Px{XYwy-akun4F7xTRr2^zeYW{gcK9)>aJDdU5;w5@ zak=<+-PLH-|04pelTb%ULpuuuJC7DgyT@D|p{!V!0v3KpDnRjANN12q6SUR3mb9<- z>2r~IApQGhstZ!3*?5V z8#)hJ0TdZg0M-BK#nGFP>$i=qk82DO z7h;Ft!D5E15OgW)&%lej*?^1~2=*Z5$2VX>V{x8SC+{i10BbtUk9@I#Vi&hX)q
Q!LwySI{Bnv%Sm)yh{^sSVJ8&h_D-BJ_YZe5eCaAWU9b$O2c z$T|{vWVRtOL!xC0DTc(Qbe`ItNtt5hr<)VijD0{U;T#bUEp381_y`%ZIav?kuYG{iyYdEBPW=*xNSc;Rlt6~F4M`5G+VtOjc z*0qGzCb@gME5udTjJA-9O<&TWd~}ysBd(eVT1-H82-doyH9RST)|+Pb{o*;$j9Tjs zhU!IlsPsj8=(x3bAKJTopW3^6AKROHR^7wZ185wJGVhA~hEc|LP;k7NEz-@4p5o}F z`AD6naG3(n=NF9HTH81=F+Q|JOz$7wm9I<+#BSmB@o_cLt2GkW9|?7mM;r!JZp89l zbo!Hp8=n!XH1{GwaDU+k)pGp`C|cXkCU5%vcH)+v@0eK>%7gWxmuMu9YLlChA|_D@ zi#5zovN_!a-0?~pUV-Rj*1P)KwdU-LguR>YM&*Nen+ln8Q$?WFCJg%DY%K}2!!1FE zDv-A%Cbwo^p(lzac&_TZ-l#9kq`mhLcY3h9ZTUVCM(Ad&=EriQY5{jJv<5K&g|*Lk zgV%ILnf1%8V2B0E&;Sp4sYbYOvvMebLwYwzkRQ#F8GpTQq#uv=J`uaSJ34OWITeSGo6+-8Xw znCk*n{kdDEi)Hi&u^)~cs@iyCkFWB2SWZU|Uc%^43ZIZQ-vWNExCCtDWjqHs;;tWf$v{}0{p0Rvxkq``)*>+Akq%|Na zA`@~-Vfe|+(AIlqru+7Ceh4nsVmO9p9jc8}HX^W&ViBDXT+uXbT#R#idPn&L>+#b6 zflC-4C5-X;kUnR~L>PSLh*gvL68}RBsu#2l`s_9KjUWRhiqF`j)`y`2`YU(>3bdBj z?>iyjEhe-~$^I5!nn%B6Wh+I`FvLNvauve~eX<+Ipl&04 zT}};W&1a3%W?dJ2=N#0t?e+aK+%t}5q%jSLvp3jZ%?&F}nOOWr>+{GFIa%wO_2`et z=JzoRR~}iKuuR+azPI8;Gf9)z3kyA4EIOSl!sRR$DlW}0>&?GbgPojmjmnln;cTqCt=ADbE zZ8GAnoM+S1(5$i8^O4t`ue;vO4i}z0wz-QEIVe5_u03;}-!G1NyY8;h^}y;tzY}i5 zqQr#Ur3Fy8sSa$Q0ys+f`!`+>9WbvU_I`Sj;$4{S>O3?#inLHCrtLy~!s#WXV=oVP zeE93*Nc`PBi4q@%Ao$x4lw9vLHM!6mn3-b_cebF|n-2vt-zYVF_&sDE--J-P;2WHo z+@n2areE0o$LjvjlV2X7ZU@j+`{*8zq`JR3gKF#EW|#+{nMyo-a>nFFTg&vhyT=b} zDa8+v0(Dgx0yRL@ZXOYIlVSZ0|MFizy0VPW8;AfA5|pe!#j zX}Py^8fl5SyS4g1WSKKtnyP+_PoOwMMwu`(i@Z)diJp~U54*-miOchy7Z35eL>^M z4p<-aIxH4VUZgS783@H%M7P9hX>t{|RU7$n4T(brCG#h9e9p! z+o`i;EGGq3&pF;~5V~eBD}lC)>if$w%Vf}AFxGqO88|ApfHf&Bvu+xdG)@vuF}Yvk z)o;~k-%+0K0g+L`Wala!$=ZV|z$e%>f0%XoLib%)!R^RoS+{!#X?h-6uu zF&&KxORdZU&EwQFITIRLo(7TA3W}y6X{?Y%y2j0It!ekU#<)$qghZtpcS>L3uh`Uj z7GY;6f$9qKynP#oS3$$a{p^{D+0oJQ71`1?OAn_m8)UGZmj3l*ZI)`V-a>MKGGFG< z&^jg#Ok%(hhm>hSrZ5;Qga4u(?^i>GiW_j9%_7M>j(^|Om$#{k+^*ULnEgzW_1gCICtAD^WpC`A z{9&DXkG#01Xo)U$OC(L5Y$DQ|Q4C6CjUKk1UkPj$nXH##J{c8e#K|&{mA*;b$r0E4 zUNo0jthwA(c&N1l=PEe8Rw_8cEl|-eya9z&H3#n`B$t#+aJ03RFMzrV@gowbe8v(c zIFM60^0&lCFO10NU4w@|61xiZ4CVXeaKjd;d?sv52XM*lS8XiVjgWpRB;&U_C0g+`6B5V&w|O6B*_q zsATxL!M}+$He)1eOWECce#eS@2n^xhlB4<_Nn?yCVEQWDs(r`|@2GqLe<#(|&P0U? z$7V5IgpWf09uIf_RazRwC?qEqRaHyL?iiS05UiGesJy%^>-C{{ypTBI&B0-iUYhk> zIk<5xpsuV@g|z(AZD+C-;A!fTG=df1=<%nxy(a(IS+U{ME4ZbDEBtcD_3V=icT6*_ z)>|J?>&6%nvHhZERBtjK+s4xnut*@>GAmA5m*OTp$!^CHTr}vM4n(X1Q*;{e-Rd2BCF-u@1ZGm z!S8hJ6L=Gl4T_SDa7Xx|-{4mxveJg=ctf`BJ*fy!yF6Dz&?w(Q_6B}WQVtNI!BVBC zKfX<>7vd6C96}XAQmF-Jd?1Q4eTfRB3q7hCh0f!(JkdWT5<{iAE#dKy*Jxq&3a1@~ z8C||Dn2mFNyrUV|<-)C^_y7@8c2Fz+2jrae9deBDu;U}tJ{^xAdxCD248(k;dCJ%o z`y3sADe>U%suxwwv~8A1+R$VB=Q?%U?4joI$um;aH+eCrBqpn- z%79D_7rb;R-;-9RTrwi9dPlg8&@tfWhhZ(Vx&1PQ+6(huX`;M9x~LrW~~#3{j0Bh2kDU$}@!fFQej4VGkJv?M4rU^x!RU zEwhu$!CA_iDjFjrJa`aocySDX16?~;+wgav;}Zut6Mg%C4>}8FL?8)Kgwc(Qlj{@#2Pt0?G`$h7P#M+qoXtlV@d}%c&OzO+QYKK`kyXaK{U(O^2DyIXCZlNQjt0^8~8JzNGrIxhj}}M z&~QZlbx%t;MJ(Vux;2tgNKGlAqphLq%pd}JG9uoVHUo?|hN{pLQ6Em%r*+7t^<);X zm~6=qChlNAVXNN*Sow->*4;}T;l;D1I-5T{Bif@4_}=>l`tK;qqDdt5zvisCKhMAH z#r}`)7VW?LZqfdmXQ%zo5bJ00{Xb9^YKrk0Nf|oIW*K@(=`o2Vndz}ZDyk{!u}PVx zzd--+_WC*U{~DH3{?GI64IB+@On&@9X>EUAo&L+G{L^dozaI4C3G#2wr~hseW@K&g zKWs{uHu-9Je!3;4pE>eBltKUXb^*hG8I&413)$J&{D4N%7PcloU6bn%jPxJyQL?g* z9g+YFFEDiE`8rW^laCNzQmi7CTnPfwyg3VDHRAl>h=In6jeaVOP@!-CP60j3+#vpL zEYmh_oP0{-gTe7Or`L6x)6w?77QVi~jD8lWN@3RHcm80iV%M1A!+Y6iHM)05iC64tb$X2lV_%Txk@0l^hZqi^%Z?#- zE;LE0uFx)R08_S-#(wC=dS&}vj6P4>5ZWjhthP=*Hht&TdLtKDR;rXEX4*z0h74FA zMCINqrh3Vq;s%3MC1YL`{WjIAPkVL#3rj^9Pj9Ss7>7duy!9H0vYF%>1jh)EPqvlr6h%R%CxDsk| z!BACz7E%j?bm=pH6Eaw{+suniuY7C9Ut~1cWfOX9KW9=H><&kQlinPV3h9R>3nJvK z4L9(DRM=x;R&d#a@oFY7mB|m8h4692U5eYfcw|QKwqRsshN(q^v$4$)HgPpAJDJ`I zkqjq(8Cd!K!+wCd=d@w%~e$=gdUgD&wj$LQ1r>-E=O@c ze+Z$x{>6(JA-fNVr)X;*)40Eym1TtUZI1Pwwx1hUi+G1Jlk~vCYeXMNYtr)1?qwyg zsX_e*$h?380O00ou?0R@7-Fc59o$UvyVs4cUbujHUA>sH!}L54>`e` zHUx#Q+Hn&Og#YVOuo*niy*GU3rH;%f``nk#NN5-xrZ34NeH$l`4@t);4(+0|Z#I>Y z)~Kzs#exIAaf--65L0UHT_SvV8O2WYeD>Mq^Y6L!Xu8%vnpofG@w!}R7M28?i1*T&zp3X4^OMCY6(Dg<-! zXmcGQrRgHXGYre7GfTJ)rhl|rs%abKT_Nt24_Q``XH{88NVPW+`x4ZdrMuO0iZ0g` z%p}y};~T5gbb9SeL8BSc`SO#ixC$@QhXxZ=B}L`tP}&k?1oSPS=4%{UOHe0<_XWln zwbl5cn(j-qK`)vGHY5B5C|QZd5)W7c@{bNVXqJ!!n$^ufc?N9C-BF2QK1(kv++h!>$QbAjq)_b$$PcJdV+F7hz0Hu@ zqj+}m0qn{t^tD3DfBb~0B36|Q`bs*xs|$i^G4uNUEBl4g;op-;Wl~iThgga?+dL7s zUP(8lMO?g{GcYpDS{NM!UA8Hco?#}eNEioRBHy4`mq!Pd-9@-97|k$hpEX>xoX+dY zDr$wfm^P&}Wu{!%?)U_(%Mn79$(ywvu*kJ9r4u|MyYLI_67U7%6Gd_vb##Nerf@>& z8W11z$$~xEZt$dPG}+*IZky+os5Ju2eRi;1=rUEeIn>t-AzC_IGM-IXWK3^6QNU+2pe=MBn4I*R@A%-iLDCOHTE-O^wo$sL_h{dcPl=^muAQb`_BRm};=cy{qSkui;`WSsj9%c^+bIDQ z0`_?KX0<-=o!t{u(Ln)v>%VGL z0pC=GB7*AQ?N7N{ut*a%MH-tdtNmNC+Yf$|KS)BW(gQJ*z$d{+{j?(e&hgTy^2|AR9vx1Xre2fagGv0YXWqtNkg*v%40v?BJBt|f9wX5 z{QTlCM}b-0{mV?IG>TW_BdviUKhtosrBqdfq&Frdz>cF~yK{P@(w{Vr7z2qKFwLhc zQuogKO@~YwyS9%+d-zD7mJG~@?EFJLSn!a&mhE5$_4xBl&6QHMzL?CdzEnC~C3$X@ zvY!{_GR06ep5;<#cKCSJ%srxX=+pn?ywDwtJ2{TV;0DKBO2t++B(tIO4)Wh`rD13P z4fE$#%zkd=UzOB74gi=-*CuID&Z3zI^-`4U^S?dHxK8fP*;fE|a(KYMgMUo`THIS1f!*6dOI2 zFjC3O=-AL`6=9pp;`CYPTdVX z8(*?V&%QoipuH0>WKlL8A*zTKckD!paN@~hh zmXzm~qZhMGVdQGd=AG8&20HW0RGV8X{$9LldFZYm zE?}`Q3i?xJRz43S?VFMmqRyvWaS#(~Lempg9nTM$EFDP(Gzx#$r)W&lpFKqcAoJh-AxEw$-bjW>`_+gEi z2w`99#UbFZGiQjS8kj~@PGqpsPX`T{YOj`CaEqTFag;$jY z8_{Wzz>HXx&G*Dx<5skhpETxIdhKH?DtY@b9l8$l?UkM#J-Snmts7bd7xayKTFJ(u zyAT&@6cAYcs{PBfpqZa%sxhJ5nSZBPji?Zlf&}#L?t)vC4X5VLp%~fz2Sx<*oN<7` z?ge=k<=X7r<~F7Tvp9#HB{!mA!QWBOf%EiSJ6KIF8QZNjg&x~-%e*tflL(ji_S^sO ztmib1rp09uon}RcsFi#k)oLs@$?vs(i>5k3YN%$T(5Or(TZ5JW9mA6mIMD08=749$ z!d+l*iu{Il7^Yu}H;lgw=En1sJpCKPSqTCHy4(f&NPelr31^*l%KHq^QE>z>Ks_bH zjbD?({~8Din7IvZeJ>8Ey=e;I?thpzD=zE5UHeO|neioJwG;IyLk?xOz(yO&0DTU~ z^#)xcs|s>Flgmp;SmYJ4g(|HMu3v7#;c*Aa8iF#UZo7CvDq4>8#qLJ|YdZ!AsH%^_7N1IQjCro

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/example-projects/webflux-example/gradle/wrapper/gradle-wrapper.properties b/example-projects/webflux-example/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..549d844 --- /dev/null +++ b/example-projects/webflux-example/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/example-projects/webflux-example/gradlew b/example-projects/webflux-example/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/example-projects/webflux-example/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/example-projects/webflux-example/gradlew.bat b/example-projects/webflux-example/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/example-projects/webflux-example/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/example-projects/webflux-example/settings.gradle b/example-projects/webflux-example/settings.gradle new file mode 100644 index 0000000..519a840 --- /dev/null +++ b/example-projects/webflux-example/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'webflux-example' + +includeBuild '../../../token-manager-for-salesforce' diff --git a/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/WebfluxTokenManagerExample.java b/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/WebfluxTokenManagerExample.java new file mode 100644 index 0000000..c4c8c03 --- /dev/null +++ b/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/WebfluxTokenManagerExample.java @@ -0,0 +1,12 @@ +package com.tgt.crm.webflux; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WebfluxTokenManagerExample { + + public static void main(String[] args) { + SpringApplication.run(WebfluxTokenManagerExample.class, args); + } +} diff --git a/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/client/SalesforceClient.java b/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/client/SalesforceClient.java new file mode 100644 index 0000000..f8caea2 --- /dev/null +++ b/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/client/SalesforceClient.java @@ -0,0 +1,41 @@ +package com.tgt.crm.webflux.client; + +import com.tgt.crm.webflux.vo.QueryResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; + +@Service +@Slf4j +public class SalesforceClient { + + private static final String SF_BASE_URL = "/services/data/v51.0"; + private static final String SF_QUERY_URL = SF_BASE_URL + "/query?q={query}"; + + private final WebClient salesforceWebClient; + + public SalesforceClient(@Qualifier("sfWebClient") final WebClient salesforceWebClient) { + this.salesforceWebClient = salesforceWebClient; + } + + public Mono executeQuery(final String query) { + + log.info("executing query to Salesforce: {}", query); + + return salesforceWebClient + .get() + .uri(SF_QUERY_URL, query) + .retrieve() + .bodyToMono(QueryResponse.class) + .doOnError( + err -> err instanceof WebClientResponseException, + err -> + log.error( + "error message: {}", + ((WebClientResponseException) err).getResponseBodyAsString())) + .doOnSuccess(response -> log.info("query response: {}", response)); + } +} diff --git a/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/controller/WebfluxController.java b/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/controller/WebfluxController.java new file mode 100644 index 0000000..eee9f74 --- /dev/null +++ b/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/controller/WebfluxController.java @@ -0,0 +1,26 @@ +package com.tgt.crm.webflux.controller; + +import com.tgt.crm.webflux.client.SalesforceClient; +import com.tgt.crm.webflux.vo.QueryResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +@AllArgsConstructor +@RequestMapping("/salesforce") +@Slf4j +public class WebfluxController { + + private final SalesforceClient salesforceClient; + + @GetMapping("/query") + public Mono querySalesforce(@RequestParam final String q) { + + return salesforceClient.executeQuery(q); + } +} diff --git a/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/vo/QueryResponse.java b/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/vo/QueryResponse.java new file mode 100644 index 0000000..5e96a8f --- /dev/null +++ b/example-projects/webflux-example/src/main/java/com/tgt/crm/webflux/vo/QueryResponse.java @@ -0,0 +1,21 @@ +package com.tgt.crm.webflux.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryResponse { + + @JsonProperty("totalSize") + private int totalSize; + + @JsonProperty("done") + private boolean done; + + @JsonProperty("records") + private List> records; +} diff --git a/example-projects/webflux-example/src/main/resources/application.yml b/example-projects/webflux-example/src/main/resources/application.yml new file mode 100644 index 0000000..97b1fc1 --- /dev/null +++ b/example-projects/webflux-example/src/main/resources/application.yml @@ -0,0 +1,15 @@ +salesforce: + host: ${SF_HOST} + username: ${SF_USERNAME} + password: ${SF_PASSWORD} + client-id: ${SF_CLIENT_ID} + client-secret: ${SF_CLIENT_SECRET} + httpclient: + max-conn-per-route: 20 + read-timeout: 30000 + connection-timeout: 60000 + +logging: + level: + com.tgt.crm.token: TRACE + reactor.netty: TRACE diff --git a/example-projects/webmvc-example/build.gradle b/example-projects/webmvc-example/build.gradle new file mode 100644 index 0000000..c2fdb23 --- /dev/null +++ b/example-projects/webmvc-example/build.gradle @@ -0,0 +1,46 @@ +buildscript { + ext { + springBootVersion = '2.2.0.RELEASE' + tokenManagerVersion = '1.0.0' + dependencyManagementVersion = '1.0.11.RELEASE' + lombokVersion = '1.18.20' + } + repositories { + mavenCentral() + google() + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" + } +} + +plugins { + id 'java' + id 'org.springframework.boot' version "${springBootVersion}" + id 'io.spring.dependency-management' version "${dependencyManagementVersion}" +} + +group = 'com.tgt.crm.mvc' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '11' + +repositories { + mavenCentral() + google() + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/target/token-manager-for-salesforce") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("USERNAME") + password = project.findProperty("gpr.token") ?: System.getenv("TOKEN") + } + } +} + +dependencies { + implementation "com.tgt.crm:token-manager-for-salesforce-webmvc:${tokenManagerVersion}" + implementation 'org.springframework.boot:spring-boot-starter-web' + compileOnly "org.projectlombok:lombok:${lombokVersion}" + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" +} + diff --git a/example-projects/webmvc-example/gradle/wrapper/gradle-wrapper.jar b/example-projects/webmvc-example/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/example-projects/webmvc-example/gradle/wrapper/gradle-wrapper.properties b/example-projects/webmvc-example/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..549d844 --- /dev/null +++ b/example-projects/webmvc-example/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/example-projects/webmvc-example/gradlew b/example-projects/webmvc-example/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/example-projects/webmvc-example/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/example-projects/webmvc-example/gradlew.bat b/example-projects/webmvc-example/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/example-projects/webmvc-example/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/example-projects/webmvc-example/settings.gradle b/example-projects/webmvc-example/settings.gradle new file mode 100644 index 0000000..8ec4d47 --- /dev/null +++ b/example-projects/webmvc-example/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'webmvc-example' + +includeBuild '../../../token-manager-for-salesforce' diff --git a/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/MvcTokenManagerExample.java b/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/MvcTokenManagerExample.java new file mode 100644 index 0000000..e51858a --- /dev/null +++ b/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/MvcTokenManagerExample.java @@ -0,0 +1,12 @@ +package com.tgt.crm.mvc; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MvcTokenManagerExample { + + public static void main(String[] args) { + SpringApplication.run(MvcTokenManagerExample.class, args); + } +} diff --git a/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/client/SalesforceClient.java b/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/client/SalesforceClient.java new file mode 100644 index 0000000..fb764cd --- /dev/null +++ b/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/client/SalesforceClient.java @@ -0,0 +1,42 @@ +package com.tgt.crm.mvc.client; + +import com.tgt.crm.mvc.vo.QueryResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +@Slf4j +public class SalesforceClient { + + private static final String SF_BASE_URL = "/services/data/v51.0"; + private static final String SF_QUERY_URL = SF_BASE_URL + "/query?q={query}"; + + private final RestTemplate salesforceRestTemplate; + + public SalesforceClient(@Qualifier("sfRestTemplate") final RestTemplate salesforceRestTemplate) { + this.salesforceRestTemplate = salesforceRestTemplate; + } + + public QueryResponse executeQuery(final String query) { + + log.info("executing query to Salesforce: {}", query); + + ResponseEntity sfResponse = + salesforceRestTemplate.exchange( + SF_QUERY_URL, + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference<>() {}, + query); + + log.info("query response: {}", sfResponse); + + return sfResponse.getBody(); + } +} diff --git a/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/controller/MvcController.java b/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/controller/MvcController.java new file mode 100644 index 0000000..ce40241 --- /dev/null +++ b/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/controller/MvcController.java @@ -0,0 +1,28 @@ +package com.tgt.crm.mvc.controller; + +import com.tgt.crm.mvc.client.SalesforceClient; +import com.tgt.crm.mvc.vo.QueryResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AllArgsConstructor +@RequestMapping("/salesforce") +@Slf4j +public class MvcController { + + private final SalesforceClient salesforceClient; + + @GetMapping("/query") + public ResponseEntity querySalesforce(@RequestParam final String q) { + + QueryResponse response = salesforceClient.executeQuery(q); + + return ResponseEntity.ok(response); + } +} diff --git a/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/vo/QueryResponse.java b/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/vo/QueryResponse.java new file mode 100644 index 0000000..64cacd4 --- /dev/null +++ b/example-projects/webmvc-example/src/main/java/com/tgt/crm/mvc/vo/QueryResponse.java @@ -0,0 +1,21 @@ +package com.tgt.crm.mvc.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueryResponse { + + @JsonProperty("totalSize") + private int totalSize; + + @JsonProperty("done") + private boolean done; + + @JsonProperty("records") + private List> records; +} diff --git a/example-projects/webmvc-example/src/main/resources/application.yml b/example-projects/webmvc-example/src/main/resources/application.yml new file mode 100644 index 0000000..b0ad32e --- /dev/null +++ b/example-projects/webmvc-example/src/main/resources/application.yml @@ -0,0 +1,22 @@ +salesforce: + host: ${SF_HOST} + username: ${SF_USERNAME} + password: ${SF_PASSWORD} + client-id: ${SF_CLIENT_ID} + client-secret: ${SF_CLIENT_SECRET} + httpclient: + max-conn-per-route: 20 + read-timeout: 30000 + connection-timeout: 60000 + webmvc: + max-pools: 50 + connection-request-timeout: 30000 + retries: 3 + retry-interval: 2000 + +logging: + level: + com.tgt.crm.token: TRACE + org.springframework.web: TRACE + org.apache.http: DEBUG + diff --git a/gradle/checks.gradle b/gradle/checks.gradle new file mode 100644 index 0000000..34324cf --- /dev/null +++ b/gradle/checks.gradle @@ -0,0 +1,145 @@ +quality { + + /** + * When disabled, quality plugins will not be registered automatically (according to sources). + * Only manually registered quality plugins will be configured. + */ + autoRegistration = true + + // Enable/disable tools (when auto registration disabled control configuration appliance) + + checkstyle = false + pmd = true + cpd = false + spotbugs = true + codenarc = false + + /** + * Enable PMD incremental analysis (cache results between builds to speed up processing). + * This is a shortcut for pmd plugin's {@code pmd.incrementalAnalysis } configuration option. + * Option is disabled by default due to possible side effects with build gradle cache or incremental builds. + */ + pmdIncremental = false + + /** + * The analysis effort level. The value specified should be one of min, default, or max. + * Higher levels increase precision and find more bugs at the expense of running time and + * memory consumption. Default is 'max'. + */ + spotbugsEffort = 'max' + + /** + * The priority threshold for reporting bugs. If set to low, all bugs are reported. + * If set to medium, medium and high priority bugs are reported. + * If set to high, only high priority bugs are reported. Default is 'medium'. + */ + spotbugsLevel = 'medium' + + /** + * Spotbugs rank should be an integer value between 1 and 20, where 1 to 4 are scariest, 5 to 9 scary, + * 10 to 14 troubling, and 15 to 20 of concern bugs. + *

+ * This option allows you to filter low-priority ranks: for example, setting {@code spotbugsMaxRank=15} will + * filter all bugs with ranks 16-20. Note that this is not the same as {@link #spotbugsLevel}: + * it has a bit different meaning (note that both priority and rank are shown for each spotbugs + * violation in console). + *

+ * The only way to apply rank filtering is through exclude filter. Plugin will automatically generate + * additional rule in your exclude filter or in default one. But it may conflict with manual rank rule declaration + * (in case if you edit exclude filter manually), so be careful when enabling this option. + */ + spotbugsMaxRank = 20 + + /** + * Max memory available for spotbugs task. Note that in gradle 4 spotbugs task maximum memory was + * 1/4 of physical memory, but in gradle 5 it become only 512mb (default for workers api). + * To minify impact of this gradle 5 change, default value in extension is 1g now, but it may be not + * enough for large projects (and so you will have to increase it manually). + *

+ * IMPORTANT: setting will not work if heap size configured directly in spotbugs task (for example, with + * spotbugsMain.maxHeapSize = '2g'. This was done in order to not break current behaviour + * (when task memory is already configured) and affect only default cases (mostly caused by gradle 5 transition). + *

+ * See: https://github.com/gradle/gradle/issues/6216 (Reduce default memory settings for daemon and + * workers). + */ + spotbugsMaxHeapSize = '2g' + + /** + * Javac lint options to show compiler warnings, not visible by default. + * Applies to all CompileJava tasks. + * Options will be added as -Xlint:option + * Full list of options: http://docs.oracle.com/javase/8/docs/technotes/tools/windows/javac.html#BHCJCABJ + */ + lintOptions = ['deprecation', 'unchecked'] + + /** + * Strict quality leads to build fail on any violation found. If disabled, all violation + * are just printed to console. + */ + strict = true + + /** + * When false, disables quality tasks execution. Allows disabling tasks without removing plugins. + * Quality tasks are still registered, but skip execution, except when task called directly or through + * checkQualityMain (or other source set) grouping task. + */ + enabled = true + + /** + * When false, disables reporting quality issues to console. Only gradle general error messages will + * remain in logs. This may be useful in cases when project contains too many warnings. + * Also, console reporting require xml reports parsing, which could be time consuming in case of too + * many errors (large xml reports). + * True by default. + */ + consoleReporting = true + + /** + * When false, no html reports will be built. True by default. + */ + htmlReports = true + + /** + * Source sets to apply checks on. + * Default is [sourceSets.main] to apply only for project sources, excluding tests. + */ + sourceSets = project.sourceSets + + /** + * Source patterns (relative to source dir) to exclude from checks. Simply sets exclusions to quality tasks. + * + * Animalsniffer is not affected because + * it's a different kind of check (and, also, it operates on classes so source patterns may not comply). + * + * Spotbugs does not support exclusion directly, but plugin will resolve excluded classes and apply + * them to xml exclude file (default one or provided by user). + * + * By default nothing is excluded. + * + * IMPORTANT: Patterns are checked relatively to source set dirs (not including them). So you can only + * match source files and packages, but not absolute file path (this is gradle specific, not plugin). + * + * @see org.gradle.api.tasks.SourceTask#exclude(java.lang.Iterable) (base class for all quality tasks) + */ + exclude = [] + + /** + * Direct sources to exclude from checks (except animalsniffer). + * This is useful as last resort, when extension or package is not enough for filtering. + * Use {@link Project#files(java.lang.Object)} or {@link Project#fileTree(java.lang.Object)} + * to create initial collections and apply filter on it (using + * {@link org.gradle.api.file.FileTree#matching(groovy.lang.Closure)}). + * + * Plugin will include files into spotbugs exclusion filter xml (default one or provided by user). + * + * Note: this must be used when excluded classes can't be extracted to different source set and + * filter by package and filename is not sufficient. + */ + FileCollection excludeSources + + /** + * User configuration files directory. Files in this directory will be used instead of default (bundled) configs. + */ + configDir = 'gradle/config/' +} diff --git a/gradle/config/pmd/pmd.xml b/gradle/config/pmd/pmd.xml new file mode 100644 index 0000000..652b04c --- /dev/null +++ b/gradle/config/pmd/pmd.xml @@ -0,0 +1,120 @@ + + + + + + Basic Java code quality rules + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle/spotless.gradle b/gradle/spotless.gradle new file mode 100644 index 0000000..a706ea9 --- /dev/null +++ b/gradle/spotless.gradle @@ -0,0 +1,44 @@ +spotless { + java { + googleJavaFormat() + } + format 'misc', { + target '**/*.md', '**/.gitignore' + trimTrailingWhitespace() + indentWithSpaces(2) + endWithNewline() + } + groovyGradle { + target 'build.gradle', 'gradle/*.gradle' + targetExclude 'out/**', 'build/**', '.idea/**', '.gradle/**' + greclipse() + trimTrailingWhitespace() + indentWithSpaces(2) + endWithNewline() + } + format 'xml', { + target fileTree('.') { + include '**/*.xml', '**/*.xsd' + exclude '**/build/**', 'out/**', '.idea/**', '**/.gradle/**', '**/pom.xml' + } + eclipseWtp('xml') + indentWithSpaces(2) + trimTrailingWhitespace() + } + format 'json', { + target '**/*.json' + targetExclude 'out/**', 'build/**', '.idea/**', '.gradle/**' + eclipseWtp('json') + indentWithSpaces(2) + trimTrailingWhitespace() + } + // requires node, not present on current docker image + // format 'yaml', { + // target '**/*.yml', '**/*.yaml' + // targetExclude 'out/**', 'build/**', '.idea/**', '.gradle/**' + // prettier().config(['parser': 'yaml']) + // trimTrailingWhitespace() + // indentWithSpaces(2) + // endWithNewline() + // } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..05679dc --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e2344df --- /dev/null +++ b/settings.gradle @@ -0,0 +1,6 @@ +rootProject.name = 'token-manager-for-salesforce' + +include 'token-manager-for-salesforce-core' +include 'token-manager-for-salesforce-webflux' +include 'token-manager-for-salesforce-webmvc' +include 'token-manager-for-salesforce-core' diff --git a/token-manager-for-salesforce-core/build.gradle b/token-manager-for-salesforce-core/build.gradle new file mode 100644 index 0000000..c818a12 --- /dev/null +++ b/token-manager-for-salesforce-core/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java-test-fixtures' +} + +// === module dependencies === + +dependencies { + implementation "com.fasterxml.jackson.core:jackson-annotations" + implementation "org.springframework.boot:spring-boot-starter" + testFixturesImplementation "com.squareup.okhttp3:mockwebserver:${mockWebserverVersion}" + testFixturesImplementation "com.squareup.okhttp3:okhttp:${mockWebserverVersion}" + testFixturesImplementation "org.springframework.boot:spring-boot-starter-test" + testFixturesImplementation "org.springframework:spring-web" + testFixturesCompileOnly "org.projectlombok:lombok:${lombokVersion}" + testFixturesAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" + testFixturesImplementation "com.fasterxml.jackson.core:jackson-databind" + testFixturesCompileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugsAnnotationsVersion}" +} diff --git a/token-manager-for-salesforce-core/settings.gradle b/token-manager-for-salesforce-core/settings.gradle new file mode 100644 index 0000000..c14f561 --- /dev/null +++ b/token-manager-for-salesforce-core/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'token-manager-for-salesforce-core' \ No newline at end of file diff --git a/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/HttpClientConfig.java b/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/HttpClientConfig.java new file mode 100644 index 0000000..e56d3f6 --- /dev/null +++ b/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/HttpClientConfig.java @@ -0,0 +1,13 @@ +package com.tgt.crm.token.core; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Data +@ConfigurationProperties("salesforce.httpclient") +public class HttpClientConfig { + + private int maxConnPerRoute = 20; + private int readTimeout = 30_000; + private int connectionTimeout = 60_000; +} diff --git a/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceAuthResponse.java b/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceAuthResponse.java new file mode 100644 index 0000000..7365ac3 --- /dev/null +++ b/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceAuthResponse.java @@ -0,0 +1,35 @@ +package com.tgt.crm.token.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SalesforceAuthResponse { + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("instance_url") + private String instanceUrl; + + private String id; + + @JsonProperty("token_type") + private String tokenType; + + @JsonProperty("issued_at") + private String issuedAt; + + private String signature; + + private String error; + + @JsonProperty("error_description") + private String errorDescription; + + public String getSalesforceAuthToken() { + return tokenType + " " + accessToken; + } +} diff --git a/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceConfig.java b/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceConfig.java new file mode 100644 index 0000000..9432af1 --- /dev/null +++ b/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceConfig.java @@ -0,0 +1,49 @@ +package com.tgt.crm.token.core; + +import static com.tgt.crm.token.core.SalesforceConstants.AUTH_URI; +import static com.tgt.crm.token.core.SalesforceConstants.MAX_AUTH_TOKEN_RETRIES_DEFAULT; +import static com.tgt.crm.token.core.SalesforceConstants.RETRY_BACKOFF_DELAY_DEFAULT; +import static com.tgt.crm.token.core.SalesforceConstants.RETRY_BACKOFF_MULTIPLIER_DEFAULT; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@Data +@ConfigurationProperties("salesforce") +@Validated +public class SalesforceConfig { + + private static final String NOT_ENV_VAR_PATTERN = "^(?!(\\$\\{.+})$).+$"; + private static final String ENV_VAR_MISSING_MSG = "Environment variable is missing"; + + @NotBlank + @Pattern(regexp = NOT_ENV_VAR_PATTERN, message = ENV_VAR_MISSING_MSG) + private String host; + + @NotBlank + @Pattern(regexp = NOT_ENV_VAR_PATTERN, message = ENV_VAR_MISSING_MSG) + private String username; + + @NotBlank + @Pattern(regexp = NOT_ENV_VAR_PATTERN, message = ENV_VAR_MISSING_MSG) + private String password; + + @NotBlank + @Pattern(regexp = NOT_ENV_VAR_PATTERN, message = ENV_VAR_MISSING_MSG) + private String clientId; + + @NotBlank + @Pattern(regexp = NOT_ENV_VAR_PATTERN, message = ENV_VAR_MISSING_MSG) + private String clientSecret; + + private String authUri = AUTH_URI; + private int maxAuthTokenRetries = MAX_AUTH_TOKEN_RETRIES_DEFAULT; + private int retryBackoffDelay = RETRY_BACKOFF_DELAY_DEFAULT; // milliseconds + + // property only used for MVC. WebClient retry uses the backoff delay value as a min delay and + // a jitter factor to randomize retry delays instead of a fixed multiplier + private int retryBackoffMultiplier = RETRY_BACKOFF_MULTIPLIER_DEFAULT; +} diff --git a/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceConstants.java b/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceConstants.java new file mode 100644 index 0000000..3c35099 --- /dev/null +++ b/token-manager-for-salesforce-core/src/main/java/com/tgt/crm/token/core/SalesforceConstants.java @@ -0,0 +1,15 @@ +package com.tgt.crm.token.core; + +@SuppressWarnings("PMD.LongVariable") +public final class SalesforceConstants { + + public static final String AUTH_URI = "/services/oauth2/token"; + public static final String EXCEPTION_COUNTER = "exception_counter"; + public static final String EXCEPTION_TYPE_TAG = "exception_type"; + public static final String TOKEN_REFRESH_EXCEPTION = "token_refresh_exception"; + public static final int MAX_AUTH_TOKEN_RETRIES_DEFAULT = 3; + public static final int RETRY_BACKOFF_DELAY_DEFAULT = 1000; + public static final int RETRY_BACKOFF_MULTIPLIER_DEFAULT = 2; + + private SalesforceConstants() {} +} diff --git a/token-manager-for-salesforce-core/src/testFixtures/java/com/tgt/crm/token/core/BaseIntegrationTest.java b/token-manager-for-salesforce-core/src/testFixtures/java/com/tgt/crm/token/core/BaseIntegrationTest.java new file mode 100644 index 0000000..f2fe682 --- /dev/null +++ b/token-manager-for-salesforce-core/src/testFixtures/java/com/tgt/crm/token/core/BaseIntegrationTest.java @@ -0,0 +1,73 @@ +package com.tgt.crm.token.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +public class BaseIntegrationTest { + + @SuppressFBWarnings("MS_PKGPROTECT") + protected static MockWebServer mockWebServer; + + protected static final int TIMEOUT = 5; // seconds + protected static final String QUERY_SUCCESSFUL = "query successful"; + protected static final String SF_URL = "/some/sf/url"; + + @BeforeAll + static void setup() throws IOException { + mockWebServer = new MockWebServer(); + mockWebServer.start(); + } + + @AfterAll + static void shutdown() throws IOException { + mockWebServer.shutdown(); + } + + @DynamicPropertySource + @SuppressWarnings("PMD.DefaultPackage") + static void registerProperties(final DynamicPropertyRegistry registry) { + registry.add("salesforce.host", () -> "http://localhost:" + mockWebServer.getPort()); + } + + protected void validateAuthRequest() throws InterruptedException { + RecordedRequest authReq = mockWebServer.takeRequest(TIMEOUT, TimeUnit.SECONDS); + assertNotNull(authReq); + assertEquals(HttpMethod.POST.name(), authReq.getMethod()); + assertEquals("/services/oauth2/token", authReq.getPath()); + assertEquals( + MediaType.APPLICATION_FORM_URLENCODED_VALUE, authReq.getHeader(HttpHeaders.CONTENT_TYPE)); + assertEquals( + "grant_type=password&username=username&password=password%21%40%23%24%25%5E%26*%28%29&client_id=clientId&client_secret=clientSecret", + authReq.getBody().readUtf8()); + } + + protected void validateSfRequest() throws InterruptedException { + validateSfRequest("Bearer bearerToken"); + } + + protected void validateSfRequest(final String authHeader) throws InterruptedException { + RecordedRequest queryReq = mockWebServer.takeRequest(TIMEOUT, TimeUnit.SECONDS); + assertNotNull(queryReq); + assertEquals(HttpMethod.GET.name(), queryReq.getMethod()); + assertEquals("/some/sf/url", queryReq.getPath()); + assertEquals(1, queryReq.getHeaders().values(HttpHeaders.CONTENT_TYPE).size()); + assertEquals(MediaType.APPLICATION_JSON_VALUE, queryReq.getHeader(HttpHeaders.CONTENT_TYPE)); + assertEquals(authHeader, queryReq.getHeader(HttpHeaders.AUTHORIZATION)); + } +} diff --git a/token-manager-for-salesforce-core/src/testFixtures/java/com/tgt/crm/token/core/MockResponseUtil.java b/token-manager-for-salesforce-core/src/testFixtures/java/com/tgt/crm/token/core/MockResponseUtil.java new file mode 100644 index 0000000..34e7623 --- /dev/null +++ b/token-manager-for-salesforce-core/src/testFixtures/java/com/tgt/crm/token/core/MockResponseUtil.java @@ -0,0 +1,74 @@ +package com.tgt.crm.token.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import lombok.extern.slf4j.Slf4j; +import okhttp3.mockwebserver.MockResponse; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +@Slf4j +public final class MockResponseUtil { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static String sfAuthSuccess; + private static String sfAuthError; + private static String sfQueryUnauthorizedResponse; + private static String sfAuthRefreshed; + + private MockResponseUtil() {} + + static { + try { + sfAuthSuccess = readFile("sfAuthSuccess.json"); + sfAuthError = readFile("sfAuthError.json"); + sfQueryUnauthorizedResponse = readFile("sfQueryUnauthorizedResponse.json"); + sfAuthRefreshed = readFile("sfAuthRefreshed.json"); + } catch (IOException e) { + log.error("Error reading resource file", e); + assert false; + } + } + + public static MockResponse getSfAuthSuccessResponse() { + return buildSuccessResponse(sfAuthSuccess); + } + + public static MockResponse getSfAuthRefreshedSuccessResponse() { + return buildSuccessResponse(sfAuthRefreshed); + } + + public static MockResponse getSfAuthErrorResponse() { + return new MockResponse() + .setResponseCode(400) + .setBody(sfAuthError) + .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + } + + public static MockResponse getSfQueryResponse() { + return buildSuccessResponse("query successful"); + } + + public static MockResponse getSfQueryUnauthorizedResponse() { + return new MockResponse() + .setResponseCode(401) + .setBody(sfQueryUnauthorizedResponse) + .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + } + + private static MockResponse buildSuccessResponse(final String body) { + return new MockResponse() + .setResponseCode(200) + .setBody(body) + .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + } + + private static String readFile(final String fileName) throws IOException { + return MAPPER + .readValue(new ClassPathResource(fileName).getInputStream(), JsonNode.class) + .toString(); + } +} diff --git a/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthError.json b/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthError.json new file mode 100644 index 0000000..68e013e --- /dev/null +++ b/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthError.json @@ -0,0 +1,4 @@ +{ + "error": "invalid_grant", + "error_description": "authentication failure" +} \ No newline at end of file diff --git a/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthRefreshed.json b/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthRefreshed.json new file mode 100644 index 0000000..7fe6b37 --- /dev/null +++ b/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthRefreshed.json @@ -0,0 +1,8 @@ +{ + "access_token": "new bearerToken", + "instance_url": "instanceUrl", + "id": "tokenId", + "token_type": "Bearer", + "issued_at": "1585760950027", + "signature": "tokenSignature" +} \ No newline at end of file diff --git a/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthSuccess.json b/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthSuccess.json new file mode 100644 index 0000000..a75274c --- /dev/null +++ b/token-manager-for-salesforce-core/src/testFixtures/resources/sfAuthSuccess.json @@ -0,0 +1,8 @@ +{ + "access_token": "bearerToken", + "instance_url": "instanceUrl", + "id": "tokenId", + "token_type": "Bearer", + "issued_at": "1585760950027", + "signature": "tokenSignature" +} \ No newline at end of file diff --git a/token-manager-for-salesforce-core/src/testFixtures/resources/sfQueryUnauthorizedResponse.json b/token-manager-for-salesforce-core/src/testFixtures/resources/sfQueryUnauthorizedResponse.json new file mode 100644 index 0000000..015db27 --- /dev/null +++ b/token-manager-for-salesforce-core/src/testFixtures/resources/sfQueryUnauthorizedResponse.json @@ -0,0 +1,6 @@ +[ + { + "message": "Session expired or invalid", + "errorCode": "INVALID_SESSION_ID" + } +] \ No newline at end of file diff --git a/token-manager-for-salesforce-webflux/build.gradle b/token-manager-for-salesforce-webflux/build.gradle new file mode 100644 index 0000000..77c9dc8 --- /dev/null +++ b/token-manager-for-salesforce-webflux/build.gradle @@ -0,0 +1,95 @@ +// === configure jacoco === + +jacoco { + toolVersion = "${jacocoPluginVersion}" +} + +def jacocoExcludes = [ + '**/AuthWebClient.class', + '**/SalesforceLibraryAutoConfiguration.class', + '**/SalesforceWebClient.class' +] + +jacocoTestReport { + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: jacocoExcludes) + })) + } +} + +jacocoTestCoverageVerification { + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: jacocoExcludes) + })) + } + violationRules { + rule { + limit { + value = 'COVEREDRATIO' + counter = 'LINE' + minimum = 1.00 + } + } + } +} + +// check will fail if minimum code coverage % is not met +check.dependsOn jacocoTestCoverageVerification +// generate a report of code coverage in build directory after test task is run +test.finalizedBy jacocoTestReport + +// === define integration test source set === + +sourceSets { + testintegration { + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + } +} + +idea { + module { + // configures IntelliJ to recognize integrationTest sources as tests + testSourceDirs += sourceSets.testintegration.java.srcDirs + testResourceDirs += sourceSets.testintegration.resources.srcDirs + scopes.TEST.plus += [ + configurations.testintegrationCompileClasspath + ] + } +} + +// define new testintegration task +task testintegration(type: Test) { + systemProperty "spring.profiles.active", System.getProperty("spring.profiles.active") + description = 'Runs integration tests.' + group = 'verification' + useJUnitPlatform() // use Junit5 + testClassesDirs = sourceSets.testintegration.output.classesDirs + classpath = sourceSets.testintegration.runtimeClasspath + shouldRunAfter test + testLogging.exceptionFormat = 'full' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } + // makes lombok available for integration tests + testintegrationImplementation.extendsFrom testImplementation + testintegrationCompileOnly.extendsFrom compileOnly + testintegrationAnnotationProcessor.extendsFrom annotationProcessor +} + +check.dependsOn testintegration + +// === module dependencies === + +dependencies { + api project(':token-manager-for-salesforce-core') + implementation "org.springframework.boot:spring-boot-starter-webflux" + implementation "org.springframework.boot:spring-boot-starter-actuator" + testImplementation "io.projectreactor:reactor-test:${reactorTestVersion}" + testintegrationImplementation(testFixtures(project(":token-manager-for-salesforce-core"))) +} diff --git a/token-manager-for-salesforce-webflux/settings.gradle b/token-manager-for-salesforce-webflux/settings.gradle new file mode 100644 index 0000000..affdf5c --- /dev/null +++ b/token-manager-for-salesforce-webflux/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'token-manager-for-salesforce-webflux' \ No newline at end of file diff --git a/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/AuthWebClient.java b/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/AuthWebClient.java new file mode 100644 index 0000000..ff064ca --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/AuthWebClient.java @@ -0,0 +1,51 @@ +package com.tgt.crm.token.webflux; + +import com.tgt.crm.token.core.SalesforceConfig; +import io.netty.handler.logging.LogLevel; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; +import reactor.netty.transport.logging.AdvancedByteBufFormat; + +@Configuration +@AllArgsConstructor +public class AuthWebClient { + + private final SalesforceConfig salesforceConfig; + + @Bean + @Qualifier("sfAuthWebClient") + @ConditionalOnClass(AdvancedByteBufFormat.class) + public WebClient sfAuthWebClientWiretap(final WebClient.Builder webClientBuilder) { + return webClientBuilder + .clientConnector( + new ReactorClientHttpConnector( + HttpClient.create() + .wiretap( + "com.tgt.crm.token.webflux.sfAuthWebClient", + LogLevel.TRACE, + AdvancedByteBufFormat.TEXTUAL))) + .baseUrl(salesforceConfig.getHost()) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .build(); + } + + @Bean + @Qualifier("sfAuthWebClient") + @ConditionalOnMissingClass("reactor.netty.transport.logging.AdvancedByteBufFormat") + public WebClient sfAuthWebClient(final WebClient.Builder webClientBuilder) { + return webClientBuilder + .clientConnector(new ReactorClientHttpConnector(HttpClient.create())) + .baseUrl(salesforceConfig.getHost()) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .build(); + } +} diff --git a/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceLibraryAutoConfiguration.java b/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceLibraryAutoConfiguration.java new file mode 100644 index 0000000..fc30a67 --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceLibraryAutoConfiguration.java @@ -0,0 +1,12 @@ +package com.tgt.crm.token.webflux; + +import com.tgt.crm.token.core.HttpClientConfig; +import com.tgt.crm.token.core.SalesforceConfig; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({SalesforceConfig.class, HttpClientConfig.class}) +@ComponentScan +public class SalesforceLibraryAutoConfiguration {} diff --git a/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceWebClient.java b/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceWebClient.java new file mode 100644 index 0000000..03845ef --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceWebClient.java @@ -0,0 +1,132 @@ +package com.tgt.crm.token.webflux; + +import com.tgt.crm.token.core.HttpClientConfig; +import com.tgt.crm.token.core.SalesforceConfig; +import io.netty.channel.ChannelOption; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.timeout.ReadTimeoutHandler; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; +import reactor.netty.resources.ConnectionProvider; +import reactor.netty.transport.logging.AdvancedByteBufFormat; + +@Configuration +@AllArgsConstructor +@Slf4j +public class SalesforceWebClient { + + private final SalesforceWebfluxAuthClient salesforceWebfluxAuthClient; + private final SalesforceConfig salesforceConfig; + private final HttpClientConfig httpClientConfig; + + @Bean + @Qualifier("sfWebClient") + @ConditionalOnClass(AdvancedByteBufFormat.class) + public WebClient sfWebClientWiretap(final WebClient.Builder webClientBuilder) { + HttpClient httpClient = + HttpClient.create( + ConnectionProvider.create( + "sfTokenManagerProvider", httpClientConfig.getMaxConnPerRoute())) + .doOnConnected( + conn -> + conn.addHandlerLast( + new ReadTimeoutHandler( + httpClientConfig.getReadTimeout(), TimeUnit.MILLISECONDS))) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, httpClientConfig.getConnectionTimeout()) + .wiretap( + "com.tgt.crm.token.webflux.sfWebClient", + LogLevel.TRACE, + AdvancedByteBufFormat.TEXTUAL); + return webClientBuilder + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .baseUrl(salesforceConfig.getHost()) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .filter(addAuthHeader()) + .filter(retryOnUnauthorized()) + .build(); + } + + /** Uses deprecated configuration to support older versions of spring boot */ + @Bean + @Qualifier("sfWebClient") + @ConditionalOnMissingClass("reactor.netty.transport.logging.AdvancedByteBufFormat") + public WebClient sfWebClient(final WebClient.Builder webClientBuilder) { + HttpClient httpClient = + HttpClient.create( + ConnectionProvider.create( + "sfTokenManagerProvider", httpClientConfig.getMaxConnPerRoute())) + .tcpConfiguration( + tcpClient -> + tcpClient + .option( + ChannelOption.CONNECT_TIMEOUT_MILLIS, + httpClientConfig.getConnectionTimeout()) + .doOnConnected( + conn -> + conn.addHandlerLast( + new ReadTimeoutHandler( + httpClientConfig.getReadTimeout(), + TimeUnit.MILLISECONDS)))); + return webClientBuilder + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .baseUrl(salesforceConfig.getHost()) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .filter(addAuthHeader()) + .filter(retryOnUnauthorized()) + .build(); + } + + private ExchangeFilterFunction addAuthHeader() { + return (request, next) -> + salesforceWebfluxAuthClient + .getToken() + .map( + token -> + ClientRequest.from(request).header(HttpHeaders.AUTHORIZATION, token).build()) + .flatMap(next::exchange); + } + + private ExchangeFilterFunction retryOnUnauthorized() { + return (request, next) -> + next.exchange(request) + .flatMap( + (Function>) + clientResponse -> { + if (clientResponse.statusCode() == HttpStatus.UNAUTHORIZED) { + log.info("received 401 response, refreshing token and retrying request"); + return salesforceWebfluxAuthClient + .refreshToken() + .map( + token -> + ClientRequest.from(request) + .headers( + headers -> + headers.replace( + HttpHeaders.AUTHORIZATION, + Collections.singletonList(token))) + .build()) + .flatMap(next::exchange); + } else { + return Mono.just(clientResponse); + } + }); + } +} diff --git a/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceWebfluxAuthClient.java b/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceWebfluxAuthClient.java new file mode 100644 index 0000000..d34cc5d --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/main/java/com/tgt/crm/token/webflux/SalesforceWebfluxAuthClient.java @@ -0,0 +1,87 @@ +package com.tgt.crm.token.webflux; + +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_COUNTER; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_TYPE_TAG; +import static com.tgt.crm.token.core.SalesforceConstants.TOKEN_REFRESH_EXCEPTION; + +import com.tgt.crm.token.core.SalesforceAuthResponse; +import com.tgt.crm.token.core.SalesforceConfig; +import io.micrometer.core.instrument.MeterRegistry; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; + +@Slf4j +@Configuration +public class SalesforceWebfluxAuthClient { + + private final WebClient authWebClient; + private final SalesforceConfig salesforceConfig; + private final MeterRegistry meterRegistry; + private String token; + + public SalesforceWebfluxAuthClient( + final @Qualifier("sfAuthWebClient") WebClient authWebClient, + final SalesforceConfig salesforceConfig, + final MeterRegistry meterRegistry) { + this.authWebClient = authWebClient; + this.salesforceConfig = salesforceConfig; + this.meterRegistry = meterRegistry; + } + + public Mono getToken() { + // will be null on first call to Salesforce + if (this.token == null) { + log.info("token is null, calling refresh token to generate first token"); + return refreshToken(); + } + return Mono.just(this.token); + } + + public Mono refreshToken() { + return authWebClient + .post() + .uri(salesforceConfig.getAuthUri()) + .body(Mono.just(initAuthString()), String.class) + .retrieve() + .bodyToMono(SalesforceAuthResponse.class) + .retryWhen( + Retry.backoff( + salesforceConfig.getMaxAuthTokenRetries(), + Duration.ofMillis(salesforceConfig.getRetryBackoffDelay())) + .onRetryExhaustedThrow( + (spec, signal) -> { + log.error("retries exhausted"); + // throw original exception instead of RetryExhaustedException wrapper + return signal.failure(); + }) + .doAfterRetry(retry -> log.error("Retry failed. ", retry.failure()))) + .doOnError( + error -> { + log.error("token refresh failed", error); + meterRegistry + .counter(EXCEPTION_COUNTER, EXCEPTION_TYPE_TAG, TOKEN_REFRESH_EXCEPTION) + .increment(); + }) + .doOnSuccess(success -> log.info("token refresh successful")) + .map(val -> this.token = val.getSalesforceAuthToken()); + } + + private String initAuthString() { + return "grant_type=password" + + "&username=" + + salesforceConfig.getUsername() + + "&password=" + + URLEncoder.encode(salesforceConfig.getPassword(), StandardCharsets.UTF_8) + + "&client_id=" + + salesforceConfig.getClientId() + + "&client_secret=" + + salesforceConfig.getClientSecret(); + } +} diff --git a/token-manager-for-salesforce-webflux/src/main/resources/META-INF/spring.factories b/token-manager-for-salesforce-webflux/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..e56d44b --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.tgt.crm.token.webflux.SalesforceLibraryAutoConfiguration \ No newline at end of file diff --git a/token-manager-for-salesforce-webflux/src/test/java/com/tgt/crm/token/webflux/SalesforceWebfluxAuthClientTest.java b/token-manager-for-salesforce-webflux/src/test/java/com/tgt/crm/token/webflux/SalesforceWebfluxAuthClientTest.java new file mode 100644 index 0000000..60067ac --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/test/java/com/tgt/crm/token/webflux/SalesforceWebfluxAuthClientTest.java @@ -0,0 +1,217 @@ +package com.tgt.crm.token.webflux; + +import static com.tgt.crm.token.core.SalesforceConstants.AUTH_URI; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_COUNTER; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_TYPE_TAG; +import static com.tgt.crm.token.core.SalesforceConstants.TOKEN_REFRESH_EXCEPTION; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tgt.crm.token.core.SalesforceConfig; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import java.io.IOException; +import java.time.Duration; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +@Slf4j +@ExtendWith(MockitoExtension.class) +public class SalesforceWebfluxAuthClientTest { + + private static final String BEARER_TOKEN = "Bearer bearerToken"; + private static final String TEST_USER_NAME = "testUserName"; + private static final String TEST_PASSWORD = "testPassword!@#$%^&*()"; + private static final String TEST_CLIENT_ID = "testClientId"; + private static final String TEST_CLIENT_SECRET = "testClientSecret"; + + @Mock private SalesforceConfig salesforceConfig; + @Mock private ExchangeFunction exchangeFunction; + @Mock private MeterRegistry meterRegistry; + @Mock private Counter counter; + + @Captor private ArgumentCaptor requestCaptor; + + private SalesforceWebfluxAuthClient systemUnderTest; + + private static String salesforceAuthResponse; + private static String sfAuthResponseRefreshed; + private static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + try { + salesforceAuthResponse = + MAPPER + .readValue( + new ClassPathResource("salesforceAuthResponse.json").getInputStream(), + JsonNode.class) + .toString(); + sfAuthResponseRefreshed = + MAPPER + .readValue( + new ClassPathResource("salesforceAuthResponseRefreshed.json").getInputStream(), + JsonNode.class) + .toString(); + } catch (IOException e) { + log.error("Error reading resource file", e); + } + } + + @BeforeEach + public void setUp() { + WebClient webClient = WebClient.builder().exchangeFunction(exchangeFunction).build(); + systemUnderTest = new SalesforceWebfluxAuthClient(webClient, salesforceConfig, meterRegistry); + } + + @Test + public void testGetToken_getTokenFirst_useCachedSecond() { + when(salesforceConfig.getUsername()).thenReturn(TEST_USER_NAME); + when(salesforceConfig.getPassword()).thenReturn(TEST_PASSWORD); + when(salesforceConfig.getClientId()).thenReturn(TEST_CLIENT_ID); + when(salesforceConfig.getClientSecret()).thenReturn(TEST_CLIENT_SECRET); + when(salesforceConfig.getAuthUri()).thenReturn(AUTH_URI); + + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(buildMockResponseSuccess()); + + Mono actual = systemUnderTest.getToken(); + StepVerifier.create(actual).expectNextMatches(BEARER_TOKEN::equals).verifyComplete(); + + // should execute callout first time + verify(exchangeFunction, times(1)).exchange(any(ClientRequest.class)); + + Mono actualCached = systemUnderTest.getToken(); + StepVerifier.create(actualCached).expectNextMatches(BEARER_TOKEN::equals).verifyComplete(); + + // verify exchange function was not called again, this means cached value was used + verify(exchangeFunction, times(1)).exchange(requestCaptor.capture()); + ClientRequest actualRequest = requestCaptor.getValue(); + assertEquals(HttpMethod.POST, actualRequest.method()); + assertEquals(AUTH_URI, actualRequest.url().getPath()); + // not possible to read body from ClientRequest currently, verified in integration tests + } + + @Test + public void testGetToken_refreshToken() { + when(salesforceConfig.getUsername()).thenReturn(TEST_USER_NAME); + when(salesforceConfig.getPassword()).thenReturn(TEST_PASSWORD); + when(salesforceConfig.getClientId()).thenReturn(TEST_CLIENT_ID); + when(salesforceConfig.getClientSecret()).thenReturn(TEST_CLIENT_SECRET); + + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(buildMockResponseSuccess()) + .thenReturn(buildMockResponseRefreshed()); + + Mono actual = systemUnderTest.refreshToken(); + StepVerifier.create(actual).expectNextMatches(BEARER_TOKEN::equals).verifyComplete(); + + // should execute callout first time + verify(exchangeFunction, times(1)).exchange(any(ClientRequest.class)); + + Mono actualRefresh = systemUnderTest.refreshToken(); + StepVerifier.create(actualRefresh) + .expectNextMatches("Bearer newBearerToken"::equals) + .verifyComplete(); + + // should refresh even if there is a cached value + verify(exchangeFunction, times(2)).exchange(any(ClientRequest.class)); + } + + @Test + public void testGetToken_fail_retries() { + when(salesforceConfig.getUsername()).thenReturn(TEST_USER_NAME); + when(salesforceConfig.getPassword()).thenReturn(TEST_PASSWORD); + when(salesforceConfig.getClientId()).thenReturn(TEST_CLIENT_ID); + when(salesforceConfig.getClientSecret()).thenReturn(TEST_CLIENT_SECRET); + + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(buildMockResponseFailure()) + .thenReturn(buildMockResponseSuccess()); + when(salesforceConfig.getMaxAuthTokenRetries()).thenReturn(3); + when(salesforceConfig.getRetryBackoffDelay()).thenReturn(50); + + Mono actual = systemUnderTest.refreshToken(); + StepVerifier.create(actual).expectNextMatches(BEARER_TOKEN::equals).verifyComplete(); + + // should fail on first callout then succeed + verify(exchangeFunction, times(2)).exchange(any(ClientRequest.class)); + } + + @Test + public void testGetToken_fail_shouldNotUpdateToken() { + when(salesforceConfig.getUsername()).thenReturn(TEST_USER_NAME); + when(salesforceConfig.getPassword()).thenReturn(TEST_PASSWORD); + when(salesforceConfig.getClientId()).thenReturn(TEST_CLIENT_ID); + when(salesforceConfig.getClientSecret()).thenReturn(TEST_CLIENT_SECRET); + + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(buildMockResponseSuccess()) + .thenReturn(buildMockResponseFailure()); + when(meterRegistry.counter(EXCEPTION_COUNTER, EXCEPTION_TYPE_TAG, TOKEN_REFRESH_EXCEPTION)) + .thenReturn(counter); + when(salesforceConfig.getMaxAuthTokenRetries()).thenReturn(3); + when(salesforceConfig.getRetryBackoffDelay()).thenReturn(50); + + Mono actual = systemUnderTest.refreshToken(); + StepVerifier.create(actual).expectNextMatches(BEARER_TOKEN::equals).verifyComplete(); + // should succeed on first refresh + verify(exchangeFunction, times(1)).exchange(any(ClientRequest.class)); + + // on subsequent retry attempts it will fail + // one attempt + 3 retries, throws RetryExhaustedException, child of RuntimeException + Mono actualRefresh = systemUnderTest.refreshToken(); + StepVerifier.create(actualRefresh) + .expectError(RuntimeException.class) + .verify(Duration.ofSeconds(10)); + + // 1 for original refresh token, 1 for 2nd refresh token, 3 retires, 5 total calls + verify(exchangeFunction, times(5)).exchange(any(ClientRequest.class)); + + verify(counter).increment(); + } + + private Mono buildMockResponseSuccess() { + return Mono.just( + ClientResponse.create(HttpStatus.OK) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(salesforceAuthResponse) + .build()); + } + + private Mono buildMockResponseRefreshed() { + return Mono.just( + ClientResponse.create(HttpStatus.OK) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(sfAuthResponseRefreshed) + .build()); + } + + private Mono buildMockResponseFailure() { + return Mono.just( + ClientResponse.create(HttpStatus.INTERNAL_SERVER_ERROR) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build()); + } +} diff --git a/token-manager-for-salesforce-webflux/src/test/resources/salesforceAuthResponse.json b/token-manager-for-salesforce-webflux/src/test/resources/salesforceAuthResponse.json new file mode 100644 index 0000000..a75274c --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/test/resources/salesforceAuthResponse.json @@ -0,0 +1,8 @@ +{ + "access_token": "bearerToken", + "instance_url": "instanceUrl", + "id": "tokenId", + "token_type": "Bearer", + "issued_at": "1585760950027", + "signature": "tokenSignature" +} \ No newline at end of file diff --git a/token-manager-for-salesforce-webflux/src/test/resources/salesforceAuthResponseRefreshed.json b/token-manager-for-salesforce-webflux/src/test/resources/salesforceAuthResponseRefreshed.json new file mode 100644 index 0000000..ae8cf1f --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/test/resources/salesforceAuthResponseRefreshed.json @@ -0,0 +1,8 @@ +{ + "access_token": "newBearerToken", + "instance_url": "instanceUrl", + "id": "tokenId", + "token_type": "Bearer", + "issued_at": "1585760950027", + "signature": "tokenSignature" +} \ No newline at end of file diff --git a/token-manager-for-salesforce-webflux/src/test/resources/sfSobjectSuccessResponse.json b/token-manager-for-salesforce-webflux/src/test/resources/sfSobjectSuccessResponse.json new file mode 100644 index 0000000..cef983b --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/test/resources/sfSobjectSuccessResponse.json @@ -0,0 +1,5 @@ +{ + "id": "5000v0000066K4nAAE", + "success": true, + "errors": [] +} \ No newline at end of file diff --git a/token-manager-for-salesforce-webflux/src/testintegration/java/com/tgt/crm/token/webflux/WebfluxIntegrationTest.java b/token-manager-for-salesforce-webflux/src/testintegration/java/com/tgt/crm/token/webflux/WebfluxIntegrationTest.java new file mode 100644 index 0000000..b90ee6c --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/testintegration/java/com/tgt/crm/token/webflux/WebfluxIntegrationTest.java @@ -0,0 +1,256 @@ +package com.tgt.crm.token.webflux; + +import static com.tgt.crm.token.core.MockResponseUtil.getSfAuthErrorResponse; +import static com.tgt.crm.token.core.MockResponseUtil.getSfAuthRefreshedSuccessResponse; +import static com.tgt.crm.token.core.MockResponseUtil.getSfAuthSuccessResponse; +import static com.tgt.crm.token.core.MockResponseUtil.getSfQueryResponse; +import static com.tgt.crm.token.core.MockResponseUtil.getSfQueryUnauthorizedResponse; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_COUNTER; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_TYPE_TAG; +import static com.tgt.crm.token.core.SalesforceConstants.TOKEN_REFRESH_EXCEPTION; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.tgt.crm.token.core.BaseIntegrationTest; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import java.time.Duration; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +@SpringBootTest( + classes = { + SalesforceLibraryAutoConfiguration.class, + WebClientAutoConfiguration.class, + SimpleMeterRegistry.class + }) +@Slf4j +public class WebfluxIntegrationTest extends BaseIntegrationTest { + + @Qualifier("sfWebClient") + @Autowired + private WebClient webClient; + + @Autowired private MeterRegistry meterRegistry; + + @BeforeEach + public void setupEach() { + meterRegistry.clear(); + } + + @Test + public void makeRequest_noTokenInCache_authSuccess_reqSuccess() throws InterruptedException { + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount = mockWebServer.getRequestCount(); + + Mono> actual = + webClient.get().uri(SF_URL).retrieve().toEntity(new ParameterizedTypeReference<>() {}); + + StepVerifier.create(actual) + .assertNext( + nxt -> { + assertEquals(HttpStatus.OK, nxt.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, nxt.getBody()); + }) + .verifyComplete(); + + validateAuthRequest(); + validateSfRequest(); + + assertEquals(2, mockWebServer.getRequestCount() - prevReqCount); + } + + @Test + void makeRequest_tokenInCache_noAuthCall_reqSuccess() throws InterruptedException { + // === make first request to cache token === + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount1 = mockWebServer.getRequestCount(); + + Mono> actual1 = + webClient.get().uri(SF_URL).retrieve().toEntity(new ParameterizedTypeReference<>() {}); + + StepVerifier.create(actual1) + .assertNext( + nxt -> { + assertEquals(HttpStatus.OK, nxt.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, nxt.getBody()); + }) + .verifyComplete(); + + validateAuthRequest(); + validateSfRequest(); + + assertEquals(2, mockWebServer.getRequestCount() - prevReqCount1); + + // === make second request to test cached token is used === + mockWebServer.enqueue(getSfQueryResponse()); + int prevReqCount2 = mockWebServer.getRequestCount(); + + Mono> actual2 = + webClient.get().uri(SF_URL).retrieve().toEntity(new ParameterizedTypeReference<>() {}); + + StepVerifier.create(actual2) + .assertNext( + nxt -> { + assertEquals(HttpStatus.OK, nxt.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, nxt.getBody()); + }) + .verifyComplete(); + + validateSfRequest(); + assertEquals(1, mockWebServer.getRequestCount() - prevReqCount2); + } + + @Test + void makeRequest_noTokenInCache_authFail_retryAuthSuccess_reqSuccess() + throws InterruptedException { + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount = mockWebServer.getRequestCount(); + + Mono> actual = + webClient.get().uri(SF_URL).retrieve().toEntity(new ParameterizedTypeReference<>() {}); + + StepVerifier.create(actual) + .assertNext( + nxt -> { + assertEquals(HttpStatus.OK, nxt.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, nxt.getBody()); + }) + .verifyComplete(); + + validateAuthRequest(); + validateAuthRequest(); + validateSfRequest(); + + assertEquals(3, mockWebServer.getRequestCount() - prevReqCount); + } + + @Test + void makeRequest_noTokenInCache_authFail_retryAuthFail_retryAuthSuccess_reqSuccess() + throws InterruptedException { + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount = mockWebServer.getRequestCount(); + + Mono> actual = + webClient.get().uri(SF_URL).retrieve().toEntity(new ParameterizedTypeReference<>() {}); + + StepVerifier.create(actual) + .assertNext( + nxt -> { + assertEquals(HttpStatus.OK, nxt.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, nxt.getBody()); + }) + .verifyComplete(); + + validateAuthRequest(); + validateAuthRequest(); + validateAuthRequest(); + validateSfRequest(); + + assertEquals(4, mockWebServer.getRequestCount() - prevReqCount); + } + + @Test + void makeRequest_noTokenInCache_authFail_exhaustRetries_reqFail() throws InterruptedException { + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthErrorResponse()); + + int prevReqCount = mockWebServer.getRequestCount(); + + Mono> actual = + webClient.get().uri(SF_URL).retrieve().toEntity(new ParameterizedTypeReference<>() {}); + + StepVerifier.create(actual) + .expectError(RuntimeException.class) + .verify(Duration.ofSeconds(TIMEOUT * 3)); + + validateAuthRequest(); + validateAuthRequest(); + validateAuthRequest(); + validateAuthRequest(); + + assertEquals(4, mockWebServer.getRequestCount() - prevReqCount); + + assertEquals( + 1, + meterRegistry + .get(EXCEPTION_COUNTER) + .tag(EXCEPTION_TYPE_TAG, TOKEN_REFRESH_EXCEPTION) + .counter() + .count()); + } + + @Test + void makeRequest_expiredTokenInCache_reqFails_tokenRefreshed_reqSuccess() + throws InterruptedException { + // === make first request to cache token === + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount1 = mockWebServer.getRequestCount(); + + Mono> actual1 = + webClient.get().uri(SF_URL).retrieve().toEntity(new ParameterizedTypeReference<>() {}); + + StepVerifier.setDefaultTimeout(Duration.ofSeconds(TIMEOUT)); + StepVerifier.create(actual1) + .assertNext( + nxt -> { + assertEquals(HttpStatus.OK, nxt.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, nxt.getBody()); + }) + .verifyComplete(); + + assertEquals(2, mockWebServer.getRequestCount() - prevReqCount1); + + validateAuthRequest(); + validateSfRequest(); + + // === make second request to test token refresh after cached is unauthorized === + mockWebServer.enqueue(getSfQueryUnauthorizedResponse()); + mockWebServer.enqueue(getSfAuthRefreshedSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount2 = mockWebServer.getRequestCount(); + + Mono> actual2 = + webClient.get().uri(SF_URL).retrieve().toEntity(new ParameterizedTypeReference<>() {}); + + StepVerifier.create(actual2) + .assertNext( + nxt -> { + assertEquals(HttpStatus.OK, nxt.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, nxt.getBody()); + }) + .verifyComplete(); + + assertEquals(3, mockWebServer.getRequestCount() - prevReqCount2); + + validateSfRequest(); + validateAuthRequest(); + validateSfRequest("Bearer new bearerToken"); + } +} diff --git a/token-manager-for-salesforce-webflux/src/testintegration/resources/application.yml b/token-manager-for-salesforce-webflux/src/testintegration/resources/application.yml new file mode 100644 index 0000000..90ddbde --- /dev/null +++ b/token-manager-for-salesforce-webflux/src/testintegration/resources/application.yml @@ -0,0 +1,10 @@ +logging: + level: + com.tgt.crm: TRACE + +salesforce: + username: username + password: password!@#$%^&*() + client-id: clientId + client-secret: clientSecret + retry-backoff-delay: 50 diff --git a/token-manager-for-salesforce-webmvc/build.gradle b/token-manager-for-salesforce-webmvc/build.gradle new file mode 100644 index 0000000..6a10a56 --- /dev/null +++ b/token-manager-for-salesforce-webmvc/build.gradle @@ -0,0 +1,98 @@ +// === configure jacoco === + +jacoco { + toolVersion = "${jacocoPluginVersion}" +} + +def jacocoExcludes = [ + '**/AuthRestTemplate.class', + '**/SalesforceLibraryAutoConfiguration.class', + '**/SalesforceRestTemplate.class', + '**/WebMvcHttpClientConfig.class', + '**/WebMvcHttpClientConstants.class' +] + +jacocoTestReport { + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: jacocoExcludes) + })) + } +} + +jacocoTestCoverageVerification { + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: jacocoExcludes) + })) + } + violationRules { + rule { + limit { + value = 'COVEREDRATIO' + counter = 'LINE' + minimum = 0.70 + } + } + } +} + +// check will fail if minimum code coverage % is not met +check.dependsOn jacocoTestCoverageVerification +// generate a report of code coverage in build directory after test task is run +test.finalizedBy jacocoTestReport + +// === define integration test source set === + +sourceSets { + testintegration { + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + } +} + +idea { + module { + // configures IntelliJ to recognize integrationTest sources as tests + testSourceDirs += sourceSets.testintegration.java.srcDirs + testResourceDirs += sourceSets.testintegration.resources.srcDirs + scopes.TEST.plus += [ + configurations.testintegrationCompileClasspath + ] + } +} + +// define new testintegration task +task testintegration(type: Test) { + systemProperty "spring.profiles.active", System.getProperty("spring.profiles.active") + description = 'Runs integration tests.' + group = 'verification' + useJUnitPlatform() // use Junit5 + testClassesDirs = sourceSets.testintegration.output.classesDirs + classpath = sourceSets.testintegration.runtimeClasspath + shouldRunAfter test + testLogging.exceptionFormat = 'full' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } + // makes lombok available for integration tests + testintegrationImplementation.extendsFrom testImplementation + testintegrationCompileOnly.extendsFrom compileOnly + testintegrationAnnotationProcessor.extendsFrom annotationProcessor +} + +check.dependsOn testintegration + +// === module dependencies === + +dependencies { + api project(':token-manager-for-salesforce-core') + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.retry:spring-retry' + implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation "org.springframework.boot:spring-boot-starter-actuator" + testintegrationImplementation(testFixtures(project(":token-manager-for-salesforce-core"))) +} diff --git a/token-manager-for-salesforce-webmvc/settings.gradle b/token-manager-for-salesforce-webmvc/settings.gradle new file mode 100644 index 0000000..12f42ff --- /dev/null +++ b/token-manager-for-salesforce-webmvc/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'token-manager-for-salesforce-webmvc' \ No newline at end of file diff --git a/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/AuthRestTemplate.java b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/AuthRestTemplate.java new file mode 100644 index 0000000..850f992 --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/AuthRestTemplate.java @@ -0,0 +1,27 @@ +package com.tgt.crm.token.mvc; + +import com.tgt.crm.token.core.SalesforceConfig; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; + +@AllArgsConstructor +@Slf4j +@Configuration +public class AuthRestTemplate { + + private final SalesforceConfig salesforceConfig; + + @Bean + public RestTemplate sfAuthRestTemplate(final RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder + .rootUri(salesforceConfig.getHost()) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .build(); + } +} diff --git a/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceLibraryAutoConfiguration.java b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceLibraryAutoConfiguration.java new file mode 100644 index 0000000..4f84ab7 --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceLibraryAutoConfiguration.java @@ -0,0 +1,18 @@ +package com.tgt.crm.token.mvc; + +import com.tgt.crm.token.core.HttpClientConfig; +import com.tgt.crm.token.core.SalesforceConfig; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.retry.annotation.EnableRetry; + +@Configuration +@EnableConfigurationProperties({ + SalesforceConfig.class, + HttpClientConfig.class, + WebMvcHttpClientConfig.class +}) +@EnableRetry +@ComponentScan +public class SalesforceLibraryAutoConfiguration {} diff --git a/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceMvcAuthClient.java b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceMvcAuthClient.java new file mode 100644 index 0000000..2b8abab --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceMvcAuthClient.java @@ -0,0 +1,109 @@ +package com.tgt.crm.token.mvc; + +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_COUNTER; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_TYPE_TAG; +import static com.tgt.crm.token.core.SalesforceConstants.MAX_AUTH_TOKEN_RETRIES_DEFAULT; +import static com.tgt.crm.token.core.SalesforceConstants.RETRY_BACKOFF_DELAY_DEFAULT; +import static com.tgt.crm.token.core.SalesforceConstants.RETRY_BACKOFF_MULTIPLIER_DEFAULT; +import static com.tgt.crm.token.core.SalesforceConstants.TOKEN_REFRESH_EXCEPTION; + +import com.tgt.crm.token.core.SalesforceAuthResponse; +import com.tgt.crm.token.core.SalesforceConfig; +import io.micrometer.core.instrument.MeterRegistry; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; +import org.springframework.web.client.RestTemplate; + +@Slf4j +@Configuration +public class SalesforceMvcAuthClient { + + private final SalesforceConfig salesforceConfig; + private final RestTemplate restTemplate; + private final MeterRegistry meterRegistry; + private String token; + + public SalesforceMvcAuthClient( + final SalesforceConfig salesforceConfig, + final @Qualifier("sfAuthRestTemplate") RestTemplate sfAuthRestTemplate, + final MeterRegistry meterRegistry) { + this.restTemplate = sfAuthRestTemplate; + this.salesforceConfig = salesforceConfig; + this.meterRegistry = meterRegistry; + } + + /** + * Use to retrieve the cached token if there is one. + * + * @return the Salesforce oAuth bearer token + */ + public String getToken() { + return this.token == null ? null : this.token; + } + + /** + * Generates a new Salesforce token by calling Salesforce OAuth endpoint. Note that the @Retryable + * annotation only works if this method is called from outside of this class. That is the reason + * we return null from getToken() instead of calling refreshToken() there. This way we can check + * if getToken() is null from wherever we call it and call generateToken() from outside of this + * class if it is. + */ + @Retryable( + maxAttemptsExpression = + "${salesforce.max-auth-token-retries:" + MAX_AUTH_TOKEN_RETRIES_DEFAULT + "}", + backoff = + @Backoff( + multiplierExpression = + "${salesforce.retry-backoff-multiplier:" + RETRY_BACKOFF_MULTIPLIER_DEFAULT + "}", + delayExpression = + "${salesforce.retry-backoff-delay:" + RETRY_BACKOFF_DELAY_DEFAULT + "}")) + public String refreshToken() { + log.debug("generateToken is called"); + + ResponseEntity salesforceAuthResponseEntity = + restTemplate.exchange( + salesforceConfig.getAuthUri(), + HttpMethod.POST, + new HttpEntity<>(initAuthString()), + SalesforceAuthResponse.class); + + assert salesforceAuthResponseEntity.getBody() != null + : "salesforce auth response body should never be null"; + this.token = salesforceAuthResponseEntity.getBody().getSalesforceAuthToken(); + log.info("token successfully generated"); + return this.token; + } + + @Recover + @SuppressWarnings("PMD.NullAssignment") + public String handleRefreshFailure(final Throwable ex) { + log.error("token refresh failed", ex); + meterRegistry + .counter(EXCEPTION_COUNTER, EXCEPTION_TYPE_TAG, TOKEN_REFRESH_EXCEPTION) + .increment(); + // set to null so the next time getToken is called, it will try to refresh token again + this.token = null; + return null; + } + + private String initAuthString() { + return "grant_type=password" + + "&username=" + + salesforceConfig.getUsername() + + "&password=" + + URLEncoder.encode(salesforceConfig.getPassword(), StandardCharsets.UTF_8) + + "&client_id=" + + salesforceConfig.getClientId() + + "&client_secret=" + + salesforceConfig.getClientSecret(); + } +} diff --git a/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceRestTemplate.java b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceRestTemplate.java new file mode 100644 index 0000000..df74cb9 --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceRestTemplate.java @@ -0,0 +1,84 @@ +package com.tgt.crm.token.mvc; + +import com.tgt.crm.token.core.HttpClientConfig; +import com.tgt.crm.token.core.SalesforceConfig; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ServiceUnavailableRetryStrategy; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.protocol.HttpContext; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@AllArgsConstructor +@Configuration +@Slf4j +public class SalesforceRestTemplate { + + private final SalesforceRestTemplateInterceptor sfRestTemplateInterceptor; + private final SalesforceConfig salesforceConfig; + private final HttpClientConfig httpClientConfig; + private final WebMvcHttpClientConfig webMvcHttpClientConfig; + + @Bean + public RestTemplate sfRestTemplate(final RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder + .requestFactory(this::getHttpFactory) + .rootUri(salesforceConfig.getHost()) + .additionalInterceptors(sfRestTemplateInterceptor) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + } + + private ClientHttpRequestFactory getHttpFactory() { + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(webMvcHttpClientConfig.getMaxPools()); + connectionManager.setDefaultMaxPerRoute(httpClientConfig.getMaxConnPerRoute()); + HttpClient httpClient = + HttpClientBuilder.create() + .setConnectionManager(connectionManager) + .setRetryHandler( + (exception, executionCount, context) -> { + log.error( + "{} exception thrown during execution count {} with message {}", + exception.getClass(), + executionCount, + exception.getMessage()); + return executionCount < webMvcHttpClientConfig.getRetries(); + }) + .setServiceUnavailableRetryStrategy( + new ServiceUnavailableRetryStrategy() { + @Override + public boolean retryRequest( + final HttpResponse response, + final int executionCount, + final HttpContext context) { + return HttpStatus.valueOf(response.getStatusLine().getStatusCode()) + .is5xxServerError() + && executionCount < webMvcHttpClientConfig.getRetries(); + } + + @Override + public long getRetryInterval() { + return webMvcHttpClientConfig.getRetryInterval(); + } + }) + .build(); + HttpComponentsClientHttpRequestFactory httpFactory = + new HttpComponentsClientHttpRequestFactory(httpClient); + httpFactory.setReadTimeout(httpClientConfig.getReadTimeout()); + httpFactory.setConnectionRequestTimeout(webMvcHttpClientConfig.getConnectionRequestTimeout()); + httpFactory.setConnectTimeout(httpClientConfig.getConnectionTimeout()); + return httpFactory; + } +} diff --git a/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceRestTemplateInterceptor.java b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceRestTemplateInterceptor.java new file mode 100644 index 0000000..b97b111 --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/SalesforceRestTemplateInterceptor.java @@ -0,0 +1,88 @@ +package com.tgt.crm.token.mvc; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.SerializationUtils; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.lang.NonNull; + +@AllArgsConstructor +@Slf4j +@Configuration +public class SalesforceRestTemplateInterceptor implements ClientHttpRequestInterceptor { + + private final SalesforceMvcAuthClient salesForceMvcAuthClient; + + @Override + @NonNull + public ClientHttpResponse intercept( + final HttpRequest request, + @NonNull final byte[] body, + final ClientHttpRequestExecution execution) + throws IOException { + log.debug("Entering intercept method for Salesforce call"); + + String token = + salesForceMvcAuthClient.getToken() == null + ? salesForceMvcAuthClient.refreshToken() + : salesForceMvcAuthClient.getToken(); + + request.getHeaders().add(HttpHeaders.AUTHORIZATION, token); + request.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + logRequest(request, body); + + ClientHttpResponse response = execution.execute(request, body); + + logResponse(response); + + // if we get a 401, refresh the token and try request again + if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) { + log.info("received 401 response, refreshing token"); + response.close(); + request.getHeaders().set(HttpHeaders.AUTHORIZATION, salesForceMvcAuthClient.refreshToken()); + response = execution.execute(request, body); + logResponse(response); + } + + return response; + } + + private void logRequest(final HttpRequest request, final byte[] body) { + if (log.isTraceEnabled()) { + HttpHeaders headerDeepCopy = SerializationUtils.clone(request.getHeaders()); + headerDeepCopy.setBearerAuth("************"); + log.trace( + "===========================request begin============================================="); + log.trace("URI : {}", request.getURI()); + log.trace("Method : {}", request.getMethod()); + log.trace("Headers : {}", headerDeepCopy); + log.trace("Request body: {}", new String(body, StandardCharsets.UTF_8)); + log.trace( + "==========================request end==============================================="); + } + } + + private void logResponse(final ClientHttpResponse response) throws IOException { + if (log.isTraceEnabled()) { + HttpHeaders headerDeepCopy = SerializationUtils.clone(response.getHeaders()); + headerDeepCopy.setBearerAuth("************"); + log.trace( + "============================response begin=========================================="); + log.trace("Status code : {}", response.getStatusCode()); + log.trace("Status text : {}", response.getStatusText()); + log.trace("Headers : {}", headerDeepCopy); + log.trace( + "=======================response end================================================="); + } + } +} diff --git a/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/WebMvcHttpClientConfig.java b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/WebMvcHttpClientConfig.java new file mode 100644 index 0000000..3d589ed --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/WebMvcHttpClientConfig.java @@ -0,0 +1,13 @@ +package com.tgt.crm.token.mvc; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Data +@ConfigurationProperties("salesforce.httpclient.webmvc") +public class WebMvcHttpClientConfig { + private int maxPools = 50; + private int connectionRequestTimeout = 30_000; + private int retries = 3; + private int retryInterval = 2_000; +} diff --git a/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/WebMvcHttpClientConstants.java b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/WebMvcHttpClientConstants.java new file mode 100644 index 0000000..753e57f --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/main/java/com/tgt/crm/token/mvc/WebMvcHttpClientConstants.java @@ -0,0 +1,12 @@ +package com.tgt.crm.token.mvc; + +@SuppressWarnings("PMD.LongVariable") +public final class WebMvcHttpClientConstants { + + public static final int MAX_POOLS_DEFAULT = 50; + public static final int CONNECTION_REQUEST_TIMEOUT_DEFAULT = 30_000; + public static final int RETRIES_DEFAULT = 3; + public static final int RETRY_INTERVAL_DEFAULT = 2_000; + + private WebMvcHttpClientConstants() {} +} diff --git a/token-manager-for-salesforce-webmvc/src/main/resources/META-INF/spring.factories b/token-manager-for-salesforce-webmvc/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..d19356c --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.tgt.crm.token.mvc.SalesforceLibraryAutoConfiguration \ No newline at end of file diff --git a/token-manager-for-salesforce-webmvc/src/test/java/com/tgt/crm/token/mvc/SalesforceMvcAuthClientTest.java b/token-manager-for-salesforce-webmvc/src/test/java/com/tgt/crm/token/mvc/SalesforceMvcAuthClientTest.java new file mode 100644 index 0000000..c4b54ba --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/test/java/com/tgt/crm/token/mvc/SalesforceMvcAuthClientTest.java @@ -0,0 +1,135 @@ +package com.tgt.crm.token.mvc; + +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_COUNTER; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_TYPE_TAG; +import static com.tgt.crm.token.core.SalesforceConstants.TOKEN_REFRESH_EXCEPTION; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.tgt.crm.token.core.SalesforceAuthResponse; +import com.tgt.crm.token.core.SalesforceConfig; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings("PMD.LinguisticNaming") +public class SalesforceMvcAuthClientTest { + + private static final String TEST_TOKEN_TYPE = "testTokenType"; + private static final String TEST_ACCESS_TOKEN = "testAccessToken"; + private static final String EXPECTED_TOKEN = TEST_TOKEN_TYPE + " " + TEST_ACCESS_TOKEN; + private static final String TEST_USER_NAME = "testUserName"; + private static final String TEST_PASSWORD = "testPassword!@#$%^&*()"; + private static final String TEST_PASSWORD_ENCODED = "testPassword%21%40%23%24%25%5E%26*%28%29"; + private static final String TEST_CLIENT_ID = "testClientId"; + private static final String TEST_CLIENT_SECRET = "testClientSecret"; + private static final String REQUEST_ENTITY_BODY = + "grant_type=password" + + "&username=" + + TEST_USER_NAME + + "&password=" + + TEST_PASSWORD_ENCODED + + "&client_id=" + + TEST_CLIENT_ID + + "&client_secret=" + + TEST_CLIENT_SECRET; + private static final String AUTH_URI = "/services/oauth2/token"; + + @InjectMocks private SalesforceMvcAuthClient tested; + @Mock private RestTemplate restTemplate; + @Mock private SalesforceConfig salesforceConfig; + @Mock private MeterRegistry meterRegistry; + @Mock private Counter counter; + + private ResponseEntity responseEntity; + private HttpEntity requestEntity; + + @BeforeEach + public void setUpEach() { + this.requestEntity = new HttpEntity<>(REQUEST_ENTITY_BODY); + + SalesforceAuthResponse validResponse = new SalesforceAuthResponse(); + validResponse.setTokenType(TEST_TOKEN_TYPE); + validResponse.setAccessToken(TEST_ACCESS_TOKEN); + this.responseEntity = new ResponseEntity<>(validResponse, HttpStatus.OK); + } + + @Test + public void getToken_noTokenInCache_nullResponse() { + assertNull(tested.getToken()); + } + + @Test + public void refreshToken_successfulResponse_tokenReturned() { + when(salesforceConfig.getUsername()).thenReturn(TEST_USER_NAME); + when(salesforceConfig.getPassword()).thenReturn(TEST_PASSWORD); + when(salesforceConfig.getClientId()).thenReturn(TEST_CLIENT_ID); + when(salesforceConfig.getClientSecret()).thenReturn(TEST_CLIENT_SECRET); + when(salesforceConfig.getAuthUri()).thenReturn(AUTH_URI); + + when(restTemplate.exchange( + AUTH_URI, HttpMethod.POST, requestEntity, SalesforceAuthResponse.class)) + .thenReturn(responseEntity); + + assertEquals(EXPECTED_TOKEN, tested.refreshToken()); + + verify(restTemplate) + .exchange(AUTH_URI, HttpMethod.POST, requestEntity, SalesforceAuthResponse.class); + } + + @Test + public void getToken_tokenInCache_cachedTokenReturned() { + when(salesforceConfig.getUsername()).thenReturn(TEST_USER_NAME); + when(salesforceConfig.getPassword()).thenReturn(TEST_PASSWORD); + when(salesforceConfig.getClientId()).thenReturn(TEST_CLIENT_ID); + when(salesforceConfig.getClientSecret()).thenReturn(TEST_CLIENT_SECRET); + when(salesforceConfig.getAuthUri()).thenReturn(AUTH_URI); + + when(restTemplate.exchange( + AUTH_URI, HttpMethod.POST, requestEntity, SalesforceAuthResponse.class)) + .thenReturn(responseEntity); + + tested.refreshToken(); + + assertEquals(EXPECTED_TOKEN, tested.getToken()); + + verify(restTemplate) + .exchange(AUTH_URI, HttpMethod.POST, requestEntity, SalesforceAuthResponse.class); + } + + @Test + public void testHandleRefreshFailure() { + when(salesforceConfig.getUsername()).thenReturn(TEST_USER_NAME); + when(salesforceConfig.getPassword()).thenReturn(TEST_PASSWORD); + when(salesforceConfig.getClientId()).thenReturn(TEST_CLIENT_ID); + when(salesforceConfig.getClientSecret()).thenReturn(TEST_CLIENT_SECRET); + when(salesforceConfig.getAuthUri()).thenReturn(AUTH_URI); + when(meterRegistry.counter(EXCEPTION_COUNTER, EXCEPTION_TYPE_TAG, TOKEN_REFRESH_EXCEPTION)) + .thenReturn(counter); + + when(restTemplate.exchange( + AUTH_URI, HttpMethod.POST, requestEntity, SalesforceAuthResponse.class)) + .thenReturn(responseEntity); + + assertEquals(EXPECTED_TOKEN, tested.refreshToken()); + + assertNull(tested.handleRefreshFailure(new RuntimeException("test exception"))); + + assertNull(tested.getToken()); + + verify(counter).increment(); + } +} diff --git a/token-manager-for-salesforce-webmvc/src/test/java/com/tgt/crm/token/mvc/SalesforceRestTemplateInterceptorTest.java b/token-manager-for-salesforce-webmvc/src/test/java/com/tgt/crm/token/mvc/SalesforceRestTemplateInterceptorTest.java new file mode 100644 index 0000000..8053d2b --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/test/java/com/tgt/crm/token/mvc/SalesforceRestTemplateInterceptorTest.java @@ -0,0 +1,110 @@ +package com.tgt.crm.token.mvc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.mock.http.client.MockClientHttpResponse; + +@SuppressWarnings("ConstantConditions") +@ExtendWith(MockitoExtension.class) +public class SalesforceRestTemplateInterceptorTest { + + private static final byte[] TEST_BYTE_ARRAY = "body".getBytes(StandardCharsets.UTF_8); + private static final String TEST_TOKEN_INVALID = "test_token_invalid"; + private static final String TEST_TOKEN = "test_token"; + + @Mock private SalesforceMvcAuthClient authClient; + @Mock private ClientHttpRequestExecution execution; + @InjectMocks private SalesforceRestTemplateInterceptor tested; + + @Test + public void nullToken_refreshToken_reqSuccess() throws IOException { + MockClientHttpRequest request = new MockClientHttpRequest(); + ClientHttpResponse response = new MockClientHttpResponse(TEST_BYTE_ARRAY, HttpStatus.OK); + + when(authClient.getToken()).thenReturn(null); + when(authClient.refreshToken()).thenReturn(TEST_TOKEN); + when(execution.execute(request, TEST_BYTE_ARRAY)).thenReturn(response); + + ClientHttpResponse actualResponse = tested.intercept(request, TEST_BYTE_ARRAY, execution); + + assertEquals(response, actualResponse); + + ArgumentCaptor argument = ArgumentCaptor.forClass(HttpRequest.class); + verify(execution).execute(argument.capture(), eq(TEST_BYTE_ARRAY)); + assertEquals( + TEST_TOKEN, argument.getValue().getHeaders().get(HttpHeaders.AUTHORIZATION).get(0)); + assertEquals( + MediaType.APPLICATION_JSON_VALUE, + argument.getValue().getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0)); + } + + @Test + public void validToken_processRequest_noRefresh() throws IOException { + MockClientHttpRequest request = new MockClientHttpRequest(); + ClientHttpResponse response = new MockClientHttpResponse(TEST_BYTE_ARRAY, HttpStatus.OK); + + when(authClient.getToken()).thenReturn(TEST_TOKEN); + when(execution.execute(request, TEST_BYTE_ARRAY)).thenReturn(response); + + ClientHttpResponse actualResponse = tested.intercept(request, TEST_BYTE_ARRAY, execution); + + verify(authClient, never()).refreshToken(); + assertEquals(response, actualResponse); + + ArgumentCaptor argument = ArgumentCaptor.forClass(HttpRequest.class); + verify(execution).execute(argument.capture(), eq(TEST_BYTE_ARRAY)); + assertEquals( + TEST_TOKEN, argument.getValue().getHeaders().get(HttpHeaders.AUTHORIZATION).get(0)); + assertEquals( + MediaType.APPLICATION_JSON_VALUE, + argument.getValue().getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0)); + } + + @Test + public void invalidToken_reqFails_tokenRefreshed_reqSuccess() throws IOException { + MockClientHttpRequest request = new MockClientHttpRequest(); + ClientHttpResponse expectedResponse = + new MockClientHttpResponse(TEST_BYTE_ARRAY, HttpStatus.OK); + ClientHttpResponse unauthorizedResponse = + new MockClientHttpResponse(TEST_BYTE_ARRAY, HttpStatus.UNAUTHORIZED); + + when(authClient.getToken()).thenReturn(TEST_TOKEN_INVALID); + when(authClient.refreshToken()).thenReturn(TEST_TOKEN); + when(execution.execute(request, TEST_BYTE_ARRAY)) + .thenReturn(unauthorizedResponse) + .thenReturn(expectedResponse); + + ClientHttpResponse actualResponse = tested.intercept(request, TEST_BYTE_ARRAY, execution); + + assertEquals(expectedResponse, actualResponse); + + ArgumentCaptor argument = ArgumentCaptor.forClass(HttpRequest.class); + verify(execution, times(2)).execute(argument.capture(), eq(TEST_BYTE_ARRAY)); + + assertEquals( + TEST_TOKEN, argument.getValue().getHeaders().get(HttpHeaders.AUTHORIZATION).get(0)); + assertEquals( + MediaType.APPLICATION_JSON_VALUE, + argument.getValue().getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0)); + } +} diff --git a/token-manager-for-salesforce-webmvc/src/testintegration/java/com/tgt/crm/token/mvc/MvcIntegrationTest.java b/token-manager-for-salesforce-webmvc/src/testintegration/java/com/tgt/crm/token/mvc/MvcIntegrationTest.java new file mode 100644 index 0000000..388d6c4 --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/testintegration/java/com/tgt/crm/token/mvc/MvcIntegrationTest.java @@ -0,0 +1,222 @@ +package com.tgt.crm.token.mvc; + +import static com.tgt.crm.token.core.MockResponseUtil.getSfAuthErrorResponse; +import static com.tgt.crm.token.core.MockResponseUtil.getSfAuthSuccessResponse; +import static com.tgt.crm.token.core.MockResponseUtil.getSfQueryResponse; +import static com.tgt.crm.token.core.MockResponseUtil.getSfQueryUnauthorizedResponse; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_COUNTER; +import static com.tgt.crm.token.core.SalesforceConstants.EXCEPTION_TYPE_TAG; +import static com.tgt.crm.token.core.SalesforceConstants.TOKEN_REFRESH_EXCEPTION; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.tgt.crm.token.core.BaseIntegrationTest; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +@SpringBootTest( + classes = { + SalesforceLibraryAutoConfiguration.class, + RestTemplateAutoConfiguration.class, + SimpleMeterRegistry.class + }) +@Slf4j +public class MvcIntegrationTest extends BaseIntegrationTest { + + @Autowired + private @Qualifier("sfRestTemplate") RestTemplate restTemplate; + + @Autowired private MeterRegistry meterRegistry; + + @BeforeEach + public void setupEach() { + meterRegistry.clear(); + } + + @Test + void makeRequest_noTokenInCache_authSuccess_reqSuccess() throws InterruptedException { + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount = mockWebServer.getRequestCount(); + + ResponseEntity res = restTemplate.getForEntity(SF_URL, String.class); + + assertEquals(HttpStatus.OK, res.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, res.getBody()); + + assertEquals(2, mockWebServer.getRequestCount() - prevReqCount); + + validateAuthRequest(); + validateSfRequest(); + } + + @Test + void makeRequest_tokenInCache_noAuthCall_reqSuccess() throws InterruptedException { + // === make first request to cache token === + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount1 = mockWebServer.getRequestCount(); + + ResponseEntity firstRes = restTemplate.getForEntity(SF_URL, String.class); + + assertEquals(HttpStatus.OK, firstRes.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, firstRes.getBody()); + + assertEquals(2, mockWebServer.getRequestCount() - prevReqCount1); + + validateAuthRequest(); + validateSfRequest(); + + // === make second request to test cached token is used === + mockWebServer.enqueue(getSfQueryResponse()); + int prevReqCount2 = mockWebServer.getRequestCount(); + + ResponseEntity secondRes = restTemplate.getForEntity(SF_URL, String.class); + + assertEquals(HttpStatus.OK, secondRes.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, secondRes.getBody()); + + assertEquals(1, mockWebServer.getRequestCount() - prevReqCount2); + validateSfRequest(); + } + + @Test + void makeRequest_expiredTokenInCache_reqFails_tokenRefreshed_reqSuccess() + throws InterruptedException { + // === make first request to cache token === + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount1 = mockWebServer.getRequestCount(); + + ResponseEntity firstRes = restTemplate.getForEntity(SF_URL, String.class); + + assertEquals(HttpStatus.OK, firstRes.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, firstRes.getBody()); + + assertEquals(2, mockWebServer.getRequestCount() - prevReqCount1); + + validateAuthRequest(); + validateSfRequest(); + + // === make second request to test token refresh after cached is unauthorized === + mockWebServer.enqueue(getSfQueryUnauthorizedResponse()); + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount2 = mockWebServer.getRequestCount(); + + ResponseEntity secondRes = restTemplate.getForEntity(SF_URL, String.class); + + assertEquals(HttpStatus.OK, secondRes.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, secondRes.getBody()); + + assertEquals(3, mockWebServer.getRequestCount() - prevReqCount2); + + validateSfRequest(); + validateAuthRequest(); + validateSfRequest(); + } + + @Test + void makeRequest_noTokenInCache_authFail_retryAuthSuccess_reqSuccess() + throws InterruptedException { + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount = mockWebServer.getRequestCount(); + + ResponseEntity res = restTemplate.getForEntity(SF_URL, String.class); + + assertEquals(HttpStatus.OK, res.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, res.getBody()); + + assertEquals(3, mockWebServer.getRequestCount() - prevReqCount); + + validateAuthRequest(); + validateAuthRequest(); + validateSfRequest(); + } + + @Test + void makeRequest_noTokenInCache_authFail_retryAuthFail_retryAuthSuccess_reqSuccess() + throws InterruptedException { + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthSuccessResponse()); + mockWebServer.enqueue(getSfQueryResponse()); + + int prevReqCount = mockWebServer.getRequestCount(); + + ResponseEntity res = restTemplate.getForEntity(SF_URL, String.class); + + assertEquals(HttpStatus.OK, res.getStatusCode()); + assertEquals(QUERY_SUCCESSFUL, res.getBody()); + + assertEquals(4, mockWebServer.getRequestCount() - prevReqCount); + + validateAuthRequest(); + validateAuthRequest(); + validateAuthRequest(); + validateSfRequest(); + } + + @Test + void makeRequest_noTokenInCache_authFail_exhaustRetries_reqFail() throws InterruptedException { + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthErrorResponse()); + mockWebServer.enqueue(getSfAuthErrorResponse()); + + int prevReqCount = mockWebServer.getRequestCount(); + + HttpClientErrorException ex = + assertThrows( + HttpClientErrorException.class, () -> restTemplate.getForEntity(SF_URL, String.class)); + + assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode()); + + assertEquals(4, mockWebServer.getRequestCount() - prevReqCount); + + validateAuthRequest(); + validateAuthRequest(); + validateAuthRequest(); + + // will still attempt the req but token will be null + RecordedRequest reqWithNoAuth = mockWebServer.takeRequest(TIMEOUT, TimeUnit.SECONDS); + assertNotNull(reqWithNoAuth); + assertEquals(HttpMethod.GET.name(), reqWithNoAuth.getMethod()); + assertEquals("/some/sf/url", reqWithNoAuth.getPath()); + assertEquals( + MediaType.APPLICATION_JSON_VALUE, reqWithNoAuth.getHeader(HttpHeaders.CONTENT_TYPE)); + assertEquals("", reqWithNoAuth.getHeader(HttpHeaders.AUTHORIZATION)); + + assertEquals( + 1, + meterRegistry + .get(EXCEPTION_COUNTER) + .tag(EXCEPTION_TYPE_TAG, TOKEN_REFRESH_EXCEPTION) + .counter() + .count()); + } +} diff --git a/token-manager-for-salesforce-webmvc/src/testintegration/resources/application.yml b/token-manager-for-salesforce-webmvc/src/testintegration/resources/application.yml new file mode 100644 index 0000000..90ddbde --- /dev/null +++ b/token-manager-for-salesforce-webmvc/src/testintegration/resources/application.yml @@ -0,0 +1,10 @@ +logging: + level: + com.tgt.crm: TRACE + +salesforce: + username: username + password: password!@#$%^&*() + client-id: clientId + client-secret: clientSecret + retry-backoff-delay: 50